diff --git a/MiraAPI/GameOptions/IModdedOption.cs b/MiraAPI/GameOptions/IModdedOption.cs index 86baf9d..95173fb 100644 --- a/MiraAPI/GameOptions/IModdedOption.cs +++ b/MiraAPI/GameOptions/IModdedOption.cs @@ -46,6 +46,11 @@ public interface IModdedOption /// Func Visible { get; set; } + /// + /// Gets or sets a value indicating whether the option should be included with presets. + /// + bool IncludeInPreset { get; set; } + /// /// Gets or sets the ConfigDefinition for the option, used for BepInEx configuration. /// @@ -78,4 +83,23 @@ public interface IModdedOption /// /// >The byte array representing the network data. void HandleNetData(byte[] data); + + /// + /// Saves the option to a preset configuration file. + /// + /// The ConfigFile representing the preset configuration. + /// Indicates whether to save the default value instead of the current value. + void SaveToPreset(ConfigFile presetConfig, bool saveDefault = false); + + /// + /// Binds the option to a configuration file. + /// + /// The ConfigFile to bind the option to. + void Bind(ConfigFile config); + + /// + /// Loads the option from a preset configuration file, applying the values to the option's configuration. + /// + /// The ConfigFile representing the preset configuration. + void LoadFromPreset(ConfigFile presetConfig); } diff --git a/MiraAPI/GameOptions/ModdedOptionsManager.cs b/MiraAPI/GameOptions/ModdedOptionsManager.cs index d602f45..0adc461 100644 --- a/MiraAPI/GameOptions/ModdedOptionsManager.cs +++ b/MiraAPI/GameOptions/ModdedOptionsManager.cs @@ -43,7 +43,7 @@ public static class ModdedOptionsManager Groups.Add(group); TypeToGroup.Add(type, group); - pluginInfo.OptionGroups.Add(group); + pluginInfo.InternalOptionGroups.Add(group); typeof(OptionGroupSingleton<>).MakeGenericType(type) #pragma warning disable S3011 @@ -122,7 +122,7 @@ public static class ModdedOptionsManager option.ConfigDefinition = new ConfigDefinition(groupName, propertyName); option.ParentMod = pluginInfo.MiraPlugin; - pluginInfo.Options.Add(option); + pluginInfo.InternalOptions.Add(option); ModdedOptions.Add(option.Id, option); group.Options.Add(option); } @@ -143,7 +143,7 @@ public static class ModdedOptionsManager // we dont know how other plugins handle their configs // this way, all the options are saved at once, instead of one by one var oldConfigSetting = new Dictionary(); - foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins()) + foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins) { oldConfigSetting.Add(plugin, plugin.PluginConfig.SaveOnConfigSet); plugin.PluginConfig.SaveOnConfigSet = false; @@ -159,7 +159,7 @@ public static class ModdedOptionsManager option.HandleNetData(netData.Data); } - foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins()) + foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins) { plugin.PluginConfig.Save(); plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin]; diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs index 0ae41bd..18f13c6 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs @@ -18,7 +18,7 @@ public class ModdedEnumOption : ModdedOption /// /// Gets the string values of the enum. /// - public string[]? Values { get; } + public string[] Values { get; } /// /// Initializes a new instance of the class. @@ -27,7 +27,8 @@ public class ModdedEnumOption : ModdedOption /// The default value as an int. /// The Enum type. /// An option list of string values to use in place of the enum name. - public ModdedEnumOption(string title, int defaultValue, Type enumType, string[]? values = null) : base(title, defaultValue) + /// Whether to include this option in the preset or not. + public ModdedEnumOption(string title, int defaultValue, Type enumType, string[]? values = null, bool includeInPreset=true) : base(title, defaultValue, includeInPreset) { Values = values ?? Enum.GetNames(enumType); Data = ScriptableObject.CreateInstance(); @@ -37,7 +38,7 @@ public class ModdedEnumOption : ModdedOption data.Type = global::OptionTypes.String; data.Values = values is null ? Enum.GetNames(enumType).Select(CustomStringName.CreateAndRegister).ToArray() - : values.Select(CustomStringName.CreateAndRegister).ToArray(); + : [.. values.Select(CustomStringName.CreateAndRegister)]; data.Index = Value; } @@ -87,7 +88,7 @@ public class ModdedEnumOption : ModdedOption /// protected override void OnValueChanged(int newValue) { - HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data?.GetValueString(newValue), false); + HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false); if (!OptionBehaviour) { return; @@ -109,7 +110,7 @@ public class ModdedEnumOption : ModdedOption where T : Enum /// /// Gets the string values of the enum. /// - public string[]? Values { get; } + public string[] Values { get; } /// /// Initializes a new instance of the class. @@ -117,7 +118,8 @@ public class ModdedEnumOption : ModdedOption where T : Enum /// The title of the option. /// The default value as an int. /// An option list of string values to use in place of the enum name. - public ModdedEnumOption(string title, T defaultValue, string[]? values = null) : base(title, defaultValue) + /// Whether to include this option in the preset or not. + public ModdedEnumOption(string title, T defaultValue, string[]? values = null, bool includeInPreset=true) : base(title, defaultValue, includeInPreset) { Values = values ?? Enum.GetNames(typeof(T)); Data = ScriptableObject.CreateInstance(); @@ -127,7 +129,7 @@ public class ModdedEnumOption : ModdedOption where T : Enum data.Type = global::OptionTypes.String; data.Values = values is null ? Enum.GetNames(typeof(T)).Select(CustomStringName.CreateAndRegister).ToArray() - : values.Select(CustomStringName.CreateAndRegister).ToArray(); + : [.. values.Select(CustomStringName.CreateAndRegister)]; data.Index = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo); } diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs index 4d38c04..a01ffe8 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs @@ -48,6 +48,7 @@ public class ModdedNumberOption : ModdedOption /// The suffix type. /// Optional format string for the option screen. /// Whether zero is infinity or not. + /// Whether to include this option in the preset or not. public ModdedNumberOption( string title, float defaultValue, @@ -56,7 +57,8 @@ public class ModdedNumberOption : ModdedOption float increment, MiraNumberSuffixes suffixType, string? formatString = null, - bool zeroInfinity = false) : base(title, defaultValue) + bool zeroInfinity = false, + bool includeInPreset = true) : base(title, defaultValue, includeInPreset) { Min = min; Max = max; diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs index 93b5b5f..3c98d24 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -4,6 +4,7 @@ using MiraAPI.Networking; using MiraAPI.PluginLoading; using Reactor.Localization.Utilities; using Reactor.Networking.Rpc; +using Reactor.Utilities; using UnityEngine; namespace MiraAPI.GameOptions.OptionTypes; @@ -16,29 +17,19 @@ public abstract class ModdedOption : IModdedOption { private IMiraPlugin? _parentMod; - /// - /// Gets the unique identifier of the option. - /// + /// public uint Id { get; } - /// - /// Gets the title of the option. - /// + /// public string Title { get; } - /// - /// Gets the StringName object of the option. - /// + /// public StringNames StringName { get; } - /// - /// Gets or sets the BaseGameSetting data of the option. - /// + /// public BaseGameSetting Data { get; protected set; } = null!; - /// - /// Gets or sets the parent mod of the option. - /// + /// public IMiraPlugin? ParentMod { get => _parentMod; @@ -66,19 +57,16 @@ public abstract class ModdedOption : IModdedOption /// public Action? ChangedEvent { get; set; } - /// - /// Gets or sets the visibility of the option. - /// + /// public Func Visible { get; set; } - /// - /// Gets or sets the option behaviour of the option. - /// + /// + public bool IncludeInPreset { get; set; } + + /// public OptionBehaviour? OptionBehaviour { get; protected set; } - /// - /// Gets or sets the config definition of the option. - /// + /// public ConfigDefinition? ConfigDefinition { get; set; } /// @@ -86,7 +74,8 @@ public abstract class ModdedOption : IModdedOption /// /// The option title. /// The default value. - protected ModdedOption(string title, T defaultValue) + /// Whether to include the option in the preset. + protected ModdedOption(string title, T defaultValue, bool includeInPreset = true) { Id = ModdedOptionsManager.NextId; Title = title; @@ -94,6 +83,7 @@ public abstract class ModdedOption : IModdedOption Value = defaultValue; StringName = CustomStringName.CreateAndRegister(Title); Visible = () => true; + IncludeInPreset = includeInPreset; } internal void ValueChanged(OptionBehaviour optionBehaviour) @@ -105,7 +95,8 @@ public abstract class ModdedOption : IModdedOption /// Sets the value of the option. /// /// The new value. - public void SetValue(T newValue) + /// Whether to send the value to other players. + public void SetValue(T newValue, bool sendRpc = true) { var oldVal = Value; Value = newValue; @@ -115,7 +106,7 @@ public abstract class ModdedOption : IModdedOption ChangedEvent?.Invoke(Value); } - if (AmongUsClient.Instance.AmHost) + if (sendRpc && AmongUsClient.Instance.AmHost) { if (ParentMod?.GetConfigFile().TryGetEntry(ConfigDefinition, out var entry) == true) { @@ -128,22 +119,44 @@ public abstract class ModdedOption : IModdedOption OnValueChanged(newValue); } - /// - /// Gets the float data of the option. - /// - /// A float object representing the option's value. + /// + public void SaveToPreset(ConfigFile presetConfig, bool saveDefault=false) + { + if (ConfigDefinition is null) + { + Logger.Error($"Attempted to save {Title} to preset, but ConfigDefinition is null."); + return; + } + Bind(presetConfig); + presetConfig[ConfigDefinition].BoxedValue = saveDefault ? DefaultValue : Value; + } + + /// + public void Bind(ConfigFile config) + { + config.Bind(ConfigDefinition, DefaultValue); + } + + /// + public void LoadFromPreset(ConfigFile presetConfig) + { + if (presetConfig.TryGetEntry(ConfigDefinition, out ConfigEntry entry)) + { + SetValue(entry.Value, false); + } + else + { + Logger.Error($"Attempted to load {Title} from preset, but ConfigDefinition is not found in preset."); + } + } + + /// public abstract float GetFloatData(); - /// - /// Gets the net data of the option. - /// - /// A NetData object representing this option's data. + /// public abstract NetData GetNetData(); - /// - /// Handles incoming net data. - /// - /// The NetData's byte array. + /// public abstract void HandleNetData(byte[] data); /// @@ -159,14 +172,7 @@ public abstract class ModdedOption : IModdedOption /// The value. public abstract T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour); - /// - /// Creates the option behaviour. - /// - /// The ToggleOption prefab. - /// The NumberOption prefab. - /// The StringOption prefab. - /// The options container. - /// A new OptionBehaviour for this modded option. + /// public abstract OptionBehaviour CreateOption( ToggleOption toggleOpt, NumberOption numberOpt, diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs index 3495e86..26ecc74 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs @@ -15,7 +15,8 @@ public class ModdedToggleOption : ModdedOption /// /// The option title. /// The default value. - public ModdedToggleOption(string title, bool defaultValue) : base(title, defaultValue) + /// Whether to include this option in the preset or not. + public ModdedToggleOption(string title, bool defaultValue, bool includeInPreset=true) : base(title, defaultValue, includeInPreset) { Data = ScriptableObject.CreateInstance(); diff --git a/MiraAPI/Hud/CustomButtonManager.cs b/MiraAPI/Hud/CustomButtonManager.cs index 69c4b92..e576595 100644 --- a/MiraAPI/Hud/CustomButtonManager.cs +++ b/MiraAPI/Hud/CustomButtonManager.cs @@ -29,7 +29,7 @@ public static class CustomButtonManager } CustomButtons.Add(button); - pluginInfo.Buttons.Add(button); + pluginInfo.InternalButtons.Add(button); typeof(CustomButtonSingleton<>).MakeGenericType(buttonType) #pragma warning disable S3011 .GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)! diff --git a/MiraAPI/Hud/InputBox.cs b/MiraAPI/Hud/InputBox.cs new file mode 100644 index 0000000..43dfb95 --- /dev/null +++ b/MiraAPI/Hud/InputBox.cs @@ -0,0 +1,114 @@ +using System; +using MiraAPI.Utilities.Assets; +using Reactor.Utilities.Attributes; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.UI; + +namespace MiraAPI.Hud; + +/// +/// Simple input box for user input. +/// +[RegisterInIl2Cpp] +public class InputBox : MonoBehaviour +{ + public InputField inputField = null!; + + public void CreateDialog(string title, string placeholder, string submitText, Action onSubmit) + { + var canvasObj = new GameObject("DialogCanvas"); + canvasObj.transform.SetParent(transform); + canvasObj.layer = LayerMask.NameToLayer("UI"); + + var canvas = canvasObj.AddComponent(); + canvas.renderMode = RenderMode.ScreenSpaceOverlay; + canvas.overrideSorting = true; + canvas.sortingOrder = 1000; // Ensure the canvas is on top of other UI elements + + canvasObj.AddComponent().uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; + canvasObj.AddComponent(); + + var panelObj = new GameObject("DialogPanel"); + panelObj.transform.SetParent(canvasObj.transform); + var panelRect = panelObj.AddComponent(); + var img = panelObj.AddComponent(); + + img.color = new Color(1, 1, 1, 0.9f); + img.raycastTarget = true; + img.sprite = MiraAssets.RoundedBox.LoadAsset(); + img.type = Image.Type.Sliced; + + panelRect.sizeDelta = new Vector2(300, 200); + panelRect.anchoredPosition = Vector2.zero; + + var titleText = CreateText(panelObj, title); + titleText.alignment = TextAnchor.MiddleCenter; + titleText.fontSize = 26; + titleText.fontStyle = FontStyle.Bold; + titleText.color = Color.white; + + var titleRect = titleText.GetComponent(); + titleRect.sizeDelta = new Vector2(280, 50); + titleRect.anchoredPosition = new Vector2(0, 70); + + var inputObj = new GameObject("InputField"); + inputObj.transform.SetParent(panelObj.transform); + var inputRect = inputObj.AddComponent(); + inputRect.sizeDelta = new Vector2(220, 40); + inputRect.anchoredPosition = new Vector2(0, 20); + + var inputBg = inputObj.AddComponent(); + inputBg.color = new Color(1, 1, 1, 0.8f); + + inputField = inputObj.AddComponent(); + inputField.textComponent = CreateText(inputObj, string.Empty, 18, Color.black); + inputField.placeholder = CreateText(inputObj, placeholder, 18, new Color(0.5f, 0.5f, 0.5f)); + + var buttonObj = new GameObject("SubmitButton"); + buttonObj.transform.SetParent(panelObj.transform); + var button = buttonObj.AddComponent