diff --git a/MiraAPI/GameOptions/IModdedOption.cs b/MiraAPI/GameOptions/IModdedOption.cs index 2679f46..0e84dc7 100644 --- a/MiraAPI/GameOptions/IModdedOption.cs +++ b/MiraAPI/GameOptions/IModdedOption.cs @@ -78,4 +78,14 @@ public interface IModdedOption /// /// The NetData's byte array. void HandleNetData(byte[] data); + + /// + /// Changes the scales and positions of the option if it's attached to the game options menu. + /// + void ChangeGameSetting(); + + /// + /// Changes the scales and positions of the option if it's attached to the roles settings menu. + /// + void ChangeRoleSetting(); } diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs index 8b4321c..3ac0a3b 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs @@ -35,10 +35,7 @@ public class ModdedEnumOption : ModdedOption data.Title = StringName; data.Type = global::OptionTypes.String; - data.Values = (values is null - ? Enum.GetNames(enumType) - : values) - .Select(CustomStringName.CreateAndRegister).ToArray(); + data.Values = Values.Select(CustomStringName.CreateAndRegister).ToArray(); data.Index = Value; } @@ -51,6 +48,10 @@ public class ModdedEnumOption : ModdedOption stringOption.SetUpFromData(Data, 20); stringOption.OnValueChanged = (Il2CppSystem.Action)ValueChanged; + // Changing the button text to make them more distinct from number options + stringOption.MinusBtn.ChangeButtonText("<"); + stringOption.PlusBtn.ChangeButtonText(">"); + // SetUpFromData method doesn't work correctly so we must set the values manually stringOption.Title = StringName; stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray(0); @@ -61,6 +62,32 @@ public class ModdedEnumOption : ModdedOption return stringOption; } + /// + public override void ChangeGameSetting() + { + var stringOption = OptionBehaviour as StringOption; + + stringOption!.MinusBtn.transform.localPosition += new Vector3(0.6f, 0f, 0f); + stringOption.PlusBtn.transform.localPosition += new Vector3(1.5f, 0f, 0f); + + // The value box itself becomes the button + stringOption.PlusBtn.gameObject.SetActive(false); + stringOption.MinusBtn.gameObject.SetActive(false); + + var background = stringOption.transform.GetChild(0); + background.localPosition += new Vector3(-0.8f, 0f, 0f); + background.localScale += new Vector3(1f, 0f, 0f); + + var title = stringOption.TitleText; + title.transform.localPosition = new(-2.0466f, 0f, -2.9968f); + title.GetComponent().sizeDelta = new(5.8f, 0.458f); + title.fontSize = 2.9f; // Why is it different for string options?? + + var valueBox = stringOption.transform.GetChild(5); + valueBox.localPosition += new Vector3(1.05f, 0f, 0f); + valueBox.localScale += new Vector3(0.2f, 0f, 0f); + } + /// public override float GetFloatData() { @@ -126,10 +153,7 @@ public class ModdedEnumOption : ModdedOption where T : struct, Enum data.Title = StringName; data.Type = global::OptionTypes.String; - data.Values = (values is null - ? Enum.GetNames() - : values) - .Select(CustomStringName.CreateAndRegister).ToArray(); + data.Values = Values.Select(CustomStringName.CreateAndRegister).ToArray(); data.Index = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo); } @@ -142,6 +166,10 @@ public class ModdedEnumOption : ModdedOption where T : struct, Enum stringOption.SetUpFromData(Data, 20); stringOption.OnValueChanged = (Action)ValueChanged; + // Changing the button text to make them more distinct from number options + stringOption.MinusBtn.ChangeButtonText("<"); + stringOption.PlusBtn.ChangeButtonText(">"); + // SetUpFromData method doesn't work correctly so we must set the values manually stringOption.Title = StringName; stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray(0); @@ -152,6 +180,32 @@ public class ModdedEnumOption : ModdedOption where T : struct, Enum return stringOption; } + /// + public override void ChangeGameSetting() + { + var stringOption = OptionBehaviour as StringOption; + + stringOption!.MinusBtn.transform.localPosition += new Vector3(0.6f, 0f, 0f); + stringOption.PlusBtn.transform.localPosition += new Vector3(1.5f, 0f, 0f); + + // The value box itself becomes the button + stringOption.PlusBtn.gameObject.SetActive(false); + stringOption.MinusBtn.gameObject.SetActive(false); + + var background = stringOption.transform.GetChild(0); + background.localPosition += new Vector3(-0.8f, 0f, 0f); + background.localScale += new Vector3(1f, 0f, 0f); + + var title = stringOption.TitleText; + title.transform.localPosition = new(-2.0466f, 0f, -2.9968f); + title.GetComponent().sizeDelta = new(5.8f, 0.458f); + title.fontSize = 2.9f; // Why is it different for string options?? + + var valueBox = stringOption.transform.GetChild(5); + valueBox.localPosition += new Vector3(1.05f, 0f, 0f); + valueBox.localScale += new Vector3(0.2f, 0f, 0f); + } + /// public override float GetFloatData() { diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedMultiSelectOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedMultiSelectOption.cs index ad5b67b..fd4de5f 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedMultiSelectOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedMultiSelectOption.cs @@ -1,11 +1,15 @@ +using AmongUs.GameOptions; using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Reflection; using Il2CppInterop.Runtime.InteropTypes.Arrays; using MiraAPI.Modules; using MiraAPI.Networking; -using Reactor.Localization.Utilities; -using Reactor.Utilities; +using MiraAPI.Utilities; +using MiraAPI.Utilities.Assets; +using Reactor.Utilities.Extensions; using UnityEngine; using Object = UnityEngine.Object; @@ -15,7 +19,7 @@ namespace MiraAPI.GameOptions.OptionTypes; /// An option for selecting an enum value. /// /// The enum type. -public class ModdedMultiSelectOption : ModdedOption> where T : struct, Enum +public class ModdedMultiSelectOption : ModdedOption where T : struct, Enum { /// /// Gets the string values of the enum. @@ -32,6 +36,11 @@ public class ModdedMultiSelectOption : ModdedOption> wher /// public T None { get; } + /// + /// Gets the list of buttons that relate to the current instance. + /// + public List Buttons { get; } + /// /// Initializes a new instance of the class. /// @@ -40,35 +49,25 @@ public class ModdedMultiSelectOption : ModdedOption> wher /// The all value. /// The none value. /// An option list of string values to use in place of the enum name. + /// The enum type doesn't have the flags attribute. public ModdedMultiSelectOption(string title, T defaultValue, T all, T none, string[]? values = null) : base(title, defaultValue) { + var tType = typeof(T); + + if (tType.GetCustomAttribute() == null) + throw new ArgumentException($"{tType.Name} is not a flag enum type"); + Values = values ?? Enum.GetNames(); All = all; None = none; Data = ScriptableObject.CreateInstance(); + Buttons = []; + var data = (MultiSelectGameSetting)Data; - data.Title = StringName; - data.Type = global::OptionTypes.String; - data.Values = (values ?? Enum.GetNames()).Select(CustomStringName.CreateAndRegister).ToArray(); - } - - /// - public override void BindConfig() - { - var entry = ParentMod?.GetConfigFile().Bind(ConfigDefinition, DefaultValue.ToString()); - if (entry != null) - { - try - { - Value = new MultiSelectValue([.. entry.Value.Split(",").Select(x => Enum.Parse(x.Trim()))]); - } - catch (Exception e) - { - Value = DefaultValue; - Logger.Error($"Failed to bind config for {Title}: {e.Message}"); - } - } + data.OptionName = Int32OptionNames.Invalid; + data.Type = global::OptionTypes.MultipleChoice; // How nice of the game to provide an enum value themselves + data.EnumNames = []; // We don't initialise the translation here, we just add one as and when needed } /// @@ -79,9 +78,23 @@ public class ModdedMultiSelectOption : ModdedOption> wher stringOption.SetUpFromData(Data, 20); stringOption.OnValueChanged = (Action)ValueChanged; + var toggle = Object.Instantiate(toggleOpt.GetComponentInChildren(), stringOption.transform); + toggle.name = "Button"; + toggle.transform.DestroyChildren(); + toggle.OverrideOnClickListeners(ToggleButtons); + + var box = toggle.GetComponent(); + var prevColliderSize = box.size; + prevColliderSize.x *= 4.97f; + box.size = prevColliderSize; + + var rend = stringOption.transform.GetChild(5).GetComponent(); + toggle.OverrideOnMouseOverListeners(() => rend.color = Buttons.Count > 0 ? Color.white : MiraAssets.AcceptedTeal); + toggle.OverrideOnMouseOutListeners(() => rend.color = Buttons.Count > 0 ? MiraAssets.AcceptedTeal : Color.white); + // SetUpFromData method doesn't work correctly so we must set the values manually stringOption.Title = StringName; - stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray(0); + stringOption.Values = (Data as MultiSelectGameSetting)?.EnumNames?.Values?.ToArray() ?? new Il2CppStructArray(0); stringOption.Value = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo); OptionBehaviour = stringOption; @@ -89,6 +102,40 @@ public class ModdedMultiSelectOption : ModdedOption> wher return stringOption; } + /// + public override void ChangeGameSetting() + { + var stringOption = OptionBehaviour as StringOption; + + // The value box itself becomes the button + stringOption!.PlusBtn.gameObject.SetActive(false); + stringOption.MinusBtn.gameObject.SetActive(false); + + var background = stringOption.transform.GetChild(0); + background.localPosition += new Vector3(-0.8f, 0f, 0f); + background.localScale += new Vector3(1f, 0f, 0f); + + var title = stringOption.TitleText; + title.transform.localPosition = new(-2.0466f, 0f, -2.9968f); + title.GetComponent().sizeDelta = new(5.8f, 0.458f); + title.fontSize = 2.9f; // Why is it different for string options?? + + var valueBox = stringOption.transform.GetChild(5); + valueBox.localPosition += new Vector3(1.05f, 0f, 0f); + valueBox.localScale += new Vector3(0.2f, 0f, 0f); + } + + private void ToggleButtons() + { + if (Buttons.Count > 0) + { + Buttons.ForEach(x => x.gameObject.Destroy()); + return; + } + + // TODO: Implement this + } + /// public override float GetFloatData() { @@ -98,23 +145,23 @@ public class ModdedMultiSelectOption : ModdedOption> wher /// public override NetData GetNetData() { - return new NetData(Id, Value.ToBytes()); + return new NetData(Id, EnumSerializer.EnumToBytes(Value)); } /// public override void HandleNetData(byte[] data) { - SetValue(MultiSelectValue.FromBytes(data)); + SetValue(EnumSerializer.EnumFromBytes(data, 0)); } /// - public override MultiSelectValue GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour) + public override T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour) { - return []; // TODO: Add implementation + return Enum.Parse(optionBehaviour.GetInt().ToString(NumberFormatInfo.InvariantInfo)); } /// - protected override void OnValueChanged(MultiSelectValue newValue) + protected override void OnValueChanged(T newValue) { HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data!.GetValueString(Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo)), false); if (!OptionBehaviour) diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs index 4d38c04..a8f58af 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs @@ -112,6 +112,27 @@ public class ModdedNumberOption : ModdedOption return numberOption; } + /// + public override void ChangeGameSetting() + { + var numberOption = OptionBehaviour as NumberOption; + + numberOption!.PlusBtn.transform.localPosition += new Vector3(0.6f, 0f, 0f); + numberOption.MinusBtn.transform.localPosition += new Vector3(1.5f, 0f, 0f); + + var background = numberOption.transform.GetChild(0); + background.localPosition += new Vector3(-0.8f, 0f, 0f); + background.localScale += new Vector3(1f, 0f, 0f); + + var title = numberOption.TitleText; + title.transform.localPosition = new(-2.0466f, 0f, -2.9968f); + title.GetComponent().sizeDelta = new(5.8f, 0.458f); + + var valueBox = numberOption.transform.GetChild(5); + valueBox.localPosition += new Vector3(1.05f, 0f, 0f); + valueBox.localScale += new Vector3(0.2f, 0f, 0f); + } + /// public override float GetFloatData() { @@ -142,7 +163,7 @@ public class ModdedNumberOption : ModdedOption Value = Mathf.Clamp(newValue, Min, Max); HudManager.Instance.Notifier.AddSettingsChangeMessage( StringName, - Data.GetValueString(Value), + Data!.GetValueString(Value), false); if (OptionBehaviour is NumberOption opt) diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs index e5eb196..e24c314 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -139,6 +139,15 @@ public abstract class ModdedOption : IModdedOption where T : struct StringOption stringOpt, Transform container); + /// + public abstract void ChangeGameSetting(); + + /// + public virtual void ChangeRoleSetting() + { + // TODO: Implement this as an abstract + } + /// /// Binds the option to the configuration file of the parent mod, if available. /// diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs index 3495e86..e6f08f4 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs @@ -41,6 +41,21 @@ public class ModdedToggleOption : ModdedOption return toggleOption; } + /// + public override void ChangeGameSetting() + { + var toggleOption = OptionBehaviour as ToggleOption; + toggleOption!.transform.GetChild(1).localPosition += new Vector3(2.2f, 0f, 0f); // Checkbox + + var title = toggleOption.TitleText; + title.transform.localPosition = new(-2.0466f, 0f, -2.9968f); + title.GetComponent().sizeDelta = new(5.8f, 0.458f); + + var background = toggleOption.transform.GetChild(2); + background.localPosition += new Vector3(-0.8f, 0f, 0f); + background.localScale += new Vector3(1f, 0f, 0f); + } + /// public override float GetFloatData() { diff --git a/MiraAPI/Modules/MultiSelectGameSetting.cs b/MiraAPI/Modules/MultiSelectGameSetting.cs index c8224d5..978c062 100644 --- a/MiraAPI/Modules/MultiSelectGameSetting.cs +++ b/MiraAPI/Modules/MultiSelectGameSetting.cs @@ -1,7 +1,9 @@ // TODO: Remove later before completing the pr #pragma warning disable using System; +using System.Collections.Generic; using AmongUs.GameOptions; +using Il2CppInterop.Runtime.Attributes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Reactor.Utilities.Attributes; @@ -10,14 +12,8 @@ namespace MiraAPI.Modules; [RegisterInIl2Cpp, Serializable] public class MultiSelectGameSetting : BaseGameSetting { - public MultiSelectGameSetting() => Type = OptionTypes.MultipleChoice; // How nice of the game to provide an enum value themselves - - public override string GetValueString(float value) - { - return TranslationController.Instance.GetString(default(StringNames), null, null); - } - public Int32OptionNames OptionName; + public Dictionary EnumNames; - public Il2CppStructArray Values; + public override string GetValueString(float value) => TranslationController.Instance.GetString(EnumNames[(int)value]); } diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index 8034eff..c72baeb 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -226,6 +226,8 @@ public static class GameOptionsMenuPatch menu.stringOptionOrigin, menu.settingsContainer)); + group.Options.ForEach(x => x.ChangeGameSetting()); + foreach (var newOpt in options) { newOpt.SetClickMask(menu.ButtonClickMask); diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 9fe48d7..9f2d431 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -325,6 +325,7 @@ public static class RoleSettingMenuPatches newOpt.transform.localPosition = new Vector3(hasImage ? 2.17f : 1.1f, num, -2f); newOpt.SetClickMask(__instance.ButtonClickMask); + option.ChangeRoleSetting(); SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren(true); foreach (var renderer in componentsInChildren) diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index e4c5281..09c6bf2 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -431,4 +431,50 @@ public static class Helpers items.Do(x => result += $"{separator}{x}"); return result[separator.Length..]; } + + /// + /// Shorthand helper for overriding the on click listeners for PassiveButton. + /// + /// The PassiveButton being modified. + /// The action that's replacing the original listeners. + public static void OverrideOnClickListeners(this PassiveButton passive, Action action) + { + if (!passive) + return; + + passive.OnClick?.RemoveAllListeners(); + passive.OnClick = new(); + passive.OnClick.AddListener(action); + passive.enabled = true; + } + + /// + /// Shorthand helper for overriding the on mouse over listeners for PassiveButton. + /// + /// + public static void OverrideOnMouseOverListeners(this PassiveButton passive, Action action) + { + if (!passive) + return; + + passive.OnMouseOver?.RemoveAllListeners(); + passive.OnMouseOver = new(); + passive.OnMouseOver.AddListener(action); + passive.enabled = true; + } + + /// + /// Shorthand helper for overriding the on mouse out listeners for PassiveButton. + /// + /// + public static void OverrideOnMouseOutListeners(this PassiveButton passive, Action action) + { + if (!passive) + return; + + passive.OnMouseOut?.RemoveAllListeners(); + passive.OnMouseOut = new(); + passive.OnMouseOut.AddListener(action); + passive.enabled = true; + } }