mirror of
https://github.com/D1GQ/BetterAmongUs.git
synced 2026-10-20 01:46:19 +00:00
Rework BAUModdedSupport
This commit is contained in:
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<T> 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
@@ -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();
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user