Rework BAUModdedSupport

This commit is contained in:
D1GQ committed 2026-05-28 21:55:53 -05:00
1 parent 91e1a205bd
commit 65d5eba3bf
9 files changed
+391 -267

No files matched your search

@@ -1,65 +0,0 @@
using BepInEx.Configuration;
using BepInEx.Unity.IL2CPP;
namespace BetterAmongUs.Examples;
/// <summary>
/// Example event handler class demonstrating how to respond to BetterAmongUs events.
/// This class must be instantiated and assigned to the BAUEvents field in the plugin.
/// </summary>
/// <remarks>
/// Each method in this class corresponds to a different lifecycle event in BetterAmongUs.
/// Methods are optional - only implement the ones your plugin needs.
/// </remarks>
internal class ModdedSupportBAUEventExample
{
/// <summary>
/// Called when BetterAmongUs is loading. Can be used to prevent BAU from loading.
/// </summary>
/// <param name="bauPlugin">The BetterAmongUs plugin instance.</param>
/// <returns>
/// Return true to allow BAU to continue loading, or false to prevent BAU from loading.
/// This can be useful if your plugin has compatibility issues with specific BAU versions.
/// </returns>
public bool OnBAULoad(BasePlugin bauPlugin)
{
return true;
}
/// <summary>
/// Called when BetterAmongUs game options have been loaded.
/// Allows plugins to read or modify BAU game options.
/// </summary>
/// <param name="options">
/// Array of game options objects from BetterAmongUs.
/// The exact type of these objects depends on BAU's internal implementation.
/// Common option types to check for:
/// Modules.OptionItems.OptionItem,
/// Modules.OptionItems.OptionItem{T},
/// Modules.OptionItems.OptionCheckboxItem,
/// Modules.OptionItems.OptionFloatItem,
/// Modules.OptionItems.OptionIntItem,
/// Modules.OptionItems.OptionPercentItem,
/// Modules.OptionItems.OptionPlayerItem,
/// Modules.OptionItems.OptionStringItem,
/// </param>
public void OnBAUOptionsLoaded(object[] options)
{
}
/// <summary>
/// Called when BetterAmongUs configuration entries have been loaded.
/// Allows plugins to read or modify BAU's BepInEx configuration entries.
/// </summary>
/// <param name="configs">
/// Array of BepInEx configuration entries from BetterAmongUs.
/// These can be cast to ConfigEntry&lt;T&gt; to access their values.
/// </param>
/// <remarks>
/// This is useful for plugins that need to interact with BAU's configuration,
/// such as reading default values or adding validation to certain settings.
/// </remarks>
public void OnBAUConfigEntriesLoaded(ConfigEntryBase[] configs)
{
}
}
@@ -0,0 +1,59 @@
using BepInEx.Configuration;
using BepInEx.Unity.IL2CPP;
using BetterAmongUs.Helpers;
using System.ComponentModel;
namespace BetterAmongUs.Examples;
/// <summary>
/// Example class demonstrating how to create modded support for BetterAmongUs.
/// </summary>
/// <remarks>
/// All methods and fields must be static and decorated with the appropriate <see cref="CategoryAttribute"/>
/// to be discovered by BetterAmongUs through reflection.
/// </remarks>
internal class ModdedSupportExampleClass
{
/// <summary>
/// Array of flags to control BetterAmongUs behavior.
/// Check BAUModdedSupportFlags for available flag constants.
/// </summary>
[Category("bau:flags")]
public static string[] BAUFlags = [];
/// <summary>
/// Called when BetterAmongUs is loading. Return false to prevent BAU from loading.
/// </summary>
/// <param name="bauPlugin">The BetterAmongUs plugin instance.</param>
/// <returns>True to allow BAU to load, false to prevent it.</returns>
[Category("bau:event.bau_load")]
public static bool OnBAULoad(BasePlugin bauPlugin)
{
Logger_.Error("TEST 1");
return true;
}
/// <summary>
/// Called when BetterAmongUs game options have been loaded.
/// </summary>
/// <param name="options">
/// Array of game option objects from BetterAmongUs. Common types include:
/// OptionItem, OptionItem{T}, OptionCheckboxItem, OptionFloatItem,
/// OptionIntItem, OptionPercentItem, OptionPlayerItem, OptionStringItem
/// </param>
[Category("bau:event.options_load")]
public static void OnBAUOptionsLoaded(object[] options)
{
Logger_.Error("TEST 2");
}
/// <summary>
/// Called when BetterAmongUs configuration entries have been loaded.
/// </summary>
/// <param name="configs">Array of BepInEx ConfigEntryBase objects from BetterAmongUs.</param>
[Category("bau:event.configs_load")]
public static void OnBAUConfigEntriesLoaded(ConfigEntryBase[] configs)
{
Logger_.Error("TEST 3");
}
}
@@ -1,39 +0,0 @@
using BepInEx.Unity.IL2CPP;
namespace BetterAmongUs.Examples;
/// <summary>
/// Example plugin demonstrating how to integrate with BetterAmongUs modded support system.
/// Shows the structure required for a plugin to declare BAU events and flags.
/// </summary>
/// <remarks>
/// This example shows a minimal plugin that implements both event handlers and flags.
/// In a real implementation, you would add your plugin's actual logic in the Load() method.
/// </remarks>
internal class ModdedSupportPluginExample : BasePlugin
{
/// <summary>
/// Event handler instance for BetterAmongUs events.
/// This field must be named exactly "BAUEvents" to be detected by the reflection system.
/// </summary>
public static ModdedSupportBAUEventExample BAUEvents = new();
/// <summary>
/// Array of flags to control BetterAmongUs behavior.
/// This field must be named exactly "BAUFlags" to be detected by the reflection system.
/// </summary>
/// <remarks>
/// Flags can disable specific BAU features or modify its behavior.
/// Check Modules.Support.BAUModdedSupportFlags for available flag constants.
/// </remarks>
public static string[] BAUFlags = [];
/// <summary>
/// Main plugin load method.
/// In a real implementation, this is where your plugin initialization would occur.
/// </summary>
public override void Load()
{
// Plugin initialization logic would go here
}
}
+2 -2
View File
@@ -146,7 +146,7 @@ internal class BAUPlugin : BasePlugin
/// </summary>
private void OnChainloaderFinished()
{
if (!BAUModdedSupportEvents.InvokeAll_OnBAULoad(this))
if (BAUModdedSupportEvents.OnBAULoadEvent.InvokeAll(this).Any(b => b == false))
return;
BAUModdedSupportFlags.Initialize();
@@ -157,7 +157,7 @@ internal class BAUPlugin : BasePlugin
Translator.Initialize();
Harmony.PatchAll();
GameSettingsPatch.SetupSettings(true);
BAUModdedSupportEvents.InvokeAll_OnBAUOptionsLoaded([.. OptionItem.AllOptions.Cast<object>()]);
BAUModdedSupportEvents.OnBAUOptionsLoadedEvent.InvokeAll([.. OptionItem.AllOptions.Cast<object>()]);
InstanceAttribute.RegisterAll();
OutfitData.Initialize();
+1 -1
View File
@@ -88,7 +88,7 @@ internal static class BAUConfigs
/// </summary>
internal static void LoadConfigs()
{
BAUModdedSupportEvents.InvokeAll_OnBAUConfigEntriesLoaded([
BAUModdedSupportEvents.OnBAUConfigEntriesLoadedEvent.InvokeAll([
AntiCheat, SendBetterRpc, BetterNotifications,
ForceOwnLanguage, ChatDarkMode, ChatInGameplay, LobbyPlayerInfo,
DisableLobbyTheme, UnlockFPS, ShowFPS,
+9 -131
View File
@@ -1,6 +1,5 @@
using BepInEx.Configuration;
using BepInEx.Unity.IL2CPP;
using System.Reflection;
namespace BetterAmongUs.Modules.Support;
@@ -8,141 +7,20 @@ namespace BetterAmongUs.Modules.Support;
/// Provides a system for modded plugins to interact with BetterAmongUs through reflection-based events.
/// Allows other plugins to define event handlers without direct assembly references.
/// </summary>
/// <remarks>
/// Plugins can implement event handlers by creating a class with specific method signatures.
/// The system automatically discovers and invokes these handlers at runtime.
/// </remarks>
public class BAUModdedSupportEvents
public static class BAUModdedSupportEvents
{
// ============================================
// Method Structures
// ============================================
/// <summary>
/// Event triggered when BetterAmongUs is loading.
/// </summary>
internal static readonly BAUSupportEvent<bool?, BasePlugin> OnBAULoadEvent = new("bau:event.bau_load");
/// <summary>
/// Default implementation of the OnBAULoad event handler.
/// Event triggered when BetterAmongUs game options have been loaded.
/// </summary>
/// <param name="bauPlugin">The BetterAmongUs plugin instance.</param>
/// <returns>Always returns true to indicate successful loading.</returns>
public bool OnBAULoad(BasePlugin bauPlugin) => true;
internal static readonly BAUSupportEvent<object?, object[]> OnBAUOptionsLoadedEvent = new("bau:event.options_load");
/// <summary>
/// Default implementation of the OnBAUOptionsLoaded event handler.
/// Event triggered when BetterAmongUs configuration entries have been loaded.
/// </summary>
/// <param name="options">The loaded game options from BetterAmongUs.</param>
public void OnBAUOptionsLoaded(object[] options) { }
/// <summary>
/// Default implementation of the OnBAUConfigEntriesLoaded event handler.
/// </summary>
/// <param name="configs">An array of BepInEx configuration entries from BetterAmongUs.</param>
public void OnBAUConfigEntriesLoaded(ConfigEntryBase[] configs) { }
// ============================================
// Method Structures
// ============================================
/// <summary>
/// Invokes the OnBAULoad event handler for all loaded plugins.
/// </summary>
/// <param name="bauPlugin">The BetterAmongUs plugin instance.</param>
/// <returns>
/// Returns false if any plugin's OnBAULoad handler returns false, otherwise returns true.
/// Returns true if no plugins implement the handler.
/// </returns>
internal static bool InvokeAll_OnBAULoad(BasePlugin bauPlugin)
{
foreach (var pluginInfo in IL2CPPChainloader.Instance.Plugins.Values)
{
var plugin = (BasePlugin)pluginInfo.Instance;
if (InvokePluginMethod<bool?>(plugin, nameof(OnBAULoad), defaultValue: true, bauPlugin) == false)
{
return false;
}
}
return true;
}
/// <summary>
/// Invokes the OnBAUOptionsLoaded event handler for all loaded plugins.
/// </summary>
/// <param name="options">The loaded game options from BetterAmongUs.</param>
internal static void InvokeAll_OnBAUOptionsLoaded(object[] options)
{
foreach (var pluginInfo in IL2CPPChainloader.Instance.Plugins.Values)
{
var plugin = (BasePlugin)pluginInfo.Instance;
InvokePluginMethod<object>(plugin, nameof(OnBAUOptionsLoaded), parameters: options);
}
}
/// <summary>
/// Invokes the OnBAUConfigEntriesLoaded event handler for all loaded plugins.
/// </summary>
/// <param name="configs">An array of BepInEx configuration entries from BetterAmongUs.</param>
internal static void InvokeAll_OnBAUConfigEntriesLoaded(ConfigEntryBase[] configs)
{
foreach (var pluginInfo in IL2CPPChainloader.Instance.Plugins.Values)
{
var plugin = (BasePlugin)pluginInfo.Instance;
InvokePluginMethod<object>(plugin, nameof(OnBAUConfigEntriesLoaded), parameters: configs);
}
}
/// <summary>
/// Invokes a specific method on a plugin's event class using reflection.
/// </summary>
/// <typeparam name="T">The expected return type of the method.</typeparam>
/// <param name="plugin">The plugin instance to invoke the method on.</param>
/// <param name="methodName">The name of the method to invoke.</param>
/// <param name="defaultValue">The value to return if the method cannot be invoked.</param>
/// <param name="parameters">The parameters to pass to the method.</param>
/// <returns>
/// The result of the method invocation, or <paramref name="defaultValue"/> if the method
/// cannot be found, invocation fails, or the return type doesn't match <typeparamref name="T"/>.
/// </returns>
private static T? InvokePluginMethod<T>(BasePlugin plugin, string methodName, T? defaultValue = default, params object[] parameters)
{
var eventClass = GetEventClass(plugin);
if (eventClass == null) return defaultValue;
var method = eventClass.GetType().GetMethod(methodName,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (method == null) return defaultValue;
try
{
var result = method.Invoke(eventClass, parameters);
return result is T typedResult ? typedResult : defaultValue;
}
catch
{
return defaultValue;
}
}
/// <summary>
/// Retrieves the event class instance from a plugin using reflection.
/// </summary>
/// <param name="plugin">The plugin instance to search for the event class.</param>
/// <returns>
/// The event class instance if found, otherwise null.
/// </returns>
private static object? GetEventClass(BasePlugin plugin)
{
const string EventFieldName = "BAUEvents";
var field = plugin.GetType().GetField(EventFieldName,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
if (field == null) return null;
var value = field.IsStatic
? field.GetValue(null)
: field.GetValue(plugin);
return value?.GetType().IsClass == true ? value : null;
}
internal static readonly BAUSupportEvent<object?, ConfigEntryBase[]> OnBAUConfigEntriesLoadedEvent = new("bau:event.configs_load");
}
+13 -29
View File
@@ -1,9 +1,6 @@
#pragma warning disable CA2211
using BepInEx;
using BepInEx.Unity.IL2CPP;
using BetterAmongUs.Helpers;
using System.Reflection;
namespace BetterAmongUs.Modules.Support;
@@ -202,6 +199,8 @@ public static class BAUModdedSupportFlags
private static readonly HashSet<string> _flags = [];
private static bool _initialized = false;
private static readonly BAUSupportVar<string[]> BAUSupportFlags = new("bau:flags");
/// <summary>
/// Initializes the modded support system by collecting flags from all loaded plugins.
/// </summary>
@@ -211,38 +210,23 @@ public static class BAUModdedSupportFlags
return;
_initialized = true;
List<string> flags = [];
foreach (var pluginInfo in IL2CPPChainloader.Instance.Plugins.Values)
{
TryGetFlags((BasePlugin)pluginInfo.Instance);
}
}
if (pluginInfo == null)
continue;
/// <summary>
/// Attempts to extract BAUFlags from a loaded plugin's fields.
/// </summary>
/// <param name="plugin">The plugin instance to extract flags from.</param>
private static void TryGetFlags(BasePlugin plugin)
{
var field = plugin.GetType().GetField("BAUFlags",
BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
if (field == null)
return;
var value = field.IsStatic ? field.GetValue(null) : field.GetValue(plugin);
if (value is not IEnumerable<string> strings)
return;
var pluginName = plugin.GetType().GetCustomAttribute<BepInPlugin>()?.Name ?? plugin.GetType().Name;
foreach (var flag in strings)
{
if (_flags.Add(flag))
var modFlags = BAUSupportFlags.GetValue(pluginInfo);
if (modFlags != null)
{
Logger_.Log($"Loaded '{flag}' flag from {pluginName}", "BAUModdedSupport");
flags.AddRange(modFlags);
}
}
foreach (var flag in flags.ToHashSet())
{
AddFlag(flag);
}
}
/// <summary>
+229
View File
@@ -0,0 +1,229 @@
using BepInEx;
using BepInEx.Unity.IL2CPP;
using BetterAmongUs.Helpers;
using System.ComponentModel;
using System.Reflection;
namespace BetterAmongUs.Modules.Support;
/// <summary>
/// Base class for reflection-based event systems that allow plugins to interact with BetterAmongUs.
/// Provides infrastructure for discovering and invoking static methods decorated with specific
/// <see cref="CategoryAttribute"/> flags across all loaded plugins.
/// </summary>
internal abstract class BAUSupportEvent(string attributeFlag)
{
/// <summary>
/// Gets the expected parameter types for the event handler method.
/// </summary>
protected abstract Type[] ArgTypes { get; }
/// <summary>
/// The category attribute flag used to identify target methods.
/// </summary>
protected string _attributeFlag = attributeFlag;
/// <summary>
/// Cache of discovered methods per plugin to avoid repeated reflection lookups.
/// </summary>
protected readonly Dictionary<PluginInfo, MethodInfo?> _methods = [];
/// <summary>
/// Invokes the event handler on all loaded plugins and collects their return values.
/// </summary>
/// <typeparam name="T">The expected return type of the event handler.</typeparam>
/// <param name="args">The arguments to pass to the event handler method.</param>
/// <returns>
/// An array containing the return values from all plugins that have a matching event handler.
/// Returns <c>default(T)</c> for plugins without a handler or if the handler returns null.
/// </returns>
protected T?[] InvokeMethodAll<T>(params object[] args)
{
List<T?> list = [];
foreach (var pluginInfo in IL2CPPChainloader.Instance.Plugins.Values)
{
if (pluginInfo == null)
continue;
list.Add(InvokeMethod<T>(pluginInfo, args));
}
return [.. list];
}
/// <summary>
/// Invokes the event handler on a specific plugin.
/// </summary>
/// <typeparam name="T">The expected return type of the event handler.</typeparam>
/// <param name="pluginInfo">The plugin to invoke the handler on.</param>
/// <param name="args">The arguments to pass to the event handler method.</param>
/// <returns>
/// The return value from the event handler, or <c>default(T)</c> if no handler is found
/// or an exception occurs during invocation.
/// </returns>
protected T? InvokeMethod<T>(PluginInfo pluginInfo, params object[] args)
{
if (pluginInfo == null || pluginInfo.Instance == null)
return default;
if (!_methods.TryGetValue(pluginInfo, out var method))
{
method = FindMethod(pluginInfo);
_methods[pluginInfo] = method;
}
if (method == null)
return default;
try
{
return (T?)method.Invoke(null, args);
}
catch (Exception ex)
{
Logger_.Error($"Invoking event '{_attributeFlag}' for {pluginInfo.GetType().Name}: {ex}");
return default;
}
}
/// <summary>
/// Finds a static method in the plugin's assembly that matches the configured attribute flag and parameter types.
/// </summary>
/// <param name="pluginInfo">The plugin to search for matching methods.</param>
/// <returns>
/// A <see cref="MethodInfo"/> representing the matching method, or <c>null</c> if no matching method is found.
/// </returns>
private MethodInfo? FindMethod(PluginInfo pluginInfo)
{
var assembly = pluginInfo.Instance.GetType().Assembly;
var expectedArgTypes = ArgTypes;
foreach (var type in assembly.GetTypes())
{
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static))
{
var attr = method.GetCustomAttribute<CategoryAttribute>();
if (attr == null || attr.Category != _attributeFlag)
continue;
var parameters = method.GetParameters();
if (parameters.Length != expectedArgTypes.Length)
continue;
bool match = true;
for (int i = 0; i < parameters.Length; i++)
{
if (parameters[i].ParameterType != expectedArgTypes[i])
{
match = false;
break;
}
}
if (match)
{
return method;
}
}
}
return null;
}
}
/// <summary>
/// Represents an event with no parameters that plugins can handle.
/// </summary>
/// <typeparam name="ReturnT">The return type of the event handler.</typeparam>
internal class BAUSupportEvent<ReturnT>(string attributeFlag) : BAUSupportEvent(attributeFlag)
{
/// <inheritdoc/>
protected override Type[] ArgTypes => [];
/// <summary>
/// Invokes the event handler on a specific plugin.
/// </summary>
/// <param name="pluginInfo">The plugin to invoke the handler on.</param>
/// <returns>The return value from the event handler, or <c>default(ReturnT)</c> if not found.</returns>
internal ReturnT? Invoke(PluginInfo pluginInfo)
{
return InvokeMethod<ReturnT>(pluginInfo);
}
/// <summary>
/// Invokes the event handler on all loaded plugins and collects their return values.
/// </summary>
/// <returns>An array containing the return values from all plugins that have a matching event handler.</returns>
internal ReturnT?[] InvokeAll()
{
return InvokeMethodAll<ReturnT>();
}
}
/// <summary>
/// Represents an event with one parameter that plugins can handle.
/// </summary>
/// <typeparam name="ReturnT">The return type of the event handler.</typeparam>
/// <typeparam name="Arg1">The type of the first parameter.</typeparam>
internal sealed class BAUSupportEvent<ReturnT, Arg1>(string attributeFlag) : BAUSupportEvent(attributeFlag)
{
/// <inheritdoc/>
protected override Type[] ArgTypes => [typeof(Arg1)];
/// <summary>
/// Invokes the event handler on a specific plugin.
/// </summary>
/// <param name="pluginInfo">The plugin to invoke the handler on.</param>
/// <param name="arg1">The first argument to pass to the event handler.</param>
/// <returns>The return value from the event handler, or <c>default(ReturnT)</c> if not found.</returns>
internal ReturnT? Invoke(PluginInfo pluginInfo, Arg1 arg1)
{
return InvokeMethod<ReturnT>(pluginInfo, arg1);
}
/// <summary>
/// Invokes the event handler on all loaded plugins and collects their return values.
/// </summary>
/// <param name="arg1">The first argument to pass to all event handlers.</param>
/// <returns>An array containing the return values from all plugins that have a matching event handler.</returns>
internal ReturnT?[] InvokeAll(Arg1 arg1)
{
return InvokeMethodAll<ReturnT>(arg1);
}
}
/// <summary>
/// Represents an event with two parameters that plugins can handle.
/// </summary>
/// <typeparam name="ReturnT">The return type of the event handler.</typeparam>
/// <typeparam name="Arg1">The type of the first parameter.</typeparam>
/// <typeparam name="Arg2">The type of the second parameter.</typeparam>
internal sealed class BAUSupportEvent<ReturnT, Arg1, Arg2>(string attributeFlag) : BAUSupportEvent(attributeFlag)
{
/// <inheritdoc/>
protected override Type[] ArgTypes => [typeof(Arg1), typeof(Arg2)];
/// <summary>
/// Invokes the event handler on a specific plugin.
/// </summary>
/// <param name="pluginInfo">The plugin to invoke the handler on.</param>
/// <param name="arg1">The first argument to pass to the event handler.</param>
/// <param name="arg2">The second argument to pass to the event handler.</param>
/// <returns>The return value from the event handler, or <c>default(ReturnT)</c> if not found.</returns>
internal ReturnT? Invoke(PluginInfo pluginInfo, Arg1 arg1, Arg2 arg2)
{
return InvokeMethod<ReturnT>(pluginInfo, arg1, arg2);
}
/// <summary>
/// Invokes the event handler on all loaded plugins and collects their return values.
/// </summary>
/// <param name="arg1">The first argument to pass to all event handlers.</param>
/// <param name="arg2">The second argument to pass to all event handlers.</param>
/// <returns>An array containing the return values from all plugins that have a matching event handler.</returns>
internal ReturnT?[] InvokeAll(Arg1 arg1, Arg2 arg2)
{
return InvokeMethodAll<ReturnT>(arg1, arg2);
}
}
+78
View File
@@ -0,0 +1,78 @@
using BepInEx;
using System.ComponentModel;
using System.Reflection;
namespace BetterAmongUs.Modules.Support;
/// <summary>
/// Provides a mechanism to retrieve values from plugins through reflection-based property/field access.
/// </summary>
/// <typeparam name="T">The type of value to retrieve from the target plugin.</typeparam>
internal sealed class BAUSupportVar<T>(string attributeFlag)
{
private readonly string _attributeFlag = attributeFlag;
private readonly Dictionary<PluginInfo, Func<T?>?> _getters = [];
/// <summary>
/// Retrieves the value from the specified plugin that matches the configured attribute flag.
/// </summary>
/// <param name="pluginInfo">The plugin information for the target plugin.</param>
/// <returns>
/// The value of the field or property decorated with the matching <see cref="CategoryAttribute"/>,
/// or <c>default(T)</c> if no matching member is found or the plugin instance is null.
/// </returns>
internal T? GetValue(PluginInfo pluginInfo)
{
if (pluginInfo == null || pluginInfo.Instance == null)
return default;
if (!_getters.TryGetValue(pluginInfo, out var getter))
{
getter = FindGetter(pluginInfo);
if (getter == null)
{
getter = () => default;
}
_getters[pluginInfo] = getter;
}
return getter();
}
/// <summary>
/// Locates a static field or property in the plugin's assembly that is decorated with the
/// configured <see cref="CategoryAttribute"/>.
/// </summary>
/// <param name="pluginInfo">The plugin information for the target plugin.</param>
/// <returns>
/// A function delegate that retrieves the value from the matching member,
/// or <c>null</c> if no matching member is found.
/// </returns>
private Func<T?>? FindGetter(PluginInfo pluginInfo)
{
foreach (var type in pluginInfo.Instance.GetType().Assembly.GetTypes())
{
// Search for matching static fields
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static))
{
var attr = field.GetCustomAttribute<CategoryAttribute>();
if (attr != null && attr.Category == _attributeFlag)
{
return () => (T?)field.GetValue(null);
}
}
// Search for matching static properties
foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static))
{
var attr = prop.GetCustomAttribute<CategoryAttribute>();
if (attr != null && attr.Category == _attributeFlag)
{
return () => (T?)prop.GetValue(null);
}
}
}
return null;
}
}