11using System . Reflection ;
2- using System . Reflection . Emit ;
32using HarmonyLib ;
43using MegaCrit . Sts2 . Core . Models ;
54using MegaCrit . Sts2 . Core . Multiplayer . Game . Lobby ;
87
98namespace STS2RitsuLib . Scaffolding . Characters . Patches
109{
11- /// <summary>
12- /// Applies <see cref="IModCharacterVanillaSelectionPolicy" /> when vanilla character-select builds visible and
13- /// random-eligible character lists.
14- /// </summary>
15- public class CharacterVanillaSelectionPolicyPatches : IPatchMethod
10+ internal static class CharacterVanillaSelectionPolicyScope
1611 {
17- private static readonly MethodInfo ? ModelDbAllCharactersGetter =
18- AccessTools . PropertyGetter ( typeof ( ModelDb ) , nameof ( ModelDb . AllCharacters ) ) ;
12+ [ ThreadStatic ] private static SelectionScope _currentScope ;
13+ [ ThreadStatic ] private static int _scopeDepth ;
14+
15+ public static void Enter ( MethodBase originalMethod )
16+ {
17+ var scope = ResolveScope ( originalMethod ) ;
18+ if ( scope == SelectionScope . None )
19+ return ;
20+
21+ if ( _scopeDepth ++ == 0 )
22+ _currentScope = scope ;
23+ }
24+
25+ public static void Exit ( MethodBase originalMethod )
26+ {
27+ var scope = ResolveScope ( originalMethod ) ;
28+ if ( scope == SelectionScope . None || _scopeDepth <= 0 )
29+ return ;
30+
31+ _scopeDepth -- ;
32+ if ( _scopeDepth == 0 )
33+ _currentScope = SelectionScope . None ;
34+ }
35+
36+ public static IEnumerable < CharacterModel > Apply ( IEnumerable < CharacterModel > source )
37+ {
38+ return _currentScope switch
39+ {
40+ SelectionScope . Visible => source . Where ( character => character is not IModCharacterVanillaSelectionPolicy
41+ {
42+ HideFromVanillaCharacterSelect : true ,
43+ } ) ,
44+ SelectionScope . RandomEligible => source . Where ( character =>
45+ character is not IModCharacterVanillaSelectionPolicy
46+ {
47+ AllowInVanillaRandomCharacterSelect : false ,
48+ } ) ,
49+ _ => source ,
50+ } ;
51+ }
1952
20- private static readonly MethodInfo ? VisibleCharactersMethod =
21- AccessTools . DeclaredMethod ( typeof ( CharacterVanillaSelectionPolicyPatches ) , nameof ( GetVisibleCharacters ) ) ;
53+ private static SelectionScope ResolveScope ( MethodBase originalMethod )
54+ {
55+ if ( originalMethod . DeclaringType == typeof ( NCharacterSelectScreen ) &&
56+ originalMethod . Name == nameof ( NCharacterSelectScreen . InitCharacterButtons ) )
57+ return SelectionScope . Visible ;
2258
23- private static readonly MethodInfo ? RandomEligibleCharactersMethod =
24- AccessTools . DeclaredMethod ( typeof ( CharacterVanillaSelectionPolicyPatches ) ,
25- nameof ( GetRandomEligibleCharacters ) ) ;
59+ if ( ( originalMethod . DeclaringType == typeof ( NCharacterSelectScreen ) &&
60+ ( originalMethod . Name == nameof ( NCharacterSelectScreen . UpdateRandomCharacterVisibility ) ||
61+ originalMethod . Name == "RollRandomCharacter" ) ) ||
62+ ( originalMethod . DeclaringType == typeof ( NCharacterSelectButton ) &&
63+ originalMethod . Name == nameof ( NCharacterSelectButton . Init ) ) ||
64+ ( originalMethod . DeclaringType == typeof ( StartRunLobby ) &&
65+ originalMethod . Name == "BeginRunLocally" ) )
66+ return SelectionScope . RandomEligible ;
2667
68+ return SelectionScope . None ;
69+ }
70+
71+ private enum SelectionScope
72+ {
73+ None ,
74+ Visible ,
75+ RandomEligible ,
76+ }
77+ }
78+
79+ /// <summary>
80+ /// Maintains selection-policy scope for vanilla character-select flows.
81+ /// </summary>
82+ public class CharacterVanillaSelectionPolicyPatches : IPatchMethod
83+ {
2784 /// <inheritdoc />
2885 public static string PatchId => "character_vanilla_selection_policy" ;
2986
@@ -47,56 +104,54 @@ public static ModPatchTarget[] GetTargets()
47104 ] ;
48105 }
49106
50- // ReSharper disable once InconsistentNaming
51107 /// <summary>
52- /// Rewrites direct reads of <see cref="ModelDb.AllCharacters" /> in target methods .
108+ /// Enters selection scope for character-list consumers .
53109 /// </summary>
54- public static IEnumerable < CodeInstruction > Transpiler ( IEnumerable < CodeInstruction > instructions ,
55- MethodBase __originalMethod )
110+ // ReSharper disable once InconsistentNaming
111+ public static void Prefix ( MethodBase __originalMethod )
56112 {
57- if ( ModelDbAllCharactersGetter == null )
58- return instructions ;
59-
60- var useVisibleList = __originalMethod . Name == nameof ( NCharacterSelectScreen . InitCharacterButtons ) ;
61- var replacement = useVisibleList ? VisibleCharactersMethod : RandomEligibleCharactersMethod ;
62- if ( replacement == null )
63- return instructions ;
113+ CharacterVanillaSelectionPolicyScope . Enter ( __originalMethod ) ;
114+ }
64115
65- var rewritten = false ;
66- var list = instructions . ToList ( ) ;
67- for ( var index = 0 ; index < list . Count ; index ++ )
68- {
69- var code = list [ index ] ;
70- if ( ! code . Calls ( ModelDbAllCharactersGetter ) )
71- continue ;
116+ /// <summary>
117+ /// Ensures scope cleanup even when target method throws.
118+ /// </summary>
119+ // ReSharper disable once InconsistentNaming
120+ public static void Finalizer ( MethodBase __originalMethod )
121+ {
122+ CharacterVanillaSelectionPolicyScope . Exit ( __originalMethod ) ;
123+ }
124+ }
72125
73- code . opcode = OpCodes . Call ;
74- code . operand = replacement ;
75- list [ index ] = code ;
76- rewritten = true ;
77- }
126+ /// <summary>
127+ /// Applies scoped selection policy to <see cref="ModelDb.AllCharacters" />.
128+ /// </summary>
129+ public class CharacterVanillaSelectionPolicyAllCharactersPatch : IPatchMethod
130+ {
131+ /// <inheritdoc />
132+ public static string PatchId => "character_vanilla_selection_policy_all_characters" ;
78133
79- if ( ! rewritten )
80- RitsuLibFramework . Logger . Debug (
81- $ "[CharacterSelection] No ModelDb.AllCharacters call found while patching { __originalMethod . DeclaringType ? . Name } .{ __originalMethod . Name } .") ;
134+ /// <inheritdoc />
135+ public static string Description => "Filter ModelDb.AllCharacters by current vanilla selection scope" ;
82136
83- return list ;
84- }
137+ /// <inheritdoc />
138+ public static bool IsCritical => false ;
85139
86- private static IEnumerable < CharacterModel > GetVisibleCharacters ( )
140+ /// <inheritdoc />
141+ public static ModPatchTarget [ ] GetTargets ( )
87142 {
88- return ModelDb . AllCharacters . Where ( character => character is not IModCharacterVanillaSelectionPolicy
89- {
90- HideFromVanillaCharacterSelect : true ,
91- } ) ;
143+ return [ new ( typeof ( ModelDb ) , nameof ( ModelDb . AllCharacters ) , MethodType . Getter ) ] ;
92144 }
93145
94- private static IEnumerable < CharacterModel > GetRandomEligibleCharacters ( )
146+ /// <summary>
147+ /// Filters getter result according to current selection scope.
148+ /// </summary>
149+ [ HarmonyAfter ( Const . BaseLibHarmonyId , Const . FrameworkContentRegistryHarmonyId ) ]
150+ [ HarmonyPriority ( Priority . Last ) ]
151+ // ReSharper disable once InconsistentNaming
152+ public static void Postfix ( ref IEnumerable < CharacterModel > __result )
95153 {
96- return ModelDb . AllCharacters . Where ( character => character is not IModCharacterVanillaSelectionPolicy
97- {
98- AllowInVanillaRandomCharacterSelect : false ,
99- } ) ;
154+ __result = CharacterVanillaSelectionPolicyScope . Apply ( __result ) ;
100155 }
101156 }
102157}
0 commit comments