From 382fabb4bbb2b42c7c192030c68492b951e04289 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Thu, 22 Aug 2024 13:08:05 -0400 Subject: [PATCH] Change options system to force all options into groups --- .../{ => Buttons}/ExampleButton.cs | 2 +- MiraAPI.Example/CustomRoleSettings.cs | 19 --- .../{ => Options}/ExampleOptions.cs | 2 +- .../{ => Options}/ExampleOptions2.cs | 2 +- .../Options/Roles/CustomRole2Settings.cs | 17 ++ .../Options/Roles/CustomRoleSettings.cs | 17 ++ MiraAPI.Example/{ => Roles}/CustomRole.cs | 9 +- MiraAPI.Example/{ => Roles}/CustomRole2.cs | 9 +- MiraAPI.Example/{ => Roles}/CustomRole3.cs | 2 +- MiraAPI.Example/SomeRandomNonGroupClass.cs | 9 - MiraAPI/GameOptions/IModdedOption.cs | 1 - MiraAPI/GameOptions/ModdedGroupSingleton.cs | 2 +- MiraAPI/GameOptions/ModdedOptionsManager.cs | 154 +++++++++++------- .../GameOptions/OptionTypes/ModdedOption.cs | 1 - .../Patches/Options/GameOptionsMenuPatch.cs | 44 +---- MiraAPI/PluginLoading/MiraPluginManager.cs | 63 +++---- 16 files changed, 163 insertions(+), 190 deletions(-) rename MiraAPI.Example/{ => Buttons}/ExampleButton.cs (95%) delete mode 100644 MiraAPI.Example/CustomRoleSettings.cs rename MiraAPI.Example/{ => Options}/ExampleOptions.cs (95%) rename MiraAPI.Example/{ => Options}/ExampleOptions2.cs (95%) create mode 100644 MiraAPI.Example/Options/Roles/CustomRole2Settings.cs create mode 100644 MiraAPI.Example/Options/Roles/CustomRoleSettings.cs rename MiraAPI.Example/{ => Roles}/CustomRole.cs (71%) rename MiraAPI.Example/{ => Roles}/CustomRole2.cs (71%) rename MiraAPI.Example/{ => Roles}/CustomRole3.cs (94%) delete mode 100644 MiraAPI.Example/SomeRandomNonGroupClass.cs diff --git a/MiraAPI.Example/ExampleButton.cs b/MiraAPI.Example/Buttons/ExampleButton.cs similarity index 95% rename from MiraAPI.Example/ExampleButton.cs rename to MiraAPI.Example/Buttons/ExampleButton.cs index d7058bc..a7aa9fb 100644 --- a/MiraAPI.Example/ExampleButton.cs +++ b/MiraAPI.Example/Buttons/ExampleButton.cs @@ -3,7 +3,7 @@ using MiraAPI.Utilities.Assets; using Reactor.Utilities; using UnityEngine; -namespace MiraAPI.Example; +namespace MiraAPI.Example.Buttons; [RegisterButton] public class ExampleButton : CustomActionButton diff --git a/MiraAPI.Example/CustomRoleSettings.cs b/MiraAPI.Example/CustomRoleSettings.cs deleted file mode 100644 index d8f6d7c..0000000 --- a/MiraAPI.Example/CustomRoleSettings.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using MiraAPI.GameOptions; -using MiraAPI.GameOptions.Attributes; -using MiraAPI.GameOptions.OptionTypes; - -namespace MiraAPI.Example; - -public class CustomRoleSettings : IModdedOptionGroup -{ - public string GroupName => "Custom Role"; - - public Type AdvancedRole => typeof(CustomRole); - - [ModdedNumberOption("amogus", min: 3, max: 9)] public float Amogus { get; set; } = 1; - - [ModdedEnumOption("dwayne the rock johnson", typeof(TestEnum))] public TestEnum DwayneTheRockJohnson { get; set; } = TestEnum.Hello; - - public ModdedToggleOption TestToggle { get; } = new ("test toggle", false); -} \ No newline at end of file diff --git a/MiraAPI.Example/ExampleOptions.cs b/MiraAPI.Example/Options/ExampleOptions.cs similarity index 95% rename from MiraAPI.Example/ExampleOptions.cs rename to MiraAPI.Example/Options/ExampleOptions.cs index 0cbb0e3..6c674a6 100644 --- a/MiraAPI.Example/ExampleOptions.cs +++ b/MiraAPI.Example/Options/ExampleOptions.cs @@ -2,7 +2,7 @@ using MiraAPI.GameOptions.Attributes; using UnityEngine; -namespace MiraAPI.Example; +namespace MiraAPI.Example.Options; public class ExampleOptions : IModdedOptionGroup { diff --git a/MiraAPI.Example/ExampleOptions2.cs b/MiraAPI.Example/Options/ExampleOptions2.cs similarity index 95% rename from MiraAPI.Example/ExampleOptions2.cs rename to MiraAPI.Example/Options/ExampleOptions2.cs index e6b8fb6..42c316f 100644 --- a/MiraAPI.Example/ExampleOptions2.cs +++ b/MiraAPI.Example/Options/ExampleOptions2.cs @@ -1,7 +1,7 @@ using MiraAPI.GameOptions; using MiraAPI.GameOptions.OptionTypes; -namespace MiraAPI.Example; +namespace MiraAPI.Example.Options; public class ExampleOptions2 : IModdedOptionGroup { diff --git a/MiraAPI.Example/Options/Roles/CustomRole2Settings.cs b/MiraAPI.Example/Options/Roles/CustomRole2Settings.cs new file mode 100644 index 0000000..9712dcd --- /dev/null +++ b/MiraAPI.Example/Options/Roles/CustomRole2Settings.cs @@ -0,0 +1,17 @@ +using System; +using MiraAPI.Example.Roles; +using MiraAPI.GameOptions; +using MiraAPI.GameOptions.Attributes; +using MiraAPI.GameOptions.OptionTypes; + +namespace MiraAPI.Example.Options.Roles; + +public class CustomRole2Settings : IModdedOptionGroup +{ + public string GroupName => "Custom Role"; + + public Type AdvancedRole => typeof(CustomRole2); + + [ModdedToggleOption("Teleportation ability")] public bool Teleport { get; set; } = true; + +} \ No newline at end of file diff --git a/MiraAPI.Example/Options/Roles/CustomRoleSettings.cs b/MiraAPI.Example/Options/Roles/CustomRoleSettings.cs new file mode 100644 index 0000000..484ad6f --- /dev/null +++ b/MiraAPI.Example/Options/Roles/CustomRoleSettings.cs @@ -0,0 +1,17 @@ +using System; +using MiraAPI.Example.Roles; +using MiraAPI.GameOptions; +using MiraAPI.GameOptions.Attributes; +using MiraAPI.GameOptions.OptionTypes; + +namespace MiraAPI.Example.Options.Roles; + +public class CustomRoleSettings : IModdedOptionGroup +{ + public string GroupName => "Custom Role"; + + public Type AdvancedRole => typeof(CustomRole); + + [ModdedNumberOption("Testing level", min: 3, max: 9)] public float TestingLevel { get; set; } = 4; + +} \ No newline at end of file diff --git a/MiraAPI.Example/CustomRole.cs b/MiraAPI.Example/Roles/CustomRole.cs similarity index 71% rename from MiraAPI.Example/CustomRole.cs rename to MiraAPI.Example/Roles/CustomRole.cs index 84c9274..44dfbe8 100644 --- a/MiraAPI.Example/CustomRole.cs +++ b/MiraAPI.Example/Roles/CustomRole.cs @@ -1,9 +1,8 @@ -using MiraAPI.GameOptions.Attributes; -using MiraAPI.Roles; +using MiraAPI.Roles; using MiraAPI.Utilities.Assets; using UnityEngine; -namespace MiraAPI.Example; +namespace MiraAPI.Example.Roles; [RegisterCustomRole] public class CustomRole : ImpostorRole, ICustomRole @@ -14,7 +13,5 @@ public class CustomRole : ImpostorRole, ICustomRole public Color RoleColor => Palette.Orange; public ModdedRoleTeams Team => ModdedRoleTeams.Impostor; public LoadableAsset OptionsScreenshot => MiraAssets.Banner; - - [ModdedNumberOption("Testing level", min: 3, max: 9, roleType: typeof(CustomRole))] public float TestingLevel { get; set; } = 4; - + } \ No newline at end of file diff --git a/MiraAPI.Example/CustomRole2.cs b/MiraAPI.Example/Roles/CustomRole2.cs similarity index 71% rename from MiraAPI.Example/CustomRole2.cs rename to MiraAPI.Example/Roles/CustomRole2.cs index e03e057..b5bbe48 100644 --- a/MiraAPI.Example/CustomRole2.cs +++ b/MiraAPI.Example/Roles/CustomRole2.cs @@ -1,9 +1,8 @@ -using MiraAPI.GameOptions.Attributes; -using MiraAPI.Roles; +using MiraAPI.Roles; using MiraAPI.Utilities.Assets; using UnityEngine; -namespace MiraAPI.Example; +namespace MiraAPI.Example.Roles; [RegisterCustomRole] public class CustomRole2 : CrewmateRole, ICustomRole @@ -14,7 +13,5 @@ public class CustomRole2 : CrewmateRole, ICustomRole public Color RoleColor => Palette.AcceptedGreen; public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate; public LoadableAsset OptionsScreenshot => MiraAssets.Banner; - - [ModdedToggleOption("Teleportation ability", roleType: typeof(CustomRole2))] public bool Teleport { get; set; } = true; - + } \ No newline at end of file diff --git a/MiraAPI.Example/CustomRole3.cs b/MiraAPI.Example/Roles/CustomRole3.cs similarity index 94% rename from MiraAPI.Example/CustomRole3.cs rename to MiraAPI.Example/Roles/CustomRole3.cs index bf309d8..a97f9df 100644 --- a/MiraAPI.Example/CustomRole3.cs +++ b/MiraAPI.Example/Roles/CustomRole3.cs @@ -2,7 +2,7 @@ using MiraAPI.Utilities.Assets; using UnityEngine; -namespace MiraAPI.Example; +namespace MiraAPI.Example.Roles; [RegisterCustomRole] public class CustomRole3 : CrewmateRole, ICustomRole diff --git a/MiraAPI.Example/SomeRandomNonGroupClass.cs b/MiraAPI.Example/SomeRandomNonGroupClass.cs deleted file mode 100644 index b931ef6..0000000 --- a/MiraAPI.Example/SomeRandomNonGroupClass.cs +++ /dev/null @@ -1,9 +0,0 @@ -using MiraAPI.GameOptions.Attributes; - -namespace MiraAPI.Example; - -public class SomeRandomNonGroupClass -{ - [ModdedStringOption("Yeah, idk", ["Idk 1", "idk 2", "idk 3", "idk 4"])] public static int YeaIdk { get; set; } = 3; - [ModdedNumberOption("Aw man", min: 45, max: 95)] public float SussyLevel { get; set; } = 63; -} \ No newline at end of file diff --git a/MiraAPI/GameOptions/IModdedOption.cs b/MiraAPI/GameOptions/IModdedOption.cs index a041cc7..eaa7add 100644 --- a/MiraAPI/GameOptions/IModdedOption.cs +++ b/MiraAPI/GameOptions/IModdedOption.cs @@ -9,7 +9,6 @@ public interface IModdedOption { public uint Id { get; } public BaseGameSetting Data { get; } - public bool HasGroup { get; set; } public IMiraPlugin ParentMod { get; set; } public Type AdvancedRole { get; set; } public OptionBehaviour OptionBehaviour { get; protected set; } diff --git a/MiraAPI/GameOptions/ModdedGroupSingleton.cs b/MiraAPI/GameOptions/ModdedGroupSingleton.cs index 637d00d..265cc2a 100644 --- a/MiraAPI/GameOptions/ModdedGroupSingleton.cs +++ b/MiraAPI/GameOptions/ModdedGroupSingleton.cs @@ -9,7 +9,7 @@ public class ModdedGroupSingleton where T : IModdedOptionGroup public static T Instance { - get => _instance ??= ModdedOptionsManager.Groups.OfType().Single(); + get => _instance ??= ModdedOptionsManager.Groups.Keys.OfType().Single(); set { if (_instance != null) diff --git a/MiraAPI/GameOptions/ModdedOptionsManager.cs b/MiraAPI/GameOptions/ModdedOptionsManager.cs index 9af63ea..f61556c 100644 --- a/MiraAPI/GameOptions/ModdedOptionsManager.cs +++ b/MiraAPI/GameOptions/ModdedOptionsManager.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using MiraAPI.Networking; +using MiraAPI.PluginLoading; using Reactor.Networking.Rpc; namespace MiraAPI.GameOptions; @@ -13,80 +14,121 @@ namespace MiraAPI.GameOptions; public class ModdedOptionsManager { private static readonly Dictionary OptionAttributes = new(); + private static readonly Dictionary TypeToGroup = new(); public static readonly Dictionary ModdedOptions = new(); - public static readonly Dictionary> GroupedOptions = new(); + public static readonly Dictionary> Groups = new(); - public static readonly List Groups = []; - public static readonly Dictionary TypeToGroup = new(); public static uint NextId => _nextId++; private static uint _nextId = 1; + + internal static bool RegisterGroup(Type type, MiraPluginInfo pluginInfo) + { + var group = (IModdedOptionGroup)Activator.CreateInstance(type); + + if (group == null) + { + Logger.Error($"Failed to create group from {type.Name}"); + return false; + } - internal static IModdedOption RegisterOption(Type type, ModdedOptionAttribute attribute, PropertyInfo property) + if (TypeToGroup.ContainsKey(type)) + { + Logger.Error($"Group {type.Name} already exists."); + return false; + } + + Groups.Add(group, []); + TypeToGroup.Add(type, group); + pluginInfo.OptionGroups.Add(group); + + return true; + } + + internal static void RegisterPropertyOption(Type type, PropertyInfo property, MiraPluginInfo pluginInfo) + { + if (!TypeToGroup.TryGetValue(type, out var group)) + { + Logger.Error($"Failed to get group for {type.Name}"); + return; + } + + var option = (IModdedOption) property.GetValue(group); + + if (option == null) + { + Logger.Error($"Failed to get option for {property.Name}"); + return; + } + + option.ParentMod = pluginInfo.MiraPlugin; + option.AdvancedRole = group.AdvancedRole; + pluginInfo.Options.Add(option); + + Groups[group].Add(option); + } + + internal static void RegisterAttributeOption(Type type, ModdedOptionAttribute attribute, PropertyInfo property, MiraPluginInfo pluginInfo) { if (OptionAttributes.ContainsKey(property)) { - return null; + Logger.Error($"Property {property.Name} already has an attribute registered."); + return; } - object newObj = Activator.CreateInstance(type); - - IModdedOption result; - - try - { - result = attribute.CreateOption(property.GetValue(newObj), property); - } - catch (Exception e) + if (!TypeToGroup.TryGetValue(type, out var group)) { - Logger.Error(e); - return null; + Logger.Error($"Failed to get group for {type.Name}"); + return; } - if (result != null) + var option = attribute.CreateOption(property.GetValue(group), property); + + if (option == null) { - var setterOriginal = property.GetSetMethod(); - var setterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertySetterPatch)); - PluginSingleton.Instance.Harmony.Patch(setterOriginal, postfix: new HarmonyMethod(setterPatch)); - - var getterOriginal = property.GetGetMethod(); - var getterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyGetterPatch)); - PluginSingleton.Instance.Harmony.Patch(getterOriginal, prefix: new HarmonyMethod(getterPatch)); - - attribute.HolderOption = result; - - OptionAttributes.Add(property, attribute); - - if (TypeToGroup.ContainsKey(type)) - { - Logger.Error($"Grouping {attribute.Title} with {TypeToGroup[type].GroupName}"); - GroupedOptions[TypeToGroup[type]].Add(result); - result.HasGroup = true; - result.AdvancedRole = TypeToGroup[type].AdvancedRole; - } + Logger.Error($"Failed to get option for {property.Name}"); + return; } + + var setterOriginal = property.GetSetMethod(); + var setterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertySetterPatch)); + PluginSingleton.Instance.Harmony.Patch(setterOriginal, postfix: new HarmonyMethod(setterPatch)); - return result; + var getterOriginal = property.GetGetMethod(); + var getterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyGetterPatch)); + PluginSingleton.Instance.Harmony.Patch(getterOriginal, prefix: new HarmonyMethod(getterPatch)); + + OptionAttributes.Add(property, attribute); + attribute.HolderOption = option; + + option.ParentMod = pluginInfo.MiraPlugin; + option.AdvancedRole = group.AdvancedRole; + pluginInfo.Options.Add(option); + + Groups[group].Add(option); } internal static void SyncAllOptions(int targetId=-1) { List data = []; - int count = 0; - foreach (var option in ModdedOptions.Values) + + var count = 0; + foreach (var netData in ModdedOptions.Values.Select(option => option.GetNetData())) { - var netData = option.GetNetData(); data.Add(netData); count += netData.GetLength(); - - if (count > 1000) + + if (count <= 1000) { - Rpc.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray()); - data.Clear(); - count = 0; + continue; } + + Rpc.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray()); + data.Clear(); + count = 0; } + if (data.Count > 0) { Rpc.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray()); @@ -98,11 +140,13 @@ public class ModdedOptionsManager { foreach (var netData in data) { - if (ModdedOptions.TryGetValue(netData.Id, out var option)) + if (!ModdedOptions.TryGetValue(netData.Id, out var option)) { - Logger.Error("Handling option " + option.Title); - option.HandleNetData(netData.Data); + continue; } + + Logger.Error("Handling option " + option.Title); + option.HandleNetData(netData.Data); } } @@ -111,21 +155,19 @@ public class ModdedOptionsManager { ModdedOptionAttribute attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod().Equals(__originalMethod)).Value; - if (attribute != null) - { - attribute.SetValue(value); - } + attribute?.SetValue(value); } public static bool PropertyGetterPatch(MethodBase __originalMethod, ref object __result) { ModdedOptionAttribute attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod().Equals(__originalMethod)).Value; - if (attribute != null) + if (attribute == null) { - __result = attribute.GetValue(); - return false; + return true; } - return true; + + __result = attribute.GetValue(); + return false; } } \ No newline at end of file diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs index 3021cd1..5bd07cb 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -12,7 +12,6 @@ public abstract class ModdedOption : IModdedOption { public uint Id { get; } public BaseGameSetting Data { get; protected init; } - public bool HasGroup { get; set; } = false; public IMiraPlugin ParentMod { get; set; } = null; public T Value { get; protected set; } public T DefaultValue { get; init; } diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index 66f0431..ea622cb 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -41,7 +41,7 @@ public static class GameOptionsMenuPatch categoryHeaderMasked.transform.localPosition = new Vector3(-0.903f, num, -2f); num -= 0.63f; - var filteredOpts = ModdedOptionsManager.GroupedOptions[group].Where(x=>x.Visible.Invoke()).ToList(); + var filteredOpts = ModdedOptionsManager.Groups[group].Where(x=>x.Visible.Invoke()).ToList(); foreach (var opt in filteredOpts) { @@ -78,48 +78,6 @@ public static class GameOptionsMenuPatch } } - var filteredOptions = GameSettingMenuPatches.SelectedMod.Options.Where(x => x.AdvancedRole is null && !x.HasGroup).ToList(); - - if (filteredOptions.Count > 0) - { - - CategoryHeaderMasked ungroupedHeader = Object.Instantiate(__instance.categoryHeaderOrigin, Vector3.zero, - Quaternion.identity, __instance.settingsContainer); - ungroupedHeader.SetHeader(CustomStringName.CreateAndRegister("Ungrouped Options"), 20); - ungroupedHeader.transform.localScale = Vector3.one * 0.63f; - ungroupedHeader.transform.localPosition = new Vector3(-0.903f, num, -2f); - - num -= 0.63f; - - - foreach (var opt in filteredOptions) - { - OptionBehaviour newOpt = opt.CreateOption(__instance.checkboxOrigin, __instance.numberOptionOrigin, - __instance.stringOptionOrigin, __instance.settingsContainer); - newOpt.transform.localPosition = new Vector3(0.952f, num, -2f); - newOpt.SetClickMask(__instance.ButtonClickMask); - newOpt.SetUpFromData(newOpt.data, 20); - - SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren(true); - for (int i = 0; i < componentsInChildren.Length; i++) - { - componentsInChildren[i].material.SetInt(PlayerMaterial.MaskLayer, 20); - } - - foreach (TextMeshPro textMeshPro in newOpt.GetComponentsInChildren(true)) - { - textMeshPro.fontMaterial.SetFloat(ShaderID.StencilComp, 3f); - textMeshPro.fontMaterial.SetFloat(ShaderID.Stencil, 20); - } - - __instance.Children.Add(newOpt); - - num -= 0.45f; - - newOpt.Initialize(); - } - } - __instance.scrollBar.SetYBoundsMax(-num - 1.65f); return false; diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index c42691b..45dccb2 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -37,8 +37,8 @@ internal class MiraPluginManager var id = MetadataHelper.GetMetadata(plugin.GetType()).GUID; var info = new MiraPluginInfo(id, plugin as IMiraPlugin, IL2CPPChainloader.Instance.Plugins[id]); - RegisterOptionsGroups(assembly, info); - RegisterOptionsAttributes(assembly, info); + RegisterAllOptions(assembly, info); + RegisterRoleAttribute(assembly, info); RegisterButtonAttribute(assembly); @@ -53,57 +53,32 @@ internal class MiraPluginManager return RegisteredPlugins.Values.First(plugin => plugin.PluginId == guid); } - private static void RegisterOptionsAttributes(Assembly assembly, MiraPluginInfo pluginInfo) + private static void RegisterAllOptions(Assembly assembly, MiraPluginInfo pluginInfo) { - foreach (var type in assembly.GetTypes()) + var filteredTypes = assembly.GetTypes().Where(type => type.IsAssignableTo(typeof(IModdedOptionGroup))); + + foreach (var type in filteredTypes) { - foreach (PropertyInfo property in type.GetProperties()) + if (!ModdedOptionsManager.RegisterGroup(type, pluginInfo)) { - foreach (var attribute in property.GetCustomAttributes()) - { - if (attribute.GetType().BaseType == typeof(ModdedOptionAttribute)) - { - pluginInfo.Options.Add(ModdedOptionsManager.RegisterOption(type, (ModdedOptionAttribute)attribute, property)); - } - } + continue; } - } - } - - private static void RegisterOptionsGroups(Assembly assembly, MiraPluginInfo pluginInfo) - { - foreach (var type in assembly.GetTypes()) - { - if (typeof(IModdedOptionGroup).IsAssignableFrom(type)) + + foreach (var property in type.GetProperties()) { - IModdedOptionGroup group = (IModdedOptionGroup)Activator.CreateInstance(type); - if (group == null) + if (property.PropertyType.IsAssignableTo(typeof(IModdedOption))) + { + ModdedOptionsManager.RegisterPropertyOption(type, property, pluginInfo); + continue; + } + + var attribute = property.GetCustomAttribute(); + if (attribute == null) { continue; } - ModdedOptionsManager.Groups.Add(group); - ModdedOptionsManager.TypeToGroup.Add(type, group); - ModdedOptionsManager.GroupedOptions.Add(group, []); - pluginInfo.OptionGroups.Add(group); - - foreach (var property in type.GetProperties()) - { - if (typeof(IModdedOption).IsAssignableFrom(property.PropertyType)) - { - IModdedOption option = (IModdedOption)property.GetValue(group); - if (option == null) - { - continue; - } - - option.HasGroup = true; - option.AdvancedRole = group.AdvancedRole; - - ModdedOptionsManager.GroupedOptions[group].Add(option); - pluginInfo.Options.Add(option); - } - } + ModdedOptionsManager.RegisterAttributeOption(type, attribute, property, pluginInfo); } } }