From 55391e7d4e53606c80ed82d13ec92102330db2ab Mon Sep 17 00:00:00 2001 From: D1GQ Date: Wed, 4 Mar 2026 19:05:24 -0600 Subject: [PATCH] Add pages to client options --- src/Modules/ClientOptionItem.cs | 203 +++++++++--------- .../Client/OptionsMenuBehaviourPatch.cs | 26 +-- 2 files changed, 113 insertions(+), 116 deletions(-) diff --git a/src/Modules/ClientOptionItem.cs b/src/Modules/ClientOptionItem.cs index 900dc29..b54d46c 100644 --- a/src/Modules/ClientOptionItem.cs +++ b/src/Modules/ClientOptionItem.cs @@ -2,7 +2,6 @@ using BetterAmongUs.Helpers; using BetterAmongUs.Patches.Client; using UnityEngine; -using Object = UnityEngine.Object; namespace BetterAmongUs.Modules; @@ -24,24 +23,23 @@ internal sealed class ClientOptionItem /// /// Gets the list of all created client option items. /// - internal static readonly List ClientOptions = []; + internal static readonly Dictionary> ClientOptions = []; /// /// Creates a toggle option with configuration binding. /// - /// The display name of the option. - /// The configuration entry to bind to. - /// The options menu behavior instance. - /// Optional action to execute when the toggle is changed. - /// Optional function to check if the toggle can be changed. - /// A new ClientOptionItem instance. - public static ClientOptionItem CreateToggle(string name, ConfigEntry config, OptionsMenuBehaviour optionsMenuBehaviour, Action? onToggle = null, Func? toggleCheck = null) + public static ClientOptionItem CreateToggle(string name, ConfigEntry config, int page, OptionsMenuBehaviour optionsMenuBehaviour, Action? onToggle = null, Func? toggleCheck = null) { - var toggleButton = CreateToggleButton(name, config.Value, optionsMenuBehaviour); + var toggleButton = CreateToggleButton(name, optionsMenuBehaviour, GetOrCreatePage(page, optionsMenuBehaviour).transform); + toggleButton.transform.localPosition = CalculateButtonPosition(page); var item = new ClientOptionItem(name, config, toggleButton); item.SetupToggleButton(onToggle, toggleCheck); - ClientOptions.Add(item); + if (!ClientOptions.TryGetValue(page, out var options)) + { + options = ClientOptions[page] = []; + } + options.Add(item); return item; } @@ -49,38 +47,18 @@ internal sealed class ClientOptionItem /// /// Creates a button option without toggle state. /// - /// The display name of the button. - /// The options menu behavior instance. - /// The action to execute when the button is clicked. - /// Optional function to check if the button can be clicked. - /// A new ClientOptionItem instance. - public static ClientOptionItem CreateButton(string name, OptionsMenuBehaviour optionsMenuBehaviour, Action onClick, Func? clickCheck = null) + public static ClientOptionItem CreateButton(string name, int page, OptionsMenuBehaviour optionsMenuBehaviour, Action onClick, Func? clickCheck = null) { - var toggleButton = CreateToggleButton(name, false, optionsMenuBehaviour); + var toggleButton = CreateToggleButton(name, optionsMenuBehaviour, GetOrCreatePage(page, optionsMenuBehaviour).transform); + toggleButton.transform.localPosition = CalculateButtonPosition(page); var item = new ClientOptionItem(name, null, toggleButton); item.SetupButton(onClick, clickCheck); - ClientOptions.Add(item); - - return item; - } - - /// - /// Creates a toggle option without configuration binding (manual state management). - /// - /// The display name of the option. - /// The initial state of the toggle. - /// The options menu behavior instance. - /// The action to execute when the toggle is changed, receiving the new state. - /// Optional function to check if the toggle can be changed. - /// A new ClientOptionItem instance. - public static ClientOptionItem CreateManualToggle(string name, bool initialState, OptionsMenuBehaviour optionsMenuBehaviour, Action onToggle, Func? toggleCheck = null) - { - var toggleButton = CreateToggleButton(name, initialState, optionsMenuBehaviour); - var item = new ClientOptionItem(name, null, toggleButton); - - item.SetupManualToggle(initialState, onToggle, toggleCheck); - ClientOptions.Add(item); + if (!ClientOptions.TryGetValue(page, out var options)) + { + options = ClientOptions[page] = []; + } + options.Add(item); return item; } @@ -88,10 +66,7 @@ internal sealed class ClientOptionItem /// /// Initializes a new instance of the ClientOptionItem class. /// - /// The name of the option. - /// The configuration entry, or null for non-config options. - /// The toggle button behavior instance. - private ClientOptionItem(string name, ConfigEntry? config, ToggleButtonBehaviour toggleButton) + internal ClientOptionItem(string name, ConfigEntry? config, ToggleButtonBehaviour toggleButton) { Config = config; ToggleButton = toggleButton; @@ -101,21 +76,10 @@ internal sealed class ClientOptionItem /// /// Creates a toggle button GameObject for the options menu. /// - /// The name of the toggle button. - /// The initial state of the toggle. - /// The options menu behavior instance. - /// A new ToggleButtonBehaviour instance. - private static ToggleButtonBehaviour CreateToggleButton(string name, bool initialState, OptionsMenuBehaviour optionsMenuBehaviour) + private static ToggleButtonBehaviour CreateToggleButton(string name, OptionsMenuBehaviour optionsMenuBehaviour, Transform parent) { - if (OptionsMenuBehaviourPatch.BetterOptionsTab?.Content == null) - { - throw new InvalidOperationException("BetterOptionsTab is not initialized"); - } - var mouseMoveToggle = optionsMenuBehaviour.DisableMouseMovement; - var toggleButton = Object.Instantiate(mouseMoveToggle, OptionsMenuBehaviourPatch.BetterOptionsTab.Content.transform); - - toggleButton.transform.localPosition = CalculateButtonPosition(); + var toggleButton = UnityEngine.Object.Instantiate(mouseMoveToggle, parent); toggleButton.name = name; toggleButton.Text.text = name; @@ -125,12 +89,15 @@ internal sealed class ClientOptionItem /// /// Calculates the position for a new button based on the current number of options. /// - /// A Vector3 representing the button position. - private static Vector3 CalculateButtonPosition() + private static Vector3 CalculateButtonPosition(int page) { + if (!ClientOptions.TryGetValue(page, out var options)) + { + options = ClientOptions[page] = []; + } return new Vector3( - ClientOptions.Count % 2 == 0 ? -1.3f : 1.3f, - 1.8f - 0.5f * (ClientOptions.Count / 2), + options.Count % 2 == 0 ? -1.3f : 1.3f, + 1.8f - 0.5f * (options.Count / 2), -6f ); } @@ -138,9 +105,7 @@ internal sealed class ClientOptionItem /// /// Sets up a configuration-bound toggle button with click handler. /// - /// Optional action to execute when toggled. - /// Optional function to check if the toggle can be changed. - private void SetupToggleButton(Action? onToggle, Func? toggleCheck) + internal void SetupToggleButton(Action? onToggle, Func? toggleCheck) { var passiveButton = ToggleButton.GetComponent(); passiveButton.OnClick = new(); @@ -163,9 +128,7 @@ internal sealed class ClientOptionItem /// /// Sets up a button (non-toggle) with click handler. /// - /// The action to execute when clicked. - /// Optional function to check if the button can be clicked. - private void SetupButton(Action onClick, Func? clickCheck) + internal void SetupButton(Action onClick, Func? clickCheck) { var passiveButton = ToggleButton.GetComponent(); passiveButton.OnClick = new(); @@ -182,31 +145,6 @@ internal sealed class ClientOptionItem }); } - /// - /// Sets up a manually managed toggle button with state tracking. - /// - /// The initial state of the toggle. - /// The action to execute when toggled, receiving the new state. - /// Optional function to check if the toggle can be changed. - private void SetupManualToggle(bool initialState, Action onToggle, Func? toggleCheck) - { - var passiveButton = ToggleButton.GetComponent(); - passiveButton.OnClick = new(); - - bool currentState = initialState; - - passiveButton.OnClick.AddListener(() => - { - if (toggleCheck?.Invoke() == false) return; - - currentState = !currentState; - UpdateManualToggle(currentState); - onToggle?.Invoke(currentState); - }); - - UpdateManualToggle(initialState); - } - /// /// Updates the visual state of a config-bound toggle button. /// @@ -217,21 +155,9 @@ internal sealed class ClientOptionItem UpdateToggleVisuals(Config.Value); } - /// - /// Updates the visual state of a manually managed toggle button. - /// - /// The current enabled state of the toggle. - internal void UpdateManualToggle(bool isEnabled) - { - if (ToggleButton == null) return; - - UpdateToggleVisuals(isEnabled); - } - /// /// Updates the visual appearance of a toggle button based on its state. /// - /// Whether the toggle is enabled. private void UpdateToggleVisuals(bool isEnabled) { var color = isEnabled ? @@ -247,4 +173,75 @@ internal sealed class ClientOptionItem ToggleButton.Text.color = textColor; ToggleButton.Text.text = $"{ToggleButton.name}: {(isEnabled ? "On" : "Off")}"; } + + /// + /// Retrieves an existing page GameObject or creates a new one with navigation buttons. + /// + private static GameObject? GetOrCreatePage(int page, OptionsMenuBehaviour optionsMenuBehaviour, bool doNotCreate = false) + { + string name = "Page " + page; + var currentPage = OptionsMenuBehaviourPatch.BetterOptionsTab.Content.transform.Find(name)?.gameObject; + if (currentPage == null) + { + if (doNotCreate) + { + return null; + } + + currentPage = new GameObject(name); + currentPage.SetActive(page == 1); + currentPage.transform.SetParent(OptionsMenuBehaviourPatch.BetterOptionsTab.Content.transform); + currentPage.transform.localPosition = Vector3.zero; + currentPage.transform.localScale = Vector3.one; + + int previous = page - 1; + if (previous > 0) + { + var previousPage = GetOrCreatePage(previous, optionsMenuBehaviour, true); + if (previousPage != null) + { + CreatePreviousButton(currentPage, previousPage, optionsMenuBehaviour); + CreateNextButton(previousPage, currentPage, optionsMenuBehaviour); + } + } + } + + return currentPage; + } + + /// + /// Creates a "Next" navigation button that switches to the specified next page. + /// + private static void CreateNextButton(GameObject page, GameObject nextPage, OptionsMenuBehaviour optionsMenuBehaviour) + { + var button = CreateToggleButton("Next >", optionsMenuBehaviour, page.transform); + button.transform.localPosition = new Vector3(2f, -2.5f, 0f); + button.transform.Find("Background")?.localScale = new Vector3(0.5f, 1f, 1f); + button.transform.Find("ButtonHighlight")?.localScale = new Vector3(0.5f, 0.95f, 1f); + var passiveButton = button.GetComponent(); + passiveButton.OnClick = new(); + passiveButton.OnClick.AddListener(() => + { + page.SetActive(false); + nextPage.SetActive(true); + }); + } + + /// + /// Creates a "Previous" navigation button that switches to the specified previous page. + /// + private static void CreatePreviousButton(GameObject page, GameObject previousPage, OptionsMenuBehaviour optionsMenuBehaviour) + { + var button = CreateToggleButton("< Prev", optionsMenuBehaviour, page.transform); + button.transform.localPosition = new Vector3(-2f, -2.5f, 0f); + button.transform.Find("Background")?.localScale = new Vector3(0.5f, 1f, 1f); + button.transform.Find("ButtonHighlight")?.localScale = new Vector3(0.5f, 0.95f, 1f); + var passiveButton = button.GetComponent(); + passiveButton.OnClick = new(); + passiveButton.OnClick.AddListener(() => + { + page.SetActive(false); + previousPage.SetActive(true); + }); + } } \ No newline at end of file diff --git a/src/Patches/Client/OptionsMenuBehaviourPatch.cs b/src/Patches/Client/OptionsMenuBehaviourPatch.cs index e2cb44b..26cf740 100644 --- a/src/Patches/Client/OptionsMenuBehaviourPatch.cs +++ b/src/Patches/Client/OptionsMenuBehaviourPatch.cs @@ -1,4 +1,4 @@ -using BepInEx; +using BepInEx; using BetterAmongUs.Data; using BetterAmongUs.Helpers; using BetterAmongUs.Managers; @@ -39,19 +39,19 @@ internal static class OptionsMenuBehaviourPatch ClientOptionItem.ClientOptions.Clear(); // Toggle options with config binding - ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.AntiCheat"), BAUPlugin.AntiCheat, __instance); - ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.SendBetterRpc"), BAUPlugin.SendBetterRpc, __instance, SendBetterRpcAction); - ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.BetterNotifications"), BAUPlugin.BetterNotifications, __instance, ClearNotifications); - ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.ForceOwnLanguage"), BAUPlugin.ForceOwnLanguage, __instance); - ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.ChatDarkMode"), BAUPlugin.ChatDarkMode, __instance, ChatPatch.SetChatTheme); - ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.ChatInGame"), BAUPlugin.ChatInGameplay, __instance); - ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.LobbyInfo"), BAUPlugin.LobbyPlayerInfo, __instance); - ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.LobbyTheme"), BAUPlugin.DisableLobbyTheme, __instance, ToggleLobbyTheme); - ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.UnlockFPS"), BAUPlugin.UnlockFPS, __instance, UpdateFrameRate); - ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.ShowFPS"), BAUPlugin.ShowFPS, __instance); + ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.AntiCheat"), BAUPlugin.AntiCheat, 1, __instance); + ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.SendBetterRpc"), BAUPlugin.SendBetterRpc, 1, __instance, SendBetterRpcAction); + ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.BetterNotifications"), BAUPlugin.BetterNotifications, 1, __instance, ClearNotifications); + ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.ForceOwnLanguage"), BAUPlugin.ForceOwnLanguage, 1, __instance); + ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.ChatDarkMode"), BAUPlugin.ChatDarkMode, 1, __instance, ChatPatch.SetChatTheme); + ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.ChatInGame"), BAUPlugin.ChatInGameplay, 1, __instance); + ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.LobbyInfo"), BAUPlugin.LobbyPlayerInfo, 1, __instance); + ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.LobbyTheme"), BAUPlugin.DisableLobbyTheme, 1, __instance, ToggleLobbyTheme); + ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.UnlockFPS"), BAUPlugin.UnlockFPS, 1, __instance, UpdateFrameRate); + ClientOptionItem.CreateToggle(Translator.GetString("BetterOption.ShowFPS"), BAUPlugin.ShowFPS, 1, __instance); // Button options (no toggle) - ClientOptionItem.CreateButton(Translator.GetString("BetterOption.SaveData"), __instance, OpenSaveData, () => + ClientOptionItem.CreateButton(Translator.GetString("BetterOption.SaveData"), 1, __instance, OpenSaveData, () => { // Only allow opening save data in lobby/main menu, not during gameplay bool cannotOpen = GameState.IsInGame && !GameState.IsLobby; @@ -62,7 +62,7 @@ internal static class OptionsMenuBehaviourPatch return !cannotOpen; }); - ClientOptionItem.CreateButton(Translator.GetString("BetterOption.ToVanilla"), __instance, SwitchToVanilla, () => + ClientOptionItem.CreateButton(Translator.GetString("BetterOption.ToVanilla"), 1, __instance, SwitchToVanilla, () => { // Prevent switching to vanilla while in a game bool cannotSwitch = GameState.IsInGame;