@@ -9,6 +9,8 @@ namespace STS2RitsuLib.Combat.CardTargeting
99 /// </summary>
1010 internal static class CustomTargetTypeRegistry
1111 {
12+ private static readonly Lock SyncRoot = new ( ) ;
13+
1214 /// <summary>
1315 /// Predicate map for custom single-target types.
1416 /// 自定义单体目标类型的谓词映射。
@@ -21,13 +23,18 @@ internal static class CustomTargetTypeRegistry
2123 /// </summary>
2224 private static readonly Dictionary < TargetType , Func < Creature , bool > > MultiTargetPredicates = [ ] ;
2325
26+ private static readonly Dictionary < TargetType , CustomTargetTypeRegistration > Registrations = [ ] ;
27+
2428 /// <summary>
2529 /// Returns whether <paramref name="type" /> belongs to any registered custom target category.
2630 /// 返回 <paramref name="type" /> 是否属于任一已注册的自定义目标类别。
2731 /// </summary>
2832 internal static bool IsRitsuCustom ( TargetType type )
2933 {
30- return SingleTargetPredicates . ContainsKey ( type ) || MultiTargetPredicates . ContainsKey ( type ) ;
34+ lock ( SyncRoot )
35+ {
36+ return Registrations . ContainsKey ( type ) ;
37+ }
3138 }
3239
3340 /// <summary>
@@ -36,7 +43,10 @@ internal static bool IsRitsuCustom(TargetType type)
3643 /// </summary>
3744 internal static bool IsCustomSingleTargetType ( TargetType type )
3845 {
39- return SingleTargetPredicates . ContainsKey ( type ) ;
46+ lock ( SyncRoot )
47+ {
48+ return SingleTargetPredicates . ContainsKey ( type ) ;
49+ }
4050 }
4151
4252 /// <summary>
@@ -45,7 +55,10 @@ internal static bool IsCustomSingleTargetType(TargetType type)
4555 /// </summary>
4656 internal static bool IsCustomMultiTargetType ( TargetType type )
4757 {
48- return MultiTargetPredicates . ContainsKey ( type ) ;
58+ lock ( SyncRoot )
59+ {
60+ return MultiTargetPredicates . ContainsKey ( type ) ;
61+ }
4962 }
5063
5164 /// <summary>
@@ -54,7 +67,13 @@ internal static bool IsCustomMultiTargetType(TargetType type)
5467 /// </summary>
5568 internal static bool TryIsAllowedSingleTarget ( TargetType type , Creature creature , out bool allowed )
5669 {
57- if ( ! SingleTargetPredicates . TryGetValue ( type , out var predicate ) )
70+ Func < Creature , bool > ? predicate ;
71+ lock ( SyncRoot )
72+ {
73+ SingleTargetPredicates . TryGetValue ( type , out predicate ) ;
74+ }
75+
76+ if ( predicate == null )
5877 {
5978 allowed = false ;
6079 return false ;
@@ -70,7 +89,13 @@ internal static bool TryIsAllowedSingleTarget(TargetType type, Creature creature
7089 /// </summary>
7190 internal static bool TryShouldIncludeMultiTarget ( TargetType type , Creature creature , out bool include )
7291 {
73- if ( ! MultiTargetPredicates . TryGetValue ( type , out var predicate ) )
92+ Func < Creature , bool > ? predicate ;
93+ lock ( SyncRoot )
94+ {
95+ MultiTargetPredicates . TryGetValue ( type , out predicate ) ;
96+ }
97+
98+ if ( predicate == null )
7499 {
75100 include = false ;
76101 return false ;
@@ -86,7 +111,17 @@ internal static bool TryShouldIncludeMultiTarget(TargetType type, Creature creat
86111 /// </summary>
87112 internal static void RegisterSingleTargetType ( TargetType type , Func < Creature , bool > predicate )
88113 {
89- SingleTargetPredicates [ type ] = predicate ;
114+ Register ( type , null , CustomTargetTypeKind . Single , predicate ) ;
115+ }
116+
117+ /// <summary>
118+ /// Registers or replaces a custom single-target predicate with a diagnostic id.
119+ /// 使用诊断 ID 注册或替换一个自定义单体目标谓词。
120+ /// </summary>
121+ internal static void RegisterSingleTargetType ( TargetType type , string id , Func < Creature , bool > predicate )
122+ {
123+ ArgumentException . ThrowIfNullOrWhiteSpace ( id ) ;
124+ Register ( type , id , CustomTargetTypeKind . Single , predicate ) ;
90125 }
91126
92127 /// <summary>
@@ -95,18 +130,25 @@ internal static void RegisterSingleTargetType(TargetType type, Func<Creature, bo
95130 /// </summary>
96131 internal static void RegisterMultiTargetType ( TargetType type , Func < Creature , bool > predicate )
97132 {
98- MultiTargetPredicates [ type ] = predicate ;
133+ Register ( type , null , CustomTargetTypeKind . Multi , predicate ) ;
99134 }
100135
101136 /// <summary>
102- /// Clears and re-registers all built-in custom target definitions .
103- /// 清空并重新注册全部内置自定义目标定义 。
137+ /// Registers or replaces a custom multi- target predicate with a diagnostic id .
138+ /// 使用诊断 ID 注册或替换一个自定义群体目标谓词 。
104139 /// </summary>
105- internal static void RegisterBuiltIns ( )
140+ internal static void RegisterMultiTargetType ( TargetType type , string id , Func < Creature , bool > predicate )
106141 {
107- SingleTargetPredicates . Clear ( ) ;
108- MultiTargetPredicates . Clear ( ) ;
142+ ArgumentException . ThrowIfNullOrWhiteSpace ( id ) ;
143+ Register ( type , id , CustomTargetTypeKind . Multi , predicate ) ;
144+ }
109145
146+ /// <summary>
147+ /// Registers all built-in custom target definitions while preserving mod-registered predicates.
148+ /// 注册全部内置自定义目标定义,同时保留 mod 注册的谓词。
149+ /// </summary>
150+ internal static void RegisterBuiltIns ( )
151+ {
110152 RegisterSingleTargetType ( CustomTargetType . Anyone , target => target is { IsAlive : true , IsPet : false } ) ;
111153 RegisterMultiTargetType ( CustomTargetType . Everyone , target => target is { IsAlive : true , IsPet : false } ) ;
112154
@@ -159,5 +201,52 @@ private static bool IsEnemyHpExtremum(Creature target, bool lowest)
159201 var extremum = lowest ? enemies . Min ( e => e . CurrentHp ) : enemies . Max ( e => e . CurrentHp ) ;
160202 return target . CurrentHp == extremum ;
161203 }
204+
205+ private static void Register (
206+ TargetType type ,
207+ string ? id ,
208+ CustomTargetTypeKind kind ,
209+ Func < Creature , bool > predicate )
210+ {
211+ ArgumentNullException . ThrowIfNull ( predicate ) ;
212+
213+ lock ( SyncRoot )
214+ {
215+ var diagnosticId = id ;
216+ if ( Registrations . TryGetValue ( type , out var existing ) )
217+ {
218+ diagnosticId ??= existing . Id ;
219+ if ( existing . Kind != kind )
220+ throw new InvalidOperationException (
221+ $ "TargetType '{ diagnosticId } ' is already registered as "
222+ + $ "{ existing . Kind } ; it cannot also be registered as { kind } .") ;
223+ }
224+
225+ diagnosticId ??= ( ( int ) type ) . ToString ( ) ;
226+ Registrations [ type ] = new ( diagnosticId , kind ) ;
227+
228+ switch ( kind )
229+ {
230+ case CustomTargetTypeKind . Single :
231+ SingleTargetPredicates [ type ] = predicate ;
232+ MultiTargetPredicates . Remove ( type ) ;
233+ break ;
234+ case CustomTargetTypeKind . Multi :
235+ MultiTargetPredicates [ type ] = predicate ;
236+ SingleTargetPredicates . Remove ( type ) ;
237+ break ;
238+ default :
239+ throw new ArgumentOutOfRangeException ( nameof ( kind ) , kind , null ) ;
240+ }
241+ }
242+ }
243+
244+ private enum CustomTargetTypeKind
245+ {
246+ Single ,
247+ Multi ,
248+ }
249+
250+ private readonly record struct CustomTargetTypeRegistration ( string Id , CustomTargetTypeKind Kind ) ;
162251 }
163252}
0 commit comments