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
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"
),
"" + 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();
}