From 1ab662bcf122736a247df37a3d14389780262181 Mon Sep 17 00:00:00 2001 From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com> Date: Mon, 21 Sep 2026 18:48:09 -0600 Subject: [PATCH 01/60] Adds a generic IModdedOptionList definition for easier typed iteration --- MiraAPI/GameOptions/IModdedOption.cs | 19 ++++++++++++++++--- .../OptionTypes/ModdedOptionList.cs | 13 +++++++++---- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/MiraAPI/GameOptions/IModdedOption.cs b/MiraAPI/GameOptions/IModdedOption.cs index 099194e..7ecc57c 100644 --- a/MiraAPI/GameOptions/IModdedOption.cs +++ b/MiraAPI/GameOptions/IModdedOption.cs @@ -1,8 +1,8 @@ -using System; -using System.Collections.Generic; -using BepInEx.Configuration; +using BepInEx.Configuration; using MiraAPI.Networking; using MiraAPI.PluginLoading; +using System; +using System.Collections.Generic; using UnityEngine; namespace MiraAPI.GameOptions; @@ -122,3 +122,16 @@ public interface IModdedOption public interface IModdedOptionList : IReadOnlyList { } + +/// +/// Interface for list of options. +/// +/// The type of . +public interface IModdedOptionList : IModdedOptionList where T : IModdedOption +{ + /// + new T this[int index] { get; } + + /// + new IEnumerator GetEnumerator(); +} diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOptionList.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOptionList.cs index 5a764b2..88b32c5 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOptionList.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOptionList.cs @@ -9,7 +9,7 @@ namespace MiraAPI.GameOptions.OptionTypes; /// Represents a modded option list. /// /// The type of options. -public class ModdedOptionList : IModdedOptionList where T : IModdedOption +public class ModdedOptionList : IModdedOptionList where T : IModdedOption { /// public int Count { get; } @@ -40,10 +40,10 @@ public class ModdedOptionList : IModdedOptionList where T : IModdedOption Options = options.ToArray(); } - /// - public IEnumerator GetEnumerator() + /// + public IEnumerator GetEnumerator() { - return ((IEnumerable)Options).GetEnumerator(); + return Options.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() @@ -51,6 +51,11 @@ public class ModdedOptionList : IModdedOptionList where T : IModdedOption return Options.GetEnumerator(); } + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)Options).GetEnumerator(); + } + /// /// Indexes the option of type . /// From ffe9194d54a882913879623d50a410bc7f34f7d8 Mon Sep 17 00:00:00 2001 From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com> Date: Fri, 25 Sep 2026 11:47:12 -0600 Subject: [PATCH 02/60] Resolved warnings --- MiraAPI/GameOptions/IModdedOption.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/MiraAPI/GameOptions/IModdedOption.cs b/MiraAPI/GameOptions/IModdedOption.cs index 7ecc57c..5d424bb 100644 --- a/MiraAPI/GameOptions/IModdedOption.cs +++ b/MiraAPI/GameOptions/IModdedOption.cs @@ -1,8 +1,8 @@ -using BepInEx.Configuration; +using System; +using System.Collections.Generic; +using BepInEx.Configuration; using MiraAPI.Networking; using MiraAPI.PluginLoading; -using System; -using System.Collections.Generic; using UnityEngine; namespace MiraAPI.GameOptions; @@ -127,11 +127,12 @@ public interface IModdedOptionList : IReadOnlyList /// Interface for list of options. /// /// The type of . -public interface IModdedOptionList : IModdedOptionList where T : IModdedOption +public interface IModdedOptionList : IModdedOptionList + where T : IModdedOption { - /// + /// new T this[int index] { get; } - /// + /// new IEnumerator GetEnumerator(); } From 1b587547f770f8dfcbe8c5c21e4bc396724ec862 Mon Sep 17 00:00:00 2001 From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com> Date: Fri, 25 Sep 2026 12:43:14 -0600 Subject: [PATCH 03/60] Added ConfigName property to IModdedOptionList to optionally rename the individual options --- MiraAPI/GameOptions/IModdedOption.cs | 6 ++++++ MiraAPI/GameOptions/ModdedOptionsManager.cs | 4 +++- MiraAPI/GameOptions/OptionTypes/ModdedOptionList.cs | 3 +++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/MiraAPI/GameOptions/IModdedOption.cs b/MiraAPI/GameOptions/IModdedOption.cs index 5d424bb..fdf71a6 100644 --- a/MiraAPI/GameOptions/IModdedOption.cs +++ b/MiraAPI/GameOptions/IModdedOption.cs @@ -121,6 +121,12 @@ public interface IModdedOption /// public interface IModdedOptionList : IReadOnlyList { + /// + /// Gets the function to name the keys of the individual options in the configuration file. + /// + /// Return to use the default of the property's name with its index appended. + /// + Func? ConfigName { get; init; } } /// diff --git a/MiraAPI/GameOptions/ModdedOptionsManager.cs b/MiraAPI/GameOptions/ModdedOptionsManager.cs index 387a1f8..3518084 100644 --- a/MiraAPI/GameOptions/ModdedOptionsManager.cs +++ b/MiraAPI/GameOptions/ModdedOptionsManager.cs @@ -198,9 +198,11 @@ public static class ModdedOptionsManager return; } + Func configName = optionList.ConfigName ?? (i => property.Name + i); + for (int i = 0; i < optionList.Count; i++) { - RegisterOption(optionList[i], group, property.Name + i, pluginInfo); + RegisterOption(optionList[i], group, configName(i), pluginInfo); } } diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOptionList.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOptionList.cs index a09b15b..11b54c3 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOptionList.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOptionList.cs @@ -20,6 +20,9 @@ public class ModdedOptionList : IModdedOptionList /// public IReadOnlyList Options { get; } + /// + public Func? ConfigName { get; init; } + /// /// Initializes a new instance of the class. /// From 2d4619f2cfe813848013726122d9d7c7a3818972 Mon Sep 17 00:00:00 2001 From: David Higueros <111460331+TheDavSmasher@users.noreply.github.com> Date: Fri, 25 Sep 2026 12:44:44 -0600 Subject: [PATCH 04/60] Used generic interface for ModdedOptionList --- MiraAPI/GameOptions/OptionTypes/ModdedOptionList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOptionList.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOptionList.cs index 11b54c3..257ae6e 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOptionList.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOptionList.cs @@ -9,7 +9,7 @@ namespace MiraAPI.GameOptions.OptionTypes; /// Represents a modded option list. /// /// The type of options. -public class ModdedOptionList : IModdedOptionList +public class ModdedOptionList : IModdedOptionList where T : IModdedOption { /// From 9f4e519b9e4d69e7933c46bf25f5d4feca6d1564 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Tue, 29 Sep 2026 20:55:42 -0500 Subject: [PATCH 05/60] Update game version --- AmongUs.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AmongUs.props b/AmongUs.props index 0819a88..b00f7ff 100644 --- a/AmongUs.props +++ b/AmongUs.props @@ -2,7 +2,7 @@ - + From ab7d43f18b4a769f1f92abaa17c9d0e3485009f5 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Tue, 29 Sep 2026 20:56:03 -0500 Subject: [PATCH 06/60] Add vanilla button checks for Influencer --- MiraAPI/Patches/Events/VanillaButtonPatches.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/MiraAPI/Patches/Events/VanillaButtonPatches.cs b/MiraAPI/Patches/Events/VanillaButtonPatches.cs index 6b12a22..70a218b 100644 --- a/MiraAPI/Patches/Events/VanillaButtonPatches.cs +++ b/MiraAPI/Patches/Events/VanillaButtonPatches.cs @@ -23,10 +23,18 @@ public static class VanillaButtonPatches { playerTarget = role.Cast().currentTarget; } + else if (role.Role is RoleTypes.Detective) + { + playerTarget = role.Cast().currentTarget; + } else if (role.Role is RoleTypes.GuardianAngel) { playerTarget = role.Cast().currentTarget; } + else if (role.Role is RoleTypes.SpiritGuide) + { + playerTarget = role.Cast().currentTarget; + } else if (role.Role is RoleTypes.Engineer) { ventTarget = role.Cast().currentTarget; @@ -53,6 +61,10 @@ public static class VanillaButtonPatches { playerTarget = role.Cast().currentTarget; } + else if (role.Role is RoleTypes.SpiritGuide) + { + playerTarget = role.Cast().currentTarget; + } var genericEvent = new VanillaButtonClickEvent(__instance, playerTarget); MiraEventManager.InvokeEvent(genericEvent); From f52c5de4cdc0653368c3985ef6e0f82303b06e1e Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Wed, 30 Sep 2026 10:31:27 -0500 Subject: [PATCH 07/60] Add wip sorting toggling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit az i know there's warnings PLEASE don't slime me out i'll fix it later i promise 🙏 --- MiraAPI/Patches/RoleGuidePatches.cs | 179 ++++++++++++++++++++++++++-- 1 file changed, 169 insertions(+), 10 deletions(-) diff --git a/MiraAPI/Patches/RoleGuidePatches.cs b/MiraAPI/Patches/RoleGuidePatches.cs index 6b17ea3..0990064 100644 --- a/MiraAPI/Patches/RoleGuidePatches.cs +++ b/MiraAPI/Patches/RoleGuidePatches.cs @@ -19,6 +19,7 @@ namespace MiraAPI.Patches; [HarmonyPatch] public static class RoleGuidePatches { + public static PassiveButton ViewButton; [HarmonyPostfix] [HarmonyPriority(Priority.First)] [HarmonyPatch(typeof(MatchInfoGuide), nameof(MatchInfoGuide.CreateNormalModeSettings))] @@ -102,6 +103,16 @@ public static class RoleGuidePatches { if (__instance.NormalModeSettings.Count == 0) { + var settingsButton = Object.Instantiate( + HudManager.Instance.SettingsButton, + __instance.TabButtons[2].transform.parent); + ViewButton = settingsButton.GetComponent(); + ViewButton.OnClick = new Button.ButtonClickedEvent(); + ViewButton.OnClick.AddListener(new Action(SwitchOrder)); + ViewButton.name = "ViewButton"; + settingsButton.transform.localPosition = new Vector3(2.4f, 0.765f, -0.1f); + settingsButton.transform.localScale = Vector3.one; + settingsButton.transform.GetChild(2).gameObject.SetActive(false); __instance.numOfTabs = 3; __instance.TabButtons[0].SelectButton(true); __instance.MatchInfoRoleMaskArea.transform.localPosition = new Vector3(-0.0184f, 0.15f, -0.1f); @@ -154,13 +165,89 @@ public static class RoleGuidePatches return false; } - private static readonly Dictionary RolePanels = []; + private static readonly Dictionary RolePanels = []; private static Scroller advancedWikiTab; private static GameObject currentAdvancedTabObject; private static TextMeshPro titleText; + public sealed class DetailedPanel(MatchInfoRolePanel panel, string title, string category, string modId) + { + public MatchInfoRolePanel Panel = panel; + public string Title = title; + public string Category = category; + public string ModId = modId; + public int Amount { get; set; } + public int Chance { get; set; } + public float LikelyhoodOfRole { get; set; } + } + + private static SortingFilter sortFilter = SortingFilter.EnabledOnly; + private static SortingMethod sortMethod = SortingMethod.Alphabetical; + private static SortingOrder sortOrder = SortingOrder.RoleName; + + public static void SwitchFilter() + { + var stepUp = (SortingFilter)((int)sortFilter + 1); + if (Enum.IsDefined(stepUp)) + { + sortFilter = stepUp; + } + else + { + sortFilter = SortingFilter.EnabledOnly; + } + + var newSorted = sortMethod is SortingMethod.Alphabetical ? RolePanels.OrderBy(GetSortingOrder()) : RolePanels.OrderByDescending(GetSortingOrder()); + + foreach (var pair in newSorted) + { + pair.Value.Panel.transform.SetAsLastSibling(); + } + } + + public static void SwitchMethod() + { + var stepUp = (SortingMethod)((int)sortMethod + 1); + if (Enum.IsDefined(stepUp)) + { + sortMethod = stepUp; + } + else + { + sortMethod = SortingMethod.Alphabetical; + } + + var newSorted = sortMethod is SortingMethod.Alphabetical ? RolePanels.OrderBy(GetSortingOrder()) : RolePanels.OrderByDescending(GetSortingOrder()); + + foreach (var pair in newSorted) + { + pair.Value.Panel.transform.SetAsLastSibling(); + } + } + + public static void SwitchOrder() + { + var stepUp = (SortingOrder)((int)sortOrder + 1); + if (Enum.IsDefined(stepUp)) + { + sortOrder = stepUp; + } + else + { + sortOrder = SortingOrder.RoleName; + } + + var newSorted = sortMethod is SortingMethod.Alphabetical ? RolePanels.OrderBy(GetSortingOrder()) : RolePanels.OrderByDescending(GetSortingOrder()); + + foreach (var pair in newSorted) + { + pair.Value.Panel.transform.SetAsLastSibling(); + } + } + public static void DisplayNormalRoleSettings(MatchInfoGuide instance, bool reset) { + var inner = instance.settingsTabs[2].GetComponent().Inner; if (reset) { RolePanels.Clear(); @@ -198,7 +285,13 @@ public static class RoleGuidePatches { var panel = Object.Instantiate( instance.MatchInfoRolePanelPrefab, - instance.settingsTabs[2].GetComponent().Inner); + inner); + var amount = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(roleBehaviour.Role); + var chance = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(roleBehaviour.Role); + panel.SetPanel( + roleBehaviour, + amount, + chance); var collider = panel.roleIcon.gameObject.AddComponent(); collider.size = new Vector2(0.13f, 0.13f); collider.offset = new Vector2(0, 0); @@ -220,7 +313,13 @@ public static class RoleGuidePatches passiveButton.OnClick = new Button.ButtonClickedEvent(); passiveButton.OnClick.AddListener( (Action)(() => { DisplayAdvancedWiki(instance, roleBehaviour); })); - RolePanels.Add(roleBehaviour, panel); + RolePanels.Add( + roleBehaviour, + new DetailedPanel( + panel, + roleBehaviour.GetRoleName(), + roleBehaviour.GetCategoryTitle(), + roleBehaviour is ICustomRole custom ? custom.ParentMod.MiraPlugin.GetAbbreviatedModName() : "AU")); } } } @@ -228,11 +327,19 @@ public static class RoleGuidePatches advancedWikiTab?.gameObject.SetActive(false); var num = 0; - foreach (var (roleData, panel) in RolePanels) + foreach (var (roleData, holder) in RolePanels) { + var panel = holder.Panel; var amount = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(roleData.Role); var chance = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(roleData.Role); var forciblyShow = roleData is ICustomRole custom ? custom.ForceShowRoleOnWiki : null; + panel.SetPanel( + roleData, + amount, + chance); + holder.Amount = amount; + holder.Chance = chance; + holder.LikelyhoodOfRole = amount * chance; if (amount == 0 || chance == 0 || (Enum.IsDefined(roleData.Role) && roleData.IsRoleBlacklisted()) || (roleData is ICustomRole custom2 && ((!custom2.CanSpawnOnCurrentMode() && forciblyShow == null) || (forciblyShow.HasValue && !forciblyShow.Value)))) @@ -242,13 +349,16 @@ public static class RoleGuidePatches } panel.gameObject.SetActive(true); - panel.SetPanel( - roleData, - amount, - chance); num++; } + var newSorted = sortMethod is SortingMethod.Alphabetical ? RolePanels.OrderBy(GetSortingOrder()) : RolePanels.OrderByDescending(GetSortingOrder()); + + foreach (var pair in newSorted) + { + pair.Value.Panel.transform.SetAsLastSibling(); + } + if (num == 0) { instance.rolesEnabledMessage.SetActive(true); @@ -262,6 +372,19 @@ public static class RoleGuidePatches } } + public static Func, string> GetSortingOrder() + { + switch (sortOrder) + { + case SortingOrder.Faction: + return x => $"{x.Value.Category} ({x.Value.Title}) {x.Value.LikelyhoodOfRole.ToString("0000.0", CultureInfo.InvariantCulture)}"; + case SortingOrder.AmountChance: + return x => $"{x.Value.LikelyhoodOfRole.ToString("0000.0", CultureInfo.InvariantCulture)} {x.Value.Title} ({x.Value.Category})"; + default: + return x => $"{x.Value.Title} ({x.Value.Category}) {x.Value.LikelyhoodOfRole.ToString("0000.0", CultureInfo.InvariantCulture)}"; + } + } + [HarmonyPrefix] [HarmonyPriority(Priority.First)] [HarmonyPatch(typeof(MatchInfoGuide), nameof(MatchInfoGuide.CreatePlayerEntries))] @@ -368,14 +491,14 @@ public static class RoleGuidePatches if (role is ICustomRole customRole) { __instance.roleName.text = customRole.RoleName; - __instance.roleDescription.text = $"{customRole.RoleFactionTitle}\n" + customRole.RoleMedDescription; + __instance.roleDescription.text = $"{role.GetCategoryTitle()}\n" + customRole.RoleMedDescription; __instance.roleIcon.sprite = customRole.Configuration.Icon?.LoadAsset(); __instance.roleCount.text += $" ({customRole.ParentMod.MiraPlugin.GetAbbreviatedModName()})"; } else { __instance.roleName.text = role.NiceName; - __instance.roleDescription.text = $"{TranslationController.Instance.GetString(role.TeamType is RoleTeamTypes.Crewmate ? StringNames.Crewmate : StringNames.Impostor)}\n" + role.BlurbMed; + __instance.roleDescription.text = $"{role.GetCategoryTitle()}\n" + role.BlurbMed; __instance.roleIcon.sprite = role.RoleIconColor; __instance.roleCount.text += " (AU)"; } @@ -388,4 +511,40 @@ public static class RoleGuidePatches __instance.roleIcon.transform.localScale = new Vector3(4f, 4f, 1f); return false; } + + public static string GetCategoryTitle(this RoleBehaviour role) + { + if (role is ICustomRole customRole) + { + return customRole.GetCategoryTitle(); + } + + return TranslationController.Instance.GetString( + role.TeamType is RoleTeamTypes.Crewmate ? StringNames.Crewmate : StringNames.Impostor); + } + + public static string GetCategoryTitle(this ICustomRole customRole) + { + return customRole.RoleFactionTitle; + } +} + +public enum SortingOrder +{ + RoleName, + Faction, + AmountChance +} + +public enum SortingMethod +{ + Alphabetical, + AlphabeticalDescending +} + +public enum SortingFilter +{ + EnabledOnly, + DisabledOnly, + AllRoles } From 79dc3280e81bcb148ac0fdedf049104dee90fae6 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Fri, 2 Oct 2026 11:52:15 -0400 Subject: [PATCH 08/60] dont use il2cpp lists unless absolutely necessary! --- .../Modifiers/Freezer/FreezeModifier.cs | 5 ++--- .../OptionTypes/ModdedPlayerOption.cs | 13 ++++++------- MiraAPI/Patches/GameModes/HudPatches.cs | 5 ++--- MiraAPI/Patches/Options/GameOptionsMenuPatch.cs | 16 ++++++++-------- MiraAPI/Patches/Options/OptionsPatches.cs | 7 +++---- MiraAPI/Patches/Options/PlayerOptionPatches.cs | 2 +- MiraAPI/Patches/Roles/KillButtonPatches.cs | 5 ++--- .../Patches/Roles/LogicRoleSelectionHnsPatch.cs | 12 ++++++------ MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs | 5 ++--- MiraAPI/Voting/VotingUtils.cs | 5 +---- 10 files changed, 33 insertions(+), 42 deletions(-) diff --git a/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs b/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs index 8cd0139..6a7f3a0 100644 --- a/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs +++ b/MiraAPI.Example/Modifiers/Freezer/FreezeModifier.cs @@ -1,5 +1,4 @@ -using Il2CppSystem; -using MiraAPI.Example.Roles; +using MiraAPI.Example.Roles; using MiraAPI.Modifiers.Types; using MiraAPI.Translation; using UnityEngine; @@ -28,7 +27,7 @@ public class FreezeModifier : TimedModifier if (Player.AmOwner || PlayerControl.LocalPlayer.Data.Role is FreezerRole) { - Player.cosmetics.SetOutline(true, new Nullable(Palette.LightBlue)); + Player.cosmetics.SetOutline(true, new Il2CppSystem.Nullable(Palette.LightBlue)); } } diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs index 32c736e..646de56 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs @@ -1,6 +1,6 @@ using System; +using System.Collections.Generic; using System.Linq; -using Il2CppSystem.Collections.Generic; using MiraAPI.Networking; using MiraAPI.Translation; using MiraAPI.Utilities; @@ -43,7 +43,7 @@ public class ModdedPlayerOption : ModdedOption data.Title = StringName; data.Type = global::OptionTypes.Player; - Values = new List(); + Values = new(); AllowNone = allowNone; Value = DefaultValue; } @@ -60,17 +60,16 @@ public class ModdedPlayerOption : ModdedOption playerOption.name = $"{ParentMod!.OptionsTitleText}.PlayerOption.{TranslationController.Instance.GetString(StringName)}"; - var filteredList = GetFilteredPlayers(); - Values = filteredList.ToIl2CppList(); + Values = GetFilteredPlayers(); playerOption.SetUpFromData(Data, 20); playerOption.OnValueChanged = (Il2CppSystem.Action)ValueChanged; playerOption.Title = StringName; playerOption.TitleText.text = Title.Translate(); - playerOption.Values = Values; + playerOption.Values = Values.ToIl2CppList(); playerOption.Value = Value; - playerOption.playerIndex = filteredList.FindIndex(p => p.PlayerId == Value); + playerOption.playerIndex = Values.FindIndex(p => p.PlayerId == Value); OptionBehaviour = playerOption; ModdedOptionsManager.CreatedPlayerOptions.TryAdd(playerOption, this); @@ -85,7 +84,7 @@ public class ModdedPlayerOption : ModdedOption /// Returns a player ran through the filter. /// /// A list of filtered players. - public System.Collections.Generic.List GetFilteredPlayers() + public List GetFilteredPlayers() { return [.. GameData.Instance.AllPlayers.ToArray().Where(x => PlayerFilter?.Invoke(x) ?? true)]; } diff --git a/MiraAPI/Patches/GameModes/HudPatches.cs b/MiraAPI/Patches/GameModes/HudPatches.cs index d237967..7cb7716 100644 --- a/MiraAPI/Patches/GameModes/HudPatches.cs +++ b/MiraAPI/Patches/GameModes/HudPatches.cs @@ -1,6 +1,5 @@ using System.Collections; using HarmonyLib; -using Il2CppSystem; using MiraAPI.GameModes; using MiraAPI.Translation; using Reactor.Utilities; @@ -16,7 +15,7 @@ internal static class HudPatches [HarmonyPostfix, HarmonyPatch(nameof(HudManager.Update))] public static void HudUpdatePatch(HudManager __instance) { - if (GameManager.Instance != null && GameManager.Instance.GameHasStarted && + if (GameManager.Instance && GameManager.Instance.GameHasStarted && CustomGameModeManager.ActiveMode != null) { CustomGameModeManager.ActiveMode.HudUpdate(__instance); @@ -32,7 +31,7 @@ internal static class HudPatches private static IEnumerator CoPostHudStart(HudManager instance) { - yield return new WaitUntil((Func)(() => GameManager.Instance != null)); + yield return new WaitUntil((Il2CppSystem.Func)(() => GameManager.Instance != null)); if (GameManager.Instance.IsHideAndSeek()) yield return null; var infoPane = instance.gameObject.transform.FindChild("LobbyInfoPane"); diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index 38ba561..7f0920d 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -1,10 +1,10 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Linq; using BepInEx.Unity.IL2CPP.Utils.Collections; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; -using Il2CppSystem.Collections.Generic; using MiraAPI.GameModes; using MiraAPI.GameOptions; using MiraAPI.Modifiers; @@ -28,9 +28,9 @@ internal static class GameOptionsMenuPatch private static readonly List VanillaHeaders = new(); private static readonly List VanillaOptions = new(); private static readonly List MainOptions = new(); - private static readonly System.Collections.Generic.Dictionary> GameModeGroups = []; - private static readonly System.Collections.Generic.Dictionary> GameModeOptions = []; - private static readonly System.Collections.Generic.Dictionary> GameModeHeaders = []; + private static readonly Dictionary> GameModeGroups = []; + private static readonly Dictionary> GameModeOptions = []; + private static readonly Dictionary> GameModeHeaders = []; private static TextMeshPro _gamemodeDescription = null!; private static SpriteRenderer _modIcon = null!; @@ -38,7 +38,7 @@ internal static class GameOptionsMenuPatch [HarmonyPatch(nameof(GameOptionsMenu.Initialize))] public static bool InitPatch(GameOptionsMenu __instance) { - __instance.Children ??= new List(); + __instance.Children ??= new(); __instance.Children.Clear(); if (MenuState.Instance.CurrentModIdx == 0) @@ -324,8 +324,8 @@ internal static class GameOptionsMenuPatch .ToList(); GameModeGroups.Add(mode, [.. filteredGroups]); - var optionBehaviours = new System.Collections.Generic.List(); - var categoryHeaders = new System.Collections.Generic.List(); + var optionBehaviours = new List(); + var categoryHeaders = new List(); var newNum = mode.ShowNormalGameSettings ? num : num - additionalVanillaScrollNum; foreach (var group in filteredGroups) { @@ -406,7 +406,7 @@ internal static class GameOptionsMenuPatch __instance.scrollBar.SetYBoundsMax(-num - 1.65f); } - private static void CreateGroup(GameOptionsMenu menu, AbstractOptionGroup group, Transform container, ref float num, ref System.Collections.Generic.List optionBehaviours, ref System.Collections.Generic.List categoryHeaders) + private static void CreateGroup(GameOptionsMenu menu, AbstractOptionGroup group, Transform container, ref float num, ref List optionBehaviours, ref List categoryHeaders) { var categoryHeaderMasked = Object.Instantiate( menu.categoryHeaderOrigin, diff --git a/MiraAPI/Patches/Options/OptionsPatches.cs b/MiraAPI/Patches/Options/OptionsPatches.cs index 64d9119..8ed0f7d 100644 --- a/MiraAPI/Patches/Options/OptionsPatches.cs +++ b/MiraAPI/Patches/Options/OptionsPatches.cs @@ -1,5 +1,4 @@ using HarmonyLib; -using Il2CppSystem; using MiraAPI.GameOptions; using MiraAPI.Roles; using MiraAPI.Translation; @@ -165,7 +164,7 @@ public static class OptionsPatches return true; } - __instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title, Array.Empty()).Translate(); + __instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title).Translate(); return false; } @@ -314,8 +313,8 @@ public static class OptionsPatches return true; } - __instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title, Array.Empty()).Translate(); - __instance.ValueText.text = TranslationController.Instance.GetString(__instance.Values[__instance.Value], Array.Empty()); + __instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title).Translate(); + __instance.ValueText.text = TranslationController.Instance.GetString(__instance.Values[__instance.Value]); return false; } diff --git a/MiraAPI/Patches/Options/PlayerOptionPatches.cs b/MiraAPI/Patches/Options/PlayerOptionPatches.cs index 9cf0134..ab19fce 100644 --- a/MiraAPI/Patches/Options/PlayerOptionPatches.cs +++ b/MiraAPI/Patches/Options/PlayerOptionPatches.cs @@ -26,8 +26,8 @@ public static class PlayerOptionPatches { filteredPlayers.Sort((a, b) => a.PlayerId.CompareTo(b.PlayerId)); + moddedPlrOpt.Values = filteredPlayers; __instance.Values = filteredPlayers.ToIl2CppList(); - moddedPlrOpt.Values = __instance.Values; __instance.playerIndex = filteredPlayers.FindIndex(p => p.PlayerId == __instance.Value); __instance.SetValueText(); } diff --git a/MiraAPI/Patches/Roles/KillButtonPatches.cs b/MiraAPI/Patches/Roles/KillButtonPatches.cs index fc32a7a..9693346 100644 --- a/MiraAPI/Patches/Roles/KillButtonPatches.cs +++ b/MiraAPI/Patches/Roles/KillButtonPatches.cs @@ -1,5 +1,4 @@ using HarmonyLib; -using Il2CppSystem; using MiraAPI.Networking; using MiraAPI.Roles; using UnityEngine; @@ -31,13 +30,13 @@ public static class KillButtonPatches if (__instance.currentTarget && __instance.currentTarget != target) { - __instance.currentTarget.cosmetics.SetOutline(false, new Nullable(Color.clear)); + __instance.currentTarget.cosmetics.SetOutline(false, new Il2CppSystem.Nullable(Color.clear)); } __instance.currentTarget = target; if (__instance.currentTarget) { - __instance.currentTarget.cosmetics.SetOutline(true, new Nullable(customRole.Configuration.KillButtonOutlineColor)); + __instance.currentTarget.cosmetics.SetOutline(true, new Il2CppSystem.Nullable(customRole.Configuration.KillButtonOutlineColor)); __instance.SetEnabled(); return false; } diff --git a/MiraAPI/Patches/Roles/LogicRoleSelectionHnsPatch.cs b/MiraAPI/Patches/Roles/LogicRoleSelectionHnsPatch.cs index 1a845fc..c1e9da7 100644 --- a/MiraAPI/Patches/Roles/LogicRoleSelectionHnsPatch.cs +++ b/MiraAPI/Patches/Roles/LogicRoleSelectionHnsPatch.cs @@ -1,8 +1,8 @@ using System; +using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; using HarmonyLib; -using Il2CppSystem.Collections.Generic; using MiraAPI.Roles; namespace MiraAPI.Patches.Roles; @@ -14,7 +14,7 @@ public static class LogicRoleSelectionHnsPatch [HarmonyPatch(nameof(LogicRoleSelectionHnS.AssignRolesForTeam))] public static bool AssignRolesForTeam( LogicRoleSelectionHnS __instance, - List players, + Il2CppSystem.Collections.Generic.List players, IGameOptions opts, RoleTeamTypes team, int teamMax, @@ -58,7 +58,7 @@ public static class LogicRoleSelectionHnsPatch case RoleTeamTypes.Impostor: Info($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Before Guaranteed Assignment"); - var newImpostors = new List(); + var newImpostors = new Il2CppSystem.Collections.Generic.List(); // Specified Seeker if (__instance.hnsManager.LogicOptionsHnS.HasImpostorPlayerID() && @@ -113,7 +113,7 @@ public static class LogicRoleSelectionHnsPatch return false; } - public static void AssignRolesFromList(List players, int teamMax, List roleList, ref int rolesAssigned) + public static void AssignRolesFromList(Il2CppSystem.Collections.Generic.List players, int teamMax, List roleList, ref int rolesAssigned) { while (roleList.Count > 0 && players.Count > 0 && rolesAssigned < teamMax) { @@ -128,7 +128,7 @@ public static class LogicRoleSelectionHnsPatch } private static void AddGuaranteedRoles( - System.Collections.Generic.IEnumerable guaranteedRoles, + IEnumerable guaranteedRoles, IGameOptions opts, List list) { @@ -146,7 +146,7 @@ public static class LogicRoleSelectionHnsPatch } } - private static void AddPotentialRoles(System.Collections.Generic.IEnumerable source, IGameOptions opts, List list) + private static void AddPotentialRoles(IEnumerable source, IGameOptions opts, List list) { var roleOptions = opts.RoleOptions; var potentialRoles = source.Where(x => !x.IsDead).Select(role => new RoleManager.RoleAssignmentData( diff --git a/MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs b/MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs index 69b4089..f047fd3 100644 --- a/MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs +++ b/MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs @@ -2,7 +2,6 @@ using System.Runtime.CompilerServices; using HarmonyLib; using Hazel; -using Il2CppSystem.Text; namespace MiraAPI.Patches.Stubs; @@ -45,9 +44,9 @@ public static class RoleBehaviourStubs /// The object. /// The to append the task hint to. [HarmonyReversePatch] - [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.AppendTaskHint), typeof(StringBuilder))] + [HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.AppendTaskHint), typeof(Il2CppSystem.Text.StringBuilder))] [MethodImpl(MethodImplOptions.NoInlining)] - public static void AppendTaskHint(RoleBehaviour instance, StringBuilder taskStringBuilder) + public static void AppendTaskHint(RoleBehaviour instance, Il2CppSystem.Text.StringBuilder taskStringBuilder) { // nothing needed } diff --git a/MiraAPI/Voting/VotingUtils.cs b/MiraAPI/Voting/VotingUtils.cs index 3d3e206..ad9e32c 100644 --- a/MiraAPI/Voting/VotingUtils.cs +++ b/MiraAPI/Voting/VotingUtils.cs @@ -300,10 +300,7 @@ public static class VotingUtils // If modified, these will visually change. votes = @event.Votes; - MeetingHud.Instance.TitleText.text = - TranslationController.Instance.GetString( - StringNames.MeetingVotingResults, - Il2CppSystem.Array.Empty()); + MeetingHud.Instance.TitleText.text = TranslationController.Instance.GetString(StringNames.MeetingVotingResults); var delays = new Dictionary(); var num = 0; From a27a45944cf3d1d1af9348032ff0d398b2dd8fe2 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Sat, 3 Oct 2026 03:06:33 -0500 Subject: [PATCH 09/60] Add WIP search box --- MiraAPI/Patches/RoleGuidePatches.cs | 89 ++++++++++++++++-- MiraAPI/Resources/WikiPlayersButtonHover.png | Bin 0 -> 6702 bytes MiraAPI/Resources/WikiPlayersButtonIdle.png | Bin 0 -> 6209 bytes MiraAPI/Resources/WikiPlayersButtonOpen.png | Bin 0 -> 6957 bytes MiraAPI/Resources/WikiRolesButtonHover.png | Bin 0 -> 6924 bytes MiraAPI/Resources/WikiRolesButtonIdle.png | Bin 0 -> 6570 bytes MiraAPI/Resources/WikiRolesButtonOpen.png | Bin 0 -> 6932 bytes MiraAPI/Resources/WikiSettingsButtonHover.png | Bin 0 -> 8442 bytes MiraAPI/Resources/WikiSettingsButtonIdle.png | Bin 0 -> 8394 bytes MiraAPI/Resources/WikiSettingsButtonOpen.png | Bin 0 -> 8493 bytes MiraAPI/Utilities/Assets/MiraAssets.cs | 45 +++++++++ 11 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 MiraAPI/Resources/WikiPlayersButtonHover.png create mode 100644 MiraAPI/Resources/WikiPlayersButtonIdle.png create mode 100644 MiraAPI/Resources/WikiPlayersButtonOpen.png create mode 100644 MiraAPI/Resources/WikiRolesButtonHover.png create mode 100644 MiraAPI/Resources/WikiRolesButtonIdle.png create mode 100644 MiraAPI/Resources/WikiRolesButtonOpen.png create mode 100644 MiraAPI/Resources/WikiSettingsButtonHover.png create mode 100644 MiraAPI/Resources/WikiSettingsButtonIdle.png create mode 100644 MiraAPI/Resources/WikiSettingsButtonOpen.png diff --git a/MiraAPI/Patches/RoleGuidePatches.cs b/MiraAPI/Patches/RoleGuidePatches.cs index 0990064..a2a7b86 100644 --- a/MiraAPI/Patches/RoleGuidePatches.cs +++ b/MiraAPI/Patches/RoleGuidePatches.cs @@ -35,15 +35,21 @@ public static class RoleGuidePatches __instance.matchInfoSettingsMaskArea.size = __instance.MatchInfoRoleMaskArea.size = new Vector2(-6, 1.8f); - __instance.matchInfoPlayersMaskArea.transform.parent.GetAllChildren().First(x => x.name.Contains("BG_Gradient")) + var playerMenuGradient = __instance.matchInfoPlayersMaskArea.transform.parent.GetAllChildren() + .First(x => x.name.Contains("BG_Gradient")); + var settingsenuGradient = __instance.matchInfoSettingsMaskArea.transform.parent.GetAllChildren() + .First(x => x.name.Contains("BG_Gradient")); + var roleMenuGradient = __instance.MatchInfoRoleMaskArea.transform.parent.GetAllChildren() + .First(x => x.name.Contains("BG_Gradient")); + playerMenuGradient .GetComponent() .maskInteraction = - __instance.matchInfoSettingsMaskArea.transform.parent.GetAllChildren() - .First(x => x.name.Contains("BG_Gradient")).GetComponent() + settingsenuGradient.GetComponent() .maskInteraction = - __instance.MatchInfoRoleMaskArea.transform.parent.GetAllChildren() - .First(x => x.name.Contains("BG_Gradient")).GetComponent() + roleMenuGradient.GetComponent() .maskInteraction = SpriteMaskInteraction.VisibleInsideMask; + roleMenuGradient.transform.localScale = new Vector3(0.5297f, 0.1565f, 1); + roleMenuGradient.transform.localPosition = new Vector3(0, -0.62f, -5); var wikiTab = Object.Instantiate(__instance.settingsTabs[2], __instance.settingsTabs[2].transform.parent); advancedWikiTab = wikiTab.GetComponent(); } @@ -124,25 +130,87 @@ public static class RoleGuidePatches __instance.matchInfoSettingsMaskArea.size = __instance.MatchInfoRoleMaskArea.size = new Vector2(-6, 1.8f); - __instance.matchInfoPlayersMaskArea.transform.parent.GetAllChildren().First(x => x.name.Contains("BG_Gradient")) + var playerMenuGradient = __instance.matchInfoPlayersMaskArea.transform.parent.GetAllChildren() + .First(x => x.name.Contains("BG_Gradient")); + var settingsenuGradient = __instance.matchInfoSettingsMaskArea.transform.parent.GetAllChildren() + .First(x => x.name.Contains("BG_Gradient")); + var roleMenuGradient = __instance.MatchInfoRoleMaskArea.transform.parent.GetAllChildren() + .First(x => x.name.Contains("BG_Gradient")); + playerMenuGradient .GetComponent() .maskInteraction = - __instance.matchInfoSettingsMaskArea.transform.parent.GetAllChildren() - .First(x => x.name.Contains("BG_Gradient")).GetComponent() + settingsenuGradient.GetComponent() .maskInteraction = - __instance.MatchInfoRoleMaskArea.transform.parent.GetAllChildren() - .First(x => x.name.Contains("BG_Gradient")).GetComponent() + roleMenuGradient.GetComponent() .maskInteraction = SpriteMaskInteraction.VisibleInsideMask; + roleMenuGradient.transform.localScale = new Vector3(0.5297f, 0.1565f, 1); + roleMenuGradient.transform.localPosition = new Vector3(0, -0.62f, -5); __instance.MatchInfoRoleMaskArea.material.SetInt(PlayerMaterial.MaskLayer, 50); __instance.matchInfoSettingsMaskArea.material.SetInt(PlayerMaterial.MaskLayer, 50); + var wikiTab = Object.Instantiate(__instance.settingsTabs[2], __instance.settingsTabs[2].transform.parent); wikiTab.transform.FindChild("MaskArea")?.transform.localPosition = new Vector3(-0.0184f, 0.15f, -0.1f); + wikiTab.transform.GetAllChildren() + .First(x => x.name.Contains("BG_Gradient")).transform.localPosition = new Vector3(0, -0.62f, -5); advancedWikiTab = wikiTab.GetComponent(); + DisplayNormalRoleSettings(__instance, true); + + var searchBox = Object.Instantiate(__instance.TabButtons[2].gameObject, __instance.TabButtons[2].transform.parent); + var searchButton = searchBox.GetComponent(); + var tmpText = searchButton.transform.GetChild(0).GetComponent(); + tmpText.GetComponent().Destroy(); + tmpText.transform.localPosition = new Vector3(-1.33f, 0.0343f, -0.2f); + tmpText.color = new Color(0, 0.9656f, 0.8679f, 1); + tmpText.text = string.Empty; + tmpText.color = new Color(0, 0.9656f, 0.8679f, 0.5f); + var inactive = searchButton.inactiveSprites; + var selected = searchButton.selectedSprites; + var highlight = searchButton.activeSprites; + var disabledSprite = searchButton.disabledSprites; + disabledSprite.GetComponent().size = new Vector2(3, 0.6f); + searchButton.Destroy(); + inactive.gameObject.SetActive(false); + selected.gameObject.SetActive(false); + highlight.gameObject.SetActive(false); + disabledSprite.gameObject.SetActive(true); + var button = searchBox.AddComponent(); + button.OnUp = true; + searchBoxTmp = searchBox.AddComponent(); + searchBoxTmp.outputText = tmpText; + button.OnClick.AddListener( + (UnityAction)(() => + { + searchBoxTmp.GiveFocus(); + })); + button.OnMouseOver = new UnityEvent(); + button.OnMouseOut = new UnityEvent(); + button.OnClick = new Button.ButtonClickedEvent(); + searchBoxTmp.OnChange = new Button.ButtonClickedEvent(); + searchBoxTmp.OnChange.AddListener( + (UnityAction)(() => + { + var text = searchBoxTmp.outputText.text; + var newSorted = RolePanels + .OrderByDescending(child => + child.Value.Panel.name.Equals(text, StringComparison.OrdinalIgnoreCase)) + .ThenByDescending(child => child.Value.Panel.name.Contains( + text, + StringComparison.InvariantCultureIgnoreCase)) + .ThenBy(GetSortingOrder()); + + foreach (var pair in newSorted) + { + pair.Value.Panel.transform.SetAsLastSibling(); + } + + advancedWikiTab.ScrollToTop(); + })); } else { DisplayNormalRoleSettings(__instance, false); + searchBoxTmp.SetText(string.Empty); } } else if (__instance.HnSModeSettings.Count == 0) @@ -167,6 +235,7 @@ public static class RoleGuidePatches private static readonly Dictionary RolePanels = []; private static Scroller advancedWikiTab; + private static TextBoxTMP searchBoxTmp; private static GameObject currentAdvancedTabObject; private static TextMeshPro titleText; diff --git a/MiraAPI/Resources/WikiPlayersButtonHover.png b/MiraAPI/Resources/WikiPlayersButtonHover.png new file mode 100644 index 0000000000000000000000000000000000000000..c4bd871b19e74c3af2edc74c05366c198a9cd624 GIT binary patch literal 6702 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 literal 0 HcmV?d00001 diff --git a/MiraAPI/Resources/WikiPlayersButtonIdle.png b/MiraAPI/Resources/WikiPlayersButtonIdle.png new file mode 100644 index 0000000000000000000000000000000000000000..6c991bef76b63a2cbc9056bcc1716ab418d8824c GIT binary patch literal 6209 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 literal 0 HcmV?d00001 diff --git a/MiraAPI/Resources/WikiPlayersButtonOpen.png b/MiraAPI/Resources/WikiPlayersButtonOpen.png new file mode 100644 index 0000000000000000000000000000000000000000..6e6ccbf9da36c80a72f83939f937b068bbbf3615 GIT binary patch literal 6957 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