From 04b7701b7925804f7b56ef97f71d7cfa4ea357e3 Mon Sep 17 00:00:00 2001 From: Az <56699741+AlchlcDvl@users.noreply.github.com> Date: Thu, 15 Oct 2026 20:46:41 -0400 Subject: [PATCH] more stuff --- MiraAPI.Example/Options/ExampleOptions.cs | 11 ++- MiraAPI.Example/Resources/Locale/en_US.xml | 1 + .../Attributes/ModdedEnumOptionAttribute.cs | 4 +- .../Attributes/ModdedNumberOptionAttribute.cs | 32 ++++++-- .../Attributes/ModdedOptionAttribute.cs | 1 + .../Attributes/ModdedPlayerOptionAttribute.cs | 49 ++++++++++++ .../Attributes/ModdedStringOptionAttribute.cs | 2 +- .../Attributes/ModdedToggleOptionAttribute.cs | 2 +- .../ModdedEnumOptionListAttribute.cs | 4 +- .../ModdedNumberOptionListAttribute.cs | 32 ++++++-- .../OptionList/ModdedOptionListAttribute.cs | 32 ++++---- .../ModdedPlayerOptionListAttribute.cs | 54 +++++++++++++ .../OptionList/ModdedStringOptionList.cs | 2 +- .../ModdedToggleOptionListAttribute.cs | 2 +- MiraAPI/GameOptions/ModdedOptionsManager.cs | 62 +++++++++------ .../OptionTypes/ModdedEnumOption.cs | 4 +- .../OptionTypes/ModdedNumberOption.cs | 76 ++++++++----------- .../OptionTypes/ModdedPlayerOption.cs | 7 +- .../OptionTypes/ModdedStringOption.cs | 2 +- .../OptionTypes/ModdedToggleOption.cs | 9 ++- .../HnsReimplemented/HideAndSeekHudHelper.cs | 2 +- MiraAPI/Hud/MiraHudHelper.cs | 2 +- MiraAPI/PluginLoading/MiraPluginManager.cs | 15 ++-- 23 files changed, 283 insertions(+), 124 deletions(-) create mode 100644 MiraAPI/GameOptions/Attributes/ModdedPlayerOptionAttribute.cs create mode 100644 MiraAPI/GameOptions/Attributes/OptionList/ModdedPlayerOptionListAttribute.cs diff --git a/MiraAPI.Example/Options/ExampleOptions.cs b/MiraAPI.Example/Options/ExampleOptions.cs index c846308..c60038c 100644 --- a/MiraAPI.Example/Options/ExampleOptions.cs +++ b/MiraAPI.Example/Options/ExampleOptions.cs @@ -27,19 +27,22 @@ public class ExampleOptions : AbstractOptionGroup [ModdedOptionVisibility(nameof(NumberOptVisibility))] // Can also target static methods public float NumberOpt { get; set; } = 4f; + [ModdedNumberOption("options.exampleOptions1.numberOpt", min: 0, max: 10, suffixType: MiraNumberSuffixes.Distance)] + public int NumberOpt2 { get; set; } = 4; // Can also be int! + [ModdedEnumOption("options.exampleOptions1.bestApi", typeof(Api), ["Mira API", "Mitochondria", "Reactor"])] + public Api BestApi { get; set; } = Api.MiraAPI; + + [ModdedEnumOption("options.exampleOptions1.worstApi", ["Mira API", "Mitochondria", "Reactor"])] // These three can target both instance and static methods [ModdedOptionGetter(nameof(GetApi))] // Can also automatically resolve to {Option Name}Getter [ModdedOptionSetter(nameof(SetApi))] // Can also automatically resolve to {Option Name}Setter [ModdedOptionChanged(nameof(ApiChanged))] // Can also automatically resolve to {Option Name}Changed - public Api BestApi { get; set; } = Api.MiraAPI; - - [ModdedEnumOption("options.exampleOptions1.worstApi", ["Mira API", "Mitochondria", "Reactor"])] public Api WorstApi { get; set; } = Api.MiraAPI; [ModdedEnumOptionList("Value State {0}", ["None", "Primary", "Secondary"])] - public List ValueStates { get; } = + public List ValueStates { get; set; } = [ValueState.Primary, ValueState.Secondary, ValueState.None, ValueState.None, ValueState.None]; public bool ToggleOpt1Visibility() diff --git a/MiraAPI.Example/Resources/Locale/en_US.xml b/MiraAPI.Example/Resources/Locale/en_US.xml index 8328c69..e446b69 100644 --- a/MiraAPI.Example/Resources/Locale/en_US.xml +++ b/MiraAPI.Example/Resources/Locale/en_US.xml @@ -47,6 +47,7 @@ Eliminate every other faction. Toggle Opt 2 Number Opt Best API + Worst API Example Options 2 YOU MUST CHOSE A PLAYER Eh whatever diff --git a/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs index 16e3b38..2754920 100644 --- a/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs @@ -32,7 +32,7 @@ public class ModdedEnumOptionAttribute(string title, Type enumType, string[]? va { return HolderOption is ModdedEnumOption opt ? Enum.ToObject(enumType, opt.Value) - : throw new InvalidOperationException($"HolderOption for option \"{Title}\" with EnumType ${enumType.FullName} is not a ModdedEnumOption"); + : throw new InvalidOperationException($"HolderOption for option \"{Title}\" with EnumType {enumType.FullName} is not a ModdedEnumOption"); } } @@ -65,6 +65,6 @@ public class ModdedEnumOptionAttribute(string title, string[]? values = null) { return HolderOption is ModdedEnumOption opt ? opt.Value - : throw new InvalidOperationException($"HolderOption for option \"{Title}\" with EnumType ${typeof(T).FullName} is not a ModdedEnumOption"); + : throw new InvalidOperationException($"HolderOption for option \"{Title}\" with EnumType {typeof(T).FullName} is not a ModdedEnumOption<{typeof(T).Name}>"); } } diff --git a/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs index f662945..9de15ee 100644 --- a/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/ModdedNumberOptionAttribute.cs @@ -28,9 +28,16 @@ public class ModdedNumberOptionAttribute( { internal override IModdedOption CreateOption(object? value, PropertyInfo property) { + var final = value switch + { + null => min + increment, + int integer => integer, + _ => (float)value, + }; + return new ModdedNumberOption( Title, - (float)(value ?? min + increment), + final, min, max, increment, @@ -42,15 +49,28 @@ public class ModdedNumberOptionAttribute( /// public override void SetValue(object value) { - var opt = HolderOption as ModdedNumberOption; - opt?.SetValue((float)value); + if (HolderOption is not ModdedNumberOption opt) + return; + + var final = value switch + { + int integer => integer, + _ => (float)value, + }; + opt.SetValue(final); } /// public override object GetValue() { - return HolderOption is ModdedNumberOption opt - ? (object)opt.Value - : throw new InvalidOperationException($"HolderOption for option \"{Title}\" is not a ModdedNumberOption"); + if (HolderOption is not ModdedNumberOption opt) + throw new InvalidOperationException($"HolderOption for option \"{Title}\" is not a ModdedNumberOption"); + + var value = opt.Value; + + if (ValueType == typeof(int)) + return (int)Math.Round(value); + + return value; } } diff --git a/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs index 98194f8..91b7856 100644 --- a/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs @@ -13,6 +13,7 @@ namespace MiraAPI.GameOptions.Attributes; public abstract class ModdedOptionAttribute(string title, Type? roleType = null, Type? modeType = null) : PropertyOptionAttribute { internal IModdedOption? HolderOption { get; set; } + internal Type ValueType { get; set; } /// /// Gets the title of the option. diff --git a/MiraAPI/GameOptions/Attributes/ModdedPlayerOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedPlayerOptionAttribute.cs new file mode 100644 index 0000000..086df17 --- /dev/null +++ b/MiraAPI/GameOptions/Attributes/ModdedPlayerOptionAttribute.cs @@ -0,0 +1,49 @@ +using System; +using System.Reflection; +using MiraAPI.GameOptions.OptionTypes; + +namespace MiraAPI.GameOptions.Attributes; + +/// +/// Attribute for a . +/// +/// The title of the option. +/// Whether the option includes a none option. If None is selected, value will return -1. +[AttributeUsage(AttributeTargets.Property)] +public class ModdedPlayerOptionAttribute( + string title, + bool allowNone = true) + : ModdedOptionAttribute(title) +{ + internal override IModdedOption? CreateOption(object? value, PropertyInfo property) + { + var option = new ModdedPlayerOption(Title, allowNone); + return option; + } + + /// + public override object GetValue() + { + if (HolderOption is not ModdedPlayerOption opt) + throw new InvalidOperationException($"Holder option for {Title} is not a ModdedPlayerOption."); + + if (ValueType == typeof(int)) + return opt.Value; + + return opt.SelectedPlayer!; + } + + /// + public override void SetValue(object value) + { + if (HolderOption is not ModdedPlayerOption opt) + return; + + var final = value switch + { + NetworkedPlayerInfo info => opt.Values.IndexOf(info), + _ => (int)value, + }; + opt?.SetValue(final); + } +} diff --git a/MiraAPI/GameOptions/Attributes/ModdedStringOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedStringOptionAttribute.cs index 2b1853f..9086b1f 100644 --- a/MiraAPI/GameOptions/Attributes/ModdedStringOptionAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/ModdedStringOptionAttribute.cs @@ -25,7 +25,7 @@ public class ModdedStringOptionAttribute( public override object GetValue() { return HolderOption is ModdedStringOption opt - ? (object)opt.Value + ? opt.Value : throw new InvalidOperationException($"Holder option for {Title} is not a ModdedStringOption."); } diff --git a/MiraAPI/GameOptions/Attributes/ModdedToggleOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedToggleOptionAttribute.cs index 8f570e6..2a2ac2f 100644 --- a/MiraAPI/GameOptions/Attributes/ModdedToggleOptionAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/ModdedToggleOptionAttribute.cs @@ -28,7 +28,7 @@ public class ModdedToggleOptionAttribute(string title) : ModdedOptionAttribute(t public override object GetValue() { return HolderOption is ModdedToggleOption opt - ? (object)opt.Value + ? opt.Value : throw new InvalidOperationException($"Holder option for {Title} is not a ModdedToggleOption."); } } diff --git a/MiraAPI/GameOptions/Attributes/OptionList/ModdedEnumOptionListAttribute.cs b/MiraAPI/GameOptions/Attributes/OptionList/ModdedEnumOptionListAttribute.cs index ec8d09f..bf2fdf0 100644 --- a/MiraAPI/GameOptions/Attributes/OptionList/ModdedEnumOptionListAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/OptionList/ModdedEnumOptionListAttribute.cs @@ -34,7 +34,7 @@ public class ModdedEnumOptionListAttribute(string title, Type enumType, string[] { return HolderOptionList?[idx] is ModdedEnumOption opt ? Enum.ToObject(enumType, opt.Value) - : throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" with EnumType ${enumType.FullName} is not a ModdedEnumOption"); + : throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" with EnumType {enumType.FullName} is not a ModdedEnumOption"); } } @@ -68,6 +68,6 @@ public class ModdedEnumOptionListAttribute(string title, string[]? values = n { return HolderOptionList?[idx] is ModdedEnumOption opt ? opt.Value - : throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" with EnumType ${typeof(T).FullName} is not a ModdedEnumOption"); + : throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" with EnumType {typeof(T).FullName} is not a ModdedEnumOption<{typeof(T).FullName}>"); } } diff --git a/MiraAPI/GameOptions/Attributes/OptionList/ModdedNumberOptionListAttribute.cs b/MiraAPI/GameOptions/Attributes/OptionList/ModdedNumberOptionListAttribute.cs index d9eec55..088883e 100644 --- a/MiraAPI/GameOptions/Attributes/OptionList/ModdedNumberOptionListAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/OptionList/ModdedNumberOptionListAttribute.cs @@ -31,9 +31,16 @@ public class ModdedNumberOptionListAttribute( { return new ModdedOptionList(value.Count, idx => { + var final = value[idx] switch + { + null => min + increment, + int integer => integer, + _ => (float)value[idx]!, + }; + return new( GetFormattedTitle(idx), - (float)(value[idx] ?? min + increment), + final, min, max, increment, @@ -46,15 +53,28 @@ public class ModdedNumberOptionListAttribute( /// public override void SetValue(int idx, object value) { - var opt = HolderOptionList?[idx] as ModdedNumberOption; - opt?.SetValue((float)value); + if (HolderOptionList?[idx] is not ModdedNumberOption opt) + return; + + var final = value switch + { + int integer => integer, + _ => (float)value, + }; + opt.SetValue(final); } /// public override object GetValue(int idx) { - return HolderOptionList?[idx] is ModdedNumberOption opt - ? (object)opt.Value - : throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" is not a ModdedNumberOption"); + if (HolderOptionList?[idx] is not ModdedNumberOption opt) + throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" is not a ModdedNumberOption"); + + var value = opt.Value; + + if (ValueType == typeof(int)) + return (int)Math.Round(value); + + return value; } } diff --git a/MiraAPI/GameOptions/Attributes/OptionList/ModdedOptionListAttribute.cs b/MiraAPI/GameOptions/Attributes/OptionList/ModdedOptionListAttribute.cs index d0ee68e..d7ef6e6 100644 --- a/MiraAPI/GameOptions/Attributes/OptionList/ModdedOptionListAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/OptionList/ModdedOptionListAttribute.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.RegularExpressions; +using MiraAPI.Translation; namespace MiraAPI.GameOptions.Attributes; @@ -14,8 +15,8 @@ namespace MiraAPI.GameOptions.Attributes; public abstract class ModdedOptionListAttribute(string title) : PropertyOptionAttribute { internal IModdedOptionList? HolderOptionList { get; set; } - - internal object? Value { get; set; } + internal IList? Value { get; set; } + internal Type ValueType { get; set; } /// /// Gets the title of the options. @@ -31,16 +32,16 @@ public abstract class ModdedOptionListAttribute(string title) : PropertyOptionAt /// The new values as an object. public override void SetValue(object value) { - Value = value; - var list = (IList)value; - if (list.Count != HolderOptionList!.Count) + Value = (IList)value; + + if (Value.Count != HolderOptionList!.Count) { throw new InvalidOperationException($"Value set to option list cannot change the list's length."); } - for (int i = 0; i < list.Count; i++) + for (int i = 0; i < Value.Count; i++) { - SetValue(i, list[i]!); + SetValue(i, Value[i]!); } } @@ -77,18 +78,19 @@ public abstract class ModdedOptionListAttribute(string title) : PropertyOptionAt [SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "I don't even know if this is worth it.")] protected string GetFormattedTitle(int index) { - Match match = Regex.Match(Title, @"\{(\d+)(?::[^}]+)?\}", default, Regex.InfiniteMatchTimeout); + var match = Regex.Match(Title.Translate(), @"\{(\d+)(?::[^}]+)?\}", default, Regex.InfiniteMatchTimeout); - if (match.Success) + if (!match.Success) { - if (match.Groups.Count == 1 && match.Groups[0].Value == "0") - { - return string.Format(Title, index); - } - - Error("ModdedOptionList Title cannot contain more than the first parameter for formatting."); + return Title + index; } + if (match.Value == "{0}" && !match.NextMatch().Success) + { + return string.Format(Title, index); + } + + Error($"ModdedOptionList Title of {Title} cannot contain more than the first parameter for formatting."); return Title + index; } } diff --git a/MiraAPI/GameOptions/Attributes/OptionList/ModdedPlayerOptionListAttribute.cs b/MiraAPI/GameOptions/Attributes/OptionList/ModdedPlayerOptionListAttribute.cs new file mode 100644 index 0000000..bbf3900 --- /dev/null +++ b/MiraAPI/GameOptions/Attributes/OptionList/ModdedPlayerOptionListAttribute.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections; +using System.Reflection; +using MiraAPI.GameOptions.OptionTypes; + +namespace MiraAPI.GameOptions.Attributes; + +/// +/// Attribute for creating a list of player options. +/// +/// The option title. +/// Whether the option includes a none option. If None is selected, value will return -1. +[AttributeUsage(AttributeTargets.Property)] +public class ModdedPlayerOptionListAttribute( + string title, + bool allowNone = true) + : ModdedOptionListAttribute(title) +{ + internal override IModdedOptionList CreateOptionList(IList value, PropertyInfo property) + { + return new ModdedOptionList(value.Count, idx => + { + return new(GetFormattedTitle(idx), allowNone); + }); + } + + /// + public override void SetValue(int idx, object value) + { + if (HolderOptionList?[idx] is not ModdedPlayerOption opt) + return; + + var final = value switch + { + NetworkedPlayerInfo info => opt.Values.IndexOf(info), + _ => (int)value, + }; + opt?.SetValue(final); + } + + /// + public override object GetValue(int idx) + { + if (HolderOptionList?[idx] is not ModdedNumberOption opt) + throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" is not a ModdedNumberOption"); + + var value = opt.Value; + + if (ValueType == typeof(int)) + return (int)Math.Round(value); + + return value; + } +} diff --git a/MiraAPI/GameOptions/Attributes/OptionList/ModdedStringOptionList.cs b/MiraAPI/GameOptions/Attributes/OptionList/ModdedStringOptionList.cs index 461ee54..78c4b66 100644 --- a/MiraAPI/GameOptions/Attributes/OptionList/ModdedStringOptionList.cs +++ b/MiraAPI/GameOptions/Attributes/OptionList/ModdedStringOptionList.cs @@ -36,7 +36,7 @@ public class ModdedStringOptionListAttribute( public override object GetValue(int idx) { return HolderOptionList?[idx] is ModdedStringOption opt - ? (object)opt.Value + ? opt.Value : throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" is not a ModdedStringOption"); } } diff --git a/MiraAPI/GameOptions/Attributes/OptionList/ModdedToggleOptionListAttribute.cs b/MiraAPI/GameOptions/Attributes/OptionList/ModdedToggleOptionListAttribute.cs index 0bcb0a6..4e01069 100644 --- a/MiraAPI/GameOptions/Attributes/OptionList/ModdedToggleOptionListAttribute.cs +++ b/MiraAPI/GameOptions/Attributes/OptionList/ModdedToggleOptionListAttribute.cs @@ -30,7 +30,7 @@ public class ModdedToggleOptionListAttribute(string title) : ModdedOptionListAtt public override object GetValue(int idx) { return HolderOptionList?[idx] is ModdedToggleOption opt - ? (object)opt.Value + ? opt.Value : throw new InvalidOperationException($"Holder option for {GetFormattedTitle(idx)} is not a ModdedToggleOption."); } } diff --git a/MiraAPI/GameOptions/ModdedOptionsManager.cs b/MiraAPI/GameOptions/ModdedOptionsManager.cs index 4dc150f..4cc8b03 100644 --- a/MiraAPI/GameOptions/ModdedOptionsManager.cs +++ b/MiraAPI/GameOptions/ModdedOptionsManager.cs @@ -62,14 +62,14 @@ public static class ModdedOptionsManager internal static bool RegisterGroup(Type type) { - if (Activator.CreateInstance(type) is not AbstractOptionGroup group) + if (TypeToGroup.ContainsKey(type)) { + Error($"Group {type.Name} already exists."); return false; } - if (TypeToGroup.ContainsKey(type)) + if (Activator.CreateInstance(type) is not AbstractOptionGroup group) { - Logger.Error($"Group {type.Name} already exists."); return false; } @@ -79,17 +79,17 @@ public static class ModdedOptionsManager internal static bool RegisterGroup(Type type, MiraPluginInfo pluginInfo) { - if (Activator.CreateInstance(type) is not AbstractOptionGroup group) - { - return false; - } - if (TypeToGroup.ContainsKey(type)) { Error($"Group {type.Name} already exists."); return false; } + if (Activator.CreateInstance(type) is not AbstractOptionGroup group) + { + return false; + } + if (typeof(AbstractGameMode).IsAssignableFrom(group.OptionableType)) { if (GameModeOptionGroups.TryGetValue(group.OptionableType, out var oldList)) @@ -117,7 +117,7 @@ public static class ModdedOptionsManager Groups.Add(group); TypeToGroup.Add(type, group); typeof(OptionGroupSingleton<>).MakeGenericType(type) - .GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)! // Message suppression points to here + .GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)! .SetValue(null, group); } @@ -164,16 +164,21 @@ public static class ModdedOptionsManager return; } - var setterOriginal = property.GetSetMethod(true); - var setterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertySetterPatch)); - PluginSingleton.Instance.Harmony.Patch(setterOriginal, postfix: new HarmonyMethod(setterPatch)); - var getterOriginal = property.GetGetMethod(true); var getterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyGetterPatch)); PluginSingleton.Instance.Harmony.Patch(getterOriginal, prefix: new HarmonyMethod(getterPatch)); + var setterOriginal = property.GetSetMethod(true); + + if (setterOriginal != null) + { + var setterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertySetterPatch)); + PluginSingleton.Instance.Harmony.Patch(setterOriginal, postfix: new HarmonyMethod(setterPatch)); + } + OptionAttributes.Add(property, attribute); attribute.HolderOption = option; + attribute.ValueType = property.PropertyType; var visibilityAttr = property.GetCustomAttribute(); var visibilityFunc = visibilityAttr?.GetVisibility(group, property); @@ -220,9 +225,9 @@ public static class ModdedOptionsManager return; } - Func configName = optionList.ConfigName ?? (i => property.Name + (i + 1)); + var configName = optionList.ConfigName ?? (i => property.Name + (i + 1)); - for (int i = 0; i < optionList.Count; i++) + for (var i = 0; i < optionList.Count; i++) { RegisterOption(optionList[i], group, configName(i), pluginInfo); } @@ -269,32 +274,41 @@ public static class ModdedOptionsManager return; } - var setterOriginal = property.GetSetMethod(true); - var setterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertySetterPatch)); - PluginSingleton.Instance.Harmony.Patch(setterOriginal, postfix: new HarmonyMethod(setterPatch)); - var getterOriginal = property.GetGetMethod(true); var getterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyGetterPatch)); PluginSingleton.Instance.Harmony.Patch(getterOriginal, prefix: new HarmonyMethod(getterPatch)); + var setterOriginal = property.GetSetMethod(true); + + if (setterOriginal != null) + { + var setterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertySetterPatch)); + PluginSingleton.Instance.Harmony.Patch(setterOriginal, postfix: new HarmonyMethod(setterPatch)); + } + var listType = propertyList.GetType(); if (PatchedListTypes.Add(listType)) { var listIndex = listType.GetProperty("Item")!; - var listSetterOriginal = listIndex.GetSetMethod(true); - var listSetterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyListSetterPatch)); - PluginSingleton.Instance.Harmony.Patch(listSetterOriginal, postfix: new HarmonyMethod(listSetterPatch)); - var listGetterOriginal = listIndex.GetGetMethod(true); var listGetterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyListGetterPatch)); PluginSingleton.Instance.Harmony.Patch(listGetterOriginal, prefix: new HarmonyMethod(listGetterPatch)); + + var listSetterOriginal = listIndex.GetSetMethod(true); + + if (listSetterOriginal != null) + { + var listSetterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyListSetterPatch)); + PluginSingleton.Instance.Harmony.Patch(listSetterOriginal, postfix: new HarmonyMethod(listSetterPatch)); + } } OptionAttributes.Add(property, attribute); attribute.HolderOptionList = optionList; - attribute.Value = propertyVal; + attribute.Value = (IList)propertyVal; + attribute.ValueType = listType.GetGenericArguments()[0]; var valueType = property.PropertyType.GetGenericArguments()[0]; diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs index 5d0b792..7931d02 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs @@ -50,7 +50,7 @@ public class ModdedEnumOption : ModdedOption $"{ParentMod!.OptionsTitleText}.EnumOption.{TranslationController.Instance.GetString(StringName)}"; stringOption.SetUpFromData(Data, 20); - stringOption.OnValueChanged = (Il2CppSystem.Action)ValueChanged; + stringOption.OnValueChanged = (Action)ValueChanged; // Changing the button text to make them more distinct from number options stringOption.MinusBtn.ChangeButtonText("<"); @@ -153,7 +153,7 @@ public class ModdedEnumOption : ModdedOption $"{ParentMod!.OptionsTitleText}.EnumOption.{TranslationController.Instance.GetString(StringName)}"; stringOption.SetUpFromData(Data, 20); - stringOption.OnValueChanged = (Il2CppSystem.Action)ValueChanged; + stringOption.OnValueChanged = (Action)ValueChanged; // Changing the button text to make them more distinct from number options stringOption.MinusBtn.ChangeButtonText("<"); diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs index a45201f..f27ed13 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs @@ -78,8 +78,36 @@ public class ModdedNumberOption : ModdedOption string? formatString = null, bool halfIncrements = false, bool includeInPreset = true) - : this(title, defaultValue, min, max, increment, suffixType, zeroBehavior, negativeBehavior, halfIncrements, formatString, includeInPreset) + : base(title, defaultValue, includeInPreset) { + Min = min; + Max = max; + Increment = increment; + SuffixType = suffixType; + ZeroWordValue = zeroBehavior; + ShiftIncrement = halfIncrements; + NegativeWordValue = negativeBehavior; + + Value = Mathf.Clamp(defaultValue, min, max); + + var data = ScriptableObject.CreateInstance(); + data.Type = global::OptionTypes.Float; + data.Title = StringName; + data.Value = Value; + data.Increment = Increment; + data.ValidRange = new FloatRange(Min, Max); + data.FormatString = formatString ?? + (defaultValue.IsInteger() && + Increment.IsInteger() && + Value.IsInteger() && + Min.IsInteger() && + Max.IsInteger() ? "0" : "0.0"); + + data.ZeroIsInfinity = ZeroInfinity; + data.SuffixType = (NumberSuffixes)SuffixType; + data.OptionName = FloatOptionNames.Invalid; + + Data = data; } /// @@ -104,54 +132,10 @@ public class ModdedNumberOption : ModdedOption string? formatString = null, bool zeroInfinity = false, bool includeInPreset = true) - : this(title, defaultValue, min, max, increment, suffixType, zeroInfinity ? "∞" : "#", "#", increment == 1f, formatString, includeInPreset) + : this(title, defaultValue, min, max, increment, zeroInfinity ? "∞" : "#", "#", suffixType, formatString, increment == 1f, includeInPreset) { } - private ModdedNumberOption( - string title, - float defaultValue, - float min, - float max, - float increment, - MiraNumberSuffixes suffixType, - string zeroBehavior, - string negativeBehavior, - bool shiftIncrement, - string? formatString, - bool includeInPreset) - : base(title, defaultValue, includeInPreset) - { - Min = min; - Max = max; - Increment = increment; - SuffixType = suffixType; - ZeroWordValue = zeroBehavior; - ShiftIncrement = shiftIncrement; - NegativeWordValue = negativeBehavior; - - Value = Mathf.Clamp(defaultValue, min, max); - - var data = ScriptableObject.CreateInstance(); - data.Type = global::OptionTypes.Float; - data.Title = StringName; - data.Value = Value; - data.Increment = Increment; - data.ValidRange = new FloatRange(Min, Max); - data.FormatString = formatString ?? - (defaultValue.IsInteger() && - Increment.IsInteger() && - Value.IsInteger() && - Min.IsInteger() && - Max.IsInteger() ? "0" : "0.0"); - - data.ZeroIsInfinity = ZeroInfinity; - data.SuffixType = (NumberSuffixes)SuffixType; - data.OptionName = FloatOptionNames.Invalid; - - Data = data; - } - /// public override OptionBehaviour CreateOption( ToggleOption toggleOpt, diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs index e45c81e..513aad0 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs @@ -29,6 +29,11 @@ public class ModdedPlayerOption : ModdedOption /// public bool AllowNone { get; set; } + /// + /// Gets the player selected by this option. May be null if allowed to have None as an option. + /// + public NetworkedPlayerInfo? SelectedPlayer => Value == -1 ? null : Values[Value]; + /// /// Initializes a new instance of the class. /// @@ -64,7 +69,7 @@ public class ModdedPlayerOption : ModdedOption Values = GetFilteredPlayers(); playerOption.SetUpFromData(Data, 20); - playerOption.OnValueChanged = (Il2CppSystem.Action)ValueChanged; + playerOption.OnValueChanged = (Action)ValueChanged; playerOption.Title = StringName; playerOption.TitleText.text = Title.Translate(); diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs index 291ba67..b1c9cc1 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs @@ -124,7 +124,7 @@ public class ModdedStringOption : ModdedOption $"{ParentMod!.OptionsTitleText}.StringOption.{TranslationController.Instance.GetString(StringName)}"; stringOption.SetUpFromData(Data, 20); - stringOption.OnValueChanged = (Il2CppSystem.Action)ValueChanged; + stringOption.OnValueChanged = (Action)ValueChanged; // Changing the button text to make them more distinct from number options stringOption.MinusBtn.ChangeButtonText("<"); diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs index 1a331d4..627245a 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs @@ -11,6 +11,11 @@ namespace MiraAPI.GameOptions.OptionTypes; /// public class ModdedToggleOption : ModdedOption { + /// + /// Gets or sets the delegate that dictates how a toggle option's value is displayed. + /// + public Func DisplayValue { get; set; } = value => value ? "On" : "Off"; + /// /// Initializes a new instance of the class. /// @@ -39,7 +44,7 @@ public class ModdedToggleOption : ModdedOption toggleOption.Title = StringName; toggleOption.TitleText.text = Title.Translate(); toggleOption.CheckMark.enabled = Value; - toggleOption.OnValueChanged = (Il2CppSystem.Action)ValueChanged; + toggleOption.OnValueChanged = (Action)ValueChanged; OptionBehaviour = toggleOption; @@ -76,7 +81,7 @@ public class ModdedToggleOption : ModdedOption ModdedOptionsManager.AddSettingsChangeMessage( HudManager.Instance.Notifier, StringName, - newValue ? "On" : "Off", + DisplayValue(newValue).Translate(), Configuration.PopUpTextColor, Configuration.PopUpIconTmp, false); diff --git a/MiraAPI/HnsReimplemented/HideAndSeekHudHelper.cs b/MiraAPI/HnsReimplemented/HideAndSeekHudHelper.cs index 2f7d67a..89347e0 100644 --- a/MiraAPI/HnsReimplemented/HideAndSeekHudHelper.cs +++ b/MiraAPI/HnsReimplemented/HideAndSeekHudHelper.cs @@ -126,7 +126,7 @@ public sealed class HideAndSeekHudHelper(nint cppPtr) : MonoBehaviour(cppPtr) currentFinalHideTime = totalFinalHideTime; if (timerBar != null) { - Object.Destroy(timerBar); + Destroy(timerBar); } finalHideAlertSfx = GameManagerCreator.Instance.HideAndSeekManagerPrefab.FinalHideAlertSFX; diff --git a/MiraAPI/Hud/MiraHudHelper.cs b/MiraAPI/Hud/MiraHudHelper.cs index f2d1d4b..27c1968 100644 --- a/MiraAPI/Hud/MiraHudHelper.cs +++ b/MiraAPI/Hud/MiraHudHelper.cs @@ -151,7 +151,7 @@ public sealed class MiraHudHelper(nint cppPtr) : MonoBehaviour(cppPtr) var oldPos = settingsButton.transform.localPosition; settingsButton.transform.localPosition = new Vector3(oldPos.x, oldPos.y, -100); var chatButton = instance.Chat.chatButton.gameObject; - ClonedChatButton = Object.Instantiate(chatButton, chatButton.transform.parent); + ClonedChatButton = Instantiate(chatButton, chatButton.transform.parent); ClonedChatButton.SetActive(false); instance.Chat.chatButtonAspectPosition = ClonedChatButton.GetComponent(); chatButton.GetComponent().Destroy(); diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index b46d155..5ce5636 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -313,9 +313,9 @@ public sealed class MiraPluginManager foreach (var property in type.GetProperties()) { - if (property.GetMethod?.IsStatic == true) + if (property.GetMethod?.IsStatic == true && property.GetCustomAttribute() == null) { - Error($"Option property {property.Name} in {type.Name} must not be static."); + Error($"Non-ignored option property {property.Name} in {type.Name} must not be static."); continue; } @@ -325,6 +325,12 @@ public sealed class MiraPluginManager continue; } + if (property.PropertyType.IsAssignableTo(typeof(IModdedOptionList))) + { + ModdedOptionsManager.RegisterPropertyOptionList(type, property, pluginInfo); + continue; + } + var attribute = property.GetCustomAttribute(); if (attribute != null) { @@ -332,11 +338,6 @@ public sealed class MiraPluginManager continue; } - if (property.PropertyType.IsAssignableTo(typeof(IModdedOptionList))) - { - ModdedOptionsManager.RegisterPropertyOptionList(type, property, pluginInfo); - } - var listAttr = property.GetCustomAttribute(); if (listAttr != null) {