diff --git a/MiraAPI.Example/CustomRole.cs b/MiraAPI.Example/CustomRole.cs index 16568e9..bb4732c 100644 --- a/MiraAPI.Example/CustomRole.cs +++ b/MiraAPI.Example/CustomRole.cs @@ -3,7 +3,7 @@ using UnityEngine; namespace MiraAPI.Example { - [RegisterCustomRole(ExamplePlugin.Id)] + [RegisterCustomRole] public class CustomRole : CrewmateRole, ICustomRole { public string RoleName => "Test Role"; diff --git a/MiraAPI.Example/ExampleOptions.cs b/MiraAPI.Example/ExampleOptions.cs index 0f04cf0..44d7296 100644 --- a/MiraAPI.Example/ExampleOptions.cs +++ b/MiraAPI.Example/ExampleOptions.cs @@ -9,11 +9,11 @@ namespace MiraAPI.Example public string GroupName => "General Group"; public Color GroupColor => Color.red; - [ModdedToggleOption("Use Thing")] public bool useThing { get; set; } = true; - [ModdedToggleOption("Use another thing")] public bool useAnotherThing { get; set; } = false; + [ModdedToggleOption("Use Thing")] public bool useThing { get; set; } = false; + [ModdedToggleOption("Use another thing")] public bool useAnotherThing { get; set; } = true; [ModdedNumberOption("Sussy level", min: 0, max: 10)] public float sussyLevel { get; set; } = 4f; - [ModdedStringOption("Sus choices", ["hello", "hello 2", "hello 3"])] public int susChoices { get; set; } = 0; - [ModdedEnumOption("Enum test", typeof(TestEnum))] public TestEnum enumTest { get; set; } = TestEnum.Hello; + [ModdedStringOption("Sus choices", ["hello", "hello 2", "hello 3"])] public int susChoices { get; set; } = 2; + [ModdedEnumOption("Enum test", typeof(TestEnum))] public TestEnum enumTest { get; set; } = TestEnum.Bye; } public enum TestEnum diff --git a/MiraAPI.Example/ExamplePlugin.cs b/MiraAPI.Example/ExamplePlugin.cs index 007f0c4..2d0975c 100644 --- a/MiraAPI.Example/ExamplePlugin.cs +++ b/MiraAPI.Example/ExamplePlugin.cs @@ -1,7 +1,7 @@ using BepInEx; using BepInEx.Unity.IL2CPP; using HarmonyLib; -using MiraAPI.Utilities.Assets; +using MiraAPI.PluginLoading; using Reactor; using Reactor.Networking; using Reactor.Networking.Attributes; @@ -12,19 +12,11 @@ namespace MiraAPI.Example; [BepInProcess("Among Us.exe")] [BepInDependency(ReactorPlugin.Id)] [ReactorModFlags(ModFlags.RequireOnAllClients)] -public partial class ExamplePlugin : BasePlugin, IMiraConfig +public partial class ExamplePlugin : BasePlugin, IMiraPlugin { public Harmony Harmony { get; } = new(Id); - public static ExamplePlugin Instance { get; private set; } - public ModdedOptionTabSettings TabSettings => new ModdedOptionTabSettings() - { - Title = "MiraAPI Example Mod", - TabIcon = MiraAssets.Empty - }; - public override void Load() { - Instance = this; Harmony.PatchAll(); } } \ No newline at end of file diff --git a/MiraAPI.Example/SomeRandomNonGroupClass.cs b/MiraAPI.Example/SomeRandomNonGroupClass.cs index 76504f9..a1f637f 100644 --- a/MiraAPI.Example/SomeRandomNonGroupClass.cs +++ b/MiraAPI.Example/SomeRandomNonGroupClass.cs @@ -4,6 +4,7 @@ namespace MiraAPI.Example { public class SomeRandomNonGroupClass { - [ModdedStringOption("Yeah, idk", ["Idk 1", "idk 2", "idk 3", "idk 4"])] public static int yeaIdk { get; set; } = 2; + [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; } } diff --git a/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs index fe0943d..c1f3a19 100644 --- a/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs @@ -1,7 +1,6 @@ using MiraAPI.GameOptions.OptionTypes; using System; using System.Reflection; -using UnityEngine; namespace MiraAPI.GameOptions.Attributes { @@ -24,7 +23,6 @@ namespace MiraAPI.GameOptions.Attributes public override IModdedOption CreateOption(object value, PropertyInfo property) { - Debug.LogError(value.ToString()); var toggleOpt = new ModdedNumberOption(Title, (float)value, Min, Max, Increment, SuffixType, ZeroInfinity, RoleType); return toggleOpt; } diff --git a/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs index e2379b5..31e94f7 100644 --- a/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs @@ -15,23 +15,6 @@ namespace MiraAPI.GameOptions.Attributes RoleType = roleType; } - public static void Register(Assembly assembly) - { - foreach (var type in assembly.GetTypes()) - { - foreach (PropertyInfo property in type.GetProperties()) - { - foreach (var attribute in property.GetCustomAttributes()) - { - if (attribute.GetType().BaseType == typeof(ModdedOptionAttribute)) - { - ModdedOptionsManager.RegisterOption(assembly, type, (ModdedOptionAttribute)attribute, property); - } - } - } - } - } - public abstract void SetValue(object val); public abstract object GetValue(); public abstract IModdedOption CreateOption(object value, PropertyInfo property); diff --git a/MiraAPI/GameOptions/IModdedOption.cs b/MiraAPI/GameOptions/IModdedOption.cs index abe00eb..c6a3acc 100644 --- a/MiraAPI/GameOptions/IModdedOption.cs +++ b/MiraAPI/GameOptions/IModdedOption.cs @@ -1,4 +1,5 @@ -using System; +using MiraAPI.PluginLoading; +using System; using UnityEngine; namespace MiraAPI.GameOptions @@ -11,9 +12,8 @@ namespace MiraAPI.GameOptions public StringNames StringName { get; } public Func Visible { get; set; } public ModdedOptionGroup Group { get; set; } - public IMiraConfig ParentMod { get; set; } + public IMiraPlugin ParentMod { get; set; } public void ValueChanged(OptionBehaviour optionBehaviour); - public string GetHudStringText(); - public OptionBehaviour CreateOption(OptionBehaviour optionBehaviour, Transform container); + public OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container); } } diff --git a/MiraAPI/GameOptions/ModdedOptionGroup.cs b/MiraAPI/GameOptions/ModdedOptionGroup.cs index 6e7742b..14b5442 100644 --- a/MiraAPI/GameOptions/ModdedOptionGroup.cs +++ b/MiraAPI/GameOptions/ModdedOptionGroup.cs @@ -1,11 +1,12 @@ -using System; +using MiraAPI.PluginLoading; +using System; using UnityEngine; namespace MiraAPI.GameOptions { public class ModdedOptionGroup { - public IMiraConfig ParentMod; + public IMiraPlugin ParentMod; public GameObject Header; public string GroupName; public Func GroupVisible; diff --git a/MiraAPI/GameOptions/ModdedOptionsManager.cs b/MiraAPI/GameOptions/ModdedOptionsManager.cs index d8d5dcf..323f456 100644 --- a/MiraAPI/GameOptions/ModdedOptionsManager.cs +++ b/MiraAPI/GameOptions/ModdedOptionsManager.cs @@ -1,12 +1,10 @@ -using BepInEx.Unity.IL2CPP; -using HarmonyLib; +using HarmonyLib; using MiraAPI.GameOptions.Attributes; using Reactor.Utilities; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using UnityEngine; namespace MiraAPI.GameOptions { @@ -15,51 +13,11 @@ namespace MiraAPI.GameOptions public static List Options = []; public static List Groups = []; private static Dictionary OptionAttributes = new(); - private static Dictionary OriginalTypes = new(); - public static Dictionary RegisteredMods = new(); + public static Dictionary OriginalTypes = new(); - public static void Initialize() + public static IModdedOption RegisterOption(Type type, ModdedOptionAttribute attribute, PropertyInfo property) { - IL2CPPChainloader.Instance.PluginLoad += (_, assembly, plugin) => - { - if (plugin.GetType().GetInterfaces().Contains(typeof(IMiraConfig))) - { - IMiraConfig config = (IMiraConfig)Activator.CreateInstance(plugin.GetType()); - RegisteredMods.Add(assembly, config); - } - - RegisterOptionGroups(assembly); - - ModdedOptionAttribute.Register(assembly); - }; - } - - private static void RegisterOptionGroups(Assembly assembly) - { - foreach (var type in assembly.GetTypes()) - { - if (typeof(IModdedOptionGroup).IsAssignableFrom(type)) - { - IModdedOptionGroup group = (IModdedOptionGroup)Activator.CreateInstance(type); - ModdedOptionGroup newGroup = new ModdedOptionGroup() - { - AdvancedRole = group.AdvancedRole, - GroupColor = group.GroupColor, - GroupName = group.GroupName, - GroupVisible = group.GroupVisible - }; - - Groups.Add(newGroup); - OriginalTypes.Add(type, newGroup); - - newGroup.ParentMod = RegisteredMods[assembly]; - } - } - } - - public static void RegisterOption(Assembly assembly, Type type, ModdedOptionAttribute attribute, PropertyInfo property) - { - if (OptionAttributes.ContainsKey(property)) return; + if (OptionAttributes.ContainsKey(property)) return null; object newObj = Activator.CreateInstance(type); IModdedOption result = attribute.CreateOption(property.GetValue(newObj), property); @@ -74,17 +32,18 @@ namespace MiraAPI.GameOptions PluginSingleton.Instance.Harmony.Patch(getterOriginal, prefix: new HarmonyMethod(getterPatch)); attribute.HolderOption = result; - result.ParentMod = RegisteredMods[assembly]; Options.Add(result); OptionAttributes.Add(property, attribute); if (OriginalTypes.ContainsKey(type)) { - Debug.LogError($"grouping {attribute.Title} with {OriginalTypes[type].GroupName}"); + Logger.Error($"Grouping {attribute.Title} with {OriginalTypes[type].GroupName}"); result.Group = OriginalTypes[type]; } } + + return result; } public static void PropertySetterPatch(MethodBase __originalMethod, object value) diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs index 20f0225..9fd6a25 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using UnityEngine; +using Object = UnityEngine.Object; namespace MiraAPI.GameOptions.OptionTypes { @@ -14,19 +15,30 @@ namespace MiraAPI.GameOptions.OptionTypes Values = Enum.GetNames(enumType); } - public override OptionBehaviour CreateOption(OptionBehaviour optionBehaviour, Transform container) + public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container) { - var stringOption = (StringOption)UnityEngine.Object.Instantiate(optionBehaviour, container); + var stringOption = Object.Instantiate(stringOpt, container); + var vals = Values.Select(CustomStringName.CreateAndRegister).ToArray(); + var data = ScriptableObject.CreateInstance(); - stringOption.name = Title; + data.Title = StringName; + data.Type = global::OptionTypes.String; + data.Values = vals; + data.Index = Value; + + stringOption.data = data; stringOption.Title = StringName; - stringOption.Value = Value; - stringOption.Values = Values.Select(CustomStringName.CreateAndRegister).ToArray(); + stringOption.TitleText.text = Title; + stringOption.Values = vals; + stringOption.OnValueChanged = (Il2CppSystem.Action)ValueChanged; - stringOption.Initialize(); OptionBehaviour = stringOption; + stringOption.Initialize(); + stringOption.Value = Value; + stringOption.ValueText.text = Values[Value]; + stringOption.SetUpFromData(stringOption.data, 20); return stringOption; } @@ -46,6 +58,8 @@ namespace MiraAPI.GameOptions.OptionTypes var opt = OptionBehaviour as StringOption; opt.Value = newValue; + + DestroyableSingleton.Instance.Notifier.AddSettingsChangeMessage(StringName, OptionBehaviour.GetValueString(Value), false); } } } diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs index d585b50..02d7333 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs @@ -21,23 +21,26 @@ namespace MiraAPI.GameOptions.OptionTypes Value = Mathf.Clamp(defaultValue, min, max); } - public override OptionBehaviour CreateOption(OptionBehaviour optionBehaviour, Transform container) + public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container) { - var numberOption = (NumberOption)Object.Instantiate(optionBehaviour, container); + var numberOption = Object.Instantiate(numberOpt, Vector3.zero, Quaternion.identity, container); + var data = ScriptableObject.CreateInstance(); + data.Title = StringName; + data.Type = global::OptionTypes.Float; + data.Increment = Increment; + data.SuffixType = SuffixType; + data.FormatString = "0"; + data.ValidRange = new FloatRange(Min, Max); + data.ZeroIsInfinity = ZeroInfinity; + data.Value = Value; - numberOption.name = Title; - numberOption.Title = StringName; - numberOption.Value = Value; - numberOption.Increment = Increment; - numberOption.SuffixType = SuffixType; - numberOption.FormatString = "0"; - numberOption.ValidRange = new FloatRange(Min, Max); - numberOption.ZeroIsInfinity = ZeroInfinity; + numberOption.data = data; + numberOption.SetUpFromData(numberOption.data, 20); numberOption.OnValueChanged = (Il2CppSystem.Action)ValueChanged; - numberOption.Initialize(); - OptionBehaviour = numberOption; + numberOption.Value = Value; + return numberOption; } @@ -59,6 +62,8 @@ namespace MiraAPI.GameOptions.OptionTypes var opt = OptionBehaviour as NumberOption; opt.Value = newValue; + + DestroyableSingleton.Instance.Notifier.AddSettingsChangeMessage(StringName, OptionBehaviour.GetValueString(Value), false); } } } diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs index d5581e4..889ea8e 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -1,4 +1,5 @@ -using MiraAPI.Roles; +using MiraAPI.PluginLoading; +using MiraAPI.Roles; using Reactor.Localization.Utilities; using System; using UnityEngine; @@ -16,7 +17,7 @@ namespace MiraAPI.GameOptions.OptionTypes public Type AdvancedRole { get; } public OptionBehaviour OptionBehaviour { get; set; } public ModdedOptionGroup Group { get; set; } = null; - public IMiraConfig ParentMod { get; set; } = null; + public IMiraPlugin ParentMod { get; set; } = null; public ModdedOption(string title, T defaultValue, Type roleType) { @@ -53,7 +54,7 @@ namespace MiraAPI.GameOptions.OptionTypes public abstract T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour); - public abstract OptionBehaviour CreateOption(OptionBehaviour optionBehaviour, Transform container); + public abstract OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container); public abstract string GetHudStringText(); } diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs index 8e107da..5aee686 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs @@ -1,6 +1,7 @@ using Reactor.Localization.Utilities; using System.Linq; using UnityEngine; +using Object = UnityEngine.Object; namespace MiraAPI.GameOptions.OptionTypes { @@ -12,19 +13,26 @@ namespace MiraAPI.GameOptions.OptionTypes Values = values; } - public override OptionBehaviour CreateOption(OptionBehaviour optionBehaviour, Transform container) + public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container) { - var stringOption = (StringOption)Object.Instantiate(optionBehaviour, container); + var stringOption = Object.Instantiate(stringOpt, container); + var vals = Values.Select(CustomStringName.CreateAndRegister).ToArray(); + var data = ScriptableObject.CreateInstance(); - stringOption.name = Title; - stringOption.Title = StringName; - stringOption.Value = Value; - stringOption.Values = Values.Select(CustomStringName.CreateAndRegister).ToArray(); + data.Title = StringName; + data.Type = global::OptionTypes.String; + data.Values = vals; + data.Index = Value; + + stringOption.data = data; stringOption.OnValueChanged = (Il2CppSystem.Action)ValueChanged; - stringOption.Initialize(); OptionBehaviour = stringOption; + stringOption.SetUpFromData(stringOption.data, 20); + + stringOption.Value = Value; + return stringOption; } @@ -44,6 +52,8 @@ namespace MiraAPI.GameOptions.OptionTypes var opt = OptionBehaviour as StringOption; opt.Value = newValue; + + DestroyableSingleton.Instance.Notifier.AddSettingsChangeMessage(StringName, OptionBehaviour.GetValueString(Value), false); } } } diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs index d01511f..3564de4 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs @@ -8,16 +8,23 @@ namespace MiraAPI.GameOptions.OptionTypes { } - public override OptionBehaviour CreateOption(OptionBehaviour optionBehaviour, Transform container) + public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container) { - var toggleOption = (ToggleOption)Object.Instantiate(optionBehaviour, container); + var toggleOption = Object.Instantiate(toggleOpt, Vector3.zero, Quaternion.identity, container); + var data = ScriptableObject.CreateInstance(); + data.Title = StringName; + data.Type = global::OptionTypes.Checkbox; - toggleOption.name = Title; - toggleOption.Title = StringName; + toggleOption.data = data; + + toggleOption.TitleText.text = Title; toggleOption.CheckMark.enabled = Value; toggleOption.OnValueChanged = (Il2CppSystem.Action)ValueChanged; - toggleOption.Initialize(); + OptionBehaviour = toggleOption; + + toggleOption.SetUpFromData(toggleOption.data, 20); + return toggleOption; } @@ -37,6 +44,8 @@ namespace MiraAPI.GameOptions.OptionTypes var toggleOpt = OptionBehaviour as ToggleOption; toggleOpt.CheckMark.enabled = newValue; + + DestroyableSingleton.Instance.Notifier.AddSettingsChangeMessage(StringName, Value ? "On" : "Off", false); } } } diff --git a/MiraAPI/GameOptions/UI/ModdedOptionsTab.cs b/MiraAPI/GameOptions/UI/ModdedOptionsTab.cs deleted file mode 100644 index a4492ae..0000000 --- a/MiraAPI/GameOptions/UI/ModdedOptionsTab.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System; -using System.Collections.Generic; -using Reactor.Utilities.Extensions; -using TMPro; -using UnityEngine; -using UnityEngine.Events; -using Object = UnityEngine.Object; - -namespace MiraAPI.GameOptions.UI; - -/* -public static class ModdedOptionsTab -{ - public static readonly List CustomTabs = []; - public static readonly List CustomScreens = []; - - public static GameObject InitializeForMod(IMiraConfig config, GameSettingMenu __instance) - { - var gameBtn = __instance.transform.FindChild("Header/Tabs/GameTab").gameObject; - var roleBtn = __instance.transform.FindChild("Header/Tabs/RoleTab").gameObject; - var screen = CreateNewMenu(config, __instance); - var tab = CreateCustomTab(config, __instance, screen, gameBtn, roleBtn); - - CustomScreens.Add(screen); - CustomTabs.Add(tab); - - SpriteRenderer rend = tab.transform.FindChild("Btn/Tab Background").GetComponent(); - rend.enabled = false; - - UpdateListeners(__instance, gameBtn.GetComponentInChildren(), roleBtn.GetComponentInChildren(), rend); - - var container = screen.transform.FindChild("GameGroup/SliderInner"); - container.DestroyChildren(); - //CreateNewResetButton(__instance, container); - - return container.gameObject; - } - - private static void UpdateListeners(GameSettingMenu __instance, PassiveButton gameB, PassiveButton roleB, SpriteRenderer rend) - { - gameB.OnClick.RemoveAllListeners(); - gameB.OnClick.AddListener((UnityAction)(() => - { - __instance.RegularGameSettings.SetActive(true); - __instance.RolesSettings.gameObject.SetActive(false); - - foreach (var screen in CustomScreens) screen.gameObject.SetActive(false); - - rend.enabled = false; - - __instance.GameSettingsHightlight.enabled = true; - __instance.RolesSettingsHightlight.enabled = false; - })); - - roleB.OnClick.RemoveAllListeners(); - roleB.OnClick.AddListener((UnityAction)(() => - { - __instance.RegularGameSettings.SetActive(false); - __instance.RolesSettings.gameObject.SetActive(true); - - foreach (var screen in CustomScreens) screen.gameObject.SetActive(false); - - rend.enabled = false; - - __instance.GameSettingsHightlight.enabled = false; - __instance.RolesSettingsHightlight.enabled = true; - })); - } - - public static GameObject CreateHeader(ToggleOption toggleOpt, Transform container, string title, Color color) - { - var header = Object.Instantiate(toggleOpt, container); - - header.Title = StringNames.None; - header.TitleText.text = title; - header.TitleText.color = color; - header.name = "Header"; - - var checkBox = header.transform.FindChild("CheckBox")?.gameObject; - if (checkBox) - { - checkBox.Destroy(); - } - - var background = header.transform.FindChild("Background")?.gameObject; - if (background) - { - background.Destroy(); - } - - header.GetComponent().Destroy(); - return header.gameObject; - } - - public static GameObject CreateCustomTab(IMiraConfig options, GameSettingMenu __instance, GameObject newSettings, - GameObject gameTab, GameObject roleTab) - { - var newTab = Object.Instantiate(gameTab, gameTab.transform.parent); - newTab.name = "Tab"; - newTab.transform.position = roleTab.transform.position; - newTab.transform.position += new Vector3(CustomTabs.Count + 1, 0, 0); - __instance.Tabs.transform.position -= new Vector3(0.5f, 0, 0); - - var inside = newTab.transform.FindChild("ColorButton"); - inside.name = "Btn"; - - var btn = inside.GetComponentInChildren(); - - btn.OnClick.RemoveAllListeners(); - - btn.OnClick.AddListener((Action)TabAction); - - var spriteRend = inside.GetComponentInChildren(); - // if (options.TabSettings.TabIcon != null) spriteRend.sprite = options.TabSettings.TabIcon.LoadAsset(); - - return newTab; - - void TabAction() - { - __instance.RegularGameSettings.SetActive(false); - __instance.RolesSettings.gameObject.SetActive(false); - - foreach (var screen in CustomScreens) - { - screen.gameObject.SetActive(false); - } - newSettings.gameObject.SetActive(true); - - SpriteRenderer rend = newTab.transform.FindChild("Btn/Tab Background").GetComponent(); - rend.enabled = true; - - gameTab.transform.FindChild("ColorButton/Tab Background").gameObject.GetComponent().enabled = false; - roleTab.transform.FindChild("Hat Button/Tab Background").gameObject.GetComponent().enabled = false; - - foreach (var tab in CustomTabs) - { - tab.transform.FindChild("Btn/Tab Background").gameObject.GetComponent().enabled = false; - } - } - } - - public static GameObject CreateNewMenu(IMiraConfig config, GameSettingMenu __instance) - { - var gameSettings = __instance.RegularGameSettings; - var newSettings = Object.Instantiate(gameSettings, gameSettings.transform.parent); - newSettings.name = "Settings"; - newSettings.SetActive(false); - - var launchpadGroup = newSettings.transform.FindChild("GameGroup").gameObject; - var text = launchpadGroup.transform.FindChild("Text").gameObject.GetComponent(); - text.gameObject.GetComponent().Destroy(); - text.text = $"{config.TabSettings.Title} Options"; - - return newSettings; - } -}*/ \ No newline at end of file diff --git a/MiraAPI/IMiraConfig.cs b/MiraAPI/IMiraConfig.cs deleted file mode 100644 index 3cde9a5..0000000 --- a/MiraAPI/IMiraConfig.cs +++ /dev/null @@ -1,17 +0,0 @@ -using MiraAPI.Utilities; -using MiraAPI.Utilities.Assets; -using UnityEngine; - -namespace MiraAPI -{ - public interface IMiraConfig - { - public ModdedOptionTabSettings TabSettings { get; } - } - - public struct ModdedOptionTabSettings(string title, LoadableAsset tabIcon) - { - public string Title { get; set; } = title; - public LoadableAsset TabIcon { get; set; } = tabIcon; - } -} diff --git a/MiraAPI/MiraAPIPlugin.cs b/MiraAPI/MiraAPIPlugin.cs index 077d721..3c67f3f 100644 --- a/MiraAPI/MiraAPIPlugin.cs +++ b/MiraAPI/MiraAPIPlugin.cs @@ -1,14 +1,14 @@ using BepInEx; using BepInEx.Unity.IL2CPP; using HarmonyLib; -using MiraAPI.GameModes; -using MiraAPI.GameOptions; -using MiraAPI.Hud; -using MiraAPI.Roles; +using MiraAPI.PluginLoading; using Reactor; using Reactor.Networking; using Reactor.Networking.Attributes; -using System.Reflection; +using Reactor.Patches; +using System; +using System.Text; +using UnityEngine; namespace MiraAPI; @@ -20,16 +20,31 @@ public partial class MiraAPIPlugin : BasePlugin { public Harmony Harmony { get; } = new(Id); public static MiraAPIPlugin Instance { get; private set; } + public static MiraPluginManager PluginManager { get; private set; } + public static Color MiraColor = new Color32(238, 154, 112, 255); public override void Load() { Instance = this; Harmony.PatchAll(); - RegisterCustomRoleAttribute.Initialize(); - ModdedOptionsManager.Initialize(); + PluginManager = new MiraPluginManager(); + PluginManager.Initialize(); - RegisterGameModeAttribute.Register(Assembly.GetCallingAssembly()); - RegisterButtonAttribute.Register(Assembly.GetExecutingAssembly()); + ReactorVersionShower.TextUpdated += (text) => + { + text.text = new StringBuilder($"{MiraColor.ToTextColor()}Mira API ") + .Append(GetShortHashVersion(Version)) + .Append("\n") + .Append(text.text) + .ToString(); + }; + } + + private static string GetShortHashVersion(string version) + { + var index = version.IndexOf("+", StringComparison.Ordinal); + + return index < 0 ? version : version[..(index + 3)]; } } diff --git a/MiraAPI/Patches/HudManagerPatches.cs b/MiraAPI/Patches/HudManagerPatches.cs index 8fcf456..3076505 100644 --- a/MiraAPI/Patches/HudManagerPatches.cs +++ b/MiraAPI/Patches/HudManagerPatches.cs @@ -1,8 +1,6 @@ using HarmonyLib; using InnerNet; -using MiraAPI.GameOptions; using MiraAPI.Hud; -using MiraAPI.Patches.Options; using MiraAPI.Roles; using Reactor.Utilities.Extensions; using UnityEngine; @@ -30,7 +28,7 @@ namespace MiraAPI.Patches } //CustomGameModeManager.ActiveMode.HudUpdate(__instance); - if (local.Data.Role is null) return; + if (local is null || local.Data is null || local.Data.Role is null) return; if (local.Data.Role is ICustomRole customRole) { diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index 03891e3..fdaf974 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -1,109 +1,122 @@ using HarmonyLib; -using Il2CppSystem; +using Il2CppSystem.Collections.Generic; using MiraAPI.GameOptions; +using MiraAPI.Utilities; +using Reactor.Localization.Utilities; using System.Linq; +using TMPro; using UnityEngine; -using Object = UnityEngine.Object; namespace MiraAPI.Patches.Options; -/* + [HarmonyPatch(typeof(GameOptionsMenu))] public static class GameOptionsMenuPatch { - /// - /// Change the OnValueChanged action for all custom game options - /// - [HarmonyPostfix, HarmonyPatch(nameof(GameOptionsMenu.Start))] - public static void StartPostfix(GameOptionsMenu __instance) + [HarmonyPrefix, HarmonyPatch(nameof(GameOptionsMenu.CreateSettings))] + public static bool SettingsPatch(GameOptionsMenu __instance) { - foreach (var customOption in ModdedOptionsManager.Options) + if (GameSettingMenuPatches.currentSelectedMod == 0) return true; + + __instance.MapPicker.gameObject.SetActive(false); + + float num = 2.1f; + foreach (IModdedOptionGroup group in GameSettingMenuPatches.selectedMod.OptionGroups) { - if (customOption.AdvancedRole is not null || !customOption.OptionBehaviour) + CategoryHeaderMasked categoryHeaderMasked = UnityEngine.Object.Instantiate(__instance.categoryHeaderOrigin, Vector3.zero, Quaternion.identity, __instance.settingsContainer); + categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister(group.GroupName), 20); + categoryHeaderMasked.transform.localScale = Vector3.one * 0.63f; + categoryHeaderMasked.transform.localPosition = new Vector3(-0.903f, num, -2f); + num -= 0.63f; + + foreach (var opt in ModdedOptionsManager.Options.Where(opt => opt.Group is not null && opt.Group.GroupName == group.GroupName)) { - continue; - } + 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); - customOption.OptionBehaviour.OnValueChanged = (Action)customOption.ValueChanged; - } - } - - - /// - /// Set the position and offset of all custom game options - /// - [HarmonyPostfix, HarmonyPatch(nameof(GameOptionsMenu.Update))] - public static void UpdatePostfix(GameOptionsMenu __instance) - { - if (!GameSettingMenu.Instance) - { - return; - } - - if (GameSettingMenu.Instance.GameSettingsTab.gameObject.active || GameSettingMenu.Instance.RoleSettingsTab.gameObject.active) - { - return; - } - - - var startOffset = 2.5f; - __instance.GetComponentInParent().ContentYBounds.max = startOffset + __instance.Children.Count * 0.5f; - - foreach (var option in ModdedOptionsManager.Options.Where(option => option.Group == null)) - { - if (!option.OptionBehaviour) continue; - - option.OptionBehaviour.enabled = option.Visible(); - option.OptionBehaviour.gameObject.SetActive(option.Visible()); - - if (option.Visible()) - { - startOffset -= 0.55f; - } - - var transform = option.OptionBehaviour.transform; - var optionPosition = transform.localPosition; - transform.localPosition = new Vector3(optionPosition.x, startOffset, optionPosition.z); - } - - foreach (var group in ModdedOptionsManager.Groups.Where(group => group.AdvancedRole == null)) - { - if (group.Header == null) - { - continue; - } - - group.Header.SetActive(group.GroupVisible()); - - if (group.GroupVisible()) - { - startOffset -= 0.5f; - } - - var position = group.Header.transform.localPosition; - group.Header.transform.localPosition = new Vector3(position.x, startOffset, position.z); - - foreach (var option in ModdedOptionsManager.Options.Where(x => x.Group == group)) - { - if (!option.OptionBehaviour) + SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren(true); + for (int i = 0; i < componentsInChildren.Length; i++) { - continue; + componentsInChildren[i].material.SetInt(PlayerMaterial.MaskLayer, 20); + } + foreach (TextMeshPro textMeshPro in newOpt.GetComponentsInChildren(true)) + { + textMeshPro.fontMaterial.SetFloat("_StencilComp", 3f); + textMeshPro.fontMaterial.SetFloat("_Stencil", 20); } - var enabled = group.GroupVisible() && option.Visible(); + __instance.Children.Add(newOpt); - option.OptionBehaviour.enabled = enabled; - option.OptionBehaviour.gameObject.SetActive(enabled); - - if (group.GroupVisible() && option.Visible()) - { - startOffset -= 0.5f; - } - - var transform = option.OptionBehaviour.transform; - var optionPosition = transform.localPosition; - transform.localPosition = new Vector3(optionPosition.x, startOffset, optionPosition.z); + num -= 0.45f; + newOpt.Initialize(); } } + + CategoryHeaderMasked ungroupedHeader = UnityEngine.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 ModdedOptionsManager.Options.Where(opt => opt.Group is null)) + { + 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("_StencilComp", 3f); + textMeshPro.fontMaterial.SetFloat("_Stencil", 20); + } + + __instance.Children.Add(newOpt); + + num -= 0.45f; + + newOpt.Initialize(); + } + + __instance.scrollBar.SetYBoundsMax(-num - 1.65f); + + return false; } -}*/ \ No newline at end of file + + [HarmonyPrefix, HarmonyPatch(nameof(GameOptionsMenu.Initialize))] + public static bool InitPatch(GameOptionsMenu __instance) + { + if (__instance.Children == null || __instance.Children.Count == 0) + { + __instance.MapPicker.gameObject.SetActive(true); + __instance.MapPicker.Initialize(20); + BaseGameSetting mapNameSetting = GameManager.Instance.GameSettingsList.MapNameSetting; + __instance.MapPicker.SetUpFromData(mapNameSetting, 20); + __instance.Children = new List(); + __instance.Children.Add(__instance.MapPicker); + __instance.CreateSettings(); + __instance.cachedData = GameOptionsManager.Instance.CurrentGameOptions; + for (int i = 0; i < __instance.Children.Count; i++) + { + OptionBehaviour optionBehaviour = __instance.Children[i]; + if (AmongUsClient.Instance && !AmongUsClient.Instance.AmHost) + { + optionBehaviour.SetAsPlayer(); + } + + if (optionBehaviour.IsCustom()) continue; + + optionBehaviour.OnValueChanged = new System.Action(__instance.ValueChanged); + } + } + + return false; + } +} \ No newline at end of file diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs index 0c666ac..34e178e 100644 --- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs @@ -1,6 +1,9 @@ using HarmonyLib; +using MiraAPI.PluginLoading; using MiraAPI.Utilities.Assets; using Reactor.Utilities.Extensions; +using System; +using System.Linq; using TMPro; using UnityEngine; using UnityEngine.Events; @@ -10,8 +13,9 @@ namespace MiraAPI.Patches.Options; [HarmonyPatch(typeof(GameSettingMenu))] public static class GameSettingMenuPatches { - public static int currentSelectedMod = 0; + public static MiraPluginInfo selectedMod = null; + public static TextMeshPro text; [HarmonyPrefix] [HarmonyPatch(nameof(GameSettingMenu.Start))] @@ -26,11 +30,11 @@ public static class GameSettingMenuPatches tmpText.name = "SelectedMod"; tmpText.transform.localPosition = new Vector3(-3.3382f, 1.5399f, -2); - TextMeshPro tmp = tmpText.GetComponent(); - tmp.text = "Default"; - tmp.fontSizeMax = 3.2f; + text = tmpText.GetComponent(); + text.fontSizeMax = 3.2f; + UpdateText(__instance.GameSettingsTab); - tmp.alignment = TextAlignmentOptions.Center; + text.alignment = TextAlignmentOptions.Center; GameObject nextButton = GameObject.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject; nextButton.transform.localPosition = new Vector3(-2.2663f, 1.5272f, -25f); @@ -48,7 +52,11 @@ public static class GameSettingMenuPatches passiveButton.inactiveSprites = nextButton.transform.FindChild("Inactive").gameObject; passiveButton.OnClick.AddListener((UnityAction)(() => { - currentSelectedMod += 1; + if (currentSelectedMod != MiraPluginManager.Instance.RegisteredPlugins.Count) + { + currentSelectedMod += 1; + } + UpdateText(__instance.GameSettingsTab); })); GameObject backButton = GameObject.Instantiate(nextButton, __instance.BackButton.transform.parent).gameObject; @@ -58,7 +66,53 @@ public static class GameSettingMenuPatches backButton.transform.FindChild("Active").gameObject.GetComponent().flipX = backButton.transform.FindChild("Inactive").gameObject.GetComponent().flipX = true; backButton.gameObject.GetComponent().OnClick.AddListener((UnityAction)(() => { - currentSelectedMod -= 1; + if (currentSelectedMod != 0) + { + currentSelectedMod -= 1; + } + UpdateText(__instance.GameSettingsTab); })); } + + public static void UpdateText(GameOptionsMenu settings) + { + if (currentSelectedMod == 0) + { + text.text = "Default"; + text.fontSizeMax = 3.2f; + } + else + { + text.fontSizeMax = 2f; + selectedMod = MiraPluginManager.Instance.RegisteredPlugins.ElementAt(currentSelectedMod - 1).Value; + if (selectedMod == null) + { + currentSelectedMod = 0; + UpdateText(settings); + } + + string name = selectedMod.PluginInfo.Metadata.Name; + text.text = name.Substring(0, Math.Min(name.Length, 15)); + } + + if (settings.Children is null || settings is null) return; + + foreach (var child in settings.Children) + { + if (child.TryCast()) continue; + if (child.gameObject is null) continue; + + child.gameObject.DestroyImmediate(); + } + + foreach (var header in settings.settingsContainer.GetComponentsInChildren()) + { + header.gameObject.DestroyImmediate(); + } + + settings.Children.Clear(); + settings.Children = null; + + settings.Initialize(); + } } diff --git a/MiraAPI/Patches/Options/HudStringPatch.cs b/MiraAPI/Patches/Options/HudStringPatch.cs deleted file mode 100644 index f079c8f..0000000 --- a/MiraAPI/Patches/Options/HudStringPatch.cs +++ /dev/null @@ -1,118 +0,0 @@ -using AmongUs.GameOptions; -using HarmonyLib; -using MiraAPI.GameOptions; -using MiraAPI.Roles; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace MiraAPI.Patches.Options; - -[HarmonyPatch(typeof(IGameOptionsExtensions), "ToHudString")] -public static class ToHudStringPatch -{ - public static int CurrentPage = 0; - /// - /// Generic method to add options - /// - public static void AddOptions(StringBuilder sb, - List options) - { - foreach (var opt in options) - { - sb.AppendLine(opt.GetHudStringText()); - } - } - - public static void Prefix() - { - if (GameManager.Instance is null || GameManager.Instance.IsHideAndSeek()) - { - return; - } - - foreach (var role in CustomRoleManager.CustomRoles.Values) - { - var customRole = role as ICustomRole; - if (customRole.IsGhostRole) - { - role.Role = RoleTypes.CrewmateGhost; - } - } - } - - /// - /// Update the HudOptions on the left of the screen if player is using Launchpad options - /// - public static void Postfix(IGameOptions __instance, ref string __result) - { - if (GameManager.Instance is null) - { - return; - } - - foreach (var pair in CustomRoleManager.CustomRoles) - { - var customRole = pair.Value as ICustomRole; - if (customRole.IsGhostRole) - { - pair.Value.Role = (RoleTypes)pair.Key; - } - } - - if (CurrentPage != 0) - { - IMiraConfig currentPlugin = ModdedOptionsManager.RegisteredMods.Values.ToArray()[CurrentPage - 1]; - if (currentPlugin == null) - { - CurrentPage = 0; - return; - } - - var sb = new StringBuilder($"{currentPlugin.TabSettings.Title} Options:\n"); - var groupsWithRoles = ModdedOptionsManager.Groups.Where(group => group.AdvancedRole != null && group.ParentMod == currentPlugin); - var groupsWithoutRoles = ModdedOptionsManager.Groups.Where(group => group.AdvancedRole == null && group.ParentMod == currentPlugin); - - AddOptions(sb, - ModdedOptionsManager.Options.Where(option => option.Group == null && option.ParentMod == currentPlugin && option.Visible()).ToList() - ); - - foreach (var group in groupsWithoutRoles) - { - if (!group.GroupVisible()) - { - continue; - } - - sb.AppendLine($"\n{group.GroupColor.ToTextColor()}{group.GroupName}"); - AddOptions(sb, ModdedOptionsManager.Options.Where(x => x.Group == group).ToList()); - } - - var customOptionGroups = groupsWithRoles as ModdedOptionGroup[] ?? groupsWithRoles.ToArray(); - if (customOptionGroups.Any()) - { - sb.AppendLine("\nRoles"); - - foreach (var group in customOptionGroups) - { - if (!group.GroupVisible()) - { - continue; - } - - sb.AppendLine($"{group.GroupColor.ToTextColor()}{group.GroupName}"); - AddOptions(sb, ModdedOptionsManager.Options.Where(x => x.Group == group).ToList()); - sb.Append("\n"); - } - } - - __result = sb.ToString(); - } - - if (CurrentPage == 0) - { - __result = "Normal Options:\n" + __result; - } - __result += $"\nUse TAB to switch pages (Page {CurrentPage}/{ModdedOptionsManager.RegisteredMods.Count})"; - } -} \ No newline at end of file diff --git a/MiraAPI/Patches/Options/OptionsPatches.cs b/MiraAPI/Patches/Options/OptionsPatches.cs new file mode 100644 index 0000000..1a329f3 --- /dev/null +++ b/MiraAPI/Patches/Options/OptionsPatches.cs @@ -0,0 +1,56 @@ +using HarmonyLib; +using Il2CppSystem; +using MiraAPI.Utilities; + +namespace MiraAPI.Patches.Options +{ + [HarmonyPatch] + public static class OptionsPatches + { + [HarmonyPrefix, HarmonyPatch(typeof(ToggleOption), nameof(ToggleOption.Initialize))] + public static bool ToggleInit(ToggleOption __instance) + { + if (!__instance.IsCustom()) return true; + __instance.TitleText.text = DestroyableSingleton.Instance.GetString(__instance.Title, Array.Empty()); + + return false; + } + + [HarmonyPrefix, HarmonyPatch(typeof(ToggleOption), nameof(ToggleOption.UpdateValue))] + public static bool ToggleUpdate(ToggleOption __instance) + { + return !__instance.IsCustom(); + } + + [HarmonyPrefix, HarmonyPatch(typeof(NumberOption), nameof(NumberOption.Initialize))] + public static bool NumberInit(NumberOption __instance) + { + if (!__instance.IsCustom()) return true; + __instance.TitleText.text = DestroyableSingleton.Instance.GetString(__instance.Title, Array.Empty()); + + return false; + } + + [HarmonyPrefix, HarmonyPatch(typeof(NumberOption), nameof(NumberOption.UpdateValue))] + public static bool NumberUpdate(NumberOption __instance) + { + return !__instance.IsCustom(); + } + + [HarmonyPrefix, HarmonyPatch(typeof(StringOption), nameof(StringOption.Initialize))] + public static bool StringInit(StringOption __instance) + { + if (!__instance.IsCustom()) return true; + __instance.TitleText.text = DestroyableSingleton.Instance.GetString(__instance.Title, Array.Empty()); + __instance.ValueText.text = DestroyableSingleton.Instance.GetString(__instance.Values[__instance.Value], Array.Empty()); + + return false; + } + + [HarmonyPrefix, HarmonyPatch(typeof(StringOption), nameof(StringOption.UpdateValue))] + public static bool StringUpdate(StringOption __instance) + { + return !__instance.IsCustom(); + } + } +} \ No newline at end of file diff --git a/MiraAPI/Patches/Roles/TaskAdderPatch.cs b/MiraAPI/Patches/Roles/TaskAdderPatch.cs index ccb5c34..fe23c7b 100644 --- a/MiraAPI/Patches/Roles/TaskAdderPatch.cs +++ b/MiraAPI/Patches/Roles/TaskAdderPatch.cs @@ -3,7 +3,9 @@ using HarmonyLib; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppSystem.Text; +using MiraAPI.PluginLoading; using MiraAPI.Roles; +using System.Linq; using UnityEngine; using Object = UnityEngine.Object; @@ -22,17 +24,16 @@ public static class TaskAdderPatch RolesFolder.gameObject.SetActive(false); RolesFolder.FolderName = "Roles"; RolesFolder.name = "RolesFolder"; - foreach (var pair in CustomRoleManager.ModRoles) + foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values) { var newFolder = Object.Instantiate(__instance.RootFolderPrefab, __instance.transform); - newFolder.FolderName = pair.Key.Metadata.Name; - newFolder.name = pair.Key.Metadata.Name; + newFolder.FolderName = newFolder.name = plugin.PluginInfo.Metadata.Name; newFolder.gameObject.SetActive(false); RolesFolder.SubFolders.Add(newFolder); - if (!ModsFolders.ContainsKey(pair.Key.Metadata.Name)) + if (!ModsFolders.ContainsKey(plugin.PluginId)) { - ModsFolders.Add(pair.Key.Metadata.Name, pair.Key.Metadata.GUID); + ModsFolders.Add(plugin.PluginInfo.Metadata.Name, plugin.PluginId); } } @@ -150,12 +151,42 @@ public static class TaskAdderPatch } } } + if (taskFolder.FolderName == "Roles") + { + for (int m = 0; m < DestroyableSingleton.Instance.AllRoles.Length; m++) + { + RoleBehaviour roleBehaviour = DestroyableSingleton.Instance.AllRoles[m]; + if (roleBehaviour.Role != RoleTypes.ImpostorGhost && roleBehaviour.Role != RoleTypes.CrewmateGhost && !CustomRoleManager.CustomRoles.ContainsKey((ushort)roleBehaviour.Role)) + { + TaskAddButton taskAddButton2 = Object.Instantiate(__instance.RoleButton); + taskAddButton2.SafePositionWorld = __instance.SafePositionWorld; + taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe"; + __instance.AddFileAsChild(RolesFolder, taskAddButton2, ref num, ref num2, ref num3); + taskAddButton2.Role = roleBehaviour; + if (taskAddButton2 != null && taskAddButton2.Button != null) + { + ControllerManager.Instance.AddSelectableUiElement(taskAddButton2.Button, false); + if (m == 0 && __instance.restorePreviousSelectionFound != null) + { + ControllerManager.Instance.SetDefaultSelection(__instance.restorePreviousSelectionFound, null); + __instance.restorePreviousSelectionByFolderName = string.Empty; + __instance.restorePreviousSelectionFound = null; + } + else if (m == 0) + { + ControllerManager.Instance.SetDefaultSelection(taskAddButton2.Button, null); + } + } + } + } + } if (ModsFolders.TryGetValue(taskFolder.FolderName, out string guid)) { - for (var m = 0; m < CustomRoleManager.GetRolesRegistered(guid).Count; m++) + MiraPluginInfo plugin = MiraPluginManager.Instance.GetPluginByGUID(guid); + for (var m = 0; m < plugin.CustomRoles.Count; m++) { - var roleBehaviour = CustomRoleManager.GetRolesRegistered(guid)[m]; + var roleBehaviour = plugin.CustomRoles.ElementAt(m).Value; if (roleBehaviour.Role != RoleTypes.ImpostorGhost && roleBehaviour.Role != RoleTypes.CrewmateGhost && !roleBehaviour.IsDead) { var taskAddButton2 = Object.Instantiate(__instance.RoleButton); diff --git a/MiraAPI/PluginLoading/IMiraPlugin.cs b/MiraAPI/PluginLoading/IMiraPlugin.cs new file mode 100644 index 0000000..85c0cc8 --- /dev/null +++ b/MiraAPI/PluginLoading/IMiraPlugin.cs @@ -0,0 +1,6 @@ +namespace MiraAPI.PluginLoading +{ + public interface IMiraPlugin + { + } +} diff --git a/MiraAPI/PluginLoading/MiraPluginInfo.cs b/MiraAPI/PluginLoading/MiraPluginInfo.cs new file mode 100644 index 0000000..bd57202 --- /dev/null +++ b/MiraAPI/PluginLoading/MiraPluginInfo.cs @@ -0,0 +1,27 @@ +using BepInEx; +using MiraAPI.GameModes; +using MiraAPI.GameOptions; +using System.Collections.Generic; + +namespace MiraAPI.PluginLoading; + +public class MiraPluginInfo +{ + public string PluginId { get; set; } + public IMiraPlugin Config { get; set; } + public PluginInfo PluginInfo { get; set; } + + public readonly List OptionGroups = []; + public readonly List Options = []; + + public readonly Dictionary CustomRoles = []; + public readonly Dictionary GameModes = []; + + public MiraPluginInfo(string id, IMiraPlugin config, PluginInfo info) + { + PluginId = id; + Config = config; + PluginInfo = info; + } + +} \ No newline at end of file diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs new file mode 100644 index 0000000..4ab3e54 --- /dev/null +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -0,0 +1,99 @@ +using BepInEx; +using BepInEx.Unity.IL2CPP; +using Il2CppInterop.Runtime.Injection; +using MiraAPI.GameOptions; +using MiraAPI.GameOptions.Attributes; +using MiraAPI.Roles; +using Reactor.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace MiraAPI.PluginLoading; + +public class MiraPluginManager +{ + public Dictionary RegisteredPlugins = []; + public static MiraPluginManager Instance; + + public void Initialize() + { + Instance = this; + IL2CPPChainloader.Instance.PluginLoad += (_, assembly, plugin) => + { + if (!plugin.GetType().GetInterfaces().Contains(typeof(IMiraPlugin))) return; + + var id = MetadataHelper.GetMetadata(plugin.GetType()).GUID; + var info = new MiraPluginInfo(id, plugin as IMiraPlugin, IL2CPPChainloader.Instance.Plugins[id]); + + RegisterRoleAttribute(assembly, info); + RegisterOptionsGroups(assembly, info); + RegisterOptionsAttributes(assembly, info); + + RegisteredPlugins.Add(assembly, info); + + Logger.Info($"Registering mod {id} with Mira API."); + }; + } + + public MiraPluginInfo GetPluginByGUID(string guid) + { + return RegisteredPlugins.Values.Where(plugin => plugin.PluginId == guid).First(); + } + + + private static void RegisterOptionsAttributes(Assembly assembly, MiraPluginInfo pluginInfo) + { + foreach (var type in assembly.GetTypes()) + { + foreach (PropertyInfo property in type.GetProperties()) + { + foreach (var attribute in property.GetCustomAttributes()) + { + if (attribute.GetType().BaseType == typeof(ModdedOptionAttribute)) + { + pluginInfo.Options.Add(ModdedOptionsManager.RegisterOption(type, (ModdedOptionAttribute)attribute, property)); + } + } + } + } + } + + private static void RegisterOptionsGroups(Assembly assembly, MiraPluginInfo pluginInfo) + { + foreach (var type in assembly.GetTypes()) + { + if (typeof(IModdedOptionGroup).IsAssignableFrom(type)) + { + IModdedOptionGroup group = (IModdedOptionGroup)Activator.CreateInstance(type); + ModdedOptionGroup newGroup = new ModdedOptionGroup() + { + AdvancedRole = group.AdvancedRole, + GroupColor = group.GroupColor, + GroupName = group.GroupName, + GroupVisible = group.GroupVisible + }; + + ModdedOptionsManager.Groups.Add(newGroup); + ModdedOptionsManager.OriginalTypes.Add(type, newGroup); + pluginInfo.OptionGroups.Add(group); + } + } + } + + private static void RegisterRoleAttribute(Assembly assembly, MiraPluginInfo pluginInfo) + { + foreach (var type in assembly.GetTypes()) + { + var attribute = type.GetCustomAttribute(); + if (attribute == null) continue; + + ClassInjector.RegisterTypeInIl2Cpp(type); + + var role = CustomRoleManager.RegisterRole(type, attribute.RoleId); + + pluginInfo.CustomRoles.Add((ushort)role.Role, role); + } + } +} \ No newline at end of file diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index 962c551..024aeec 100644 --- a/MiraAPI/Roles/CustomRoleManager.cs +++ b/MiraAPI/Roles/CustomRoleManager.cs @@ -1,7 +1,6 @@ using AmongUs.GameOptions; -using BepInEx; -using BepInEx.Unity.IL2CPP; using Il2CppInterop.Runtime; +using MiraAPI.Networking; using Reactor.Localization.Utilities; using Reactor.Networking.Rpc; using Reactor.Utilities; @@ -9,7 +8,6 @@ using Reactor.Utilities.Extensions; using System; using System.Collections.Generic; using System.Linq; -using MiraAPI.Networking; using TMPro; using UnityEngine; using Object = UnityEngine.Object; @@ -18,46 +16,40 @@ namespace MiraAPI.Roles; public static class CustomRoleManager { - public static readonly Dictionary CustomRoles = []; - public static readonly Dictionary> ModRoles = []; + public static Dictionary CustomRoles = new(); + + public static int GetNextRoleId(ushort requestedId) + { + while (CustomRoles.ContainsKey(requestedId)) + { + requestedId++; + } + + return requestedId; + } public static void RegisterInRoleManager() { RoleManager.Instance.AllRoles = RoleManager.Instance.AllRoles.Concat(CustomRoles.Values).ToArray(); } - internal static void RegisterRole(Type roleType, ushort roleId, string modId) + internal static RoleBehaviour RegisterRole(Type roleType, ushort roleId) { - if (!IL2CPPChainloader.Instance.Plugins.TryGetValue(modId, out PluginInfo info)) - { - Debug.LogError($"{modId} is not a valid mod!"); - return; - } - if (!(typeof(RoleBehaviour).IsAssignableFrom(roleType) && typeof(ICustomRole).IsAssignableFrom(roleType))) { - return; - } - - if (CustomRoles.Any(behaviour => behaviour.GetType() == roleType)) - { - return; + Logger.Error($"{roleType?.Name} does not inherit from RoleBehaviour or ICustomRole."); + return null; } var roleBehaviour = (RoleBehaviour)new GameObject(roleType.Name).DontDestroy().AddComponent(Il2CppType.From(roleType)); if (roleBehaviour is not ICustomRole customRole) { - return; + roleBehaviour.gameObject.Destroy(); + return null; } - if (CustomRoles.ContainsKey(roleId)) - { - Debug.LogError($"Role ID conflict for {customRole.RoleName}. Use a different ID."); - return; - } - - roleBehaviour.Role = (RoleTypes)roleId; + roleBehaviour.Role = (RoleTypes)GetNextRoleId(roleId); roleBehaviour.TeamType = customRole.Team == ModdedRoleTeams.Neutral ? RoleTeamTypes.Crewmate : (RoleTeamTypes)customRole.Team; roleBehaviour.NameColor = customRole.RoleColor; roleBehaviour.StringName = CustomStringName.CreateAndRegister(customRole.RoleName); @@ -76,47 +68,27 @@ public static class CustomRoleManager RoleManager.GhostRoles.Add(roleBehaviour.Role); } - if (ModRoles.TryGetValue(info, out List roles)) - { - roles.Add(roleBehaviour); - } - else - { - ModRoles.TryAdd(info, [roleBehaviour]); - } - CustomRoles.Add(roleId, roleBehaviour); if (customRole.HideSettings) { - return; + return roleBehaviour; } var config = PluginSingleton.Instance.Config; config.Bind(customRole.NumConfigDefinition, 1); config.Bind(customRole.ChanceConfigDefinition, 100); + return roleBehaviour; } - public static List GetRolesRegistered(string modId) - { - PluginInfo info = ModRoles.Keys.First(key => key.Metadata.GUID == modId); - if (ModRoles.TryGetValue(info, out List roles)) - { - return roles; - } - else - { - Debug.LogError($"{modId} has no roles registered."); - return null; - } - } + public static bool GetCustomRoleBehaviour(RoleTypes roleType, out ICustomRole result) { CustomRoles.TryGetValue((ushort)roleType, out var temp); - if (temp is ICustomRole role) + if (temp != null) { - result = role; + result = temp as ICustomRole; return true; } diff --git a/MiraAPI/Roles/RegisterCustomRoleAttribute.cs b/MiraAPI/Roles/RegisterCustomRoleAttribute.cs index 0c49aeb..f32cdd7 100644 --- a/MiraAPI/Roles/RegisterCustomRoleAttribute.cs +++ b/MiraAPI/Roles/RegisterCustomRoleAttribute.cs @@ -1,46 +1,18 @@ -using BepInEx.Unity.IL2CPP; -using MiraAPI.Utilities; -using System; -using System.Collections.Generic; -using System.Reflection; +using System; namespace MiraAPI.Roles; [AttributeUsage(AttributeTargets.Class)] public class RegisterCustomRoleAttribute : Attribute { - private static readonly HashSet RegisteredAssemblies = []; public ushort RoleId { get; } - public string ModId { get; } - public RegisterCustomRoleAttribute(string modId) + public RegisterCustomRoleAttribute() { - ModId = modId; RoleId = (ushort)(20 + CustomRoleManager.CustomRoles.Count); } - public RegisterCustomRoleAttribute(string modId, ushort roleId) + public RegisterCustomRoleAttribute(ushort roleId) { - ModId = modId; RoleId = roleId; } - - public static void Initialize() - { - IL2CPPChainloader.Instance.PluginLoad += (_, assembly, plugin) => Register(assembly); - } - - public static void Register(Assembly assembly) - { - if (!RegisteredAssemblies.Add(assembly)) return; - - foreach (var type in assembly.GetTypes()) - { - var attribute = type.GetCustomAttribute(); - if (attribute != null) - { - Helpers.RegisterType(type); - CustomRoleManager.RegisterRole(type, attribute.RoleId, attribute.ModId); - } - } - } } \ No newline at end of file diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 00a4c7d..4be7630 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -1,4 +1,5 @@ -using MiraAPI.Roles; +using MiraAPI.GameOptions; +using MiraAPI.Roles; using Reactor.Utilities.Extensions; using System.Linq; using UnityEngine; @@ -7,6 +8,13 @@ namespace MiraAPI.Utilities { public static class Extensions { + public static bool IsCustom(this OptionBehaviour optionBehaviour) + { + if (ModdedOptionsManager.Options.Any(opt => opt.OptionBehaviour == optionBehaviour)) return true; + + return false; + } + public static void UpdateBodies(this PlayerControl playerControl, Color outlineColor, ref DeadBody target) { foreach (var body in Object.FindObjectsOfType()) diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index 5cef351..2eb2130 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -1,7 +1,4 @@ -using HarmonyLib; -using Il2CppInterop.Runtime.Injection; -using MiraAPI.Roles; -using System; +using MiraAPI.Roles; using System.Linq; using System.Text; using UnityEngine; @@ -17,24 +14,6 @@ namespace MiraAPI.Utilities return ShipStatus.Instance.AllRooms.ToList().Find(room => room.roomArea.OverlapPoint(pos)); } - public static void RegisterType(Type type) - { - if (ClassInjector.IsTypeRegisteredInIl2Cpp(type)) - { - return; - } - - try - { - ClassInjector.RegisterTypeInIl2Cpp(type); - } - catch (Exception e) - { - Debug.LogError($"Failed to register {type.FullDescription()}: {e}"); - } - } - - public static string GetSuffix(NumberSuffixes suffix) { return suffix switch