From 84321f290e8874a32f5694f2e21ff7755950f6e3 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:25:53 -0500 Subject: [PATCH 01/23] Fix game option checks --- MiraAPI.Example/Options/Modifiers/GeneralModifierOptions.cs | 2 +- MiraAPI/GameOptions/AbstractOptionGroup.cs | 2 +- MiraAPI/Patches/Options/GameOptionsMenuPatch.cs | 4 ++-- MiraAPI/Patches/Options/GameSettingMenuPatches.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/MiraAPI.Example/Options/Modifiers/GeneralModifierOptions.cs b/MiraAPI.Example/Options/Modifiers/GeneralModifierOptions.cs index 1414799..0bcbcdc 100644 --- a/MiraAPI.Example/Options/Modifiers/GeneralModifierOptions.cs +++ b/MiraAPI.Example/Options/Modifiers/GeneralModifierOptions.cs @@ -9,7 +9,7 @@ public class GeneralModifierOptions : AbstractOptionGroup public override uint GroupPriority => 0; - public override bool ShowInModifiersMenu => true; + public override MenuCategory ParentMenu => MenuCategory.Modifiers; [ModdedToggleOption("Some Boolean Option")] public bool SomeBooleanOption { get; set; } = true; diff --git a/MiraAPI/GameOptions/AbstractOptionGroup.cs b/MiraAPI/GameOptions/AbstractOptionGroup.cs index f21a3cb..cb82e1c 100644 --- a/MiraAPI/GameOptions/AbstractOptionGroup.cs +++ b/MiraAPI/GameOptions/AbstractOptionGroup.cs @@ -40,7 +40,7 @@ public abstract class AbstractOptionGroup /// /// Gets a value indicating which menu the group is in. /// - public virtual MenuCategory ParentMenu => MenuCategory.Roles; + public virtual MenuCategory ParentMenu => MenuCategory.Game; /// /// Gets the function that determines whether the group should be visible or not. diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index 7b5e7a8..fa393c2 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -63,7 +63,7 @@ internal static class GameOptionsMenuPatch var filteredGroups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups .Where(x => x.OptionableType == null && - x.ParentMenu == MenuCategory.Roles) ?? []; + x.ParentMenu == MenuCategory.Game) ?? []; foreach (var group in filteredGroups) { @@ -218,7 +218,7 @@ internal static class GameOptionsMenuPatch } var filteredGroups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x => x.OptionableType == null && x.ParentMenu == MenuCategory.Roles) ?? []; + .Where(x => x.OptionableType == null && x.ParentMenu == MenuCategory.Game) ?? []; foreach (var group in filteredGroups) { diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs index 30ed1dd..6e12c61 100644 --- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs @@ -432,7 +432,7 @@ internal static class GameSettingMenuPatches x => x.ParentMenu == MenuCategory.Modifiers || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true); var modHasOptions = SelectedMod.InternalOptionGroups.Exists(x => x.OptionableType == null && - x.ParentMenu != MenuCategory.Modifiers && x.ParentMenu != MenuCategory.Roles); + x.ParentMenu == MenuCategory.Game); _modifiersButton.gameObject.SetActive(true); _smallRolesButton.gameObject.SetActive(true); From 2eecba2e5166484b9034117080e161f0e4e81f35 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Wed, 1 Jul 2026 19:57:29 -0500 Subject: [PATCH 02/23] Add TMP icon support for roles --- MiraAPI.Example/Roles/ChameleonRole.cs | 3 + MiraAPI.Example/Roles/FreezerRole.cs | 3 + .../Options/NotificationPopperPatch.cs | 2 + .../Patches/Options/RoleSettingMenuPatches.cs | 3 +- MiraAPI/Patches/TmpAwakePatch.cs | 32 +++++++ MiraAPI/Resources/BlankSprite.png | Bin 0 -> 96 bytes MiraAPI/Roles/CustomRoleConfiguration.cs | 7 ++ MiraAPI/Roles/ICustomRole.cs | 1 + MiraAPI/Utilities/Assets/MiraAssets.cs | 2 + MiraAPI/Utilities/Assets/TmpSpriteUtils.cs | 83 ++++++++++++++++++ 10 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 MiraAPI/Patches/TmpAwakePatch.cs create mode 100644 MiraAPI/Resources/BlankSprite.png create mode 100644 MiraAPI/Utilities/Assets/TmpSpriteUtils.cs diff --git a/MiraAPI.Example/Roles/ChameleonRole.cs b/MiraAPI.Example/Roles/ChameleonRole.cs index c1d7779..f601551 100644 --- a/MiraAPI.Example/Roles/ChameleonRole.cs +++ b/MiraAPI.Example/Roles/ChameleonRole.cs @@ -1,6 +1,7 @@ using AmongUs.GameOptions; using MiraAPI.Patches.Stubs; using MiraAPI.Roles; +using MiraAPI.Utilities.Assets; using Reactor.Utilities; using TMPro; using UnityEngine; @@ -20,6 +21,8 @@ public class ChameloenRole : CrewmateRole, ICustomRole public CustomRoleConfiguration Configuration => new(this) { OptionsScreenshot = ExampleAssets.Banner, + Icon = MiraAssets.CrewmateFile, + IconTmp = TmpSpriteUtils.CreateSpriteAsset(MiraAssets.CrewmateFile.LoadAsset(), "ApiExample.Role.Impostor.ChameleonRole"), IntroSound = CustomRoleUtils.GetIntroSound(RoleTypes.Shapeshifter), }; diff --git a/MiraAPI.Example/Roles/FreezerRole.cs b/MiraAPI.Example/Roles/FreezerRole.cs index bef24d2..5a7f4bd 100644 --- a/MiraAPI.Example/Roles/FreezerRole.cs +++ b/MiraAPI.Example/Roles/FreezerRole.cs @@ -1,4 +1,5 @@ using MiraAPI.Roles; +using MiraAPI.Utilities.Assets; using UnityEngine; namespace MiraAPI.Example.Roles; @@ -13,6 +14,8 @@ public class FreezerRole : ImpostorRole, ICustomRole public CustomRoleConfiguration Configuration => new CustomRoleConfiguration(this) { + Icon = MiraAssets.ImpostorFile, + IconTmp = TmpSpriteUtils.CreateSpriteAsset(MiraAssets.ImpostorFile.LoadAsset(), "ApiExample.Role.Impostor.FreezerRole"), OptionsScreenshot = ExampleAssets.Banner, MaxRoleCount = 2, }; diff --git a/MiraAPI/Patches/Options/NotificationPopperPatch.cs b/MiraAPI/Patches/Options/NotificationPopperPatch.cs index b2c51e9..0ba8605 100644 --- a/MiraAPI/Patches/Options/NotificationPopperPatch.cs +++ b/MiraAPI/Patches/Options/NotificationPopperPatch.cs @@ -26,9 +26,11 @@ public static class NotificationPopperPatch var textColor = customRole.OptionsMenuColor.ToTextColor(); + var icon = customRole.Configuration.IconTmp ? $"" : string.Empty; var item = TranslationController.Instance.GetString( StringNames.LobbyChangeSettingNotificationRole, string.Concat( + icon, "", textColor, TranslationController.Instance.GetString(key, Array.Empty()), diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index d123161..67a4894 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -437,8 +437,7 @@ public static class RoleSettingMenuPatches roleSetting.Role.StringName, roleSetting.RoleMaxCount, roleSetting.RoleChance, - roleSetting.Role.TeamType, - false); + roleSetting.Role.TeamType); if (AmongUsClient.Instance.AmHost) { diff --git a/MiraAPI/Patches/TmpAwakePatch.cs b/MiraAPI/Patches/TmpAwakePatch.cs new file mode 100644 index 0000000..3cb9ee6 --- /dev/null +++ b/MiraAPI/Patches/TmpAwakePatch.cs @@ -0,0 +1,32 @@ +using System.Linq; +using HarmonyLib; +using MiraAPI.Utilities.Assets; +using TMPro; + +namespace MiraAPI.Patches; +[HarmonyPatch] +public static class TmpAwakePatch +{ + [HarmonyPatch(typeof(TextMeshPro), nameof(TextMeshPro.Awake))] + [HarmonyPostfix] + public static void TmpAwakePostfix(TextMeshPro __instance) + { + if (!TmpSpriteUtils.AssetHolder || !__instance.m_spriteAsset) + { + return; + } + + /* + if (__instance.m_spriteAsset.name == "RoleIcons") + { + Error($"Stencil Comp: {__instance.m_spriteAsset.material.GetFloat(ShaderUtilities.ID_StencilComp)}"); + Error($"Stencil ID: {__instance.m_spriteAsset.material.GetFloat(ShaderUtilities.ID_StencilID)}"); + Error($"Stencil Op: {__instance.m_spriteAsset.material.GetFloat(ShaderUtilities.ID_StencilOp)}"); + Error($"Stencil Write: {__instance.m_spriteAsset.material.GetFloat(ShaderUtilities.ID_StencilWriteMask)}"); + Error($"Stencil Read: {__instance.m_spriteAsset.material.GetFloat(ShaderUtilities.ID_StencilReadMask)}"); + Error($"Cull Mode: {__instance.m_spriteAsset.material.GetFloat(ShaderUtilities.ShaderTag_CullMode)}"); + }*/ + __instance.m_spriteAsset.fallbackSpriteAssets.Add(TmpSpriteUtils.AssetHolder); + __instance.UpdateMeshPadding(); + } +} diff --git a/MiraAPI/Resources/BlankSprite.png b/MiraAPI/Resources/BlankSprite.png new file mode 100644 index 0000000000000000000000000000000000000000..9523fc80e377918a9872497a16ba0276bf690c35 GIT binary patch literal 96 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}1TpU97 jtAP@HsS%!OzP=1vKsE;ugTSTW$so$p)z4*}Q$iB}m<<%} literal 0 HcmV?d00001 diff --git a/MiraAPI/Roles/CustomRoleConfiguration.cs b/MiraAPI/Roles/CustomRoleConfiguration.cs index 819eb1c..f756fed 100644 --- a/MiraAPI/Roles/CustomRoleConfiguration.cs +++ b/MiraAPI/Roles/CustomRoleConfiguration.cs @@ -3,6 +3,7 @@ using System; using Il2CppInterop.Runtime.Attributes; using MiraAPI.Patches.Freeplay; using MiraAPI.Utilities.Assets; +using TMPro; using UnityEngine; namespace MiraAPI.Roles; @@ -87,6 +88,12 @@ public record struct CustomRoleConfiguration [HideFromIl2Cpp] public LoadableAsset? Icon { get; set; } = null; + /// + /// Gets or sets the for the Role Icon. + /// + [HideFromIl2Cpp] + public TMP_SpriteAsset IconTmp { get; set; } + /// /// Gets or sets the Intro sound for the Role. /// diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index 5b72650..1915949 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -5,6 +5,7 @@ using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.PluginLoading; using Reactor.Utilities; +using TMPro; using UnityEngine; namespace MiraAPI.Roles; diff --git a/MiraAPI/Utilities/Assets/MiraAssets.cs b/MiraAPI/Utilities/Assets/MiraAssets.cs index 8ae8986..10bceda 100644 --- a/MiraAPI/Utilities/Assets/MiraAssets.cs +++ b/MiraAPI/Utilities/Assets/MiraAssets.cs @@ -134,4 +134,6 @@ public static class MiraAssets /// Gets the sprite used for timed modifier file in TaskAdderGame. /// public static LoadableResourceAsset TimedModifierFile { get; } = new("MiraAPI.Resources.TimedModifierFile.png"); + + internal static LoadableResourceAsset BlankSprite { get; } = new("MiraAPI.Resources.BlankSprite.png"); } diff --git a/MiraAPI/Utilities/Assets/TmpSpriteUtils.cs b/MiraAPI/Utilities/Assets/TmpSpriteUtils.cs new file mode 100644 index 0000000..8256eb9 --- /dev/null +++ b/MiraAPI/Utilities/Assets/TmpSpriteUtils.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using Reactor.Utilities.Extensions; +using TMPro; +using UnityEngine; + +namespace MiraAPI.Utilities.Assets; +public static class TmpSpriteUtils +{ + private static Shader _spriteShader; + private static Dictionary LoadedSprites = []; + public static TMP_SpriteAsset AssetHolder; + public static TMP_SpriteAsset CreateSpriteAsset(Sprite sprite, string assetName) + { + return CreateSpriteAsset(sprite.texture, sprite.rect, assetName); + } + public static TMP_SpriteAsset CreateSpriteAsset(Texture2D sourceTexture, Rect rect, string assetName) + { + if (LoadedSprites.TryGetValue(assetName, out var existingAsset)) + { + return existingAsset; + } + + if (!AssetHolder) + { + AssetHolder = ScriptableObject.CreateInstance(); + AssetHolder.DontUnload().DontDestroy(); + AssetHolder.fallbackSpriteAssets = new(); + } + TMP_SpriteAsset spriteAsset = ScriptableObject.CreateInstance(); + spriteAsset.name = assetName; + spriteAsset.spriteSheet = sourceTexture; + + if (!_spriteShader) + { + _spriteShader = Shader.Find("TextMeshPro/Sprite"); + } + + var material = new Material(_spriteShader) + { + name = assetName + " Material", + }; + material.SetTexture(ShaderUtilities.ID_MainTex, sourceTexture); + material.SetFloat(ShaderUtilities.ID_StencilComp, 0); + material.SetFloat(ShaderUtilities.ID_StencilID, 0); + material.SetFloat(ShaderUtilities.ID_StencilOp, 0); + material.SetFloat(ShaderUtilities.ID_StencilWriteMask, 255); + material.SetFloat(ShaderUtilities.ID_StencilReadMask, 255); + material.SetFloat(ShaderUtilities.ShaderTag_CullMode, 0); + spriteAsset.material = material; + + spriteAsset.spriteInfoList = new Il2CppSystem.Collections.Generic.List(); + + AddSpriteToAsset(spriteAsset, rect, assetName); + + spriteAsset.DontUnload().DontDestroy(); + spriteAsset.UpdateLookupTables(); + spriteAsset.fallbackSpriteAssets = new(); + AssetHolder.fallbackSpriteAssets.Add(spriteAsset); + AssetHolder.UpdateLookupTables(); + + LoadedSprites.Add(assetName, spriteAsset); + return spriteAsset; + } + + private static void AddSpriteToAsset(TMP_SpriteAsset spriteAsset, Rect rect, string spriteName) + { + TMP_Sprite newSprite = new TMP_Sprite + { + name = spriteName, + hashCode = TMP_TextUtilities.GetSimpleHashCode(spriteName), + x = 0, + y = 0, + width = rect.width, + height = rect.height, + xOffset = -(rect.width / 3), + yOffset = rect.height / 1.25f, + xAdvance = rect.width, + scale = 1, + }; + + spriteAsset.spriteInfoList.Add(newSprite); + } +} From ff30d5fdd63075d61622d444e5eb1e5e043e53d9 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Wed, 1 Jul 2026 20:30:44 -0500 Subject: [PATCH 03/23] Add tmp sprite scaling support --- MiraAPI/Utilities/Assets/TmpSpriteUtils.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/MiraAPI/Utilities/Assets/TmpSpriteUtils.cs b/MiraAPI/Utilities/Assets/TmpSpriteUtils.cs index 8256eb9..b3ab8eb 100644 --- a/MiraAPI/Utilities/Assets/TmpSpriteUtils.cs +++ b/MiraAPI/Utilities/Assets/TmpSpriteUtils.cs @@ -9,11 +9,11 @@ public static class TmpSpriteUtils private static Shader _spriteShader; private static Dictionary LoadedSprites = []; public static TMP_SpriteAsset AssetHolder; - public static TMP_SpriteAsset CreateSpriteAsset(Sprite sprite, string assetName) + public static TMP_SpriteAsset CreateSpriteAsset(Sprite sprite, string assetName, float scale = 1) { - return CreateSpriteAsset(sprite.texture, sprite.rect, assetName); + return CreateSpriteAsset(sprite.texture, sprite.rect, assetName, scale); } - public static TMP_SpriteAsset CreateSpriteAsset(Texture2D sourceTexture, Rect rect, string assetName) + public static TMP_SpriteAsset CreateSpriteAsset(Texture2D sourceTexture, Rect rect, string assetName, float scale = 1) { if (LoadedSprites.TryGetValue(assetName, out var existingAsset)) { @@ -50,7 +50,7 @@ public static class TmpSpriteUtils spriteAsset.spriteInfoList = new Il2CppSystem.Collections.Generic.List(); - AddSpriteToAsset(spriteAsset, rect, assetName); + AddSpriteToAsset(spriteAsset, rect, assetName, scale); spriteAsset.DontUnload().DontDestroy(); spriteAsset.UpdateLookupTables(); @@ -62,7 +62,7 @@ public static class TmpSpriteUtils return spriteAsset; } - private static void AddSpriteToAsset(TMP_SpriteAsset spriteAsset, Rect rect, string spriteName) + private static void AddSpriteToAsset(TMP_SpriteAsset spriteAsset, Rect rect, string spriteName, float scale) { TMP_Sprite newSprite = new TMP_Sprite { @@ -75,7 +75,7 @@ public static class TmpSpriteUtils xOffset = -(rect.width / 3), yOffset = rect.height / 1.25f, xAdvance = rect.width, - scale = 1, + scale = scale, }; spriteAsset.spriteInfoList.Add(newSprite); From 850e82155cfa3e99cb5e8164845cd63364dc243b Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Wed, 1 Jul 2026 23:39:57 -0500 Subject: [PATCH 04/23] Improve tmp offsets --- MiraAPI/Patches/TmpAwakePatch.cs | 16 ++++++---------- MiraAPI/Utilities/Assets/TmpSpriteUtils.cs | 2 +- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/MiraAPI/Patches/TmpAwakePatch.cs b/MiraAPI/Patches/TmpAwakePatch.cs index 3cb9ee6..2d43d23 100644 --- a/MiraAPI/Patches/TmpAwakePatch.cs +++ b/MiraAPI/Patches/TmpAwakePatch.cs @@ -11,21 +11,17 @@ public static class TmpAwakePatch [HarmonyPostfix] public static void TmpAwakePostfix(TextMeshPro __instance) { - if (!TmpSpriteUtils.AssetHolder || !__instance.m_spriteAsset) + if (!TmpSpriteUtils.AssetHolder || __instance.m_spriteAsset == TmpSpriteUtils.AssetHolder) { return; } - /* - if (__instance.m_spriteAsset.name == "RoleIcons") + if (!__instance.m_spriteAsset) { - Error($"Stencil Comp: {__instance.m_spriteAsset.material.GetFloat(ShaderUtilities.ID_StencilComp)}"); - Error($"Stencil ID: {__instance.m_spriteAsset.material.GetFloat(ShaderUtilities.ID_StencilID)}"); - Error($"Stencil Op: {__instance.m_spriteAsset.material.GetFloat(ShaderUtilities.ID_StencilOp)}"); - Error($"Stencil Write: {__instance.m_spriteAsset.material.GetFloat(ShaderUtilities.ID_StencilWriteMask)}"); - Error($"Stencil Read: {__instance.m_spriteAsset.material.GetFloat(ShaderUtilities.ID_StencilReadMask)}"); - Error($"Cull Mode: {__instance.m_spriteAsset.material.GetFloat(ShaderUtilities.ShaderTag_CullMode)}"); - }*/ + __instance.m_spriteAsset = TmpSpriteUtils.AssetHolder; + __instance.UpdateMeshPadding(); + return; + } __instance.m_spriteAsset.fallbackSpriteAssets.Add(TmpSpriteUtils.AssetHolder); __instance.UpdateMeshPadding(); } diff --git a/MiraAPI/Utilities/Assets/TmpSpriteUtils.cs b/MiraAPI/Utilities/Assets/TmpSpriteUtils.cs index b3ab8eb..cdb4315 100644 --- a/MiraAPI/Utilities/Assets/TmpSpriteUtils.cs +++ b/MiraAPI/Utilities/Assets/TmpSpriteUtils.cs @@ -72,7 +72,7 @@ public static class TmpSpriteUtils y = 0, width = rect.width, height = rect.height, - xOffset = -(rect.width / 3), + xOffset = -(rect.width / 3) + (scale - 1) * (rect.width / 2), yOffset = rect.height / 1.25f, xAdvance = rect.width, scale = scale, From 3eb0a4fdb3eb382731e5a474403752a104db94bd Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Wed, 8 Jul 2026 17:31:20 -0500 Subject: [PATCH 05/23] Fix some instant crashing Happens very quickly on practice mode when spam switching tasks --- MiraAPI/Utilities/Extensions.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 6c8b74b..bc3ad0e 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -342,9 +342,8 @@ public static class Extensions { yield return Resources.UnloadUnusedAssets(); - GC.Collect(0, GCCollectionMode.Forced, blocking: true); GC.WaitForPendingFinalizers(); - GC.Collect(0, GCCollectionMode.Forced, blocking: true); + GC.Collect(0, GCCollectionMode.Forced); } /// From 1b718092eee566788f28c50c300ecdc7506e3258 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Wed, 8 Jul 2026 18:13:34 -0500 Subject: [PATCH 06/23] Move GC clearing out of coroutines --- MiraAPI/Utilities/Extensions.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index bc3ad0e..717f0b9 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -326,7 +326,7 @@ public static class Extensions yield return null; if (clearGc) { - yield return CoFreeResources(); + ClearGarbageCollector(); } } @@ -335,15 +335,8 @@ public static class Extensions /// public static void ClearGarbageCollector() { - Coroutines.Start(CoFreeResources()); - } - - private static IEnumerator CoFreeResources() - { - yield return Resources.UnloadUnusedAssets(); - - GC.WaitForPendingFinalizers(); - GC.Collect(0, GCCollectionMode.Forced); + Resources.UnloadUnusedAssets(); + GC.Collect(); } /// From dac1d82d5cbd7c775209e86f01a3f01602f760d8 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Thu, 9 Jul 2026 22:57:31 -0500 Subject: [PATCH 07/23] Reduce heavy garbage collection temporarily --- MiraAPI/Patches/Freeplay/TaskAdderPatches.cs | 7 ++++++- MiraAPI/Patches/Menu/HatsTabPatches.cs | 1 - MiraAPI/Patches/Menu/NameplatesTabPatches.cs | 1 - MiraAPI/Patches/Menu/SkinsTabPatches.cs | 1 - MiraAPI/Patches/Menu/VisorsTabPatches.cs | 1 - MiraAPI/Patches/Options/GameOptionsMenuPatch.cs | 7 +++++++ MiraAPI/Patches/Options/RoleSettingMenuPatches.cs | 3 --- 7 files changed, 13 insertions(+), 8 deletions(-) diff --git a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs index 3e4adc9..fa91e7b 100644 --- a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs +++ b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs @@ -254,6 +254,12 @@ public static class TaskAdderPatches .ToArray() .Any(x => x.FolderName == child.FolderName); + [HarmonyPostfix] + [HarmonyPatch(nameof(TaskAdderGame.OnDisable))] + public static void ClosePatch() + { + Utilities.Extensions.ClearGarbageCollector(); + } // yes it might be crazy patching the entire method, but i tried so many other methods and only this works :cry: // true -chip [HarmonyPrefix] @@ -278,7 +284,6 @@ public static class TaskAdderPatches __instance.ActiveItems.ToArray().Do(x => x.gameObject.DeepDestroy(false)); __instance.ActiveItems.Clear(); - Utilities.Extensions.ClearGarbageCollector(); float num = 0f; float num2 = 0f; diff --git a/MiraAPI/Patches/Menu/HatsTabPatches.cs b/MiraAPI/Patches/Menu/HatsTabPatches.cs index 3ad0fa3..b06b11a 100644 --- a/MiraAPI/Patches/Menu/HatsTabPatches.cs +++ b/MiraAPI/Patches/Menu/HatsTabPatches.cs @@ -103,7 +103,6 @@ public static class HatsTabPatches foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.DeepDestroy(false); __instance.ColorChips.Clear(); __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.DeepDestroy(false)); - Utilities.Extensions.ClearGarbageCollector(); var groupNameText = __instance.GetComponentInChildren(false); var group = sortedHats.Where(x => x.Key == storeNames[page]); diff --git a/MiraAPI/Patches/Menu/NameplatesTabPatches.cs b/MiraAPI/Patches/Menu/NameplatesTabPatches.cs index bd9a6ce..4e9b693 100644 --- a/MiraAPI/Patches/Menu/NameplatesTabPatches.cs +++ b/MiraAPI/Patches/Menu/NameplatesTabPatches.cs @@ -84,7 +84,6 @@ public static class NameplatesTabPatches foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.DeepDestroy(false); __instance.ColorChips.Clear(); __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.DeepDestroy(false)); - Utilities.Extensions.ClearGarbageCollector(); var groupNameText = __instance.GetComponentInChildren(false); diff --git a/MiraAPI/Patches/Menu/SkinsTabPatches.cs b/MiraAPI/Patches/Menu/SkinsTabPatches.cs index 5395ff6..44d08d5 100644 --- a/MiraAPI/Patches/Menu/SkinsTabPatches.cs +++ b/MiraAPI/Patches/Menu/SkinsTabPatches.cs @@ -86,7 +86,6 @@ public static class SkinsTabPatches foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.DeepDestroy(false); __instance.ColorChips.Clear(); __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.DeepDestroy(false)); - Utilities.Extensions.ClearGarbageCollector(); var groupNameText = __instance.GetComponentInChildren(false); diff --git a/MiraAPI/Patches/Menu/VisorsTabPatches.cs b/MiraAPI/Patches/Menu/VisorsTabPatches.cs index ed5f103..c5fe9a4 100644 --- a/MiraAPI/Patches/Menu/VisorsTabPatches.cs +++ b/MiraAPI/Patches/Menu/VisorsTabPatches.cs @@ -83,7 +83,6 @@ public static class VisorsTabPatches foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.DeepDestroy(false); __instance.ColorChips.Clear(); __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.DeepDestroy(false)); - Utilities.Extensions.ClearGarbageCollector(); var groupNameText = __instance.GetComponentInChildren(false); diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index fa393c2..cbc11aa 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -35,6 +35,13 @@ internal static class GameOptionsMenuPatch HudManager.Instance.PlayerCam.OverrideScreenShakeEnabled = true; } + [HarmonyPostfix] + [HarmonyPatch(nameof(GameOptionsMenu.CloseMenu))] + public static void FullMenuClosePatch(GameOptionsMenu __instance) + { + Utilities.Extensions.ClearGarbageCollector(); + } + [HarmonyPostfix] [HarmonyPatch(nameof(GameOptionsMenu.Update))] // ReSharper disable once InconsistentNaming diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 67a4894..55947c8 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -76,7 +76,6 @@ public static class RoleSettingMenuPatches __instance.roleChances = new Il2CppSystem.Collections.Generic.List(); __instance.advancedSettingChildren = new Il2CppSystem.Collections.Generic.List(); - Utilities.Extensions.ClearGarbageCollector(); var maskBg = __instance.scrollBar.transform.FindChild("MaskBg"); var hitbox = __instance.scrollBar.transform.FindChild("Hitbox"); @@ -328,7 +327,6 @@ public static class RoleSettingMenuPatches obj.gameObject.DeepDestroy(false); } RoleOptionSettings.Clear(); - Utilities.Extensions.ClearGarbageCollector(); __instance.SetQuotaTab(); })); headerBtn.SetButtonEnableState(true); @@ -456,7 +454,6 @@ public static class RoleSettingMenuPatches CurrentRole = role; __instance.advancedSettingChildren.Clear(); - Utilities.Extensions.ClearGarbageCollector(); // TODO: create sub groups under the role settings. var filteredOptions = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups From 662b52f78d06932dc9aec45ba7aa99be0d435c28 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Thu, 9 Jul 2026 22:57:59 -0500 Subject: [PATCH 08/23] Allow populated votes to be modified --- MiraAPI/Voting/VotingUtils.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MiraAPI/Voting/VotingUtils.cs b/MiraAPI/Voting/VotingUtils.cs index abafdd0..130e4c4 100644 --- a/MiraAPI/Voting/VotingUtils.cs +++ b/MiraAPI/Voting/VotingUtils.cs @@ -207,6 +207,9 @@ public static class VotingUtils return; } + // If modified, these will visually change. + votes = @event.Votes; + MeetingHud.Instance.TitleText.text = TranslationController.Instance.GetString( StringNames.MeetingVotingResults, From 9d7e41a3c9f2e06317ead8a5de686080b3bccc6e Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Thu, 9 Jul 2026 23:03:02 -0500 Subject: [PATCH 09/23] Fix duplicated Populate Results Event --- MiraAPI/Patches/Voting/MeetingHudPatches.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/MiraAPI/Patches/Voting/MeetingHudPatches.cs b/MiraAPI/Patches/Voting/MeetingHudPatches.cs index 2aa9a82..7a4338a 100644 --- a/MiraAPI/Patches/Voting/MeetingHudPatches.cs +++ b/MiraAPI/Patches/Voting/MeetingHudPatches.cs @@ -208,13 +208,6 @@ internal static class MeetingHudPatches public static bool PopulateResultsPatch(MeetingHud __instance, ref Il2CppStructArray states) { var votes = states.Select(x=> new CustomVote(x.VoterId, x.VotedForId)).ToList(); - var @event = new PopulateResultsEvent(votes); - MiraEventManager.InvokeEvent(@event); - - if (@event.IsCancelled) - { - return false; - } VotingUtils.HandlePopulateResults(votes); return false; From 5cccc192b1b43539c95b1e0e87df4233b4dc3026 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:07:40 -0500 Subject: [PATCH 10/23] Add support for better option notfs --- .../Options/Roles/FreezerRoleSettings.cs | 8 +++ MiraAPI/GameOptions/AbstractOptionGroup.cs | 7 +++ MiraAPI/GameOptions/IModdedOption.cs | 10 ++++ MiraAPI/GameOptions/ModdedOptionsManager.cs | 33 ++++++++++++ .../GameOptions/OptionNotifConfiguration.cs | 53 +++++++++++++++++++ .../OptionTypes/ModdedEnumOption.cs | 16 +++++- .../OptionTypes/ModdedNumberOption.cs | 7 ++- .../GameOptions/OptionTypes/ModdedOption.cs | 9 +++- .../OptionTypes/ModdedPlayerOption.cs | 8 ++- .../OptionTypes/ModdedToggleOption.cs | 8 ++- .../Options/NotificationPopperPatch.cs | 2 +- 11 files changed, 153 insertions(+), 8 deletions(-) create mode 100644 MiraAPI/GameOptions/OptionNotifConfiguration.cs diff --git a/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs b/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs index 1473068..1be3649 100644 --- a/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs +++ b/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs @@ -2,11 +2,19 @@ using MiraAPI.GameOptions; using MiraAPI.GameOptions.Attributes; using MiraAPI.Utilities; +using MiraAPI.Utilities.Assets; +using TMPro; +using UnityEngine; namespace MiraAPI.Example.Options.Roles; public class FreezerRoleSettings : AbstractOptionGroup { + public override OptionNotifConfiguration Configuration => new( + Palette.Blue, + TmpSpriteUtils.CreateSpriteAsset( + MiraAssets.ImpostorFile.LoadAsset(), + "ApiExample.Role.Impostor.FreezerRole")); public override string GroupName => "Custom Role"; [ModdedNumberOption("Freeze Duration", 1, 15, 1, MiraNumberSuffixes.Seconds)] diff --git a/MiraAPI/GameOptions/AbstractOptionGroup.cs b/MiraAPI/GameOptions/AbstractOptionGroup.cs index cb82e1c..07d008a 100644 --- a/MiraAPI/GameOptions/AbstractOptionGroup.cs +++ b/MiraAPI/GameOptions/AbstractOptionGroup.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using Il2CppInterop.Runtime.Attributes; using MiraAPI.Modifiers; +using TMPro; using UnityEngine; namespace MiraAPI.GameOptions; @@ -58,6 +60,11 @@ public abstract class AbstractOptionGroup /// public virtual uint GroupPriority => uint.MaxValue; + /// + /// Gets the default notification settings for the group. + /// + public virtual OptionNotifConfiguration Configuration => new(new Color(0.7333f, 0.7333f, 0.7333f, 1)); + internal bool AllOptionsHidden { get; set; } internal CategoryHeaderMasked? Header { get; set; } diff --git a/MiraAPI/GameOptions/IModdedOption.cs b/MiraAPI/GameOptions/IModdedOption.cs index 907ab7d..646c22b 100644 --- a/MiraAPI/GameOptions/IModdedOption.cs +++ b/MiraAPI/GameOptions/IModdedOption.cs @@ -21,6 +21,16 @@ public interface IModdedOption /// string Title { get; set; } + /// + /// Gets or sets the parent of the option. + /// + AbstractOptionGroup ParentGroup { get; set; } + + /// + /// Gets the notification settings for the option. + /// + OptionNotifConfiguration Configuration { get; } + /// /// Gets the for the option, used for localization. /// diff --git a/MiraAPI/GameOptions/ModdedOptionsManager.cs b/MiraAPI/GameOptions/ModdedOptionsManager.cs index 3e6e04e..7dd26ee 100644 --- a/MiraAPI/GameOptions/ModdedOptionsManager.cs +++ b/MiraAPI/GameOptions/ModdedOptionsManager.cs @@ -12,6 +12,8 @@ using MiraAPI.PluginLoading; using MiraAPI.Utilities; using Reactor.Networking.Rpc; using Reactor.Utilities; +using TMPro; +using UnityEngine; namespace MiraAPI.GameOptions; @@ -30,6 +32,36 @@ public static class ModdedOptionsManager internal static uint NextId => _nextId++; private static uint _nextId = 1; + public static void AddSettingsChangeMessage(NotificationPopper notif, StringNames key, string value, Color textColor, TMP_SpriteAsset? sprite, bool playSound = true) + { + var item = string.Empty; + var text = textColor.ToTextColor(); + if (sprite != null) + { + item = TranslationController.Instance.GetString( + StringNames.LobbyChangeSettingNotification, + string.Concat( + "", + text, + TranslationController.Instance.GetString(key), + ""), + "" + value + "" + ); + } + else + { + item = TranslationController.Instance.GetString( + StringNames.LobbyChangeSettingNotification, + "" + + text + + TranslationController.Instance.GetString(key) + "", + "" + value + ""); + } + notif.SettingsChangeMessageLogic(key, item, playSound); + } + internal static bool RegisterGroup(Type type, MiraPluginInfo pluginInfo) { if (Activator.CreateInstance(type) is not AbstractOptionGroup group) @@ -123,6 +155,7 @@ public static class ModdedOptionsManager option.ConfigDefinition = new ConfigDefinition(groupName, propertyName); + option.ParentGroup = group; option.ParentMod = pluginInfo.MiraPlugin; pluginInfo.InternalOptions.Add(option); ModdedOptions.Add(option.Id, option); diff --git a/MiraAPI/GameOptions/OptionNotifConfiguration.cs b/MiraAPI/GameOptions/OptionNotifConfiguration.cs new file mode 100644 index 0000000..e16106f --- /dev/null +++ b/MiraAPI/GameOptions/OptionNotifConfiguration.cs @@ -0,0 +1,53 @@ +using System; +using Il2CppInterop.Runtime.Attributes; +using TMPro; +using UnityEngine; +namespace MiraAPI.GameOptions; + +/// +/// Used to configure the specific visuals for option notifications. +/// +public record struct OptionNotifConfiguration +{ +#pragma warning disable S1133 + [Obsolete("Default constructor is not supported")] +#pragma warning restore S1133 +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + public OptionNotifConfiguration() +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member + { + throw new NotImplementedException("Default constructor is not supported."); + } + + /// + /// Initializes a new instance of the struct based on a group. + /// + /// The in which you are configuring. + public OptionNotifConfiguration(AbstractOptionGroup group) + { + PopUpIconTmp = group.Configuration.PopUpIconTmp; + PopUpTextColor = group.Configuration.PopUpTextColor; + } + + /// + /// Initializes a new instance of the struct from scratch. + /// + /// The text for the configuration. + /// The icon for the configuration. + public OptionNotifConfiguration(Color color, TMP_SpriteAsset asset = null!) + { + PopUpIconTmp = asset; + PopUpTextColor = color; + } + + /// + /// Gets or sets the for the icon on the options pop-up. + /// + public TMP_SpriteAsset PopUpIconTmp { get; set; } + + /// + /// Gets or sets the for the text on the options pop-up. + /// + [HideFromIl2Cpp] + public Color PopUpTextColor { get; set; } +} diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs index 1f92418..30809bd 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs @@ -90,7 +90,13 @@ public class ModdedEnumOption : ModdedOption /// protected override void OnValueChanged(int newValue) { - HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false); + ModdedOptionsManager.AddSettingsChangeMessage( + HudManager.Instance.Notifier, + StringName, + Data.GetValueString(newValue), + Configuration.PopUpTextColor, + Configuration.PopUpIconTmp, + false); if (!OptionBehaviour) { return; @@ -183,7 +189,13 @@ public class ModdedEnumOption : ModdedOption where T : Enum /// protected override void OnValueChanged(T newValue) { - HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo)), false); + ModdedOptionsManager.AddSettingsChangeMessage( + HudManager.Instance.Notifier, + StringName, + Data.GetValueString(Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo)), + Configuration.PopUpTextColor, + Configuration.PopUpIconTmp, + false); if (!OptionBehaviour) { return; diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs index 1a5ef33..87f4ca9 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs @@ -225,9 +225,12 @@ public class ModdedNumberOption : ModdedOption protected override void OnValueChanged(float newValue) { Value = Mathf.Clamp(newValue, Min, Max); - HudManager.Instance.Notifier.AddSettingsChangeMessage( + ModdedOptionsManager.AddSettingsChangeMessage( + HudManager.Instance.Notifier, StringName, - Data.GetValueString(Value), + Data.GetValueString(newValue), + Configuration.PopUpTextColor, + Configuration.PopUpIconTmp, false); if (OptionBehaviour is NumberOption opt) diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs index 206894d..a023bac 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -1,10 +1,11 @@ using System; using BepInEx.Configuration; +using Il2CppInterop.Runtime.Attributes; using MiraAPI.Networking; using MiraAPI.PluginLoading; using Reactor.Localization.Utilities; using Reactor.Networking.Rpc; -using Reactor.Utilities; +using TMPro; using UnityEngine; namespace MiraAPI.GameOptions.OptionTypes; @@ -190,4 +191,10 @@ public abstract class ModdedOption : IModdedOption { return option.Value; } + + /// + public AbstractOptionGroup ParentGroup { get; set; } + + /// + public OptionNotifConfiguration Configuration => new(ParentGroup); } diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs index f6516cd..db397bd 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs @@ -126,7 +126,13 @@ public class ModdedPlayerOption : ModdedOption /// protected override void OnValueChanged(int newValue) { - HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false); + ModdedOptionsManager.AddSettingsChangeMessage( + HudManager.Instance.Notifier, + StringName, + Data.GetValueString(newValue), + Configuration.PopUpTextColor, + Configuration.PopUpIconTmp, + false); if (!OptionBehaviour) { return; diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs index 0b13f97..55b69b9 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs @@ -71,7 +71,13 @@ public class ModdedToggleOption : ModdedOption /// protected override void OnValueChanged(bool newValue) { - HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, newValue ? "On" : "Off", false); + ModdedOptionsManager.AddSettingsChangeMessage( + HudManager.Instance.Notifier, + StringName, + newValue ? "On" : "Off", + Configuration.PopUpTextColor, + Configuration.PopUpIconTmp, + false); if (!OptionBehaviour) { return; diff --git a/MiraAPI/Patches/Options/NotificationPopperPatch.cs b/MiraAPI/Patches/Options/NotificationPopperPatch.cs index 0ba8605..349f103 100644 --- a/MiraAPI/Patches/Options/NotificationPopperPatch.cs +++ b/MiraAPI/Patches/Options/NotificationPopperPatch.cs @@ -33,7 +33,7 @@ public static class NotificationPopperPatch icon, "", textColor, - TranslationController.Instance.GetString(key, Array.Empty()), + TranslationController.Instance.GetString(key), "" ), "" + roleCount + "", From 7afd899e567bfa317f83227c864460b80ccb44ae Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:47:53 -0500 Subject: [PATCH 11/23] Improve example options --- .../Options/Roles/TeleporterOptions.cs | 2 +- MiraAPI/GameOptions/AbstractOptionGroup.cs | 33 +++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/MiraAPI.Example/Options/Roles/TeleporterOptions.cs b/MiraAPI.Example/Options/Roles/TeleporterOptions.cs index b6d02a4..d05569b 100644 --- a/MiraAPI.Example/Options/Roles/TeleporterOptions.cs +++ b/MiraAPI.Example/Options/Roles/TeleporterOptions.cs @@ -6,7 +6,7 @@ using MiraAPI.Utilities; namespace MiraAPI.Example.Options.Roles; -public class TeleporterOptions : AbstractOptionGroup +public class TeleporterOptions : AbstractRoleOptionGroup { public override string GroupName => "Teleporter"; diff --git a/MiraAPI/GameOptions/AbstractOptionGroup.cs b/MiraAPI/GameOptions/AbstractOptionGroup.cs index 07d008a..8be08c6 100644 --- a/MiraAPI/GameOptions/AbstractOptionGroup.cs +++ b/MiraAPI/GameOptions/AbstractOptionGroup.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; using Il2CppInterop.Runtime.Attributes; using MiraAPI.Modifiers; +using MiraAPI.Roles; using TMPro; using UnityEngine; @@ -79,9 +81,7 @@ public abstract class AbstractOptionGroup : AbstractOptionGroup where T : IOp /// public override Type OptionableType => typeof(T); - /// - /// Gets a value indicating which menu the group is in. - /// + /// public override MenuCategory ParentMenu { get @@ -100,6 +100,33 @@ public abstract class AbstractOptionGroup : AbstractOptionGroup where T : IOp } } +/// +/// Base class for option groups. An option group is a collection of options that are displayed together in the options menu. +/// +/// The custom role that the group is for. +public abstract class AbstractRoleOptionGroup() : AbstractOptionGroup where T : ICustomRole +{ + /// + public override Type OptionableType => typeof(T); + + /// + public override MenuCategory ParentMenu => MenuCategory.Roles; + + /// + public override OptionNotifConfiguration Configuration + { + get + { + var role = CustomRoleManager.CustomMiraRoles.FirstOrDefault(x => x.GetType() == OptionableType); + if (role == null) + { + return new(new Color(0.7333f, 0.7333f, 0.7333f, 1)); + } + return new(role.RoleColor, role.Configuration.IconTmp); + } + } +} + /// /// Menu categories for option groups. /// From eb18d82d8ee568a687febff4d88c2315e64e757f Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Fri, 17 Jul 2026 21:34:10 -0500 Subject: [PATCH 12/23] Allow overriding configuration on modded option --- MiraAPI/GameOptions/OptionTypes/ModdedOption.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs index a023bac..50457b6 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -196,5 +196,5 @@ public abstract class ModdedOption : IModdedOption public AbstractOptionGroup ParentGroup { get; set; } /// - public OptionNotifConfiguration Configuration => new(ParentGroup); + public virtual OptionNotifConfiguration Configuration => new(ParentGroup); } From e541840da997277ade8878ed4e628aece2360ce4 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:35:52 -0400 Subject: [PATCH 13/23] improve lag issues in settings menu on 17.4 --- .../Patches/Options/GameOptionsMenuPatch.cs | 232 +++++++++--------- .../Patches/Options/RoleSettingMenuPatches.cs | 145 ++++++----- MiraAPI/Utilities/Extensions.cs | 40 +++ 3 files changed, 244 insertions(+), 173 deletions(-) diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index cbc11aa..e8ad1a2 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -1,6 +1,10 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using AmongUs.GameOptions; +using BepInEx.Unity.IL2CPP.Utils.Collections; using HarmonyLib; using Il2CppInterop.Runtime.Attributes; using MiraAPI.GameOptions; @@ -178,7 +182,8 @@ internal static class GameOptionsMenuPatch private static void ModifiersUpdate(ref float num) { var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x => x.ParentMenu is MenuCategory.Modifiers || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? []; + .Where(x => x.ParentMenu is MenuCategory.Modifiers || + x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? []; foreach (var modGroup in groups) { @@ -189,7 +194,8 @@ internal static class GameOptionsMenuPatch private static void ModifiersCreate(GameOptionsMenu menu) { var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x => x.ParentMenu is MenuCategory.Modifiers || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? []; + .Where(x => x.ParentMenu is MenuCategory.Modifiers || + x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? []; foreach (var group in groups) { CreateGroup(menu, group); @@ -208,20 +214,17 @@ internal static class GameOptionsMenuPatch __instance.MapPicker.gameObject.SetActive(false); - if (__instance.name == "MODIFIERS TAB") + switch (__instance.name) { - ModifiersCreate(__instance); - return false; - } - else if (__instance.name == "CUSTOM TAB 1") - { - CustomMenuOneCreate(__instance); - return false; - } - else if (__instance.name == "CUSTOM TAB 2") - { - CustomMenuTwoCreate(__instance); - return false; + case "MODIFIERS TAB": + ModifiersCreate(__instance); + return false; + case "CUSTOM TAB 1": + CustomMenuOneCreate(__instance); + return false; + case "CUSTOM TAB 2": + CustomMenuTwoCreate(__instance); + return false; } var filteredGroups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups @@ -318,117 +321,120 @@ internal static class GameOptionsMenuPatch defaultPreset = preset; } - foreach (var newOpt in options) - { - newOpt.SetClickMask(menu.ButtonClickMask); - - SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren(true); - foreach (var renderer in componentsInChildren) + menu.StartCoroutine( + options.CoLoopWithBudget((newOpt) => { - if (group.GroupColor != MiraApiPlugin.DefaultHeaderColor) + newOpt.SetClickMask(menu.ButtonClickMask); + + SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren(true); + foreach (var renderer in componentsInChildren) { - renderer.color = group.GroupColor.FindAlternateColor(); - if (renderer.transform.parent.TryGetComponent(out var btn)) + if (group.GroupColor != MiraApiPlugin.DefaultHeaderColor) { - btn.interactableColor = group.GroupColor.FindAlternateColor(); - btn.interactableHoveredColor = Color.white; + renderer.color = group.GroupColor.FindAlternateColor(); + if (renderer.transform.parent.TryGetComponent(out var btn)) + { + btn.interactableColor = group.GroupColor.FindAlternateColor(); + btn.interactableHoveredColor = Color.white; + } } + + renderer.material.SetInt(PlayerMaterial.MaskLayer, 20); } - renderer.material.SetInt(PlayerMaterial.MaskLayer, 20); - } - - foreach (var textMeshPro in newOpt.GetComponentsInChildren(true)) - { - if (group.GroupColor != MiraApiPlugin.DefaultHeaderColor) + foreach (var textMeshPro in newOpt.GetComponentsInChildren(true)) { - textMeshPro.color = group.GroupColor; + if (group.GroupColor != MiraApiPlugin.DefaultHeaderColor) + { + textMeshPro.color = group.GroupColor; + } + + textMeshPro.fontMaterial.SetFloat(ShaderID.StencilComp, 3f); + textMeshPro.fontMaterial.SetFloat(ShaderID.Stencil, 20); } - textMeshPro.fontMaterial.SetFloat(ShaderID.StencilComp, 3f); - textMeshPro.fontMaterial.SetFloat(ShaderID.Stencil, 20); - } - - if (newOpt is ToggleOption toggle) - { - toggle.CheckMark.sprite = MiraAssets.Checkmark.LoadAsset(); - toggle.CheckMark.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor - ? group.GroupColor - : MiraAssets.AcceptedTeal; - var rend = toggle.CheckMark.transform.parent.FindChild("ActiveSprite") - .GetComponent(); - rend.sprite = MiraAssets.CheckmarkBox.LoadAsset(); - rend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor - ? group.GroupColor - : MiraAssets.AcceptedTeal; - } - - menu.Children.Add(newOpt); - var resetBtn = new GameObject("ResetOption"); - resetBtn.transform.parent = newOpt.transform; - resetBtn.transform.localScale = new(.5f, .5f, 1); - resetBtn.layer = LayerMask.NameToLayer("UI"); - resetBtn.transform.localPosition = new Vector3(-3.1f, 0f, -2f); - var resetRend = resetBtn.AddComponent(); - resetRend.sprite = MiraAssets.ResetButton.LoadAsset(); - resetRend.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; - resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) - ? Color.white - : group.GroupColor.FindAlternateColor(); - var resetBoxCol = resetBtn.gameObject.AddComponent(); - resetBoxCol.size = new Vector2(1f, 1f); - resetBoxCol.offset = new Vector2(0, 0); - var passiveButton = resetBtn.AddComponent(); - passiveButton.OnClick = new Button.ButtonClickedEvent(); - passiveButton.ClickSound = menu.BackButton.GetComponent().ClickSound; - passiveButton.OnMouseOver = new UnityEvent(); - passiveButton.OnMouseOver.AddListener( - (UnityAction)(() => + if (newOpt is ToggleOption toggle) { - resetRend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor + toggle.CheckMark.sprite = MiraAssets.Checkmark.LoadAsset(); + toggle.CheckMark.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor ? group.GroupColor : MiraAssets.AcceptedTeal; - })); - passiveButton.OnMouseOut = new UnityEvent(); - passiveButton.OnMouseOut.AddListener( - (UnityAction)(() => - { - resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) - ? Color.white - : group.GroupColor.FindAlternateColor(); - })); - if (newOpt is ToggleOption toggleOpt) - { - passiveButton.OnClick.AddListener( - (UnityAction)(() => - { - defaultPreset!.ResetOption(toggleOpt); - })); - } - else if (newOpt is NumberOption numOpt) - { - passiveButton.OnClick.AddListener( - (UnityAction)(() => - { - defaultPreset!.ResetOption(numOpt); - })); - } - else if (newOpt is StringOption strOpt) - { - passiveButton.OnClick.AddListener( - (UnityAction)(() => - { - defaultPreset!.ResetOption(strOpt); - })); - } - if (!defaultPreset!.IsOptionInPreset(newOpt)) - { - resetBtn.Destroy(); - } + var rend = toggle.CheckMark.transform.parent.FindChild("ActiveSprite") + .GetComponent(); + rend.sprite = MiraAssets.CheckmarkBox.LoadAsset(); + rend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor + ? group.GroupColor + : MiraAssets.AcceptedTeal; + } - newOpt.Initialize(); - newOpt.gameObject.SetActive(false); - } + menu.Children.Add(newOpt); + var resetBtn = new GameObject("ResetOption"); + resetBtn.transform.parent = newOpt.transform; + resetBtn.transform.localScale = new(.5f, .5f, 1); + resetBtn.layer = LayerMask.NameToLayer("UI"); + resetBtn.transform.localPosition = new Vector3(-3.1f, 0f, -2f); + var resetRend = resetBtn.AddComponent(); + resetRend.sprite = MiraAssets.ResetButton.LoadAsset(); + resetRend.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; + resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) + ? Color.white + : group.GroupColor.FindAlternateColor(); + var resetBoxCol = resetBtn.gameObject.AddComponent(); + resetBoxCol.size = new Vector2(1f, 1f); + resetBoxCol.offset = new Vector2(0, 0); + var passiveButton = resetBtn.AddComponent(); + passiveButton.OnClick = new Button.ButtonClickedEvent(); + passiveButton.ClickSound = menu.BackButton.GetComponent().ClickSound; + passiveButton.OnMouseOver = new UnityEvent(); + passiveButton.OnMouseOver.AddListener( + (UnityAction)(() => + { + resetRend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor + ? group.GroupColor + : MiraAssets.AcceptedTeal; + })); + passiveButton.OnMouseOut = new UnityEvent(); + passiveButton.OnMouseOut.AddListener( + (UnityAction)(() => + { + resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) + ? Color.white + : group.GroupColor.FindAlternateColor(); + })); + if (newOpt is ToggleOption toggleOpt) + { + passiveButton.OnClick.AddListener( + (UnityAction)(() => + { + defaultPreset!.ResetOption(toggleOpt); + })); + } + else if (newOpt is NumberOption numOpt) + { + passiveButton.OnClick.AddListener( + (UnityAction)(() => + { + defaultPreset!.ResetOption(numOpt); + })); + } + else if (newOpt is StringOption strOpt) + { + passiveButton.OnClick.AddListener( + (UnityAction)(() => + { + defaultPreset!.ResetOption(strOpt); + })); + } + + if (!defaultPreset!.IsOptionInPreset(newOpt)) + { + resetBtn.Destroy(); + } + + newOpt.Initialize(); + newOpt.gameObject.SetActive(false); + }).WrapToIl2Cpp() + ); var boxCol = categoryHeaderMasked.gameObject.AddComponent(); boxCol.size = new Vector2(7, 0.7f); diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 55947c8..18daa7d 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -1,9 +1,10 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; +using BepInEx.Unity.IL2CPP.Utils.Collections; using HarmonyLib; -using Il2CppInterop.Runtime.InteropTypes.Arrays; using MiraAPI.GameOptions; using MiraAPI.Networking; using MiraAPI.Roles; @@ -11,7 +12,6 @@ using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Localization.Utilities; using Reactor.Networking.Rpc; -using Reactor.Utilities; using Reactor.Utilities.Extensions; using TMPro; using UnityEngine; @@ -49,9 +49,7 @@ public static class RoleSettingMenuPatches __instance.scrollBar.SetScrollBounds(__instance); } - [HarmonyPrefix] - [HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))] - public static bool SetQuotaTabPatch(RolesSettingsMenu __instance) + private static IEnumerator CoQuotaTabPatch(RolesSettingsMenu roleMenu) { foreach (var obj in Headers) { @@ -59,8 +57,10 @@ public static class RoleSettingMenuPatches { continue; } + obj.DeepDestroy(false); } + Headers.Clear(); foreach (var obj in RoleOptionSettings) { @@ -68,24 +68,26 @@ public static class RoleSettingMenuPatches { continue; } + obj.gameObject.DeepDestroy(false); } + RoleOptionSettings.Clear(); CurrentRole = null; CurrentRoleOptions = null; - __instance.roleChances = new Il2CppSystem.Collections.Generic.List(); - __instance.advancedSettingChildren = new Il2CppSystem.Collections.Generic.List(); + roleMenu.roleChances = new Il2CppSystem.Collections.Generic.List(); + roleMenu.advancedSettingChildren = new Il2CppSystem.Collections.Generic.List(); - var maskBg = __instance.scrollBar.transform.FindChild("MaskBg"); - var hitbox = __instance.scrollBar.transform.FindChild("Hitbox"); - var dividerImage = __instance.transform.FindChild("HeaderButtons/DividerImage"); + var maskBg = roleMenu.scrollBar.transform.FindChild("MaskBg"); + var hitbox = roleMenu.scrollBar.transform.FindChild("Hitbox"); + var dividerImage = roleMenu.transform.FindChild("HeaderButtons/DividerImage"); if (GameSettingMenuPatches.SelectedModIdx == 0) { - __instance.AllButton.transform.parent.gameObject.SetActive(true); - __instance.AllButton.gameObject.SetActive(true); - __instance.scrollBar.transform.localPosition = new Vector3(-1.4957f, 0.657f, -4); + roleMenu.AllButton.transform.parent.gameObject.SetActive(true); + roleMenu.AllButton.gameObject.SetActive(true); + roleMenu.scrollBar.transform.localPosition = new Vector3(-1.4957f, 0.657f, -4); maskBg.localPosition = new Vector3(1.5353f, -.5734f, -.1f); maskBg.localScale = new Vector3(6.6811f, 3.3563f, 0.5598f); hitbox.localPosition = new Vector3(0.3297f, -.2333f, 4f); @@ -94,67 +96,70 @@ public static class RoleSettingMenuPatches var num = 0.662f; - var list = CustomRoleManager.AllRoles.Where( - r => !r.IsCustomRole() && !r.IsRoleBlacklisted() && r.TeamType == RoleTeamTypes.Crewmate && r.Role != RoleTypes.Crewmate && - r.Role != RoleTypes.CrewmateGhost).ToList(); - var list2 = CustomRoleManager.AllRoles.Where( - r => !r.IsCustomRole() && !r.IsRoleBlacklisted() && r.TeamType == RoleTeamTypes.Impostor && r.Role != RoleTypes.Impostor && - r.Role != RoleTypes.ImpostorGhost).ToList(); + var list = CustomRoleManager.AllRoles.Where(r => + !r.IsCustomRole() && !r.IsRoleBlacklisted() && r.TeamType == RoleTeamTypes.Crewmate && + r.Role != RoleTypes.Crewmate && + r.Role != RoleTypes.CrewmateGhost).ToList(); + var list2 = CustomRoleManager.AllRoles.Where(r => + !r.IsCustomRole() && !r.IsRoleBlacklisted() && r.TeamType == RoleTeamTypes.Impostor && + r.Role != RoleTypes.Impostor && + r.Role != RoleTypes.ImpostorGhost).ToList(); - if (__instance.roleTabs == null || __instance.roleTabs.Count == 0) + if (roleMenu.roleTabs == null || roleMenu.roleTabs.Count == 0) { var num2 = -1.928f; - __instance.roleTabs = new(); - __instance.roleTabs.Add(__instance.AllButton); + roleMenu.roleTabs = new(); + roleMenu.roleTabs.Add(roleMenu.AllButton); foreach (var t in list) { - __instance.AddRoleTab(t, ref num2); + roleMenu.AddRoleTab(t, ref num2); } foreach (var t in list2) { - __instance.AddRoleTab(t, ref num2); + roleMenu.AddRoleTab(t, ref num2); } } var categoryHeaderEditRole = Object.Instantiate( - __instance.categoryHeaderEditRoleOrigin, + roleMenu.categoryHeaderEditRoleOrigin, Vector3.zero, Quaternion.identity, - __instance.RoleChancesSettings.transform); + roleMenu.RoleChancesSettings.transform); categoryHeaderEditRole.SetHeader(StringNames.CrewmateRolesHeader, 20); categoryHeaderEditRole.transform.localPosition = new Vector3(4.986f, num, -2f); num -= 0.522f; var num3 = 0; foreach (var t in list) { - __instance.CreateQuotaOption(t, ref num, num3); + roleMenu.CreateQuotaOption(t, ref num, num3); num3++; } num -= 0.22f; var categoryHeaderEditRole2 = Object.Instantiate( - __instance.categoryHeaderEditRoleOrigin, + roleMenu.categoryHeaderEditRoleOrigin, Vector3.zero, Quaternion.identity, - __instance.RoleChancesSettings.transform); + roleMenu.RoleChancesSettings.transform); categoryHeaderEditRole2.SetHeader(StringNames.ImpostorRolesHeader, 20); categoryHeaderEditRole2.transform.localPosition = new Vector3(4.986f, num, -2f); num -= 0.522f; foreach (var t in list2) { - __instance.CreateQuotaOption(t, ref num, num3); + roleMenu.CreateQuotaOption(t, ref num, num3); num3++; } - return false; + Info($"Created {num3} role options for the default game."); + yield return null; } ScrollerNum = 0.522f; - __instance.AllButton.transform.parent.gameObject.SetActive(false); - __instance.AllButton.gameObject.SetActive(false); - __instance.scrollBar.transform.localPosition = new Vector3(-1.4957f, 1.5261f, -4); + roleMenu.AllButton.transform.parent.gameObject.SetActive(false); + roleMenu.AllButton.gameObject.SetActive(false); + roleMenu.scrollBar.transform.localPosition = new Vector3(-1.4957f, 1.5261f, -4); maskBg.localPosition = new Vector3(1.5353f, -1.0607f, -.1f); maskBg.localScale = new Vector3(6.6811f, 4.1563f, 0.5598f); hitbox.localPosition = new Vector3(0.3297f, -.6333f, 4f); @@ -168,7 +173,8 @@ public static class RoleSettingMenuPatches if (roleGroups is null) { - return false; + Error("No role groups found for the selected mod."); + yield break; } // sort the groups by priority @@ -176,13 +182,14 @@ public static class RoleSettingMenuPatches .OrderBy(x => x.Key.Priority) .ThenBy(x => x.Key.Name); - var quotaThing = __instance.categoryHeaderEditRoleOrigin.transform.FindChild("QuotaHeader"); + var quotaThing = roleMenu.categoryHeaderEditRoleOrigin.transform.FindChild("QuotaHeader"); var usingNewQuota = false; - var template = __instance.transform.parent.parent.GetComponent().GameSettingsTab.categoryHeaderOrigin; + var template = roleMenu.transform.parent.parent.GetComponent().GameSettingsTab + .categoryHeaderOrigin; foreach (var grouping in sortedRoleGroups) { - if (!grouping.Any() || grouping.All(x=> x.Configuration.HideSettings || !x.VisibleInSettings())) + if (!grouping.Any() || grouping.All(x => x.Configuration.HideSettings || !x.VisibleInSettings())) { continue; } @@ -202,7 +209,7 @@ public static class RoleSettingMenuPatches template, Vector3.zero, Quaternion.identity, - __instance.RoleChancesSettings.transform); + roleMenu.RoleChancesSettings.transform); categoryHeaderMasked.SetHeader(name, 20); @@ -245,19 +252,23 @@ public static class RoleSettingMenuPatches categoryHeaderMasked.Background.color = Palette.ImpostorRoleHeaderRed; break; default: - categoryHeaderMasked.Title.color = group.Color.Equals(MiraApiPlugin.DefaultHeaderColor) ? Color.white : group.Color.FindAlternateColor(); + categoryHeaderMasked.Title.color = group.Color.Equals(MiraApiPlugin.DefaultHeaderColor) + ? Color.white + : group.Color.FindAlternateColor(); categoryHeaderMasked.Divider.color = group.Color; categoryHeaderMasked.Background.color = group.Color; break; } - categoryHeaderMasked.Title.fontStyle = __instance.categoryHeaderEditRoleOrigin.Title.fontStyle; - categoryHeaderMasked.Title.font = __instance.categoryHeaderEditRoleOrigin.Title.font; - categoryHeaderMasked.Title.fontMaterial = __instance.categoryHeaderEditRoleOrigin.Title.fontMaterial; + categoryHeaderMasked.Title.fontStyle = roleMenu.categoryHeaderEditRoleOrigin.Title.fontStyle; + categoryHeaderMasked.Title.font = roleMenu.categoryHeaderEditRoleOrigin.Title.font; + categoryHeaderMasked.Title.fontMaterial = roleMenu.categoryHeaderEditRoleOrigin.Title.fontMaterial; categoryHeaderMasked.Divider.color = categoryHeaderMasked.Background.color; categoryHeaderMasked.Background.transform.localPosition = new Vector3(0.55f, -0.1833f, 0); - categoryHeaderMasked.Background.size = new Vector2(categoryHeaderMasked.Background.size.x + 1.5f, categoryHeaderMasked.Background.size.y); + categoryHeaderMasked.Background.size = new Vector2( + categoryHeaderMasked.Background.size.x + 1.5f, + categoryHeaderMasked.Background.size.y); categoryHeaderMasked.transform.localScale = Vector3.one * 0.63f; categoryHeaderMasked.transform.localPosition = new Vector3(-0.44f, ScrollerNum, -2f); @@ -277,20 +288,21 @@ public static class RoleSettingMenuPatches if (!RoleGroupHidden[group]) { - foreach (var role in grouping) - { - if (role is not RoleBehaviour roleBehaviour) + yield return + grouping.CoLoopWithBudget(role => { - continue; - } + if (role is not RoleBehaviour roleBehaviour) + { + return; + } - var option = CreateQuotaOption(__instance, roleBehaviour, num4); - if (option is not null) - { - RoleOptionSettings.Add(option); - num4++; - } - } + var option = CreateQuotaOption(roleMenu, roleBehaviour, num4); + if (option is not null) + { + RoleOptionSettings.Add(option); + num4++; + } + }); } var boxCol = categoryHeaderMasked.gameObject.AddComponent(); @@ -298,25 +310,28 @@ public static class RoleSettingMenuPatches boxCol.offset = new Vector2(1.5f, -0.3f); var headerBtn = categoryHeaderMasked.gameObject.AddComponent(); - headerBtn.ClickSound = __instance.BackButton.GetComponent().ClickSound; + headerBtn.ClickSound = roleMenu.BackButton.GetComponent().ClickSound; headerBtn.OnMouseOver = new UnityEvent(); headerBtn.OnMouseOut = new UnityEvent(); headerBtn.OnClick.AddListener( (UnityAction)(() => { - RolePositions[GameSettingMenuPatches.SelectedModIdx] = __instance.scrollBar.Inner.localPosition; + RolePositions[GameSettingMenuPatches.SelectedModIdx] = roleMenu.scrollBar.Inner.localPosition; if (RoleGroupHidden.TryGetValue(group, out var groupHidden)) { RoleGroupHidden[group] = !groupHidden; } + foreach (var obj in Headers) { if (!obj) { continue; } + obj.DeepDestroy(false); } + Headers.Clear(); foreach (var obj in RoleOptionSettings) { @@ -324,10 +339,12 @@ public static class RoleSettingMenuPatches { continue; } + obj.gameObject.DeepDestroy(false); } + RoleOptionSettings.Clear(); - __instance.SetQuotaTab(); + roleMenu.SetQuotaTab(); })); headerBtn.SetButtonEnableState(true); @@ -337,7 +354,14 @@ public static class RoleSettingMenuPatches } } - __instance.scrollBar.SetScrollBounds(__instance); + roleMenu.scrollBar.SetScrollBounds(roleMenu); + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))] + public static bool SetQuotaTabPatch(RolesSettingsMenu __instance) + { + __instance.StartCoroutine(CoQuotaTabPatch(__instance).WrapToIl2Cpp()); return false; } @@ -362,6 +386,7 @@ public static class RoleSettingMenuPatches } [HarmonyPrefix] + // TODO: turn this into a fixed update [HarmonyPatch(nameof(RolesSettingsMenu.Update))] public static bool UpdatePatch(RolesSettingsMenu __instance) { diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 717f0b9..4590ad6 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -1,10 +1,12 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; +using System.Timers; using HarmonyLib; using MiraAPI.GameOptions; using MiraAPI.Networking; @@ -239,6 +241,44 @@ public static class Extensions } } + /// + /// Runs a given action in a loop with a frame budget based on the current target frame rate. Once the budget is hit, the loop continues on the next frame. + /// + /// The collection to iterate over. + /// The action to perform on each item. + /// The type of the collection. + /// Coroutine. + public static IEnumerator CoLoopWithBudget(this IEnumerable collection, Action action) + { + var fps = Application.targetFrameRate > 0 ? Application.targetFrameRate : 60; + long budget = 1 / (fps * 2); // default is half of current frame time. + yield return collection.CoLoopWithBudget(budget, action); + } + + /// + /// Runs a given action in a loop with a given frame budget. Once the budget is hit, the loop continues on the next frame. + /// + /// The collection to iterate over. + /// The # of milliseconds to spend in the loop. + /// The action to perform on each item. + /// The type of the collection. + /// Coroutine. + public static IEnumerator CoLoopWithBudget(this IEnumerable collection, long frameBudget, Action action) + { + var timer = new Stopwatch(); + timer.Start(); + foreach (var item in collection) + { + action(item); + + if (timer.ElapsedMilliseconds > frameBudget) + { + timer.Restart(); + yield return null; + } + } + } + private static IEnumerator Nuke(GameObject? go, bool clearGc) { if (go == null) From f9721a8d698c980b44a839368d36651e3b1cdab2 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:06:52 -0400 Subject: [PATCH 14/23] improve stability and FPS on 17.4+ --- MiraAPI/GameOptions/AbstractOptionGroup.cs | 2 + .../ModifierDisplayComponent.cs | 4 +- MiraAPI/Patches/HudManagerPatches.cs | 2 +- MiraAPI/Patches/Menu/HatsTabPatches.cs | 2 +- MiraAPI/Patches/Menu/NameplatesTabPatches.cs | 2 +- MiraAPI/Patches/Menu/SkinsTabPatches.cs | 2 +- MiraAPI/Patches/Menu/VisorsTabPatches.cs | 2 +- .../Patches/Options/GameOptionsMenuPatch.cs | 206 +++++++++--------- .../Patches/Options/GamePresetsTabPatches.cs | 2 +- .../Patches/Options/RoleSettingMenuPatches.cs | 31 ++- MiraAPI/Patches/Roles/HudManagerPatches.cs | 2 +- MiraAPI/Presets/PresetManager.cs | 3 +- MiraAPI/Presets/SavePresetPopup.cs | 2 +- MiraAPI/Utilities/Extensions.cs | 11 +- 14 files changed, 143 insertions(+), 130 deletions(-) diff --git a/MiraAPI/GameOptions/AbstractOptionGroup.cs b/MiraAPI/GameOptions/AbstractOptionGroup.cs index 8be08c6..5ee95c2 100644 --- a/MiraAPI/GameOptions/AbstractOptionGroup.cs +++ b/MiraAPI/GameOptions/AbstractOptionGroup.cs @@ -70,6 +70,8 @@ public abstract class AbstractOptionGroup internal bool AllOptionsHidden { get; set; } internal CategoryHeaderMasked? Header { get; set; } + + internal Coroutine? CreationCoroutine { get; set; } } /// diff --git a/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs b/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs index 4a6411f..4b4c71e 100644 --- a/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs +++ b/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs @@ -138,7 +138,7 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr) internal void DestroyComponent(ModifierUiComponent component) { _modifiers.Remove(component.Modifier!); - component.gameObject.DeepDestroy(); + component.gameObject.Destroy(); RefreshModifiers(); } @@ -208,7 +208,7 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr) continue; } - mod.Value.gameObject.DeepDestroy(); + mod.Value.gameObject.Destroy(); _modifiers.Remove(mod.Key); } diff --git a/MiraAPI/Patches/HudManagerPatches.cs b/MiraAPI/Patches/HudManagerPatches.cs index a6cd68b..a508ccf 100644 --- a/MiraAPI/Patches/HudManagerPatches.cs +++ b/MiraAPI/Patches/HudManagerPatches.cs @@ -148,7 +148,7 @@ public static class HudManagerPatches foreach (var t in BottomLeft.GetComponentsInChildren(true)) { - t.gameObject.DeepDestroy(false); + t.gameObject.Destroy(); } Utilities.Extensions.ClearGarbageCollector(); diff --git a/MiraAPI/Patches/Menu/HatsTabPatches.cs b/MiraAPI/Patches/Menu/HatsTabPatches.cs index b06b11a..43d96c3 100644 --- a/MiraAPI/Patches/Menu/HatsTabPatches.cs +++ b/MiraAPI/Patches/Menu/HatsTabPatches.cs @@ -100,7 +100,7 @@ public static class HatsTabPatches if (loadRoutine != null) Coroutines.Stop(loadRoutine); hatIndex = 0; - foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.DeepDestroy(false); + foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy(); __instance.ColorChips.Clear(); __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.DeepDestroy(false)); diff --git a/MiraAPI/Patches/Menu/NameplatesTabPatches.cs b/MiraAPI/Patches/Menu/NameplatesTabPatches.cs index 4e9b693..6e96d5f 100644 --- a/MiraAPI/Patches/Menu/NameplatesTabPatches.cs +++ b/MiraAPI/Patches/Menu/NameplatesTabPatches.cs @@ -81,7 +81,7 @@ public static class NameplatesTabPatches private static void GenerateHats(NameplatesTab __instance, int page) { - foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.DeepDestroy(false); + foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy(); __instance.ColorChips.Clear(); __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.DeepDestroy(false)); diff --git a/MiraAPI/Patches/Menu/SkinsTabPatches.cs b/MiraAPI/Patches/Menu/SkinsTabPatches.cs index 44d08d5..e1c97a5 100644 --- a/MiraAPI/Patches/Menu/SkinsTabPatches.cs +++ b/MiraAPI/Patches/Menu/SkinsTabPatches.cs @@ -83,7 +83,7 @@ public static class SkinsTabPatches private static void GenerateHats(SkinsTab __instance, int page) { - foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.DeepDestroy(false); + foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy(); __instance.ColorChips.Clear(); __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.DeepDestroy(false)); diff --git a/MiraAPI/Patches/Menu/VisorsTabPatches.cs b/MiraAPI/Patches/Menu/VisorsTabPatches.cs index c5fe9a4..a1627cf 100644 --- a/MiraAPI/Patches/Menu/VisorsTabPatches.cs +++ b/MiraAPI/Patches/Menu/VisorsTabPatches.cs @@ -80,7 +80,7 @@ public static class VisorsTabPatches private static void GenerateHats(VisorsTab __instance, int page) { - foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.DeepDestroy(false); + foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.Destroy(); __instance.ColorChips.Clear(); __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.DeepDestroy(false)); diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index e8ad1a2..3068a1b 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -85,9 +85,9 @@ internal static class GameOptionsMenuPatch __instance.scrollBar.SetYBoundsMax(-num - 1.65f); } - private static void UpdateGroup(AbstractOptionGroup group, ref float num) + private static void UpdateGroup(AbstractOptionGroup? group, ref float num) { - if (group.Options.Count == 0 || group.Header is null) + if (group is null || group.Options.Count == 0 || group.Header is null || group.CreationCoroutine is not null) { return; } @@ -113,7 +113,7 @@ internal static class GameOptionsMenuPatch { var newOpt = opt.OptionBehaviour; - if (newOpt is null) + if (!newOpt || newOpt == null) { continue; } @@ -274,7 +274,7 @@ internal static class GameOptionsMenuPatch return false; } - private static void CreateGroup(GameOptionsMenu menu, AbstractOptionGroup group) + private static IEnumerator CoCreateGroup(GameOptionsMenu menu, AbstractOptionGroup group) { var categoryHeaderMasked = Object.Instantiate( menu.categoryHeaderOrigin, @@ -321,120 +321,118 @@ internal static class GameOptionsMenuPatch defaultPreset = preset; } - menu.StartCoroutine( - options.CoLoopWithBudget((newOpt) => + yield return options.CoLoopWithBudget(newOpt => + { + newOpt.SetClickMask(menu.ButtonClickMask); + + SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren(true); + foreach (var renderer in componentsInChildren) { - newOpt.SetClickMask(menu.ButtonClickMask); - - SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren(true); - foreach (var renderer in componentsInChildren) + if (group.GroupColor != MiraApiPlugin.DefaultHeaderColor) { - if (group.GroupColor != MiraApiPlugin.DefaultHeaderColor) + renderer.color = group.GroupColor.FindAlternateColor(); + if (renderer.transform.parent.TryGetComponent(out var btn)) { - renderer.color = group.GroupColor.FindAlternateColor(); - if (renderer.transform.parent.TryGetComponent(out var btn)) - { - btn.interactableColor = group.GroupColor.FindAlternateColor(); - btn.interactableHoveredColor = Color.white; - } + btn.interactableColor = group.GroupColor.FindAlternateColor(); + btn.interactableHoveredColor = Color.white; } - - renderer.material.SetInt(PlayerMaterial.MaskLayer, 20); } - foreach (var textMeshPro in newOpt.GetComponentsInChildren(true)) - { - if (group.GroupColor != MiraApiPlugin.DefaultHeaderColor) - { - textMeshPro.color = group.GroupColor; - } + renderer.material.SetInt(PlayerMaterial.MaskLayer, 20); + } - textMeshPro.fontMaterial.SetFloat(ShaderID.StencilComp, 3f); - textMeshPro.fontMaterial.SetFloat(ShaderID.Stencil, 20); + foreach (var textMeshPro in newOpt.GetComponentsInChildren(true)) + { + if (group.GroupColor != MiraApiPlugin.DefaultHeaderColor) + { + textMeshPro.color = group.GroupColor; } - if (newOpt is ToggleOption toggle) + textMeshPro.fontMaterial.SetFloat(ShaderID.StencilComp, 3f); + textMeshPro.fontMaterial.SetFloat(ShaderID.Stencil, 20); + } + + if (newOpt is ToggleOption toggle) + { + toggle.CheckMark.sprite = MiraAssets.Checkmark.LoadAsset(); + toggle.CheckMark.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor + ? group.GroupColor + : MiraAssets.AcceptedTeal; + var rend = toggle.CheckMark.transform.parent.FindChild("ActiveSprite") + .GetComponent(); + rend.sprite = MiraAssets.CheckmarkBox.LoadAsset(); + rend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor + ? group.GroupColor + : MiraAssets.AcceptedTeal; + } + + menu.Children.Add(newOpt); + var resetBtn = new GameObject("ResetOption"); + resetBtn.transform.parent = newOpt.transform; + resetBtn.transform.localScale = new(.5f, .5f, 1); + resetBtn.layer = LayerMask.NameToLayer("UI"); + resetBtn.transform.localPosition = new Vector3(-3.1f, 0f, -2f); + var resetRend = resetBtn.AddComponent(); + resetRend.sprite = MiraAssets.ResetButton.LoadAsset(); + resetRend.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; + resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) + ? Color.white + : group.GroupColor.FindAlternateColor(); + var resetBoxCol = resetBtn.gameObject.AddComponent(); + resetBoxCol.size = new Vector2(1f, 1f); + resetBoxCol.offset = new Vector2(0, 0); + var passiveButton = resetBtn.AddComponent(); + passiveButton.OnClick = new Button.ButtonClickedEvent(); + passiveButton.ClickSound = menu.BackButton.GetComponent().ClickSound; + passiveButton.OnMouseOver = new UnityEvent(); + passiveButton.OnMouseOver.AddListener( + (UnityAction)(() => { - toggle.CheckMark.sprite = MiraAssets.Checkmark.LoadAsset(); - toggle.CheckMark.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor + resetRend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor ? group.GroupColor : MiraAssets.AcceptedTeal; - var rend = toggle.CheckMark.transform.parent.FindChild("ActiveSprite") - .GetComponent(); - rend.sprite = MiraAssets.CheckmarkBox.LoadAsset(); - rend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor - ? group.GroupColor - : MiraAssets.AcceptedTeal; - } - - menu.Children.Add(newOpt); - var resetBtn = new GameObject("ResetOption"); - resetBtn.transform.parent = newOpt.transform; - resetBtn.transform.localScale = new(.5f, .5f, 1); - resetBtn.layer = LayerMask.NameToLayer("UI"); - resetBtn.transform.localPosition = new Vector3(-3.1f, 0f, -2f); - var resetRend = resetBtn.AddComponent(); - resetRend.sprite = MiraAssets.ResetButton.LoadAsset(); - resetRend.maskInteraction = SpriteMaskInteraction.VisibleInsideMask; - resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) - ? Color.white - : group.GroupColor.FindAlternateColor(); - var resetBoxCol = resetBtn.gameObject.AddComponent(); - resetBoxCol.size = new Vector2(1f, 1f); - resetBoxCol.offset = new Vector2(0, 0); - var passiveButton = resetBtn.AddComponent(); - passiveButton.OnClick = new Button.ButtonClickedEvent(); - passiveButton.ClickSound = menu.BackButton.GetComponent().ClickSound; - passiveButton.OnMouseOver = new UnityEvent(); - passiveButton.OnMouseOver.AddListener( + })); + passiveButton.OnMouseOut = new UnityEvent(); + passiveButton.OnMouseOut.AddListener( + (UnityAction)(() => + { + resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) + ? Color.white + : group.GroupColor.FindAlternateColor(); + })); + if (newOpt is ToggleOption toggleOpt) + { + passiveButton.OnClick.AddListener( (UnityAction)(() => { - resetRend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor - ? group.GroupColor - : MiraAssets.AcceptedTeal; + defaultPreset!.ResetOption(toggleOpt); })); - passiveButton.OnMouseOut = new UnityEvent(); - passiveButton.OnMouseOut.AddListener( + } + else if (newOpt is NumberOption numOpt) + { + passiveButton.OnClick.AddListener( (UnityAction)(() => { - resetRend.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) - ? Color.white - : group.GroupColor.FindAlternateColor(); + defaultPreset!.ResetOption(numOpt); })); - if (newOpt is ToggleOption toggleOpt) - { - passiveButton.OnClick.AddListener( - (UnityAction)(() => - { - defaultPreset!.ResetOption(toggleOpt); - })); - } - else if (newOpt is NumberOption numOpt) - { - passiveButton.OnClick.AddListener( - (UnityAction)(() => - { - defaultPreset!.ResetOption(numOpt); - })); - } - else if (newOpt is StringOption strOpt) - { - passiveButton.OnClick.AddListener( - (UnityAction)(() => - { - defaultPreset!.ResetOption(strOpt); - })); - } + } + else if (newOpt is StringOption strOpt) + { + passiveButton.OnClick.AddListener( + (UnityAction)(() => + { + defaultPreset!.ResetOption(strOpt); + })); + } - if (!defaultPreset!.IsOptionInPreset(newOpt)) - { - resetBtn.Destroy(); - } + if (!defaultPreset!.IsOptionInPreset(newOpt)) + { + resetBtn.Destroy(); + } - newOpt.Initialize(); - newOpt.gameObject.SetActive(false); - }).WrapToIl2Cpp() - ); + newOpt.Initialize(); + newOpt.gameObject.SetActive(false); + }).WrapToIl2Cpp(); var boxCol = categoryHeaderMasked.gameObject.AddComponent(); boxCol.size = new Vector2(7, 0.7f); @@ -453,5 +451,17 @@ internal static class GameOptionsMenuPatch : "(Click to close)"; })); headerBtn.SetButtonEnableState(true); + + group.CreationCoroutine = null; + } + + private static void CreateGroup(GameOptionsMenu menu, AbstractOptionGroup group) + { + if (group.CreationCoroutine != null) + { + GameSettingMenu.Instance.StopCoroutine(group.CreationCoroutine); + } + + group.CreationCoroutine = GameSettingMenu.Instance.StartCoroutine(CoCreateGroup(menu, group).WrapToIl2Cpp()); } } diff --git a/MiraAPI/Patches/Options/GamePresetsTabPatches.cs b/MiraAPI/Patches/Options/GamePresetsTabPatches.cs index a4b6cca..2c6cc68 100644 --- a/MiraAPI/Patches/Options/GamePresetsTabPatches.cs +++ b/MiraAPI/Patches/Options/GamePresetsTabPatches.cs @@ -262,7 +262,7 @@ internal static class GamePresetsTabPatches iconSpriteRend.sprite = MiraAssets.RefreshIcon.LoadAsset(); Helpers.DivideSize(refreshButton.gameObject, 2f); - refreshButton.buttonText.gameObject.DeepDestroy(); + refreshButton.buttonText.gameObject.Destroy(); refreshButton.OnClick = new Button.ButtonClickedEvent(); refreshButton.OnClick.AddListener((UnityAction)Refresh); diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 18daa7d..b6f61c4 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -58,7 +58,7 @@ public static class RoleSettingMenuPatches continue; } - obj.DeepDestroy(false); + obj.Destroy(); } Headers.Clear(); @@ -69,7 +69,7 @@ public static class RoleSettingMenuPatches continue; } - obj.gameObject.DeepDestroy(false); + obj.gameObject.Destroy(); } RoleOptionSettings.Clear(); @@ -228,9 +228,9 @@ public static class RoleSettingMenuPatches var blankLabel = quotaInst.transform.FindChild("BlankLabel").gameObject; var chanceLabel = quotaInst.transform.FindChild("Chance Label").gameObject; var countLabel = quotaInst.transform.FindChild("# Label").gameObject; - blankLabel.DeepDestroy(false); - chanceLabel.DeepDestroy(false); - countLabel.DeepDestroy(false); + blankLabel.Destroy(); + chanceLabel.Destroy(); + countLabel.Destroy(); usingNewQuota = true; quotaThing = quotaInst; } @@ -329,7 +329,7 @@ public static class RoleSettingMenuPatches continue; } - obj.DeepDestroy(false); + obj.Destroy(); } Headers.Clear(); @@ -340,7 +340,7 @@ public static class RoleSettingMenuPatches continue; } - obj.gameObject.DeepDestroy(false); + obj.gameObject.Destroy(); } RoleOptionSettings.Clear(); @@ -355,13 +355,21 @@ public static class RoleSettingMenuPatches } roleMenu.scrollBar.SetScrollBounds(roleMenu); + _quotaTabCoroutine = null; } + private static Coroutine? _quotaTabCoroutine; + [HarmonyPrefix] [HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))] public static bool SetQuotaTabPatch(RolesSettingsMenu __instance) { - __instance.StartCoroutine(CoQuotaTabPatch(__instance).WrapToIl2Cpp()); + if (_quotaTabCoroutine != null) + { + GameSettingMenu.Instance.StopCoroutine(_quotaTabCoroutine); + } + + _quotaTabCoroutine = GameSettingMenu.Instance.StartCoroutine(CoQuotaTabPatch(__instance).WrapToIl2Cpp()); return false; } @@ -474,7 +482,7 @@ public static class RoleSettingMenuPatches { foreach (var optBehaviour in __instance.AdvancedRolesSettings.GetComponentsInChildren()) { - optBehaviour.gameObject.DeepDestroy(false); + optBehaviour.gameObject.Destroy(); } CurrentRole = role; @@ -486,8 +494,6 @@ public static class RoleSettingMenuPatches .SelectMany(x => x.Options) .ToList() ?? []; - CurrentRoleOptions = filteredOptions; - foreach (var option in filteredOptions) { var newOpt = option.CreateOption( @@ -518,6 +524,7 @@ public static class RoleSettingMenuPatches newOpt.Initialize(); } + CurrentRoleOptions = filteredOptions; __instance.scrollBar.ScrollToTop(); } @@ -670,7 +677,7 @@ public static class RoleSettingMenuPatches var newButton = Object.Instantiate(roleOptionSetting.buttons[0], roleOptionSetting.transform); newButton.name = "ConfigButton"; newButton.transform.localPosition = new Vector3(0.4473f, -0.3f, -2f); - newButton.transform.FindChild("Text_TMP").gameObject.DeepDestroy(); + newButton.transform.FindChild("Text_TMP").gameObject.Destroy(); newButton.activeSprites.Destroy(); var btnRend = newButton.transform.FindChild("ButtonSprite").GetComponent(); diff --git a/MiraAPI/Patches/Roles/HudManagerPatches.cs b/MiraAPI/Patches/Roles/HudManagerPatches.cs index 79c2306..6827341 100644 --- a/MiraAPI/Patches/Roles/HudManagerPatches.cs +++ b/MiraAPI/Patches/Roles/HudManagerPatches.cs @@ -73,7 +73,7 @@ public static class HudManagerPatches } else if (RoleTab != null) { - RoleTab.gameObject.DeepDestroy(); + RoleTab.gameObject.Destroy(); RoleTab = null; } } diff --git a/MiraAPI/Presets/PresetManager.cs b/MiraAPI/Presets/PresetManager.cs index bec1216..d346c52 100644 --- a/MiraAPI/Presets/PresetManager.cs +++ b/MiraAPI/Presets/PresetManager.cs @@ -6,6 +6,7 @@ using MiraAPI.PluginLoading; using MiraAPI.Roles; using MiraAPI.Utilities; using Reactor.Utilities; +using Reactor.Utilities.Extensions; using UnityEngine; namespace MiraAPI.Presets; @@ -62,7 +63,7 @@ public static class PresetManager { if (btn != null) { - btn.DeepDestroy(); + btn.Destroy(); } } diff --git a/MiraAPI/Presets/SavePresetPopup.cs b/MiraAPI/Presets/SavePresetPopup.cs index 23ce48b..87959e7 100644 --- a/MiraAPI/Presets/SavePresetPopup.cs +++ b/MiraAPI/Presets/SavePresetPopup.cs @@ -26,7 +26,7 @@ public class SavePresetPopup(nint cppPtr) : Minigame(cppPtr) // Cleanup holder object public void OnDestroy() { - transform.parent.gameObject.DeepDestroy(); + transform.parent.gameObject.Destroy(); } public override void Close() diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 4590ad6..c4b9ef3 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -58,7 +58,7 @@ public static class Extensions bool isComplete; if (self.amClosing == Minigame.CloseState.Closing) { - self.gameObject.DeepDestroy(); + self.gameObject.Destroy(); return; } if (self.CloseSound && Constants.ShouldPlaySfx()) @@ -231,14 +231,7 @@ public static class Extensions /// Whether to run the garbage collector immediately. public static void DeepDestroy(this GameObject obj, bool clearGc = true) { - if (MainMenuManagerPatches.NeedsDeepDestroy) - { - Coroutines.Start(Nuke(obj, clearGc)); - } - else - { - obj?.Destroy(); - } + obj.Destroy(); } /// From 992bdbcbf8310ff03073994b96d6b3f10a427176 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:10:06 -0400 Subject: [PATCH 15/23] cleanup usings --- MiraAPI.Example/ExamplePlugin.cs | 3 +-- MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs | 2 -- MiraAPI/Events/MiraEventManager.cs | 1 - MiraAPI/Events/Vanilla/Meeting/Voting/AfterVoteEvent.cs | 4 +--- MiraAPI/Events/Vanilla/Meeting/Voting/BeforeVoteEvent.cs | 4 +--- MiraAPI/GameEnd/GameOverManager.cs | 1 - MiraAPI/GameModes/CustomGameModeManager.cs | 1 - MiraAPI/GameOptions/AbstractOptionGroup.cs | 2 -- MiraAPI/GameOptions/OptionTypes/ModdedOption.cs | 2 -- MiraAPI/Hud/CustomActionButton.cs | 1 - MiraAPI/Hud/CustomButtonManager.cs | 1 - MiraAPI/Hud/MultiTargetButton.cs | 1 - MiraAPI/Keybinds/KeybindManager.cs | 1 - MiraAPI/Keybinds/VanillaKeybind.cs | 1 - .../Attributes/LocalSettingsButtonAttribute.cs | 1 - MiraAPI/LocalSettings/LocalSettingsButton.cs | 1 - MiraAPI/LocalSettings/LocalSettingsManager.cs | 2 -- MiraAPI/Modifiers/ModifierComponent.cs | 1 - .../Modifiers/ModifierDisplay/ModifierDisplayComponent.cs | 1 - MiraAPI/Modifiers/ModifierExtensions.cs | 1 - MiraAPI/Modifiers/ModifierManager.cs | 1 - MiraAPI/Modifiers/Types/TimedModifier.cs | 1 - MiraAPI/Networking/CustomGameOverRpc.cs | 1 - MiraAPI/Networking/CustomMurderRpc.cs | 3 --- MiraAPI/Patches/Events/ReportBodyEventPatch.cs | 1 - MiraAPI/Patches/Events/VanillaButtonPatches.cs | 4 +--- MiraAPI/Patches/Hud/ButtonResetPatches.cs | 1 - MiraAPI/Patches/IntroCutscenePatches.cs | 4 +--- MiraAPI/Patches/Keybinds/ControlMapperPatches.cs | 5 ----- MiraAPI/Patches/Keybinds/KeybindsPatch.cs | 1 - MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs | 1 - MiraAPI/Patches/Options/GameOptionsMenuPatch.cs | 4 ---- MiraAPI/Patches/Options/GamePresetsTabPatches.cs | 1 - MiraAPI/Patches/Options/NotificationPopperPatch.cs | 2 -- MiraAPI/Patches/Options/OptionsPatches.cs | 5 +---- MiraAPI/Patches/Options/PlayerOptionPatches.cs | 6 +----- MiraAPI/Patches/PlayerControlPatches.cs | 1 - MiraAPI/Patches/Roles/HowToPlayPatch.cs | 3 +-- MiraAPI/Patches/Roles/HudManagerPatches.cs | 2 -- MiraAPI/Patches/Roles/RoleManagerPatches.cs | 1 - MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs | 1 - MiraAPI/Patches/ServerDropdownPatch.cs | 1 - MiraAPI/Patches/TmpAwakePatch.cs | 1 - MiraAPI/Patches/Voting/DummyBehaviourPatch.cs | 1 - MiraAPI/PluginLoading/MiraPluginInfo.cs | 1 - MiraAPI/Presets/PresetManager.cs | 2 -- MiraAPI/Roles/CustomRoleManager.cs | 1 - MiraAPI/Roles/ICustomRole.cs | 2 -- MiraAPI/Utilities/Assets/LoadableAddressableAsset.cs | 1 - MiraAPI/Utilities/Assets/LoadableAddressableAssets.cs | 1 - MiraAPI/Utilities/Extensions.cs | 3 --- MiraAPI/Utilities/Helpers.cs | 2 -- MiraAPI/Voting/VotingUtils.cs | 1 - 53 files changed, 8 insertions(+), 90 deletions(-) diff --git a/MiraAPI.Example/ExamplePlugin.cs b/MiraAPI.Example/ExamplePlugin.cs index 0aed9c4..b20ec41 100644 --- a/MiraAPI.Example/ExamplePlugin.cs +++ b/MiraAPI.Example/ExamplePlugin.cs @@ -1,5 +1,4 @@ -using System.Linq; -using BepInEx; +using BepInEx; using BepInEx.Configuration; using BepInEx.Unity.IL2CPP; using HarmonyLib; diff --git a/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs b/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs index 1be3649..3c1fdeb 100644 --- a/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs +++ b/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs @@ -3,8 +3,6 @@ using MiraAPI.GameOptions; using MiraAPI.GameOptions.Attributes; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; -using TMPro; -using UnityEngine; namespace MiraAPI.Example.Options.Roles; diff --git a/MiraAPI/Events/MiraEventManager.cs b/MiraAPI/Events/MiraEventManager.cs index 005415b..30ffb74 100644 --- a/MiraAPI/Events/MiraEventManager.cs +++ b/MiraAPI/Events/MiraEventManager.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Reflection; -using Reactor.Utilities; namespace MiraAPI.Events; diff --git a/MiraAPI/Events/Vanilla/Meeting/Voting/AfterVoteEvent.cs b/MiraAPI/Events/Vanilla/Meeting/Voting/AfterVoteEvent.cs index 95583f3..60a3d36 100644 --- a/MiraAPI/Events/Vanilla/Meeting/Voting/AfterVoteEvent.cs +++ b/MiraAPI/Events/Vanilla/Meeting/Voting/AfterVoteEvent.cs @@ -1,6 +1,4 @@ -using MiraAPI.Voting; - -namespace MiraAPI.Events.Vanilla.Meeting.Voting; +namespace MiraAPI.Events.Vanilla.Meeting.Voting; /// /// Event that is invoked after the local player successfully votes a player or skips. This event is not cancelable. diff --git a/MiraAPI/Events/Vanilla/Meeting/Voting/BeforeVoteEvent.cs b/MiraAPI/Events/Vanilla/Meeting/Voting/BeforeVoteEvent.cs index 74fde38..e6b28f9 100644 --- a/MiraAPI/Events/Vanilla/Meeting/Voting/BeforeVoteEvent.cs +++ b/MiraAPI/Events/Vanilla/Meeting/Voting/BeforeVoteEvent.cs @@ -1,6 +1,4 @@ -using MiraAPI.Voting; - -namespace MiraAPI.Events.Vanilla.Meeting.Voting; +namespace MiraAPI.Events.Vanilla.Meeting.Voting; /// /// Event that is invoked before the local player confirms a vote on a player or when skipping. This event is cancelable. diff --git a/MiraAPI/GameEnd/GameOverManager.cs b/MiraAPI/GameEnd/GameOverManager.cs index eb39835..28d2f48 100644 --- a/MiraAPI/GameEnd/GameOverManager.cs +++ b/MiraAPI/GameEnd/GameOverManager.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using Reactor.Utilities; namespace MiraAPI.GameEnd; diff --git a/MiraAPI/GameModes/CustomGameModeManager.cs b/MiraAPI/GameModes/CustomGameModeManager.cs index 7e274c4..3edd028 100644 --- a/MiraAPI/GameModes/CustomGameModeManager.cs +++ b/MiraAPI/GameModes/CustomGameModeManager.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Reactor.Utilities; namespace MiraAPI.GameModes; diff --git a/MiraAPI/GameOptions/AbstractOptionGroup.cs b/MiraAPI/GameOptions/AbstractOptionGroup.cs index 5ee95c2..f8f0877 100644 --- a/MiraAPI/GameOptions/AbstractOptionGroup.cs +++ b/MiraAPI/GameOptions/AbstractOptionGroup.cs @@ -2,10 +2,8 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using Il2CppInterop.Runtime.Attributes; using MiraAPI.Modifiers; using MiraAPI.Roles; -using TMPro; using UnityEngine; namespace MiraAPI.GameOptions; diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs index 50457b6..4300c4f 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -1,11 +1,9 @@ using System; using BepInEx.Configuration; -using Il2CppInterop.Runtime.Attributes; using MiraAPI.Networking; using MiraAPI.PluginLoading; using Reactor.Localization.Utilities; using Reactor.Networking.Rpc; -using TMPro; using UnityEngine; namespace MiraAPI.GameOptions.OptionTypes; diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs index f5cf521..4c5df0c 100644 --- a/MiraAPI/Hud/CustomActionButton.cs +++ b/MiraAPI/Hud/CustomActionButton.cs @@ -7,7 +7,6 @@ using MiraAPI.LocalSettings; using MiraAPI.Patches; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; -using Reactor.Utilities; using Rewired; using TMPro; using UnityEngine; diff --git a/MiraAPI/Hud/CustomButtonManager.cs b/MiraAPI/Hud/CustomButtonManager.cs index 5a1ba3b..70ca14b 100644 --- a/MiraAPI/Hud/CustomButtonManager.cs +++ b/MiraAPI/Hud/CustomButtonManager.cs @@ -4,7 +4,6 @@ using System.Collections.ObjectModel; using System.Reflection; using MiraAPI.Events.Mira; using MiraAPI.PluginLoading; -using UnityEngine; namespace MiraAPI.Hud; diff --git a/MiraAPI/Hud/MultiTargetButton.cs b/MiraAPI/Hud/MultiTargetButton.cs index 12458d4..d52d845 100644 --- a/MiraAPI/Hud/MultiTargetButton.cs +++ b/MiraAPI/Hud/MultiTargetButton.cs @@ -1,6 +1,5 @@ using System.Linq; using HarmonyLib; -using Reactor.Utilities.Extensions; using UnityEngine; namespace MiraAPI.Hud; diff --git a/MiraAPI/Keybinds/KeybindManager.cs b/MiraAPI/Keybinds/KeybindManager.cs index 207fab5..042322a 100644 --- a/MiraAPI/Keybinds/KeybindManager.cs +++ b/MiraAPI/Keybinds/KeybindManager.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using Il2CppInterop.Runtime.Attributes; using MiraAPI.Utilities; -using Reactor.Utilities; using Rewired; namespace MiraAPI.Keybinds; diff --git a/MiraAPI/Keybinds/VanillaKeybind.cs b/MiraAPI/Keybinds/VanillaKeybind.cs index 594e583..5f34288 100644 --- a/MiraAPI/Keybinds/VanillaKeybind.cs +++ b/MiraAPI/Keybinds/VanillaKeybind.cs @@ -1,5 +1,4 @@ using System.Globalization; -using Rewired; namespace MiraAPI.Keybinds; diff --git a/MiraAPI/LocalSettings/Attributes/LocalSettingsButtonAttribute.cs b/MiraAPI/LocalSettings/Attributes/LocalSettingsButtonAttribute.cs index 043a81d..83a3955 100644 --- a/MiraAPI/LocalSettings/Attributes/LocalSettingsButtonAttribute.cs +++ b/MiraAPI/LocalSettings/Attributes/LocalSettingsButtonAttribute.cs @@ -1,5 +1,4 @@ using System; -using BepInEx.Configuration; namespace MiraAPI.LocalSettings.Attributes; diff --git a/MiraAPI/LocalSettings/LocalSettingsButton.cs b/MiraAPI/LocalSettings/LocalSettingsButton.cs index 6f1fa57..5a1f371 100644 --- a/MiraAPI/LocalSettings/LocalSettingsButton.cs +++ b/MiraAPI/LocalSettings/LocalSettingsButton.cs @@ -1,6 +1,5 @@ using System; using MiraAPI.Utilities; -using Reactor.Localization.Utilities; using Reactor.Utilities.Extensions; using TMPro; using UnityEngine; diff --git a/MiraAPI/LocalSettings/LocalSettingsManager.cs b/MiraAPI/LocalSettings/LocalSettingsManager.cs index 2f6fc16..aa1c217 100644 --- a/MiraAPI/LocalSettings/LocalSettingsManager.cs +++ b/MiraAPI/LocalSettings/LocalSettingsManager.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Reflection; using BepInEx.Unity.IL2CPP; -using MiraAPI.GameOptions; -using Reactor.Utilities; namespace MiraAPI.LocalSettings; diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index 09e411a..6432b18 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -8,7 +8,6 @@ using MiraAPI.LocalSettings; using MiraAPI.Modifiers.ModifierDisplay; using MiraAPI.Modifiers.Types; using MiraAPI.Patches.Roles; -using Reactor.Utilities; using Reactor.Utilities.Attributes; using UnityEngine; diff --git a/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs b/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs index 4b4c71e..e93f477 100644 --- a/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs +++ b/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs @@ -4,7 +4,6 @@ using System.Linq; using HarmonyLib; using Il2CppInterop.Runtime.Attributes; using MiraAPI.LocalSettings; -using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Utilities.Attributes; using Reactor.Utilities.Extensions; diff --git a/MiraAPI/Modifiers/ModifierExtensions.cs b/MiraAPI/Modifiers/ModifierExtensions.cs index 20f08be..e70a26a 100644 --- a/MiraAPI/Modifiers/ModifierExtensions.cs +++ b/MiraAPI/Modifiers/ModifierExtensions.cs @@ -5,7 +5,6 @@ using MiraAPI.Networking; using MiraAPI.Networking.Modifiers; using Reactor.Networking.Attributes; using Reactor.Networking.Rpc; -using Reactor.Utilities; namespace MiraAPI.Modifiers; diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs index 877e43b..8d5621e 100644 --- a/MiraAPI/Modifiers/ModifierManager.cs +++ b/MiraAPI/Modifiers/ModifierManager.cs @@ -6,7 +6,6 @@ using MiraAPI.Modifiers.Types; using MiraAPI.PluginLoading; using MiraAPI.Roles; using MiraAPI.Utilities; -using Reactor.Utilities; using Reactor.Utilities.Extensions; using Random = System.Random; diff --git a/MiraAPI/Modifiers/Types/TimedModifier.cs b/MiraAPI/Modifiers/Types/TimedModifier.cs index 6fe1c1d..472b3cf 100644 --- a/MiraAPI/Modifiers/Types/TimedModifier.cs +++ b/MiraAPI/Modifiers/Types/TimedModifier.cs @@ -1,5 +1,4 @@ using System; -using Reactor.Utilities; using UnityEngine; namespace MiraAPI.Modifiers.Types; diff --git a/MiraAPI/Networking/CustomGameOverRpc.cs b/MiraAPI/Networking/CustomGameOverRpc.cs index f7df4a7..6475d6a 100644 --- a/MiraAPI/Networking/CustomGameOverRpc.cs +++ b/MiraAPI/Networking/CustomGameOverRpc.cs @@ -4,7 +4,6 @@ using InnerNet; using MiraAPI.GameEnd; using Reactor.Networking.Attributes; using Reactor.Networking.Rpc; -using Reactor.Utilities; namespace MiraAPI.Networking; diff --git a/MiraAPI/Networking/CustomMurderRpc.cs b/MiraAPI/Networking/CustomMurderRpc.cs index 9a6ff0b..7009819 100644 --- a/MiraAPI/Networking/CustomMurderRpc.cs +++ b/MiraAPI/Networking/CustomMurderRpc.cs @@ -10,9 +10,6 @@ using MiraAPI.Events.Vanilla.Gameplay; using MiraAPI.Utilities; using Reactor.Networking.Attributes; using Reactor.Networking.Rpc; -using Reactor.Utilities; -using Reactor.Utilities.Extensions; -using Rewired; using UnityEngine; using Object = UnityEngine.Object; diff --git a/MiraAPI/Patches/Events/ReportBodyEventPatch.cs b/MiraAPI/Patches/Events/ReportBodyEventPatch.cs index 305311e..3eb1c29 100644 --- a/MiraAPI/Patches/Events/ReportBodyEventPatch.cs +++ b/MiraAPI/Patches/Events/ReportBodyEventPatch.cs @@ -4,7 +4,6 @@ using InnerNet; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Meeting; using MiraAPI.Utilities; -using Reactor.Utilities; namespace MiraAPI.Patches.Events; diff --git a/MiraAPI/Patches/Events/VanillaButtonPatches.cs b/MiraAPI/Patches/Events/VanillaButtonPatches.cs index 490e401..71df73f 100644 --- a/MiraAPI/Patches/Events/VanillaButtonPatches.cs +++ b/MiraAPI/Patches/Events/VanillaButtonPatches.cs @@ -1,9 +1,7 @@ -using System.Linq; -using AmongUs.GameOptions; +using AmongUs.GameOptions; using HarmonyLib; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Gameplay; -using MiraAPI.Events.Vanilla.Usables; namespace MiraAPI.Patches.Events; diff --git a/MiraAPI/Patches/Hud/ButtonResetPatches.cs b/MiraAPI/Patches/Hud/ButtonResetPatches.cs index 5e46048..ab0dc32 100644 --- a/MiraAPI/Patches/Hud/ButtonResetPatches.cs +++ b/MiraAPI/Patches/Hud/ButtonResetPatches.cs @@ -1,6 +1,5 @@ using HarmonyLib; using MiraAPI.Hud; -using Reactor.Utilities; namespace MiraAPI.Patches.Hud; diff --git a/MiraAPI/Patches/IntroCutscenePatches.cs b/MiraAPI/Patches/IntroCutscenePatches.cs index 7d52287..4ee990c 100644 --- a/MiraAPI/Patches/IntroCutscenePatches.cs +++ b/MiraAPI/Patches/IntroCutscenePatches.cs @@ -1,12 +1,10 @@ -using System.Linq; -using System.Reflection; +using System.Reflection; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Gameplay; using MiraAPI.Roles; using MiraAPI.Utilities; -using Reactor.Utilities; namespace MiraAPI.Patches; diff --git a/MiraAPI/Patches/Keybinds/ControlMapperPatches.cs b/MiraAPI/Patches/Keybinds/ControlMapperPatches.cs index f0c51c0..6666a2c 100644 --- a/MiraAPI/Patches/Keybinds/ControlMapperPatches.cs +++ b/MiraAPI/Patches/Keybinds/ControlMapperPatches.cs @@ -1,13 +1,8 @@ using System.Linq; using HarmonyLib; using MiraAPI.Keybinds; -using MiraAPI.LocalSettings; -using Reactor.Utilities; -using Reactor.Utilities.Extensions; using Rewired.UI.ControlMapper; -using TMPro; using UnityEngine; -using UnityEngine.Events; using UnityEngine.UI; namespace MiraAPI.Patches.Keybinds; diff --git a/MiraAPI/Patches/Keybinds/KeybindsPatch.cs b/MiraAPI/Patches/Keybinds/KeybindsPatch.cs index c08a47f..9c1034f 100644 --- a/MiraAPI/Patches/Keybinds/KeybindsPatch.cs +++ b/MiraAPI/Patches/Keybinds/KeybindsPatch.cs @@ -1,6 +1,5 @@ using HarmonyLib; using MiraAPI.Keybinds; -using MonoMod.Utils; using Rewired; namespace MiraAPI.Patches.Keybinds; diff --git a/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs b/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs index ab8ed2c..10ab62d 100644 --- a/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs +++ b/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs @@ -3,7 +3,6 @@ using System.Linq; using HarmonyLib; using MiraAPI.LocalSettings; using MiraAPI.Utilities.Assets; -using Reactor.Utilities; using Reactor.Utilities.Extensions; using UnityEngine; using UnityEngine.Events; diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index 3068a1b..b50b430 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -1,12 +1,8 @@ using System; using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using AmongUs.GameOptions; using BepInEx.Unity.IL2CPP.Utils.Collections; using HarmonyLib; -using Il2CppInterop.Runtime.Attributes; using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.Presets; diff --git a/MiraAPI/Patches/Options/GamePresetsTabPatches.cs b/MiraAPI/Patches/Options/GamePresetsTabPatches.cs index 2c6cc68..ff75606 100644 --- a/MiraAPI/Patches/Options/GamePresetsTabPatches.cs +++ b/MiraAPI/Patches/Options/GamePresetsTabPatches.cs @@ -8,7 +8,6 @@ using MiraAPI.Presets; using MiraAPI.Roles; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; -using Reactor.Utilities; using Reactor.Utilities.Extensions; using TMPro; using UnityEngine; diff --git a/MiraAPI/Patches/Options/NotificationPopperPatch.cs b/MiraAPI/Patches/Options/NotificationPopperPatch.cs index 349f103..8024ea4 100644 --- a/MiraAPI/Patches/Options/NotificationPopperPatch.cs +++ b/MiraAPI/Patches/Options/NotificationPopperPatch.cs @@ -1,8 +1,6 @@ using System.Linq; using HarmonyLib; -using Il2CppSystem; using MiraAPI.Roles; -using Object = Il2CppSystem.Object; namespace MiraAPI.Patches.Options; diff --git a/MiraAPI/Patches/Options/OptionsPatches.cs b/MiraAPI/Patches/Options/OptionsPatches.cs index 0f99691..36651f9 100644 --- a/MiraAPI/Patches/Options/OptionsPatches.cs +++ b/MiraAPI/Patches/Options/OptionsPatches.cs @@ -1,9 +1,6 @@ -using System.Collections.Generic; -using System.Linq; -using HarmonyLib; +using HarmonyLib; using Il2CppSystem; using MiraAPI.GameOptions; -using MiraAPI.GameOptions.OptionTypes; using MiraAPI.Roles; using MiraAPI.Utilities; using UnityEngine; diff --git a/MiraAPI/Patches/Options/PlayerOptionPatches.cs b/MiraAPI/Patches/Options/PlayerOptionPatches.cs index 2b77bc1..9cf0134 100644 --- a/MiraAPI/Patches/Options/PlayerOptionPatches.cs +++ b/MiraAPI/Patches/Options/PlayerOptionPatches.cs @@ -1,12 +1,8 @@ -using System; -using System.Linq; +using System.Linq; using HarmonyLib; using MiraAPI.GameOptions; -using MiraAPI.GameOptions.OptionTypes; using MiraAPI.Utilities; -using Reactor.Utilities; using UnityEngine; -using Object = Il2CppSystem.Object; namespace MiraAPI.Patches.Options; diff --git a/MiraAPI/Patches/PlayerControlPatches.cs b/MiraAPI/Patches/PlayerControlPatches.cs index 3c851b0..77b83c2 100644 --- a/MiraAPI/Patches/PlayerControlPatches.cs +++ b/MiraAPI/Patches/PlayerControlPatches.cs @@ -8,7 +8,6 @@ using MiraAPI.Hud; using MiraAPI.Modifiers; using MiraAPI.Utilities; using MiraAPI.Voting; -using Reactor.Utilities; using Reactor.Utilities.Extensions; namespace MiraAPI.Patches; diff --git a/MiraAPI/Patches/Roles/HowToPlayPatch.cs b/MiraAPI/Patches/Roles/HowToPlayPatch.cs index ee6e5ff..c7cc2e5 100644 --- a/MiraAPI/Patches/Roles/HowToPlayPatch.cs +++ b/MiraAPI/Patches/Roles/HowToPlayPatch.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Linq; +using System.Linq; using AmongUs.GameOptions; using HarmonyLib; using MiraAPI.Roles; diff --git a/MiraAPI/Patches/Roles/HudManagerPatches.cs b/MiraAPI/Patches/Roles/HudManagerPatches.cs index 6827341..3de9b71 100644 --- a/MiraAPI/Patches/Roles/HudManagerPatches.cs +++ b/MiraAPI/Patches/Roles/HudManagerPatches.cs @@ -1,9 +1,7 @@ using HarmonyLib; using InnerNet; using MiraAPI.Roles; -using MiraAPI.Utilities; using Reactor.Utilities.Extensions; -using UnityEngine; namespace MiraAPI.Patches.Roles; diff --git a/MiraAPI/Patches/Roles/RoleManagerPatches.cs b/MiraAPI/Patches/Roles/RoleManagerPatches.cs index 713ffdf..8d13595 100644 --- a/MiraAPI/Patches/Roles/RoleManagerPatches.cs +++ b/MiraAPI/Patches/Roles/RoleManagerPatches.cs @@ -1,7 +1,6 @@ using System.Linq; using AmongUs.GameOptions; using HarmonyLib; -using InnerNet; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Gameplay; using MiraAPI.Modifiers; diff --git a/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs b/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs index ff57b37..d4d80d3 100644 --- a/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs +++ b/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs @@ -2,7 +2,6 @@ using HarmonyLib; using Il2CppSystem.Collections.Generic; using MiraAPI.Roles; -using Reactor.Utilities; namespace MiraAPI.Patches.Roles; diff --git a/MiraAPI/Patches/ServerDropdownPatch.cs b/MiraAPI/Patches/ServerDropdownPatch.cs index 6ff7e68..7749c9b 100644 --- a/MiraAPI/Patches/ServerDropdownPatch.cs +++ b/MiraAPI/Patches/ServerDropdownPatch.cs @@ -1,6 +1,5 @@ using System.Linq; using HarmonyLib; -using Reactor.Utilities; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; diff --git a/MiraAPI/Patches/TmpAwakePatch.cs b/MiraAPI/Patches/TmpAwakePatch.cs index 2d43d23..9405e01 100644 --- a/MiraAPI/Patches/TmpAwakePatch.cs +++ b/MiraAPI/Patches/TmpAwakePatch.cs @@ -1,4 +1,3 @@ -using System.Linq; using HarmonyLib; using MiraAPI.Utilities.Assets; using TMPro; diff --git a/MiraAPI/Patches/Voting/DummyBehaviourPatch.cs b/MiraAPI/Patches/Voting/DummyBehaviourPatch.cs index 9e5c8a4..1d8c1e9 100644 --- a/MiraAPI/Patches/Voting/DummyBehaviourPatch.cs +++ b/MiraAPI/Patches/Voting/DummyBehaviourPatch.cs @@ -7,7 +7,6 @@ using MiraAPI.Events; using MiraAPI.Events.Vanilla.Meeting.Voting; using MiraAPI.Utilities; using MiraAPI.Voting; -using Reactor.Utilities; using Reactor.Utilities.Extensions; using UnityEngine; diff --git a/MiraAPI/PluginLoading/MiraPluginInfo.cs b/MiraAPI/PluginLoading/MiraPluginInfo.cs index 0df7cb5..db030b0 100644 --- a/MiraAPI/PluginLoading/MiraPluginInfo.cs +++ b/MiraAPI/PluginLoading/MiraPluginInfo.cs @@ -5,7 +5,6 @@ using BepInEx.Configuration; using MiraAPI.GameModes; using MiraAPI.GameOptions; using MiraAPI.Hud; -using MiraAPI.LocalSettings; using MiraAPI.Modifiers; using MiraAPI.Presets; diff --git a/MiraAPI/Presets/PresetManager.cs b/MiraAPI/Presets/PresetManager.cs index d346c52..f753db5 100644 --- a/MiraAPI/Presets/PresetManager.cs +++ b/MiraAPI/Presets/PresetManager.cs @@ -4,8 +4,6 @@ using System.Linq; using BepInEx.Configuration; using MiraAPI.PluginLoading; using MiraAPI.Roles; -using MiraAPI.Utilities; -using Reactor.Utilities; using Reactor.Utilities.Extensions; using UnityEngine; diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index 9e6540f..d85ed19 100644 --- a/MiraAPI/Roles/CustomRoleManager.cs +++ b/MiraAPI/Roles/CustomRoleManager.cs @@ -11,7 +11,6 @@ using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Localization.Utilities; using Reactor.Networking.Rpc; -using Reactor.Utilities; using Reactor.Utilities.Extensions; using TMPro; using UnityEngine; diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index 1915949..c0c97be 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -4,8 +4,6 @@ using BepInEx.Configuration; using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.PluginLoading; -using Reactor.Utilities; -using TMPro; using UnityEngine; namespace MiraAPI.Roles; diff --git a/MiraAPI/Utilities/Assets/LoadableAddressableAsset.cs b/MiraAPI/Utilities/Assets/LoadableAddressableAsset.cs index b9e8c55..144f608 100644 --- a/MiraAPI/Utilities/Assets/LoadableAddressableAsset.cs +++ b/MiraAPI/Utilities/Assets/LoadableAddressableAsset.cs @@ -1,5 +1,4 @@ using System; -using Reactor.Utilities; using UnityEngine.AddressableAssets; namespace MiraAPI.Utilities.Assets; diff --git a/MiraAPI/Utilities/Assets/LoadableAddressableAssets.cs b/MiraAPI/Utilities/Assets/LoadableAddressableAssets.cs index 9cabf83..c70100a 100644 --- a/MiraAPI/Utilities/Assets/LoadableAddressableAssets.cs +++ b/MiraAPI/Utilities/Assets/LoadableAddressableAssets.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using Reactor.Utilities; using UnityEngine.AddressableAssets; namespace MiraAPI.Utilities.Assets; diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index c4b9ef3..a16d1e1 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -6,14 +6,11 @@ using System.Globalization; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; -using System.Timers; using HarmonyLib; using MiraAPI.GameOptions; using MiraAPI.Networking; -using MiraAPI.Patches.Menu; using MiraAPI.Roles; using MiraAPI.Voting; -using Reactor.Utilities; using Reactor.Utilities.Extensions; using Rewired; using Rewired.Data; diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index c089e40..9f66817 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -2,12 +2,10 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using AmongUs.GameOptions; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; using MiraAPI.Roles; using MiraAPI.Utilities.Assets; -using Reactor.Utilities; using Rewired; using TMPro; using UnityEngine; diff --git a/MiraAPI/Voting/VotingUtils.cs b/MiraAPI/Voting/VotingUtils.cs index 130e4c4..fb117e4 100644 --- a/MiraAPI/Voting/VotingUtils.cs +++ b/MiraAPI/Voting/VotingUtils.cs @@ -5,7 +5,6 @@ using MiraAPI.Events.Vanilla.Meeting.Voting; using MiraAPI.Networking; using MiraAPI.Utilities; using Reactor.Networking.Attributes; -using Reactor.Utilities; namespace MiraAPI.Voting; From 0a0ad7441955a3928afb9fc7993d8db0950cde44 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:02:29 -0400 Subject: [PATCH 16/23] improve GC collection --- MiraAPI/Utilities/Extensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index a16d1e1..9dd91e9 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -366,7 +366,8 @@ public static class Extensions public static void ClearGarbageCollector() { Resources.UnloadUnusedAssets(); - GC.Collect(); + Il2CppSystem.GC.Collect(0, Il2CppSystem.GCCollectionMode.Forced); + GC.Collect(0, GCCollectionMode.Forced); } /// From 1b40b1c5e43bb3a410078fbbd525c6be5521bbbe Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:23:38 -0400 Subject: [PATCH 17/23] attempt to improve gc behavior --- MiraAPI/Patches/Options/GameSettingMenuPatches.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs index 6e12c61..995da8e 100644 --- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs @@ -419,6 +419,8 @@ internal static class GameSettingMenuPatches _customTwoButton!.transform.localPosition = _customTwoBtnOgPos; menu.RoleSettingsButton.transform.localPosition = _roleBtnOgPos; + CleanupTab(settings, roles); + if (SelectedModIdx != 0) { _customOneButton.buttonText.text = SelectedMod!.MiraPlugin.CustomOptionMenuNameOne; @@ -560,8 +562,6 @@ internal static class GameSettingMenuPatches { menu.ChangeTab(0, false); } - - CleanupTab(settings, roles); } private static void ClearOptions(Il2CppSystem.Collections.Generic.List options) @@ -606,6 +606,8 @@ internal static class GameSettingMenuPatches CleanupSettings(_customTwoTab, MenuCategory.CustomTwo); } + Utilities.Extensions.ClearGarbageCollector(); + void CleanupRoleSettings(RolesSettingsMenu rolesMenu) { if (rolesMenu.advancedSettingChildren != null) From 8750e5c8948e73f655b72a09e2a4b3664af3e39e Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Sat, 25 Jul 2026 17:20:14 -0500 Subject: [PATCH 18/23] Reorder modifier validity checks --- MiraAPI/Modifiers/ModifierManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs index 8d5621e..12a9519 100644 --- a/MiraAPI/Modifiers/ModifierManager.cs +++ b/MiraAPI/Modifiers/ModifierManager.cs @@ -178,7 +178,7 @@ public static class ModifierManager private static bool IsGameModifierValid(PlayerControl player, GameModifier modifier, uint modifierId) { return (player.Data.Role is not ICustomRole role || role.IsModifierApplicable(modifier)) && - modifier.IsModifierValidOn(player.Data.Role) && !player.HasModifier(modifierId) && + !player.HasModifier(modifierId) && modifier.IsModifierValidOn(player.Data.Role) && modifier.CanSpawnOnCurrentMode(); } } From 7c085b1072f8ed4b3ea2f0ce3af3f142ecef73a0 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Sat, 25 Jul 2026 18:03:29 -0500 Subject: [PATCH 19/23] Add modifier post assignment-check --- MiraAPI/Modifiers/ModifierManager.cs | 7 ++++++- MiraAPI/Modifiers/Types/GameModifier.cs | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs index 12a9519..c2688a0 100644 --- a/MiraAPI/Modifiers/ModifierManager.cs +++ b/MiraAPI/Modifiers/ModifierManager.cs @@ -152,7 +152,7 @@ public static class ModifierManager } var candidates = availablePlayers - .Where(x => IsGameModifierValid(x, modifier, modifier.TypeId)) + .Where(x => IsGameModifierPostCheck(x, modifier, modifier.TypeId)) .ToList(); if (candidates.Count == 0) @@ -181,4 +181,9 @@ public static class ModifierManager !player.HasModifier(modifierId) && modifier.IsModifierValidOn(player.Data.Role) && modifier.CanSpawnOnCurrentMode(); } + private static bool IsGameModifierPostCheck(PlayerControl player, GameModifier modifier, uint modifierId) + { + return (player.Data.Role is not ICustomRole role || role.IsModifierApplicable(modifier)) && + !player.HasModifier(modifierId) && modifier.IsModifierValidOnPostCheck(player.Data.Role); + } } diff --git a/MiraAPI/Modifiers/Types/GameModifier.cs b/MiraAPI/Modifiers/Types/GameModifier.cs index b58f8e0..f5ae883 100644 --- a/MiraAPI/Modifiers/Types/GameModifier.cs +++ b/MiraAPI/Modifiers/Types/GameModifier.cs @@ -33,6 +33,13 @@ public abstract class GameModifier : BaseModifier /// if the modifier is valid on the role, otherwise . public virtual bool IsModifierValidOn(RoleBehaviour role) => true; + /// + /// Determines whether the modifier is valid on a role, which runs after players are determined but before the modifiers are given. + /// + /// The role to be checked. + /// if the modifier is valid on the role, otherwise . + public virtual bool IsModifierValidOnPostCheck(RoleBehaviour role) => IsModifierValidOn(role); + /// /// Determines whether the player won the game with this modifier. The modifier with the highest priority will override other modifiers. /// From 228ed9711ada2243a58fe0c9cf26ba8611191278 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Sun, 26 Jul 2026 11:05:13 -0500 Subject: [PATCH 20/23] Add mod name abbreviation --- MiraAPI/PluginLoading/IMiraPlugin.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/MiraAPI/PluginLoading/IMiraPlugin.cs b/MiraAPI/PluginLoading/IMiraPlugin.cs index f1ef793..05470cc 100644 --- a/MiraAPI/PluginLoading/IMiraPlugin.cs +++ b/MiraAPI/PluginLoading/IMiraPlugin.cs @@ -1,4 +1,5 @@ -using BepInEx.Configuration; +using System.Linq; +using BepInEx.Configuration; namespace MiraAPI.PluginLoading; @@ -12,6 +13,15 @@ public interface IMiraPlugin /// string OptionsTitleText { get; } + /// + /// Gets the abbreviated name to display for other mods to pick up. + /// + /// The text for the plugin. + public virtual string GetAbbreviatedModName() + { + return new(OptionsTitleText.Where(c => !char.IsLower(c) && !char.IsWhiteSpace(c)).ToArray()); + } + /// /// Gets the name for the first custom category in the game options menu, if any. /// From 30a39fa2239fee1e052c7828ccab61df2e767c9a Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Sun, 26 Jul 2026 11:12:28 -0500 Subject: [PATCH 21/23] Add DisplayOnOptionsMenu for mira api mods --- MiraAPI/Patches/Options/GameSettingMenuPatches.cs | 10 +++++----- MiraAPI/Patches/Options/LobbyViewPanePatches.cs | 6 +++--- MiraAPI/PluginLoading/IMiraPlugin.cs | 5 +++++ MiraAPI/PluginLoading/MiraPluginManager.cs | 9 +++++++++ 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs index 995da8e..d9cc5fb 100644 --- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs @@ -199,7 +199,7 @@ internal static class GameSettingMenuPatches { SaveScrollPositions(__instance); SelectedModIdx += 1; - if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins.Length) + if (SelectedModIdx > MiraPluginManager.Instance.RegisteredOptionablePlugins.Length) { SelectedModIdx = 0; } @@ -220,7 +220,7 @@ internal static class GameSettingMenuPatches SelectedModIdx -= 1; if (SelectedModIdx < 0) { - SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins.Length; + SelectedModIdx = MiraPluginManager.Instance.RegisteredOptionablePlugins.Length; } UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab); @@ -399,17 +399,17 @@ internal static class GameSettingMenuPatches { if (_text is not null && SelectedModIdx == 0) { - _text.text = $"(Page 0/{MiraPluginManager.Instance.RegisteredPlugins.Length})\nDefault"; + _text.text = $"(Page 0/{MiraPluginManager.Instance.RegisteredOptionablePlugins.Length})\nMain"; _text.fontSizeMax = 3.2f; SelectedMod = null; } else if (_text is not null) { _text.fontSizeMax = 2.3f; - SelectedMod = MiraPluginManager.Instance.RegisteredPlugins[SelectedModIdx - 1]; + SelectedMod = MiraPluginManager.Instance.RegisteredOptionablePlugins[SelectedModIdx - 1]; var name = SelectedMod.MiraPlugin.OptionsTitleText; - _text.text = $"(Page {SelectedModIdx}/{MiraPluginManager.Instance.RegisteredPlugins.Length})\n" + name[..Math.Min(name.Length, 25)]; + _text.text = $"(Page {SelectedModIdx}/{MiraPluginManager.Instance.RegisteredOptionablePlugins.Length})\n" + name[..Math.Min(name.Length, 25)]; } bool replaceWithModifiers = true; diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index c543447..5a4460d 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -25,7 +25,7 @@ public static class LobbyViewPanePatches private static MiraPluginInfo? SelectedMod => SelectedModIdx == 0 ? null - : MiraPluginManager.Instance.RegisteredPlugins[SelectedModIdx - 1]; + : MiraPluginManager.Instance.RegisteredOptionablePlugins[SelectedModIdx - 1]; private static PassiveButton? ModifiersTabButton { get; set; } @@ -75,7 +75,7 @@ public static class LobbyViewPanePatches (UnityAction)(() => { SelectedModIdx += 1; - if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins.Length) + if (SelectedModIdx > MiraPluginManager.Instance.RegisteredOptionablePlugins.Length) { SelectedModIdx = 0; } @@ -99,7 +99,7 @@ public static class LobbyViewPanePatches SelectedModIdx -= 1; if (SelectedModIdx < 0) { - SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins.Length; + SelectedModIdx = MiraPluginManager.Instance.RegisteredOptionablePlugins.Length; } Refresh(__instance); diff --git a/MiraAPI/PluginLoading/IMiraPlugin.cs b/MiraAPI/PluginLoading/IMiraPlugin.cs index 05470cc..a29dd4c 100644 --- a/MiraAPI/PluginLoading/IMiraPlugin.cs +++ b/MiraAPI/PluginLoading/IMiraPlugin.cs @@ -8,6 +8,11 @@ namespace MiraAPI.PluginLoading; /// public interface IMiraPlugin { + /// + /// Gets or sets a value indicating whether to display the plugin in the options menu. + /// + public virtual bool DisplayOnOptionsMenu => true; + /// /// Gets the name to display on the options menu. /// diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index 4639b4f..171ab90 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -34,6 +34,10 @@ public sealed class MiraPluginManager internal MiraPluginInfo[] RegisteredPlugins { get; private set; } = null!; + private readonly Dictionary _registeredOptionablePlugins = []; + + internal MiraPluginInfo[] RegisteredOptionablePlugins { get; private set; } = null!; + internal Dictionary> QueuedRoleRegistrations { get; } = []; internal static MiraPluginManager Instance { get; private set; } = new(); @@ -127,6 +131,10 @@ public sealed class MiraPluginManager QueuedRoleRegistrations.Add(info, roles); _registeredPlugins.Add(assembly, info); + if (miraPlugin.DisplayOnOptionsMenu) + { + _registeredOptionablePlugins.Add(assembly, info); + } info.SavePublicCollections(); PresetManager.CreateDefaultPreset(info); @@ -141,6 +149,7 @@ public sealed class MiraPluginManager // Cache all the registered plugins into an array for easy access RegisteredPlugins = [.. _registeredPlugins.Values]; + RegisteredOptionablePlugins = [.. _registeredOptionablePlugins.Values]; ModifierManager.Modifiers = new ReadOnlyCollection(ModifierManager.InternalModifiers); }; From 8c5aee4bebaa197b7d235c56898ef9ae2bccf9de Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Sun, 26 Jul 2026 11:56:21 -0500 Subject: [PATCH 22/23] Fix vanilla roles tab being broken --- MiraAPI/Patches/Options/LobbyViewPanePatches.cs | 2 +- MiraAPI/Patches/Options/RoleSettingMenuPatches.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index 5a4460d..dd80dc4 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -109,7 +109,7 @@ public static class LobbyViewPanePatches private static void Refresh(LobbyViewSettingsPane menu) { ModifiersTabButton?.gameObject.SetActive(SelectedModIdx != 0); - menu.gameModeText.text = SelectedMod?.PluginInfo.Metadata.Name ?? "Default"; + menu.gameModeText.text = SelectedMod?.PluginInfo.Metadata.Name ?? "Main"; menu.RefreshTab(); menu.scrollBar.ScrollToTop(); } diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index b6f61c4..cb1283b 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -152,7 +152,7 @@ public static class RoleSettingMenuPatches } Info($"Created {num3} role options for the default game."); - yield return null; + yield break; } ScrollerNum = 0.522f; From edee889ffe0bc8249a0dce68149b3961d1f5484e Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Thu, 30 Jul 2026 23:15:24 -0500 Subject: [PATCH 23/23] Fix hidden local tabs breaking the menu --- MiraAPI/LocalSettings/LocalSettingsManager.cs | 5 +++++ MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/MiraAPI/LocalSettings/LocalSettingsManager.cs b/MiraAPI/LocalSettings/LocalSettingsManager.cs index aa1c217..874e258 100644 --- a/MiraAPI/LocalSettings/LocalSettingsManager.cs +++ b/MiraAPI/LocalSettings/LocalSettingsManager.cs @@ -12,6 +12,7 @@ public static class LocalSettingsManager { internal static readonly Dictionary TypeToTab = []; internal static readonly List Tabs = []; + internal static readonly List AvailableTabs = []; internal static bool RegisterTab(Type type, BasePlugin pluginInfo) { @@ -27,6 +28,10 @@ public static class LocalSettingsManager } Tabs.Add(tab); + if (tab.ShouldCreateButton) + { + AvailableTabs.Add(tab); + } TypeToTab.Add(type, tab); typeof(LocalSettingsTabSingleton<>).MakeGenericType(type) diff --git a/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs b/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs index 10ab62d..a8cab15 100644 --- a/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs +++ b/MiraAPI/Patches/LocalSettings/OptionsMenuPatches.cs @@ -69,7 +69,7 @@ public static class OptionsMenuPatches int i = 0; int tabIdx = 0; int page = 1; - foreach (var settings in LocalSettingsManager.Tabs) + foreach (var settings in LocalSettingsManager.AvailableTabs) { var tab = settings.CreateTab(__instance); if (settings.ShouldCreateButton) @@ -164,12 +164,12 @@ public static class OptionsMenuPatches public static bool OpenTabGroupPrefix(OptionsMenuBehaviour __instance, ref int index) { __instance.Tabs.ToList().ForEach(x => x.Close()); // Close all vanilla tabs - LocalSettingsManager.Tabs.ForEach(CustomClose); // Close all mod tabs + LocalSettingsManager.AvailableTabs.ForEach(CustomClose); // Close all mod tabs // Tabs with index 10 and above are the mod settings tabs if (index >= 10) { - CustomOpen(LocalSettingsManager.Tabs[index - 10]); + CustomOpen(LocalSettingsManager.AvailableTabs[index - 10]); return false; } @@ -183,7 +183,7 @@ public static class OptionsMenuPatches [HarmonyPatch(nameof(OptionsMenuBehaviour.Open))] public static void OpenPostfix(OptionsMenuBehaviour __instance) { - LocalSettingsManager.Tabs.ForEach(CustomClose); + LocalSettingsManager.AvailableTabs.ForEach(CustomClose); currentPage = 1; UpdatePages(); }