From 5cc29d903a82d35ccdc7348d8ef550e92a1cccec Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sun, 26 Jan 2025 14:01:30 -0500 Subject: [PATCH 01/13] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 08a6d67..29ff563 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![](https://dcbadge.limes.pink/api/server/all-of-us-launchpad-794950428756410429)](https://discord.gg/all-of-us-launchpad-794950428756410429) +[![](https://dcbadge.limes.pink/api/server/TQeQdCCcx8)](https://discord.gg/TQeQdCCcx8) # Mira API From cd6c4f5390f04dfb7ecc921a92a5131d20a3f472 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sun, 26 Jan 2025 14:01:44 -0500 Subject: [PATCH 02/13] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 29ff563..7cf4013 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ A thorough, but simple, Among Us modding API and utility library that covers: Mira API strives to be simple and easy to use, while also using as many base game elements as possible. The result is a less intrusive, better modding API that covers general use cases. -**Join the [Discord](https://discord.gg/all-of-us-launchpad-794950428756410429) for support and to stay updated on the latest releases** +**Join the [Discord](https://discord.gg/TQeQdCCcx8) for support and to stay updated on the latest releases** # Usage From f5b4bdd1d404967c490315e03fd4c378ef41dcf5 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sun, 26 Jan 2025 15:25:27 -0500 Subject: [PATCH 03/13] Update discord links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7cf4013..8fd2e23 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![](https://dcbadge.limes.pink/api/server/TQeQdCCcx8)](https://discord.gg/TQeQdCCcx8) +[![](https://dcbadge.limes.pink/api/server/FYYqJU2bvp)](https://discord.gg/FYYqJU2bvp) # Mira API @@ -14,7 +14,7 @@ A thorough, but simple, Among Us modding API and utility library that covers: Mira API strives to be simple and easy to use, while also using as many base game elements as possible. The result is a less intrusive, better modding API that covers general use cases. -**Join the [Discord](https://discord.gg/TQeQdCCcx8) for support and to stay updated on the latest releases** +**Join the [Discord](https://discord.gg/FYYqJU2bvp) for support and to stay updated on the latest releases** # Usage From 9884df356ed03e98d70e73e0cfdc0a1a86aceb09 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Fri, 27 Jun 2025 14:28:09 -0400 Subject: [PATCH 04/13] hide hidden role settings in advanced menu --- MiraAPI/Patches/Options/RoleSettingMenuPatches.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 4dc7317..0950bb5 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -334,8 +334,9 @@ public static class RoleSettingMenuPatches // TODO: create sub groups under the role settings. var filteredOptions = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x=> x.OptionableType == role.GetType()) - .SelectMany(x=>x.Options) + .Where(x => x.GroupVisible() && x.OptionableType == role.GetType()) + .SelectMany(x => x.Options) + .Where(x => x.Visible()) .ToList() ?? []; foreach (var option in filteredOptions) From 720491e5cfe662826dba27d1fbe740db27951cdb Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:16:14 -0400 Subject: [PATCH 05/13] make role options wrap around --- MiraAPI/Patches/Options/OptionsPatches.cs | 144 ++++++++++++++++-- .../Patches/Options/RoleSettingMenuPatches.cs | 4 + MiraAPI/Roles/CustomRoleUtils.cs | 17 ++- 3 files changed, 154 insertions(+), 11 deletions(-) diff --git a/MiraAPI/Patches/Options/OptionsPatches.cs b/MiraAPI/Patches/Options/OptionsPatches.cs index c61613c..1a388d8 100644 --- a/MiraAPI/Patches/Options/OptionsPatches.cs +++ b/MiraAPI/Patches/Options/OptionsPatches.cs @@ -1,5 +1,6 @@ using HarmonyLib; using Il2CppSystem; +using MiraAPI.Roles; using MiraAPI.Utilities; namespace MiraAPI.Patches.Options; @@ -7,6 +8,138 @@ namespace MiraAPI.Patches.Options; [HarmonyPatch] public static class OptionsPatches { + [HarmonyPrefix] + [HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.IncreaseChance))] + public static bool RoleIncreaseChancePrefix(RoleOptionSetting __instance) + { + if (!__instance.Role.IsCustomRole()) + { + return true; + } + + if (__instance.roleChance == 0) + { + __instance.roleMaxCount = 1; + } + + __instance.roleChance += 10; + if (__instance.roleChance > 100) + { + __instance.roleChance = 0; + } + + __instance.OnValueChanged.Invoke(__instance); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.DecreaseChance))] + public static bool RoleDecreaseChancePrefix(RoleOptionSetting __instance) + { + if (!__instance.Role.IsCustomRole()) + { + return true; + } + + if (__instance.roleChance == 0) + { + __instance.roleMaxCount = 1; + } + + __instance.roleChance -= 10; + if (__instance.roleChance < 0) + { + __instance.roleChance = 100; + } + + __instance.OnValueChanged.Invoke(__instance); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.IncreaseCount))] + public static bool RoleIncreaseCountPrefix(RoleOptionSetting __instance) + { + if (!__instance.Role.IsCustomRole()) + { + return true; + } + + if (__instance.roleMaxCount == 0) + { + __instance.roleChance = 50; + } + + __instance.roleMaxCount += 1; + if (__instance.roleMaxCount > __instance.role.MaxCount) + { + __instance.roleMaxCount = 0; + } + + __instance.OnValueChanged.Invoke(__instance); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.DecreaseCount))] + public static bool RoleDecreaseCountPrefix(RoleOptionSetting __instance) + { + if (!__instance.Role.IsCustomRole()) + { + return true; + } + + if (__instance.roleMaxCount == 0) + { + __instance.roleChance = 50; + } + + __instance.roleMaxCount -= 1; + if (__instance.roleMaxCount < 0) + { + __instance.roleMaxCount = __instance.role.MaxCount; + } + + __instance.OnValueChanged.Invoke(__instance); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(NumberOption), nameof(NumberOption.AdjustButtonsActiveState))] + [HarmonyPatch(typeof(StringOption), nameof(StringOption.AdjustButtonsActiveState))] + [HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.AdjustChanceButtonsActiveState))] + [HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.AdjustCountButtonsActiveState))] + public static bool AdjustButtonsPrefix(OptionBehaviour __instance) + { + if (__instance.IsCustom()) + { + if (__instance.TryCast() is { } numberOption) + { + numberOption.MinusBtn.SetInteractable(true); + numberOption.PlusBtn.SetInteractable(true); + } + if (__instance.TryCast() is { } stringOption) + { + stringOption.MinusBtn.SetInteractable(true); + stringOption.PlusBtn.SetInteractable(true); + } + + return false; + } + + if (__instance.TryCast() is { } roleOptionSetting && roleOptionSetting.Role.IsCustomRole()) + { + roleOptionSetting.CountMinusBtn.SetInteractable(true); + roleOptionSetting.CountPlusBtn.SetInteractable(true); + roleOptionSetting.ChanceMinusBtn.SetInteractable(true); + roleOptionSetting.ChancePlusBtn.SetInteractable(true); + + return false; + } + + return true; + } + [HarmonyPrefix] [HarmonyPatch(typeof(ToggleOption), nameof(ToggleOption.Initialize))] public static bool ToggleInit(ToggleOption __instance) @@ -16,8 +149,7 @@ public static class OptionsPatches return true; } - __instance.TitleText.text = - TranslationController.Instance.GetString(__instance.Title, Array.Empty()); + __instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title, Array.Empty()); return false; } @@ -45,14 +177,6 @@ public static class OptionsPatches return !__instance.IsCustom(); } - [HarmonyPrefix] - [HarmonyPatch(typeof(NumberOption), nameof(NumberOption.AdjustButtonsActiveState))] - [HarmonyPatch(typeof(StringOption), nameof(StringOption.AdjustButtonsActiveState))] - public static bool AdjustButtonsPrefix(OptionBehaviour __instance) - { - return !__instance.IsCustom(); - } - [HarmonyPrefix] [HarmonyPatch(typeof(NumberOption), nameof(NumberOption.Initialize))] public static bool NumberInit(NumberOption __instance) diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 0950bb5..44fc973 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -493,6 +493,10 @@ public static class RoleSettingMenuPatches roleOptionSetting.labelSprite.color = customRole.OptionsMenuColor; roleOptionSetting.OnValueChanged = new Action(ValueChanged); roleOptionSetting.SetClickMask(__instance.ButtonClickMask); + roleOptionSetting.ChanceMinusBtn.SetInteractable(true); + roleOptionSetting.ChancePlusBtn.SetInteractable(true); + roleOptionSetting.CountMinusBtn.SetInteractable(true); + roleOptionSetting.CountPlusBtn.SetInteractable(true); __instance.roleChances.Add(roleOptionSetting); roleOptionSetting.titleText.transform.localPosition = new Vector3(-0.5376f, -0.2923f, 0f); diff --git a/MiraAPI/Roles/CustomRoleUtils.cs b/MiraAPI/Roles/CustomRoleUtils.cs index 7f0ec29..01b402d 100644 --- a/MiraAPI/Roles/CustomRoleUtils.cs +++ b/MiraAPI/Roles/CustomRoleUtils.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using AmongUs.GameOptions; using MiraAPI.Utilities.Assets; +using Reactor.Utilities; using UnityEngine; namespace MiraAPI.Roles; @@ -56,7 +57,21 @@ public static class CustomRoleUtils public static LoadableAsset? GetIntroSound(RoleTypes roleType) { var role = RoleManager.Instance.AllRoles.FirstOrDefault(role => role.Role == roleType); - if (role is ICustomRole customRole) return customRole.Configuration.IntroSound; + if (role is ICustomRole customRole) + { + return customRole.Configuration.IntroSound; + } + return new PreloadedAsset(role!.IntroSound); } + + /// + /// Determines if a role is a custom role or not. + /// + /// The RoleBehaviour to check. + /// True if the role is a custom role, false otherwise. + public static bool IsCustomRole(this RoleBehaviour role) + { + return CustomRoleManager.CustomRoles.ContainsKey((ushort)role.Role); + } } From 9388ff487ae0ceba760c36aa159006481eee1328 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:21:58 -0400 Subject: [PATCH 06/13] fix scrolling bug on role menu --- MiraAPI/Patches/Options/RoleSettingMenuPatches.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 44fc973..71ecdc1 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -211,9 +211,10 @@ public static class RoleSettingMenuPatches headerBtn.OnClick.AddListener( (UnityAction)(() => { - if (RoleGroupHidden.TryGetValue(group, out var value)) + RolePositions[GameSettingMenuPatches.SelectedModIdx] = __instance.scrollBar.Inner.localPosition; + if (RoleGroupHidden.TryGetValue(group, out var groupHidden)) { - RoleGroupHidden[group] = !value; + RoleGroupHidden[group] = !groupHidden; } foreach (var header in Headers) { From 0d41ebbc1324628185489c492660a293d10cc5fe Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:47:38 -0400 Subject: [PATCH 07/13] adjust server dropdown to fit 2 columns --- MiraAPI/Patches/ServerDropdownPatch.cs | 52 ++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 MiraAPI/Patches/ServerDropdownPatch.cs diff --git a/MiraAPI/Patches/ServerDropdownPatch.cs b/MiraAPI/Patches/ServerDropdownPatch.cs new file mode 100644 index 0000000..0fb96e3 --- /dev/null +++ b/MiraAPI/Patches/ServerDropdownPatch.cs @@ -0,0 +1,52 @@ +using HarmonyLib; +using UnityEngine; +using UnityEngine.Events; + +namespace MiraAPI.Patches; + +[HarmonyPatch(typeof(ServerDropdown), nameof(ServerDropdown.FillServerOptions))] +public static class ServerDropdownPatch +{ + public static bool Prefix(ServerDropdown __instance) + { + var num = 0; + __instance.background.size = new Vector2(8.4f, 4.8f); + + foreach (var regionInfo in DestroyableSingleton.Instance.AvailableRegions) + { + if (DestroyableSingleton.Instance.CurrentRegion.Equals(regionInfo)) + { + __instance.defaultButtonSelected = __instance.firstOption; + __instance.firstOption.ChangeButtonText( + DestroyableSingleton.Instance.GetStringWithDefault( + regionInfo.TranslateName, + regionInfo.Name)); + } + else + { + var region = regionInfo; + var serverListButton = __instance.ButtonPool.Get(); + var x = num % 2 == 0 ? -2 : 2; + var y = -0.55f * (num / 2); + serverListButton.transform.localPosition = new Vector3(x, __instance.y_posButton + y, -1f); + serverListButton.transform.localScale = Vector3.one; + serverListButton.Text.text = + DestroyableSingleton.Instance.GetStringWithDefault( + regionInfo.TranslateName, + regionInfo.Name); + serverListButton.Text.ForceMeshUpdate(); + serverListButton.Button.OnClick.RemoveAllListeners(); + serverListButton.Button.OnClick.AddListener((UnityAction)(() => { __instance.ChooseOption(region); })); + __instance.controllerSelectable.Add(serverListButton.Button); + __instance.background.transform.localPosition = new Vector3( + 0f, + __instance.initialYPos + (-0.3f * (num / 2)), + 0f); + __instance.background.size = new Vector2(__instance.background.size.x, 1.2f + (0.6f * (num / 2))); + num++; + } + } + + return false; + } +} From f43191c4f2bdfb838edda6cac8685504084b98cc Mon Sep 17 00:00:00 2001 From: ang-xd Date: Fri, 27 Jun 2025 19:16:56 -0400 Subject: [PATCH 08/13] Fix role option visibility & shift 5 increments on chance --- MiraAPI/Patches/Options/OptionsPatches.cs | 10 ++++- .../Patches/Options/RoleSettingMenuPatches.cs | 44 +++++++++++++++---- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/MiraAPI/Patches/Options/OptionsPatches.cs b/MiraAPI/Patches/Options/OptionsPatches.cs index 1a388d8..70a8ff4 100644 --- a/MiraAPI/Patches/Options/OptionsPatches.cs +++ b/MiraAPI/Patches/Options/OptionsPatches.cs @@ -2,6 +2,8 @@ using Il2CppSystem; using MiraAPI.Roles; using MiraAPI.Utilities; +using UnityEngine; +using Object = Il2CppSystem.Object; namespace MiraAPI.Patches.Options; @@ -22,7 +24,9 @@ public static class OptionsPatches __instance.roleMaxCount = 1; } - __instance.roleChance += 10; + var increment = Input.GetKey(KeyCode.LeftShift) ? 5 : 10; + __instance.roleChance += increment; + if (__instance.roleChance > 100) { __instance.roleChance = 0; @@ -46,7 +50,9 @@ public static class OptionsPatches __instance.roleMaxCount = 1; } - __instance.roleChance -= 10; + var increment = Input.GetKey(KeyCode.LeftShift) ? 5 : 10; + __instance.roleChance -= increment; + if (__instance.roleChance < 0) { __instance.roleChance = 100; diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 71ecdc1..6ce680d 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -30,6 +30,8 @@ public static class RoleSettingMenuPatches private static float ScrollerNum { get; set; } = 0.522f; + private static RoleBehaviour? CurrentRole { get; set; } + [HarmonyPostfix] [HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))] public static void SetQuotaTabPostfix(RolesSettingsMenu __instance) @@ -45,6 +47,7 @@ public static class RoleSettingMenuPatches RoleOptionSettings.ForEach(Object.Destroy); Headers.Clear(); RoleOptionSettings.Clear(); + CurrentRole = null; __instance.roleChances = new Il2CppSystem.Collections.Generic.List(); __instance.advancedSettingChildren = new Il2CppSystem.Collections.Generic.List(); @@ -262,9 +265,36 @@ public static class RoleSettingMenuPatches [HarmonyPrefix] [HarmonyPatch(nameof(RolesSettingsMenu.Update))] - public static bool UpdatePatch() + public static bool UpdatePatch(RolesSettingsMenu __instance) { - return GameSettingMenuPatches.SelectedModIdx == 0; + if (GameSettingMenuPatches.SelectedModIdx == 0) return true; + if (CurrentRole == null) return false; + + var filteredOptions = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups + .Where(x => x.GroupVisible() && x.OptionableType == CurrentRole.GetType()) + .SelectMany(x => x.Options) + .ToList() ?? []; + + var hasImage = CurrentRole.RoleScreenshot != null; + var num = hasImage ? -0.872f : -1; + + foreach (var opt in filteredOptions) + { + if (opt.OptionBehaviour == null) continue; + + if (!opt.Visible.Invoke()) + { + opt.OptionBehaviour.gameObject.SetActive(false); + continue; + } + + opt.OptionBehaviour.transform.localPosition = new Vector3(hasImage ? 2.17f : 1.1f, num, -2f); + opt.OptionBehaviour.gameObject.SetActive(true); + num += -0.45f; + } + + __instance.scrollBar.SetYBoundsMax(-num - 3); + return false; } [HarmonyPostfix] @@ -328,16 +358,14 @@ public static class RoleSettingMenuPatches optBehaviour.gameObject.DestroyImmediate(); } + CurrentRole = role; __instance.advancedSettingChildren.Clear(); - var hasImage = role.RoleScreenshot != null; - var num = hasImage ? -0.872f : -1; // TODO: create sub groups under the role settings. var filteredOptions = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups .Where(x => x.GroupVisible() && x.OptionableType == role.GetType()) .SelectMany(x => x.Options) - .Where(x => x.Visible()) .ToList() ?? []; foreach (var option in filteredOptions) @@ -348,7 +376,6 @@ public static class RoleSettingMenuPatches __instance.stringOptionOrigin, __instance.AdvancedRolesSettings.transform); - newOpt.transform.localPosition = new Vector3(hasImage ? 2.17f : 1.1f, num, -2f); newOpt.SetClickMask(__instance.ButtonClickMask); SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren(true); @@ -366,11 +393,10 @@ public static class RoleSettingMenuPatches newOpt.LabelBackground.enabled = false; __instance.advancedSettingChildren.Add(newOpt); - num += -0.45f; + newOpt.gameObject.SetActive(false); newOpt.Initialize(); } - __instance.scrollBar.SetYBoundsMax(-num - 3); __instance.scrollBar.ScrollToTop(); } @@ -506,7 +532,7 @@ public static class RoleSettingMenuPatches if (GameSettingMenuPatches.SelectedMod is null || GameSettingMenuPatches.SelectedMod.InternalOptionGroups - .Exists(x => x.OptionableType == role.GetType())) + .Exists(x => x.GroupVisible() && x.OptionableType == role.GetType() && x.Options.Any(y => y.Visible()))) { var newButton = Object.Instantiate(roleOptionSetting.buttons[0], roleOptionSetting.transform); newButton.name = "ConfigButton"; From bc7f772de9be6aeb0c957cb4f0130e165e8c542b Mon Sep 17 00:00:00 2001 From: ang-xd Date: Sat, 28 Jun 2025 18:44:31 -0400 Subject: [PATCH 09/13] Add ModdedPlayerOption and fix role options scroll --- MiraAPI.Example/Options/ExampleOptions2.cs | 2 + MiraAPI/GameOptions/IModdedOption.cs | 3 +- MiraAPI/GameOptions/ModdedOptionsManager.cs | 2 + .../OptionTypes/ModdedEnumOption.cs | 4 +- .../OptionTypes/ModdedNumberOption.cs | 1 + .../GameOptions/OptionTypes/ModdedOption.cs | 2 + .../OptionTypes/ModdedPlayerOption.cs | 134 ++++++++++++++++++ .../OptionTypes/ModdedToggleOption.cs | 2 +- .../Patches/Options/GameOptionsMenuPatch.cs | 1 + MiraAPI/Patches/Options/OptionsPatches.cs | 13 +- .../Patches/Options/PlayerOptionPatches.cs | 128 +++++++++++++++++ .../Patches/Options/RoleSettingMenuPatches.cs | 18 +-- MiraAPI/Utilities/Extensions.cs | 18 +++ 13 files changed, 315 insertions(+), 13 deletions(-) create mode 100644 MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs create mode 100644 MiraAPI/Patches/Options/PlayerOptionPatches.cs diff --git a/MiraAPI.Example/Options/ExampleOptions2.cs b/MiraAPI.Example/Options/ExampleOptions2.cs index 9abf375..bc3c910 100644 --- a/MiraAPI.Example/Options/ExampleOptions2.cs +++ b/MiraAPI.Example/Options/ExampleOptions2.cs @@ -10,6 +10,8 @@ public class ExampleOptions2 : AbstractOptionGroup public override uint GroupPriority => 0; // This group will be displayed first. The default value is uint.MaxValue. + public ModdedPlayerOption PlayerOption2 { get; } = new("YOU MUST CHOSE A PLAYER", false); + public ModdedPlayerOption PlayerOption { get; } = new("Eh whatever"); public ModdedToggleOption ToggleOpt1 { get; } = new("Toggle Option 1", false); public ModdedToggleOption ToggleOpt2 { get; } = new("Toggle Option 2", false) diff --git a/MiraAPI/GameOptions/IModdedOption.cs b/MiraAPI/GameOptions/IModdedOption.cs index 95173fb..947b6ff 100644 --- a/MiraAPI/GameOptions/IModdedOption.cs +++ b/MiraAPI/GameOptions/IModdedOption.cs @@ -62,9 +62,10 @@ public interface IModdedOption /// The ToggleOption template. /// The NumberOption template. /// The StringOption template. + /// The PlayerOption template. /// >The Transform container for the option. /// >The created OptionBehaviour object. - OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container); + OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container); /// /// Gets the value as a float. diff --git a/MiraAPI/GameOptions/ModdedOptionsManager.cs b/MiraAPI/GameOptions/ModdedOptionsManager.cs index 0adc461..86e2006 100644 --- a/MiraAPI/GameOptions/ModdedOptionsManager.cs +++ b/MiraAPI/GameOptions/ModdedOptionsManager.cs @@ -6,6 +6,7 @@ using System.Reflection; using BepInEx.Configuration; using HarmonyLib; using MiraAPI.GameOptions.Attributes; +using MiraAPI.GameOptions.OptionTypes; using MiraAPI.Networking; using MiraAPI.PluginLoading; using MiraAPI.Utilities; @@ -22,6 +23,7 @@ public static class ModdedOptionsManager private static readonly Dictionary OptionAttributes = []; private static readonly Dictionary TypeToGroup = []; + internal static readonly Dictionary CreatedPlayerOptions = []; internal static readonly Dictionary ModdedOptions = []; internal static readonly List Groups = []; diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs index 18f13c6..0bec22f 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs @@ -44,7 +44,7 @@ public class ModdedEnumOption : ModdedOption } /// - public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container) + public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container) { var stringOption = Object.Instantiate(stringOpt, container); @@ -135,7 +135,7 @@ public class ModdedEnumOption : ModdedOption where T : Enum } /// - public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container) + public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container) { var stringOption = Object.Instantiate(stringOpt, container); diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs index a01ffe8..53378e1 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs @@ -93,6 +93,7 @@ public class ModdedNumberOption : ModdedOption ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, + PlayerOption playerOpt, Transform container) { var numberOption = Object.Instantiate(numberOpt, Vector3.zero, Quaternion.identity, container); diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs index 3c98d24..311f294 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -37,6 +37,7 @@ public abstract class ModdedOption : IModdedOption { if (_parentMod != null || value == null) return; _parentMod = value; + var entry = _parentMod.GetConfigFile().Bind(ConfigDefinition, DefaultValue); Value = entry.Value; } @@ -177,6 +178,7 @@ public abstract class ModdedOption : IModdedOption ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, + PlayerOption playerOpt, Transform container); /// diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs new file mode 100644 index 0000000..8431d1f --- /dev/null +++ b/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs @@ -0,0 +1,134 @@ +using System; +using System.Linq; +using Il2CppSystem.Collections.Generic; +using MiraAPI.Networking; +using MiraAPI.Utilities; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace MiraAPI.GameOptions.OptionTypes; + +/// +/// An option for selecting an ingame player. It returns an index of the values list, NOT A PLAYER ID. To get a player out of this, index the Values list. +/// +public class ModdedPlayerOption : ModdedOption +{ + /// + /// Gets or sets the included players. + /// + public List Values { get; set; } + + /// + /// Gets or sets a filter for the players included in the option. + /// + public Func? PlayerFilter { get; set; } + + /// + /// Gets or sets a value indicating whether the option includes a "None" option. If None is selected, value will return -1. + /// + public bool AllowNone { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The title of the option. + /// Whether the option includes a none option. If None is selected, value will return -1. + public ModdedPlayerOption(string title, bool allowNone = true) : base(title, allowNone ? -1 : 0, false) + { + Data = ScriptableObject.CreateInstance(); + var data = (PlayerSelectionGameSetting)Data; + + data.Title = StringName; + data.Type = global::OptionTypes.Player; + + Values = new List(); + AllowNone = allowNone; + Value = DefaultValue; + } + + /// + public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, + PlayerOption playerOpt, Transform container) + { + var playerOption = Object.Instantiate(playerOpt, container); + + var filteredList = GetFilteredPlayers(); + Values = filteredList.ToIl2CppList(); + + playerOption.SetUpFromData(Data, 20); + playerOption.OnValueChanged = (Il2CppSystem.Action)ValueChanged; + + playerOption.Title = StringName; + playerOption.TitleText.text = TranslationController.Instance.GetString(StringName); + playerOption.Values = Values; + playerOption.Value = Value; + playerOption.playerIndex = filteredList.FindIndex(p => p.PlayerId == Value); + + OptionBehaviour = playerOption; + ModdedOptionsManager.CreatedPlayerOptions.TryAdd(playerOption, this); + + playerOption.SetValueText(); + playerOption.AdjustButtonsActiveState(); + + return playerOption; + } + + /// + /// Returns a player ran through the filter. + /// + /// A list of filtered players. + public System.Collections.Generic.List GetFilteredPlayers() + { + return GameData.Instance.AllPlayers.ToArray() + .Where(x => PlayerFilter?.Invoke(x) ?? true) + .ToList(); + } + + /// + /// Returns a player from the value. If AllowNone is enabled and none is enabled this will return null. + /// + /// A player or null if chosen none. + public NetworkedPlayerInfo? GetPlayerValue() + { + return Value < 0 ? null : Values[Value]; + } + + /// + public override float GetFloatData() + { + return Value; + } + + /// + public override NetData GetNetData() + { + return new NetData(Id, BitConverter.GetBytes(Value)); + } + + /// + public override void HandleNetData(byte[] data) + { + SetValue(BitConverter.ToInt32(data)); + } + + /// + public override int GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour) + { + return optionBehaviour.GetInt(); + } + + /// + protected override void OnValueChanged(int newValue) + { + HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false); + if (!OptionBehaviour) + { + return; + } + + if (OptionBehaviour is PlayerOption opt) + { + opt.Value = newValue; + } + } +} diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs index 26ecc74..2cdd0e1 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs @@ -26,7 +26,7 @@ public class ModdedToggleOption : ModdedOption } /// - public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container) + public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container) { var toggleOption = Object.Instantiate(toggleOpt, Vector3.zero, Quaternion.identity, container); diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index 0d88920..155b50d 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -220,6 +220,7 @@ internal static class GameOptionsMenuPatch menu.checkboxOrigin, menu.numberOptionOrigin, menu.stringOptionOrigin, + menu.playerOptionOrigin, menu.settingsContainer)); foreach (var newOpt in options) diff --git a/MiraAPI/Patches/Options/OptionsPatches.cs b/MiraAPI/Patches/Options/OptionsPatches.cs index 70a8ff4..469a053 100644 --- a/MiraAPI/Patches/Options/OptionsPatches.cs +++ b/MiraAPI/Patches/Options/OptionsPatches.cs @@ -1,5 +1,9 @@ -using HarmonyLib; +using System.Collections.Generic; +using System.Linq; +using HarmonyLib; using Il2CppSystem; +using MiraAPI.GameOptions; +using MiraAPI.GameOptions.OptionTypes; using MiraAPI.Roles; using MiraAPI.Utilities; using UnityEngine; @@ -113,6 +117,7 @@ public static class OptionsPatches [HarmonyPrefix] [HarmonyPatch(typeof(NumberOption), nameof(NumberOption.AdjustButtonsActiveState))] [HarmonyPatch(typeof(StringOption), nameof(StringOption.AdjustButtonsActiveState))] + [HarmonyPatch(typeof(PlayerOption), nameof(PlayerOption.AdjustButtonsActiveState))] [HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.AdjustChanceButtonsActiveState))] [HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.AdjustCountButtonsActiveState))] public static bool AdjustButtonsPrefix(OptionBehaviour __instance) @@ -129,6 +134,11 @@ public static class OptionsPatches stringOption.MinusBtn.SetInteractable(true); stringOption.PlusBtn.SetInteractable(true); } + if (__instance.TryCast() is { } playerOption) + { + playerOption.MinusBtn.SetInteractable(true); + playerOption.PlusBtn.SetInteractable(true); + } return false; } @@ -178,6 +188,7 @@ public static class OptionsPatches [HarmonyPrefix] [HarmonyPatch(typeof(StringOption), nameof(StringOption.UpdateValue))] [HarmonyPatch(typeof(NumberOption), nameof(NumberOption.UpdateValue))] + [HarmonyPatch(typeof(PlayerOption), nameof(PlayerOption.UpdateValue))] public static bool UpdateValuePrefix(OptionBehaviour __instance) { return !__instance.IsCustom(); diff --git a/MiraAPI/Patches/Options/PlayerOptionPatches.cs b/MiraAPI/Patches/Options/PlayerOptionPatches.cs new file mode 100644 index 0000000..2b77bc1 --- /dev/null +++ b/MiraAPI/Patches/Options/PlayerOptionPatches.cs @@ -0,0 +1,128 @@ +using System; +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; + +[HarmonyPatch(typeof(PlayerOption))] +public static class PlayerOptionPatches +{ + // Updates the list of players according to the filter. + [HarmonyPrefix] + [HarmonyPatch(nameof(PlayerOption.FixedUpdate))] + public static bool PlayerOptionUpdatePatch(PlayerOption __instance) + { + if (!__instance.IsCustom()) + { + return true; + } + + if (!ModdedOptionsManager.CreatedPlayerOptions.TryGetValue(__instance, out var moddedPlrOpt)) return false; + + var filteredPlayers = moddedPlrOpt.GetFilteredPlayers(); + if (filteredPlayers.Count != __instance.Values.Count) + { + filteredPlayers.Sort((a, b) => a.PlayerId.CompareTo(b.PlayerId)); + + __instance.Values = filteredPlayers.ToIl2CppList(); + moddedPlrOpt.Values = __instance.Values; + __instance.playerIndex = filteredPlayers.FindIndex(p => p.PlayerId == __instance.Value); + __instance.SetValueText(); + } + + __instance.CheckValueChanged(); + + return false; + } + + // Disable going under 0 if you aren't allowed to choose None. + [HarmonyPrefix] + [HarmonyPatch(nameof(PlayerOption.UpdatePlayerIndex))] + public static bool UpdatePlayerIndexPatch(PlayerOption __instance, int index) + { + if (!__instance.IsCustom() || ModdedOptionsManager.CreatedPlayerOptions[__instance].AllowNone) + { + return true; + } + + var newIndex = Mathf.Clamp(index, 0, __instance.Values.Count - 1); + __instance.playerIndex = newIndex; + __instance.Value = __instance.Values[newIndex].PlayerId; + return false; + } + + // Replace "Round Robin" with "None" in the Host Options Menu. + [HarmonyPostfix] + [HarmonyPatch(nameof(PlayerOption.SetValueText))] + public static void SetValueTextPatch(PlayerOption __instance) + { + if (__instance.playerIndex < 0) + { + __instance.ValueText.text = TranslationController.Instance.GetString(StringNames.FilterNone); + } + } + + // Innersloth hardcode literally replaces the value with the HNS impostor option OnEnable, so I have to patch that out. Smh. + [HarmonyPrefix] + [HarmonyPatch(nameof(PlayerOption.OnEnable))] + public static bool OnEnablePatch(PlayerOption __instance) + { + return !__instance.IsCustom(); + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(PlayerOption.GetInt))] + public static bool GetIntPatch(PlayerOption __instance, ref int __result) + { + if (!__instance.IsCustom()) + { + return true; + } + + __result = __instance.Value; + return false; + } + + // Used to replace "Round Robin" with "None" for option notification & view pane. + [HarmonyPrefix] + [HarmonyPatch(typeof(PlayerSelectionGameSetting), nameof(PlayerSelectionGameSetting.GetValueString))] + public static bool GetStringPatch(PlayerSelectionGameSetting __instance, float value, ref string __result) + { + var option = ModdedOptionsManager.ModdedOptions.FirstOrDefault(x => x.Value.Data == __instance).Value; + if (option == null) return true; + + var networkedPlayerInfo = GameData.Instance.AllPlayers.ToArray().FirstOrDefault(p => p.PlayerId == (int)value); + if (networkedPlayerInfo != null) + { + __result = networkedPlayerInfo.PlayerName; + return false; + } + + __result = TranslationController.Instance.GetString(StringNames.FilterNone); + return false; + } + + // "UpdateValue" was inlined and I needed to replace it, so I just rewrote the method where it's called from. + [HarmonyPrefix] + [HarmonyPatch(nameof(PlayerOption.CheckValueChanged))] + public static bool UpdateValuePatch(PlayerOption __instance) + { + if (!__instance.IsCustom()) + { + return true; + } + + if (__instance.oldValue == __instance.Value) return false; + + __instance.oldValue = __instance.Value; + __instance.SetValueText(); + + return false; + } +} diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 6ce680d..80f96c9 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; +using MiraAPI.GameOptions; using MiraAPI.Networking; using MiraAPI.Roles; using MiraAPI.Utilities; @@ -31,6 +32,7 @@ public static class RoleSettingMenuPatches private static float ScrollerNum { get; set; } = 0.522f; private static RoleBehaviour? CurrentRole { get; set; } + private static List? CurrentRoleOptions { get; set; } [HarmonyPostfix] [HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))] @@ -48,6 +50,7 @@ public static class RoleSettingMenuPatches Headers.Clear(); RoleOptionSettings.Clear(); CurrentRole = null; + CurrentRoleOptions = null; __instance.roleChances = new Il2CppSystem.Collections.Generic.List(); __instance.advancedSettingChildren = new Il2CppSystem.Collections.Generic.List(); @@ -268,17 +271,12 @@ public static class RoleSettingMenuPatches public static bool UpdatePatch(RolesSettingsMenu __instance) { if (GameSettingMenuPatches.SelectedModIdx == 0) return true; - if (CurrentRole == null) return false; - - var filteredOptions = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x => x.GroupVisible() && x.OptionableType == CurrentRole.GetType()) - .SelectMany(x => x.Options) - .ToList() ?? []; + if (CurrentRole == null || CurrentRoleOptions == null) return false; var hasImage = CurrentRole.RoleScreenshot != null; var num = hasImage ? -0.872f : -1; - foreach (var opt in filteredOptions) + foreach (var opt in CurrentRoleOptions) { if (opt.OptionBehaviour == null) continue; @@ -306,6 +304,8 @@ public static class RoleSettingMenuPatches return; } + CurrentRole = null; + CurrentRoleOptions = null; __instance.scrollBar.SetScrollBounds(__instance); } @@ -361,19 +361,21 @@ public static class RoleSettingMenuPatches CurrentRole = role; __instance.advancedSettingChildren.Clear(); - // TODO: create sub groups under the role settings. var filteredOptions = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups .Where(x => x.GroupVisible() && x.OptionableType == role.GetType()) .SelectMany(x => x.Options) .ToList() ?? []; + CurrentRoleOptions = filteredOptions; + foreach (var option in filteredOptions) { var newOpt = option.CreateOption( __instance.checkboxOrigin, __instance.numberOptionOrigin, __instance.stringOptionOrigin, + GameSettingMenu.Instance.GameSettingsTab.playerOptionOrigin, __instance.AdvancedRolesSettings.transform); newOpt.SetClickMask(__instance.ButtonClickMask); diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 64e02f9..57887d8 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -154,6 +154,24 @@ public static class Extensions return TutorialManager.InstanceExists || AmongUsClient.Instance.HostId == playerControl.OwnerId; } + /// + /// Used to convert a System.Collections.Generic.List to Il2cppSystem. + /// + /// The list. + /// The type in the list. + /// The converted list. + public static Il2CppSystem.Collections.Generic.List ToIl2CppList(this List systemList) + { + var il2cppList = new Il2CppSystem.Collections.Generic.List(); + + foreach (var item in systemList) + { + il2cppList.Add(item); + } + + return il2cppList; + } + /// /// Determines if a float is an integer. /// From eba2a037d3319dbc5d97557276e06fe644755955 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Sat, 28 Jun 2025 19:21:47 -0400 Subject: [PATCH 10/13] Bug fixes and chat pasting --- MiraAPI.Example/ExampleColors.cs | 5 ++- MiraAPI/Colors/CustomColor.cs | 33 +++++++++++++++---- MiraAPI/Patches/ChatControllerPatch.cs | 34 ++++++++++++++++++++ MiraAPI/Patches/Menu/HatsTabPatches.cs | 2 +- MiraAPI/Patches/Menu/NameplatesTabPatches.cs | 2 +- MiraAPI/Patches/Menu/SkinsTabPatches.cs | 2 +- MiraAPI/Patches/Menu/VisorsTabPatches.cs | 2 +- 7 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 MiraAPI/Patches/ChatControllerPatch.cs diff --git a/MiraAPI.Example/ExampleColors.cs b/MiraAPI.Example/ExampleColors.cs index 6833d3c..d61eb49 100644 --- a/MiraAPI.Example/ExampleColors.cs +++ b/MiraAPI.Example/ExampleColors.cs @@ -6,7 +6,10 @@ namespace MiraAPI.Example; [RegisterCustomColors] public static class ExampleColors { - public static CustomColor Cerulean { get; } = new("Cerulean", new Color(0.0f, 0.48f, 0.65f)); + public static CustomColor Cerulean { get; } = new("Cerulean", new Color(0.0f, 0.48f, 0.65f)) + { + ColorBrightness = CustomColorBrightness.Lighter, + }; public static CustomColor Rose { get; } = new("Rose", new Color(0.98f, 0.26f, 0.62f)); diff --git a/MiraAPI/Colors/CustomColor.cs b/MiraAPI/Colors/CustomColor.cs index 7bbe87a..89663d1 100644 --- a/MiraAPI/Colors/CustomColor.cs +++ b/MiraAPI/Colors/CustomColor.cs @@ -13,19 +13,24 @@ namespace MiraAPI.Colors; public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColor) { /// - /// Gets the main color. + /// Gets or sets the main color. /// - public Color32 MainColor { get; } = mainColor; + public Color32 MainColor { get; set; } = mainColor; /// - /// Gets the shadow color. + /// Gets or sets the shadow color. /// - public Color32 ShadowColor { get; } = shadowColor; + public Color32 ShadowColor { get; set; } = shadowColor; /// - /// Gets the name of the color. + /// Gets or sets the name of the color. /// - public StringNames Name { get; } = name; + public StringNames Name { get; set; } = name; + + /// + /// Gets or sets whether the color is lighter or darker. Can be used to help with colorblind stuff. + /// + public CustomColorBrightness ColorBrightness { get; set; } = CustomColorBrightness.Darker; /// public CustomColor(StringNames name, Color32 mainColor) : this(name, mainColor, mainColor.GetShadowColor(60)) @@ -42,3 +47,19 @@ public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 sha { } } + +/// +/// Can be used to help with colorblind stuff. +/// +public enum CustomColorBrightness +{ + /// + /// A lighter color. + /// + Lighter, + + /// + /// A darker color. + /// + Darker, +} diff --git a/MiraAPI/Patches/ChatControllerPatch.cs b/MiraAPI/Patches/ChatControllerPatch.cs new file mode 100644 index 0000000..0108ece --- /dev/null +++ b/MiraAPI/Patches/ChatControllerPatch.cs @@ -0,0 +1,34 @@ +using HarmonyLib; +using UnityEngine; + +namespace MiraAPI.Patches; + +/// +/// Allows players to paste text into chat. +/// Source: https://github.com/CallOfCreator/NewMod/blob/main/NewMod/Patches/ClipboardPatch.cs +/// +[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))] +public static class ChatControllerPatch +{ + public static void Prefix(ChatController __instance) + { + if (!HudManager.Instance.Chat.IsOpenOrOpening) return; + + var ctrlPressed = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); + + if (!ctrlPressed || !Input.GetKeyDown(KeyCode.V)) return; + + var clipboard = GUIUtility.systemCopyBuffer; + + if (string.IsNullOrWhiteSpace(clipboard)) return; + clipboard = clipboard.Replace("<", string.Empty) + .Replace(">", string.Empty) + .Replace("\r", string.Empty); + + if (!Input.GetKey(KeyCode.LeftShift) && !Input.GetKey(KeyCode.RightShift)) + clipboard = clipboard.Replace("\n", string.Empty); + + __instance.freeChatField.textArea.SetText(__instance.freeChatField.textArea.text + clipboard); + } +} + diff --git a/MiraAPI/Patches/Menu/HatsTabPatches.cs b/MiraAPI/Patches/Menu/HatsTabPatches.cs index daf87b1..f985027 100644 --- a/MiraAPI/Patches/Menu/HatsTabPatches.cs +++ b/MiraAPI/Patches/Menu/HatsTabPatches.cs @@ -114,7 +114,7 @@ public static class HatsTabPatches yield return null; } - __instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f; + __instance.SetScrollerBounds(); yield return new WaitForSeconds(0.01f); } __instance.currentHatIsEquipped = true; diff --git a/MiraAPI/Patches/Menu/NameplatesTabPatches.cs b/MiraAPI/Patches/Menu/NameplatesTabPatches.cs index d66c8f3..bb5482a 100644 --- a/MiraAPI/Patches/Menu/NameplatesTabPatches.cs +++ b/MiraAPI/Patches/Menu/NameplatesTabPatches.cs @@ -91,7 +91,7 @@ public static class NameplatesTabPatches hatIndex += 1; } - __instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f; + __instance.SetScrollerBounds(); __instance.currentNameplateIsEquipped = true; } diff --git a/MiraAPI/Patches/Menu/SkinsTabPatches.cs b/MiraAPI/Patches/Menu/SkinsTabPatches.cs index b102fb1..2e9ef2e 100644 --- a/MiraAPI/Patches/Menu/SkinsTabPatches.cs +++ b/MiraAPI/Patches/Menu/SkinsTabPatches.cs @@ -94,7 +94,7 @@ public static class SkinsTabPatches hatIndex += 1; } - __instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f; + __instance.SetScrollerBounds(); __instance.currentSkinIsEquipped = true; } diff --git a/MiraAPI/Patches/Menu/VisorsTabPatches.cs b/MiraAPI/Patches/Menu/VisorsTabPatches.cs index 81845c6..d88cdab 100644 --- a/MiraAPI/Patches/Menu/VisorsTabPatches.cs +++ b/MiraAPI/Patches/Menu/VisorsTabPatches.cs @@ -90,7 +90,7 @@ public static class VisorsTabPatches hatIndex += 1; } - __instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f; + __instance.SetScrollerBounds(); __instance.currentVisorIsEquipped = true; } From de77a9fa92e6e1a590da73741becde6ed054e4a8 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Sat, 28 Jun 2025 20:55:22 -0400 Subject: [PATCH 11/13] Add page counter in options screen --- MiraAPI/Patches/Options/GameSettingMenuPatches.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs index 7313206..fd9c78b 100644 --- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs @@ -288,7 +288,7 @@ internal static class GameSettingMenuPatches { if (_text is not null && SelectedModIdx == 0) { - _text.text = "Default"; + _text.text = $"(Page 0/{MiraPluginManager.Instance.RegisteredPlugins.Length})\nDefault"; _text.fontSizeMax = 3.2f; SelectedMod = null; } @@ -298,7 +298,7 @@ internal static class GameSettingMenuPatches SelectedMod = MiraPluginManager.Instance.RegisteredPlugins[SelectedModIdx - 1]; var name = SelectedMod.MiraPlugin.OptionsTitleText; - _text.text = name[..Math.Min(name.Length, 25)]; + _text.text = $"(Page {SelectedModIdx}/{MiraPluginManager.Instance.RegisteredPlugins.Length})\n" + name[..Math.Min(name.Length, 25)]; } bool replaceWithModifiers = true; From 55555604f3cd55540a77c625341b088884194e08 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sat, 28 Jun 2025 22:57:04 -0400 Subject: [PATCH 12/13] error proof buttons --- MiraAPI/Patches/Hud/ButtonResetPatches.cs | 36 ++++++++--------------- MiraAPI/Patches/HudManagerPatches.cs | 19 ++++++++++-- MiraAPI/Patches/PlayerControlPatches.cs | 16 +++++++--- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/MiraAPI/Patches/Hud/ButtonResetPatches.cs b/MiraAPI/Patches/Hud/ButtonResetPatches.cs index 0859bea..29b190d 100644 --- a/MiraAPI/Patches/Hud/ButtonResetPatches.cs +++ b/MiraAPI/Patches/Hud/ButtonResetPatches.cs @@ -1,37 +1,25 @@ using HarmonyLib; using MiraAPI.Hud; +using Reactor.Utilities; namespace MiraAPI.Patches.Hud; -/// -/// Reset button patches. -/// -[HarmonyPatch] +[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] +[HarmonyPatch(typeof(ExileController), nameof(ExileController.ReEnableGameplay))] public static class ButtonResetPatches { - /// - /// Resets the cooldown and effect of all custom buttons when the meeting starts. - /// - [HarmonyPostfix] - [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] - public static void MeetingHudStartPostfix() + public static void Postfix() { foreach (var customActionButton in CustomButtonManager.CustomButtons) { - customActionButton.ResetCooldownAndOrEffect(); - } - } - - /// - /// Resets the cooldown and effect of all custom buttons after the exile screen is closed. - /// - [HarmonyPostfix] - [HarmonyPatch(typeof(ExileController), nameof(ExileController.ReEnableGameplay))] - public static void ExileControllerReEnableGameplayPostfix() - { - foreach (var customActionButton in CustomButtonManager.CustomButtons) - { - customActionButton.ResetCooldownAndOrEffect(); + try + { + customActionButton.ResetCooldownAndOrEffect(); + } + catch (System.Exception ex) + { + Logger.Error($"Error resetting cooldown and effect for button {customActionButton.GetType().Name}: {ex}"); + } } } } diff --git a/MiraAPI/Patches/HudManagerPatches.cs b/MiraAPI/Patches/HudManagerPatches.cs index 27a8026..14c2444 100644 --- a/MiraAPI/Patches/HudManagerPatches.cs +++ b/MiraAPI/Patches/HudManagerPatches.cs @@ -1,5 +1,6 @@ using HarmonyLib; using MiraAPI.Hud; +using Reactor.Utilities; using Reactor.Utilities.Extensions; using UnityEngine; using Object = UnityEngine.Object; @@ -77,7 +78,14 @@ public static class HudManagerPatches continue; } - button.CreateButton(location); + try + { + button.CreateButton(location); + } + catch (System.Exception e) + { + Logger.Error($"Failed to create custom button {button.GetType().Name}: {e}"); + } } gridArrange.Start(); @@ -105,7 +113,14 @@ public static class HudManagerPatches foreach (var button in CustomButtonManager.CustomButtons) { - button.SetActive(isActive, role); + try + { + button.SetActive(isActive, role); + } + catch (System.Exception e) + { + Logger.Error($"Failed to set custom button {button.GetType().Name} active: {e}"); + } } } } diff --git a/MiraAPI/Patches/PlayerControlPatches.cs b/MiraAPI/Patches/PlayerControlPatches.cs index c93aa6d..a0b88cd 100644 --- a/MiraAPI/Patches/PlayerControlPatches.cs +++ b/MiraAPI/Patches/PlayerControlPatches.cs @@ -7,6 +7,7 @@ using MiraAPI.Hud; using MiraAPI.Modifiers; using MiraAPI.Utilities; using MiraAPI.Voting; +using Reactor.Utilities; using Reactor.Utilities.Extensions; namespace MiraAPI.Patches; @@ -94,12 +95,19 @@ internal static class PlayerControlPatches continue; } - if (!button.Enabled(__instance.Data?.Role)) + try { - continue; - } + if (!button.Enabled(__instance.Data?.Role)) + { + continue; + } - button.FixedUpdateHandler(__instance); + button.FixedUpdateHandler(__instance); + } + catch (System.Exception e) + { + Logger.Error($"Failed to update custom button {button.GetType().Name}: {e}"); + } } } } From bf652b68c3a90e43cd84dbc217ab8279bc9c7344 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sat, 28 Jun 2025 23:04:30 -0400 Subject: [PATCH 13/13] fix warnings --- MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs | 8 ++++++-- MiraAPI/Patches/ChatControllerPatch.cs | 1 - MiraAPI/Presets/SavePresetPopup.cs | 3 --- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs index 8431d1f..6069a2c 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs @@ -47,8 +47,12 @@ public class ModdedPlayerOption : ModdedOption } /// - public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, - PlayerOption playerOpt, Transform container) + public override OptionBehaviour CreateOption( + ToggleOption toggleOpt, + NumberOption numberOpt, + StringOption stringOpt, + PlayerOption playerOpt, + Transform container) { var playerOption = Object.Instantiate(playerOpt, container); diff --git a/MiraAPI/Patches/ChatControllerPatch.cs b/MiraAPI/Patches/ChatControllerPatch.cs index 0108ece..a30307c 100644 --- a/MiraAPI/Patches/ChatControllerPatch.cs +++ b/MiraAPI/Patches/ChatControllerPatch.cs @@ -31,4 +31,3 @@ public static class ChatControllerPatch __instance.freeChatField.textArea.SetText(__instance.freeChatField.textArea.text + clipboard); } } - diff --git a/MiraAPI/Presets/SavePresetPopup.cs b/MiraAPI/Presets/SavePresetPopup.cs index 9449857..87959e7 100644 --- a/MiraAPI/Presets/SavePresetPopup.cs +++ b/MiraAPI/Presets/SavePresetPopup.cs @@ -96,6 +96,3 @@ public class SavePresetPopup(nint cppPtr) : Minigame(cppPtr) popup.Begin(null); } } - - -