From 44415019cc35401f84d354750486be429af1bd8b Mon Sep 17 00:00:00 2001 From: amber Date: Tue, 10 Mar 2026 19:49:44 -0400 Subject: [PATCH] Full gamemode option in vanilla `Game Options` menu. --- MiraAPI.Example/GameModes/ExampleMode.cs | 2 + MiraAPI/GameModes/CustomGameModeManager.cs | 16 ++-- MiraAPI/GameModes/GameModeOption.cs | 89 ++++++++++++++++--- .../Patches/Options/GameOptionsMenuPatch.cs | 2 - MiraAPI/Patches/Options/OptionsPatches.cs | 21 +---- MiraAPI/PluginLoading/MiraPluginManager.cs | 4 +- MiraAPI/Utilities/Extensions.cs | 5 +- 7 files changed, 93 insertions(+), 46 deletions(-) diff --git a/MiraAPI.Example/GameModes/ExampleMode.cs b/MiraAPI.Example/GameModes/ExampleMode.cs index ff8292d..4868516 100644 --- a/MiraAPI.Example/GameModes/ExampleMode.cs +++ b/MiraAPI.Example/GameModes/ExampleMode.cs @@ -1,4 +1,5 @@ using MiraAPI.GameModes; +using UnityEngine; namespace MiraAPI.Example.GameModes; @@ -6,4 +7,5 @@ public class ExampleMode : AbstractGameMode { public override string Name => "Example Mode"; public override string Description => "An example gamemode."; + public override Color Color => Color.red; } diff --git a/MiraAPI/GameModes/CustomGameModeManager.cs b/MiraAPI/GameModes/CustomGameModeManager.cs index 7aef0fc..3cf4562 100644 --- a/MiraAPI/GameModes/CustomGameModeManager.cs +++ b/MiraAPI/GameModes/CustomGameModeManager.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using MiraAPI.GameOptions; using MiraAPI.PluginLoading; using Reactor.Utilities; using Reactor.Utilities.Extensions; @@ -45,7 +44,7 @@ public static class CustomGameModeManager IdToModeMap.Add(GetNextId(), mode); pluginInfo.GameModes.Add(LastId, mode); - + GameModeOption.AddOption($"{mode.Name}"); mode.ID = LastId; } @@ -53,7 +52,7 @@ public static class CustomGameModeManager /// Checks to see if the default game mode is on. /// /// True if the default mode is one. - public static bool IsDefault() => (uint)OptionGroupSingleton.Instance.CurrentMode.Value == 0; + public static bool IsDefault() => (uint) GameModeOption.Value == 0; /// /// Gets the current gamemode. @@ -64,6 +63,8 @@ public static class CustomGameModeManager { var defaultMode = new DefaultMode(); IdToModeMap.Add(0, defaultMode); + // no need to add to game mode option as it already contains it + // because we cannot have the option be created with no values defaultMode.ID = 0; } @@ -76,19 +77,14 @@ public static class CustomGameModeManager else if (id != 0) { ActiveMode = IdToModeMap[0]; - OptionGroupSingleton.Instance.CurrentMode.SetValue(0); + GameModeOption.Value = 0; Logger.Warning($"Unable to find game mode of id {id}!"); } - - if (OptionGroupSingleton.Instance.CurrentMode.OptionBehaviour != null && ActiveMode != null) - { - ((NumberOption)OptionGroupSingleton.Instance.CurrentMode.OptionBehaviour).TitleText.SetText($"Game Mode: {ActiveMode}"); - } } internal static void GetAndSetGameMode() { - var id = (uint)OptionGroupSingleton.Instance.CurrentMode.Value; + var id = (uint)GameModeOption.Value; if (IdToModeMap.TryGetValue(id, out var mode)) { diff --git a/MiraAPI/GameModes/GameModeOption.cs b/MiraAPI/GameModes/GameModeOption.cs index 83de6ff..cc8ee49 100644 --- a/MiraAPI/GameModes/GameModeOption.cs +++ b/MiraAPI/GameModes/GameModeOption.cs @@ -1,22 +1,85 @@ -using MiraAPI.GameOptions; -using MiraAPI.GameOptions.OptionTypes; +using System.Collections.Generic; +using HarmonyLib; +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using Il2CppSystem; +using Reactor.Localization.Utilities; +using UnityEngine; +using UnityEngine.ProBuilder; +using Object = UnityEngine.Object; namespace MiraAPI.GameModes; /// /// The game mode option. /// -// TODO: Replace this with a proper gamemode selection. -public class GameModeOption : AbstractOptionGroup +[HarmonyPatch] +public static class GameModeOption { - /// - public override string GroupName => "Game Mode"; - - /// - /// Gets or sets the game mode option. - /// - public ModdedNumberOption CurrentMode { get; set; } = new("Game Mode: {0}", 0, 0, CustomGameModeManager.LastId, 1, Utilities.MiraNumberSuffixes.None) + public static int Value { - ChangedEvent = x => CustomGameModeManager.SetGameMode((uint)x), - }; + get => + OptionBehaviour != null + ? OptionBehaviour.GetInt() + : 0; + set + { + if (OptionBehaviour == null) + return; + OptionBehaviour.Value = value; + OptionBehaviour.UpdateValue(); + } + } + + internal static StringOption OptionBehaviour { get; private set; } = null!; + + private static readonly List Queue = []; + // loading takes place before option creation + internal static void AddOption(string opt) + { + if (OptionBehaviour == null) + { + Queue.Add(opt); + return; + } + OptionBehaviour.Values = (Il2CppStructArray)OptionBehaviour.Values.Add(CustomStringName.CreateAndRegister(opt)); + } + [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.CreateSettings))] + [HarmonyPostfix] + public static void CreateSettingsPatch(GameOptionsMenu __instance) + { + float num = -9.5f; + CategoryHeaderMasked categoryHeaderMasked = Object.Instantiate(__instance.categoryHeaderOrigin, Vector3.zero, Quaternion.identity, __instance.settingsContainer); + categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister("Custom"), 20); + categoryHeaderMasked.transform.localScale = Vector3.one * 0.63f; + categoryHeaderMasked.transform.localPosition = new Vector3(-0.903f, num, -2f); + OptionBehaviour = Object.Instantiate( + __instance.stringOptionOrigin, + Vector3.zero, + Quaternion.identity, + __instance.settingsContainer); + OptionBehaviour.transform.localPosition = new Vector3(0.952f, num - 0.63f, -2f); + OptionBehaviour.SetClickMask(__instance.ButtonClickMask); + StringGameSetting setting = ScriptableObject.CreateInstance(); + setting.Type = OptionTypes.MultipleChoice; + setting.Title = CustomStringName.CreateAndRegister("Gamemode"); + setting.Index = 0; + setting.Values = new Il2CppStructArray([CustomStringName.CreateAndRegister("Default")]); + OptionBehaviour.SetUpFromData(setting, 20); + OptionBehaviour.OnValueChanged = (Action) ((OptionBehaviour opt) => + { + CustomGameModeManager.SetGameMode((uint)opt.GetInt()); + }); + __instance.Children.Add(OptionBehaviour); + __instance.scrollBar.SetYBoundsMax(9f); + foreach (var str in Queue) + AddOption(str); + Queue.Clear(); + } + + [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.ValueChanged))] + [HarmonyPrefix] + public static bool ValueChanged(GameOptionsMenu __instance, OptionBehaviour option) + { + return !OptionBehaviour.Equals(option); + } } diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index e0afd34..c8ee3ce 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -1,8 +1,6 @@ using System; using System.Linq; -using AmongUs.GameOptions; using HarmonyLib; -using Il2CppInterop.Runtime.Attributes; using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.Presets; diff --git a/MiraAPI/Patches/Options/OptionsPatches.cs b/MiraAPI/Patches/Options/OptionsPatches.cs index 7398b7c..fbcbe38 100644 --- a/MiraAPI/Patches/Options/OptionsPatches.cs +++ b/MiraAPI/Patches/Options/OptionsPatches.cs @@ -1,15 +1,10 @@ -using System.Collections.Generic; -using System.Linq; -using HarmonyLib; +using HarmonyLib; using Il2CppSystem; using MiraAPI.GameModes; using MiraAPI.GameOptions; -using MiraAPI.Utilities; -using Reactor.Utilities.Extensions; -using MiraAPI.GameOptions; -using MiraAPI.GameOptions.OptionTypes; using MiraAPI.Roles; using MiraAPI.Utilities; +using Reactor.Utilities.Extensions; using UnityEngine; using Object = Il2CppSystem.Object; @@ -206,17 +201,7 @@ public static class OptionsPatches { return true; } - - if (__instance == OptionGroupSingleton.Instance.CurrentMode.OptionBehaviour) - { - __instance.TitleText.text = - TranslationController.Instance.GetString(__instance.Title, $"{CustomGameModeManager.ActiveMode}"); - } - else - { - __instance.TitleText.text = - TranslationController.Instance.GetString(__instance.Title); - } + __instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title); return false; } diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index 336fef9..2a52201 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -44,7 +44,6 @@ public sealed class MiraPluginManager internal void Initialize() { Instance = this; - ModdedOptionsManager.RegisterGroup(typeof(GameModeOption)); // TODO: dont do this CustomGameModeManager.RegisterDefaultMode(); CustomGameModeManager.GetAndSetGameMode(); IL2CPPChainloader.Instance.PluginLoad += RegisterPlugin; @@ -67,6 +66,9 @@ public sealed class MiraPluginManager continue; } dict.Add(value.Name, key); + if (((AmongUs.GameOptions.GameModes) key) == AmongUs.GameOptions.GameModes.Normal) + return; + // 'Default' was still being registered twice and idk why GameModesHelpers.ModeToName.Add((AmongUs.GameOptions.GameModes)key, CustomStringName.CreateAndRegister(value.Name)); } EnumInjector.InjectEnumValues(dict); diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 70f8c15..d895664 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -5,11 +5,11 @@ using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using HarmonyLib; +using MiraAPI.GameModes; using MiraAPI.GameOptions; using MiraAPI.Networking; using MiraAPI.Roles; using MiraAPI.Voting; -using Reactor.Utilities; using Rewired; using Rewired.Data; using TMPro; @@ -429,7 +429,8 @@ public static class Extensions public static bool IsCustom(this OptionBehaviour optionBehaviour) { return ModdedOptionsManager.ModdedOptions.Values.Any( - opt => opt.OptionBehaviour && opt.OptionBehaviour == optionBehaviour); + opt => opt.OptionBehaviour && opt.OptionBehaviour == optionBehaviour) + || optionBehaviour.Equals(GameModeOption.OptionBehaviour); } ///