using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using BepInEx; using BepInEx.Unity.IL2CPP; using Il2CppInterop.Runtime.Injection; using MiraAPI.Colors; using MiraAPI.Events; using MiraAPI.GameEnd; using MiraAPI.GameModes; using MiraAPI.GameOptions; using MiraAPI.GameOptions.Attributes; using MiraAPI.Hud; using MiraAPI.Modifiers; using MiraAPI.Roles; using MiraAPI.Utilities; using Reactor.Localization.Utilities; using Reactor.Networking; using Reactor.Utilities; namespace MiraAPI.PluginLoading; /// /// Mira Plugin manager. /// public sealed class MiraPluginManager { private readonly Dictionary _registeredPlugins = []; internal MiraPluginInfo[] RegisteredPlugins() => [.. _registeredPlugins.Values]; internal static MiraPluginManager Instance { get; private set; } = new(); internal void Initialize() { Instance = this; ModdedOptionsManager.RegisterGroup(typeof(GameModeOption)); // TODO: dont do this CustomGameModeManager.RegisterDefaultMode(); CustomGameModeManager.GetAndSetGameMode(); IL2CPPChainloader.Instance.PluginLoad += RegisterPlugin; IL2CPPChainloader.Instance.Finished += PaletteManager.RegisterAllColors; IL2CPPChainloader.Instance.Finished += MiraEventManager.SortAllHandlers; IL2CPPChainloader.Instance.Finished += () => { CustomButtonManager.Buttons = new ReadOnlyCollection(CustomButtonManager.CustomButtons); }; IL2CPPChainloader.Instance.Finished += () => { var dict = new Dictionary(); foreach (var (key, value) in CustomGameModeManager.IdToModeMap) { if (value is DefaultMode) { continue; } dict.Add(value.Name, key); GameModesHelpers.ModeToName.Add((AmongUs.GameOptions.GameModes)key, CustomStringName.CreateAndRegister(value.Name)); } EnumInjector.InjectEnumValues(dict); }; } private void RegisterPlugin(PluginInfo pluginInfo, Assembly assembly, BasePlugin plugin) { if (plugin is not IMiraPlugin miraPlugin || _registeredPlugins.ContainsKey(assembly)) { return; } var info = new MiraPluginInfo(miraPlugin, pluginInfo); var roles = new List(); foreach (var type in assembly.GetTypes()) { if (type.GetCustomAttribute() != null) { continue; } foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public)) { var eventAttribute = method.GetCustomAttribute(); if (eventAttribute != null) { var parameters = method.GetParameters(); if (parameters.Length != 1 || !parameters[0].ParameterType.IsSubclassOf(typeof(MiraEvent))) { Logger.Error($"Invalid event registration method {method.Name} in {type.Name}"); continue; } var paramType = parameters[0].ParameterType; MiraEventManager.RegisterEventHandler(paramType, method, eventAttribute.Priority); } } if (RegisterModifier(type, info)) { continue; } if (RegisterOptions(type, info)) { continue; } if (RegisterRoleAttribute(type, info, out var role)) { roles.Add(role); continue; } if (RegisterButtonAttribute(type, info)) { continue; } if (RegisterGameOver(type)) { continue; } if (RegisterGameModeAttribute(type, info)) { continue; } RegisterColorClasses(type); } info.OptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority)); CustomRoleManager.RegisterRoleTypes(roles, info); _registeredPlugins.Add(assembly, info); Logger.Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API."); } /// /// Get a mira plugin by its GUID. /// /// The plugin GUID. /// A MiraPluginInfo. public static MiraPluginInfo? GetPluginByGuid(string pluginId) { return Instance._registeredPlugins.Values.FirstOrDefault(plugin => plugin.PluginId == pluginId); } private static bool RegisterGameOver(Type type) { try { return GameOverManager.RegisterGameOver(type); } catch (Exception e) { Logger.Error($"Failed to register game over {type.Name}: {e}"); return false; } } private static bool RegisterOptions(Type type, MiraPluginInfo pluginInfo) { try { if (!type.IsAssignableTo(typeof(AbstractOptionGroup))) { return false; } if (!ModdedOptionsManager.RegisterGroup(type, pluginInfo)) { return false; } foreach (var property in type.GetProperties()) { if (property.PropertyType.IsAssignableTo(typeof(IModdedOption))) { ModdedOptionsManager.RegisterPropertyOption(type, property, pluginInfo); continue; } var attribute = property.GetCustomAttribute(); if (attribute == null) { continue; } ModdedOptionsManager.RegisterAttributeOption(type, attribute, property, pluginInfo); } return true; } catch (Exception e) { Logger.Error($"Failed to register options for {type.Name}: {e}"); } return false; } private static bool RegisterRoleAttribute(Type type, MiraPluginInfo pluginInfo, [NotNullWhen(true)] out Type? role) { role = null; try { if (!(typeof(RoleBehaviour).IsAssignableFrom(type) && typeof(ICustomRole).IsAssignableFrom(type))) { return false; } if (!ModList.GetById(pluginInfo.PluginId).IsRequiredOnAllClients) { Logger.Error("Custom roles are only supported on all clients."); return false; } role = type; return true; } catch (Exception e) { Logger.Error($"Failed to register role for {type.Name}: {e}"); } return false; } private static void RegisterColorClasses(Type type) { try { if (type.GetCustomAttribute() == null) { return; } if (!type.IsStatic()) { Logger.Error($"Color class {type.Name} must be static."); return; } foreach (var property in type.GetProperties()) { if (property.PropertyType != typeof(CustomColor)) { continue; } if (property.GetValue(null) is not CustomColor color) { Logger.Error($"Color property {property.Name} in {type.Name} is not a CustomColor."); continue; } PaletteManager.CustomColors.Add(color); } } catch (Exception e) { Logger.Error($"Failed to register color class {type.Name}: {e}"); } } private static bool RegisterModifier(Type type, MiraPluginInfo info) { try { return ModifierManager.RegisterModifier(type, info); } catch (Exception e) { Logger.Error($"Failed to register modifier {type.Name}: {e}"); return false; } } private static bool RegisterButtonAttribute(Type type, MiraPluginInfo pluginInfo) { try { return CustomButtonManager.RegisterButton(type, pluginInfo); } catch (Exception e) { Logger.Error($"Failed to register button {type.Name}: {e}"); } return false; } private static bool RegisterGameModeAttribute(Type type, MiraPluginInfo pluginInfo) { try { if (type.IsAssignableTo(typeof(AbstractGameMode))) { // TODO: bool CustomGameModeManager.RegisterGameMode(type, pluginInfo); return true; } } catch (Exception e) { Logger.Error($"Failed to register gamemode {type.Name}: {e}"); } return false; } }