1+ using FrooxEngine ;
2+ using HarmonyLib ;
3+ using MonkeyLoader . Events ;
4+ using MonkeyLoader . Resonite . UI . ContextMenus ;
5+
6+ namespace MonkeyLoader . Resonite . UI . Inspectors
7+ {
8+ /// <summary>
9+ /// Represents a dispatchable base class for all <see cref="FieldDriveReceiver{T}"/> and
10+ /// <see cref="ReferenceDriveReceiver{T}"/> <see cref="ContextMenu"/> items generation events.
11+ /// </summary>
12+ [ DispatchableBaseEvent , SubscribableBaseEvent ]
13+ public abstract class DriveReceiverMenuItemsGenerationEvent : ContextMenuItemsGenerationEvent , ITargetSyncMemberEvent
14+ {
15+ private static readonly Type [ ] _contextMenuConstructorParameters = [ typeof ( ContextMenu ) ] ;
16+
17+ private static readonly Dictionary < Type , Func < ContextMenu , DriveReceiverMenuItemsGenerationEvent > > _contextMenuConstructorsBySummonerType = [ ] ;
18+
19+ private static readonly Type _fieldEventType = typeof ( FieldDriveReceiverMenuItemsGenerationEvent < > ) ;
20+ private static readonly Type _fieldReceiverType = typeof ( FieldDriveReceiver < > ) ;
21+ private static readonly Type _objectType = typeof ( object ) ;
22+ private static readonly Type _referenceEventType = typeof ( ReferenceDriveReceiverMenuItemsGenerationEvent < > ) ;
23+ private static readonly Type _referenceReceiverType = typeof ( ReferenceDriveReceiver < > ) ;
24+
25+ /// <inheritdoc/>
26+ public Slot ? Slot => SlotCore ;
27+
28+ /// <summary>
29+ /// Gets the <see cref="ISyncMember"/> that the summoning
30+ /// <see cref="FieldDriveReceiver{T}"/> or <see cref="ReferenceDriveReceiver{T}"/> targets.<br/>
31+ /// This will be an <see cref="IField{T}"/> for <see cref="FieldDriveReceiver{T}"/>s,
32+ /// and an <see cref="ISyncRef{T}"/> for <see cref="ReferenceDriveReceiver{T}"/>s.
33+ /// </summary>
34+ public ISyncMember Target => TargetCore ;
35+
36+ /// <inheritdoc/>
37+ public User ? User => UserCore ;
38+
39+ /// <summary>
40+ /// Internal implementation of <see cref="Slot"/>.
41+ /// </summary>
42+ protected abstract Slot ? SlotCore { get ; }
43+
44+ /// <summary>
45+ /// Internal implementation of <see cref="Target"/>.
46+ /// </summary>
47+ protected abstract ISyncMember TargetCore { get ; }
48+
49+ /// <summary>
50+ /// Internal implementation of <see cref="User"/>.
51+ /// </summary>
52+ protected abstract User ? UserCore { get ; }
53+
54+ /// <inheritdoc/>
55+ protected DriveReceiverMenuItemsGenerationEvent ( ContextMenu contextMenu )
56+ : base ( contextMenu )
57+ { }
58+
59+ /// <summary>
60+ /// Creates a new <see cref="ContextMenu"/> items generation event for the given
61+ /// <paramref name="contextMenu"/> and its <see cref="Slot.ActiveUser">active</see> <see cref="User"/>,
62+ /// when the <see cref="ContextMenu.CurrentSummoner">summoner</see> is a <see cref="FieldDriveReceiver{T}"/>
63+ /// or <see cref="ReferenceDriveReceiver{T}"/>.
64+ /// </summary>
65+ /// <inheritdoc cref="DriveReceiverMenuItemsGenerationEvent(ContextMenu)"/>
66+ internal static DriveReceiverMenuItemsGenerationEvent CreateForDriveReceiver ( ContextMenu contextMenu )
67+ {
68+ ArgumentNullException . ThrowIfNull ( contextMenu ) ;
69+
70+ if ( contextMenu . CurrentSummoner is null )
71+ throw new ArgumentException ( $ "Summoner was missing for Context Menu: { contextMenu . ParentHierarchyToString ( ) } ") ;
72+
73+ var summonerType = contextMenu . CurrentSummoner . GetType ( ) ;
74+
75+ var currentType = summonerType ;
76+ Func < ContextMenu , DriveReceiverMenuItemsGenerationEvent > ? contextMenuConstructor = null ;
77+
78+ while ( currentType != _objectType )
79+ {
80+ if ( _contextMenuConstructorsBySummonerType . TryGetValue ( currentType , out var customContextMenuConstructor ) )
81+ {
82+ contextMenuConstructor = customContextMenuConstructor ;
83+ break ;
84+ }
85+
86+ if ( currentType . IsGenericType && currentType . GenericTypeArguments . Length is 1 )
87+ {
88+ var currentGenericType = currentType . GetGenericTypeDefinition ( ) ;
89+ Type parameterType = currentType . GenericTypeArguments [ 0 ] ;
90+
91+ if ( currentGenericType == _referenceReceiverType )
92+ {
93+ contextMenuConstructor = GetDriveReceiverContextMenuConstructor ( _referenceEventType , parameterType ) ;
94+ break ;
95+ }
96+
97+ if ( currentGenericType == _fieldReceiverType )
98+ {
99+ contextMenuConstructor = GetDriveReceiverContextMenuConstructor ( _fieldEventType , parameterType ) ;
100+ break ;
101+ }
102+ }
103+
104+ // Will never be null since we stop when currentType is object
105+ currentType = currentType . BaseType ! ;
106+ }
107+
108+ if ( contextMenuConstructor is null )
109+ throw new ArgumentException ( $ "Summoner was not of type { typeof ( FieldDriveReceiver < > ) . CompactDescription ( ) } or { typeof ( ReferenceDriveReceiver < > ) . CompactDescription ( ) } or derived from them for Context Menu: { contextMenu . ParentHierarchyToString ( ) } ") ;
110+
111+ return contextMenuConstructor ( contextMenu ) ;
112+ }
113+
114+ private static Func < ContextMenu , DriveReceiverMenuItemsGenerationEvent > GetDriveReceiverContextMenuConstructor ( Type rawEventType , Type parameterType )
115+ {
116+ var eventType = rawEventType . MakeGenericType ( parameterType ) ;
117+ var constructorMethod = AccessTools . DeclaredConstructor ( eventType , _contextMenuConstructorParameters ) ;
118+ return contextMenu => ( DriveReceiverMenuItemsGenerationEvent ) constructorMethod . Invoke ( [ contextMenu ] ) ;
119+ }
120+ }
121+
122+ /// <summary>
123+ /// Represents a dispatchable base class for all<see cref="FieldDriveReceiver{T}"/> <see cref="ContextMenu"/> items generation events.
124+ /// </summary>
125+ [ DispatchableBaseEvent , SubscribableBaseEvent ]
126+ public abstract class FieldDriveReceiverMenuItemsGenerationEvent : DriveReceiverMenuItemsGenerationEvent
127+ {
128+ /// <summary>
129+ /// Gets the <see cref="IField"/> that the summoning <see cref="FieldDriveReceiver{T}"/> targets.
130+ /// </summary>
131+ public new IField Target => TargetFieldCore ;
132+
133+ /// <summary>
134+ /// Internal implementation of <see cref="Target"/>.
135+ /// </summary>
136+ protected abstract IField TargetFieldCore { get ; }
137+
138+ /// <inheritdoc/>
139+ protected FieldDriveReceiverMenuItemsGenerationEvent ( ContextMenu contextMenu )
140+ : base ( contextMenu )
141+ { }
142+ }
143+
144+ /// <summary>
145+ /// Represents the event data for the concrete <see cref="FieldDriveReceiver{T}"/> <see cref="ContextMenu"/> items generation events.
146+ /// </summary>
147+ public sealed class FieldDriveReceiverMenuItemsGenerationEvent < T > : FieldDriveReceiverMenuItemsGenerationEvent
148+ {
149+ /// <summary>
150+ /// Gets the <see cref="FieldDriveReceiver{T}"/> that the <see cref="ContextMenu">ContextMenu</see> is being summoned by.
151+ /// </summary>
152+ public new FieldDriveReceiver < T > Summoner { get ; }
153+
154+ /// <summary>
155+ /// Gets the <see cref="IField{T}"/> that the summoning <see cref="FieldDriveReceiver{T}"/> targets.
156+ /// </summary>
157+ public new IField < T > Target { get ; }
158+
159+ /// <inheritdoc/>
160+ protected override sealed Slot ? SlotCore { get ; }
161+
162+ /// <inheritdoc/>
163+ protected override sealed IWorldElement SummonerCore => Summoner ;
164+
165+ /// <inheritdoc/>
166+ protected override sealed ISyncMember TargetCore => Target ;
167+
168+ /// <inheritdoc/>
169+ protected override sealed IField TargetFieldCore => Target ;
170+
171+ /// <inheritdoc/>
172+ protected override sealed User ? UserCore { get ; }
173+
174+ /// <inheritdoc/>
175+ public FieldDriveReceiverMenuItemsGenerationEvent ( ContextMenu contextMenu )
176+ : base ( contextMenu )
177+ {
178+ Summoner = ContextMenu . CurrentSummoner as FieldDriveReceiver < T >
179+ ?? throw new ArgumentException ( $ "Summoner was null or not of type { typeof ( FieldDriveReceiver < T > ) . CompactDescription ( ) } for Context Menu: { contextMenu . ParentHierarchyToString ( ) } ") ;
180+
181+ Target = Summoner . Field . Target ;
182+
183+ SlotCore = Target . FindNearestParent < Slot > ( ) ;
184+ UserCore = Slot ? . ActiveUser ?? Target . FindNearestParent < User > ( ) ;
185+ }
186+ }
187+
188+ /// <summary>
189+ /// Represents a dispatchable base class for all <see cref="ReferenceDriveReceiver{T}"/> <see cref="ContextMenu"/> items generation events.
190+ /// </summary>
191+ [ DispatchableBaseEvent , SubscribableBaseEvent ]
192+ public abstract class ReferenceDriveReceiverMenuItemsGenerationEvent : DriveReceiverMenuItemsGenerationEvent
193+ {
194+ /// <summary>
195+ /// Gets the <see cref="ISyncRef"/> that the summoning <see cref="ReferenceDriveReceiver{T}"/> targets.
196+ /// </summary>
197+ public new ISyncRef Target => TargetSyncRefCore ;
198+
199+ /// <summary>
200+ /// Internal implementation of <see cref="Target"/>.
201+ /// </summary>
202+ protected abstract ISyncRef TargetSyncRefCore { get ; }
203+
204+ /// <inheritdoc/>
205+ protected ReferenceDriveReceiverMenuItemsGenerationEvent ( ContextMenu contextMenu )
206+ : base ( contextMenu )
207+ { }
208+ }
209+
210+ /// <summary>
211+ /// Represents the event data for the concrete <see cref="ReferenceDriveReceiver{T}"/> <see cref="ContextMenu"/> items generation events.
212+ /// </summary>
213+ public sealed class ReferenceDriveReceiverMenuItemsGenerationEvent < T > : ReferenceDriveReceiverMenuItemsGenerationEvent
214+ where T : class , IWorldElement
215+ {
216+ /// <summary>
217+ /// Gets the <see cref="ReferenceDriveReceiver{T}"/> that the <see cref="ContextMenu">ContextMenu</see> is being summoned by.
218+ /// </summary>
219+ public new ReferenceDriveReceiver < T > Summoner { get ; }
220+
221+ /// <summary>
222+ /// Gets the <see cref="ISyncRef{T}"/> that the summoning <see cref="ReferenceDriveReceiver{T}"/> targets.
223+ /// </summary>
224+ public new ISyncRef < T > Target { get ; }
225+
226+ /// <inheritdoc/>
227+ protected override sealed Slot ? SlotCore { get ; }
228+
229+ /// <inheritdoc/>
230+ protected override sealed IWorldElement SummonerCore => Summoner ;
231+
232+ /// <inheritdoc/>
233+ protected override sealed ISyncMember TargetCore => Target ;
234+
235+ /// <inheritdoc/>
236+ protected override sealed ISyncRef TargetSyncRefCore => Target ;
237+
238+ /// <inheritdoc/>
239+ protected override sealed User ? UserCore { get ; }
240+
241+ /// <inheritdoc/>
242+ public ReferenceDriveReceiverMenuItemsGenerationEvent ( ContextMenu contextMenu )
243+ : base ( contextMenu )
244+ {
245+ Summoner = ContextMenu . CurrentSummoner as ReferenceDriveReceiver < T >
246+ ?? throw new ArgumentException ( $ "Summoner was null or not of type { typeof ( ReferenceDriveReceiver < T > ) . CompactDescription ( ) } for Context Menu: { contextMenu . ParentHierarchyToString ( ) } ") ;
247+
248+ Target = Summoner . Reference . Target ;
249+
250+ SlotCore = Target . FindNearestParent < Slot > ( ) ;
251+ UserCore = Slot ? . ActiveUser ?? Target . FindNearestParent < User > ( ) ;
252+ }
253+ }
254+ }
0 commit comments