diff --git a/Example mod/ConfigExamples.cs b/Example mod/ConfigExamples.cs index 33f351a5..bf341d17 100644 --- a/Example mod/ConfigExamples.cs +++ b/Example mod/ConfigExamples.cs @@ -157,7 +157,7 @@ public class Config : ConfigFile /// Here, we are specifying the name of a method which can handle any OnChange event, for the purposes of demonstrating /// its usage. See for an example usage. /// - [Choice("My index-based choice", "One", "Two", "Three"), OnChange(nameof(MyChoiceValueChangedEvent))] + [Choice("My index-based choice", "1", "2", "3"), OnChange(nameof(MyChoiceValueChangedEvent))] public int ChoiceIndex; /// @@ -182,7 +182,7 @@ public class Config : ConfigFile /// An option of will be represented by the "1", /// will be represented by the "2", and so on. /// - [Choice("My customised enum-based choice", "1", "2", "3"), OnChange(nameof(MyChoiceValueChangedEvent))] + [Choice("My customised enum-based choice", "One", "Three"), OnChange(nameof(MyChoiceValueChangedEvent))] public CustomChoice ChoiceCustomEnum; /// @@ -273,10 +273,15 @@ private void MyToggleValueChangedEvent(ToggleChangedEventArgs e) // On change events for different types of options: - private void MyChoiceValueChangedEvent(ChoiceChangedEventArgs e) + private void MyChoiceValueChangedEvent(object sender, ChoiceChangedEventArgs e) { - T choice = e.Value; - ConfigExamples.LogSource.LogInfo($"Choice value {e.Id} was changed: {choice}"); + if (e != null) + { + ConfigExamples.LogSource.LogInfo($"Choice value {e.Value} was changed by {sender}"); + return; + } + + ConfigExamples.LogSource.LogInfo($"Choice value was changed by {sender} but event was null!"); } private void MyKeybindValueChangedEvent(KeybindChangedEventArgs e) diff --git a/Nautilus/Options/Attributes/ConfigFileMetadata.cs b/Nautilus/Options/Attributes/ConfigFileMetadata.cs index 4af3120e..060a5190 100644 --- a/Nautilus/Options/Attributes/ConfigFileMetadata.cs +++ b/Nautilus/Options/Attributes/ConfigFileMetadata.cs @@ -4,9 +4,11 @@ using System.Diagnostics; using System.Linq; using System.Reflection; +using HarmonyLib; using Nautilus.Json; using Nautilus.Utility; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using TMPro; using UnityEngine; @@ -222,7 +224,7 @@ private void addModOptionMetadata(MemberInfo memberInfo, MemberType Name = memberInfo.Name, ValueType = underlyingType }, - OnGameObjectCreatedMetadata = GetEventMetadata(memberInfo) + OnGameObjectCreatedMetadata = GetEventMetadata(memberInfo, underlyingType) }; if (memberType == MemberType.Method) @@ -232,7 +234,7 @@ private void addModOptionMetadata(MemberInfo memberInfo, MemberType if (typeof(TAttribute) != typeof(ButtonAttribute)) { - modOptionMetadata.OnChangeMetadata = GetEventMetadata(memberInfo); + modOptionMetadata.OnChangeMetadata = GetEventMetadata(memberInfo, underlyingType); } ModOptionAttributesMetadata.Add(modOptionAttribute.Id, modOptionMetadata); @@ -250,8 +252,9 @@ private void addModOptionMetadata(MemberInfo memberInfo, MemberType /// The type of attribute defined on the member to gather metadata for. /// /// The member to gather attribute metadata for. + /// The type of the option value. /// - private IEnumerable> GetEventMetadata(MemberInfo memberInfo) + private IEnumerable> GetEventMetadata(MemberInfo memberInfo, Type underlyingType) where TAttribute : ModOptionEventAttribute { List> metadatas = new(); @@ -260,7 +263,8 @@ private IEnumerable> GetEventMetadata(MemberIn MemberInfoMetadata methodMetadata = new() { MemberType = MemberType.Method, - Name = attribute.MethodName + Name = attribute.MethodName, + ValueType = underlyingType }; methodMetadata.ParseMethodParameterTypes(); metadatas.Add(methodMetadata); @@ -568,66 +572,102 @@ private void InvokeOnChangeEvents(ModOptionAttributeMetadata modOptionMetadat case ChoiceAttribute choiceAttribute when memberInfoMetadata.ValueType.IsEnum && (choiceAttribute.Options == null || !choiceAttribute.Options.Any()): // Enum-based choice where the values are parsed from the enum type - { - string[] options = Enum.GetNames(memberInfoMetadata.ValueType); - var value = (T)memberInfoMetadata.GetValue(Config); - ChoiceChangedEventArgs eventArgs = new(id, Array.IndexOf(options, value), value); - InvokeOnChangeEvents(modOptionMetadata, sender, eventArgs); - } + { + try + { + string[] options = Enum.GetNames(memberInfoMetadata.ValueType); + var value = (T) memberInfoMetadata.GetValue(Config); + var eventArgsConstructor = typeof(ChoiceChangedEventArgs<>).MakeGenericType(memberInfoMetadata.ValueType); + var eventArgs = Activator.CreateInstance(eventArgsConstructor, id, Array.IndexOf(options, value), value); + AccessTools.Method(this.GetType(), nameof(InvokeOnChangeEvents)) + .MakeGenericMethod(eventArgsConstructor) + .Invoke(this, new object[] { modOptionMetadata, sender, eventArgs }); + } + catch (Exception ex) + { + InternalLogger.Error($"[OptionsMenuBuilder] {ex.Message}"); + } + } break; - case ChoiceAttribute _ when memberInfoMetadata.ValueType.IsEnum: + case ChoiceAttribute choiceAttribute when memberInfoMetadata.ValueType.IsEnum: // Enum-based choice where the values are defined as custom strings - { - string value = memberInfoMetadata.GetValue(Config).ToString(); - int index = Math.Max(Array.IndexOf(Enum.GetValues(memberInfoMetadata.ValueType), value), 0); - ChoiceChangedEventArgs eventArgs = new(id, index, value); - InvokeOnChangeEvents(modOptionMetadata, sender, eventArgs); - } + { + try + { + string value = memberInfoMetadata.GetValue(Config).ToString(); + int index = Math.Max(Array.IndexOf(Enum.GetValues(memberInfoMetadata.ValueType), value), 0); + var eventArgsConstructor = typeof(ChoiceChangedEventArgs<>).MakeGenericType(memberInfoMetadata.ValueType); + var eventArgs = Activator.CreateInstance(eventArgsConstructor, id, Array.IndexOf(choiceAttribute.Options, value), value); + AccessTools.Method(this.GetType(), nameof(InvokeOnChangeEvents)) + .MakeGenericMethod(eventArgsConstructor) + .Invoke(this, new object[] { modOptionMetadata, sender, eventArgs }); + } + catch(Exception ex) + { + InternalLogger.Error($"[OptionsMenuBuilder] {ex.Message}"); + } + } break; case ChoiceAttribute choiceAttribute when memberInfoMetadata.ValueType == typeof(string): // string-based choice value - { - string[] options = choiceAttribute.Options; - string value = memberInfoMetadata.GetValue(Config); - ChoiceChangedEventArgs eventArgs = new(id, Array.IndexOf(options, value), value); - InvokeOnChangeEvents(modOptionMetadata, sender, eventArgs); - } + { + string[] options = choiceAttribute.Options; + string value = memberInfoMetadata.GetValue(Config); + ChoiceChangedEventArgs eventArgs = new(id, Array.IndexOf(options, value), value); + InvokeOnChangeEvents(modOptionMetadata, sender, eventArgs); + } break; case ChoiceAttribute choiceAttribute when memberInfoMetadata.ValueType == typeof(int): // index-based choice value - { - string[] options = choiceAttribute.Options; - int index = memberInfoMetadata.GetValue(Config); - ChoiceChangedEventArgs eventArgs = new(id, index, options[index]); - InvokeOnChangeEvents(modOptionMetadata, sender, eventArgs); - } + { + int[] options = new int[choiceAttribute.Options.Length]; + for (int i = 0; i < choiceAttribute.Options.Length; i++) + { + options[i] = int.Parse(choiceAttribute.Options[i]); + } + int index = memberInfoMetadata.GetValue(Config); + ChoiceChangedEventArgs eventArgs = new(id, index, options[index]); + InvokeOnChangeEvents(modOptionMetadata, sender, eventArgs); + } + break; + case ChoiceAttribute choiceAttribute: + { + string[] options = choiceAttribute.Options; + var value = (T) memberInfoMetadata.GetValue(Config); + int index = memberInfoMetadata.GetValue(Config); + var eventArgsConstructor = typeof(ChoiceChangedEventArgs<>).MakeGenericType(memberInfoMetadata.ValueType); + var eventArgs = Activator.CreateInstance(eventArgsConstructor, id, Array.IndexOf(options, value), value); + AccessTools.Method(this.GetType(), nameof(InvokeOnChangeEvents)) + .MakeGenericMethod(eventArgsConstructor) + .Invoke(this, new object[] { modOptionMetadata, sender, eventArgs }); + } break; case ColorPickerAttribute _: - { - ColorChangedEventArgs eventArgs = new(id, memberInfoMetadata.GetValue(Config)); - InvokeOnChangeEvents(modOptionMetadata, sender, eventArgs); - } + { + ColorChangedEventArgs eventArgs = new(id, memberInfoMetadata.GetValue(Config)); + InvokeOnChangeEvents(modOptionMetadata, sender, eventArgs); + } break; case KeybindAttribute _: - { - KeybindChangedEventArgs eventArgs = new(id, memberInfoMetadata.GetValue(Config)); - InvokeOnChangeEvents(modOptionMetadata, sender, eventArgs); - } + { + KeybindChangedEventArgs eventArgs = new(id, memberInfoMetadata.GetValue(Config)); + InvokeOnChangeEvents(modOptionMetadata, sender, eventArgs); + } break; case SliderAttribute _: - { - SliderChangedEventArgs eventArgs = new(id, Convert.ToSingle(memberInfoMetadata.GetValue(Config))); - InvokeOnChangeEvents(modOptionMetadata, sender, eventArgs); - } + { + SliderChangedEventArgs eventArgs = new(id, Convert.ToSingle(memberInfoMetadata.GetValue(Config))); + InvokeOnChangeEvents(modOptionMetadata, sender, eventArgs); + } break; case ToggleAttribute _: - { - ToggleChangedEventArgs eventArgs = new(id, memberInfoMetadata.GetValue(Config)); - InvokeOnChangeEvents(modOptionMetadata, sender, eventArgs); - } + { + ToggleChangedEventArgs eventArgs = new(id, memberInfoMetadata.GetValue(Config)); + InvokeOnChangeEvents(modOptionMetadata, sender, eventArgs); + } break; } } @@ -681,21 +721,39 @@ private void InvokeEvent(MemberInfoMetadata memberInfoMetadata, obje for (int i = 0; i < parameterTypes.Length; i++) { - if (!senderFound && parameterTypes[i] == typeof(object)) + var type = parameterTypes[i]; + + if (!senderFound && type == typeof(object)) { senderFound = true; parameters[i] = sender; } - else if (!eventArgsFound && parameterTypes[i] == typeof(TSource)) + else if (!eventArgsFound && type == typeof(TSource)) { eventArgsFound = true; parameters[i] = e; } - else if (!modOptionEventFound && parameterTypes[i] == typeof(EventArgs)) + else if (!modOptionEventFound && type == typeof(EventArgs)) { modOptionEventFound = true; parameters[i] = e; } + else if (!modOptionEventFound && type.IsGenericType && + type.GetGenericTypeDefinition() == typeof(ChoiceChangedEventArgs<>)) + { + InternalLogger.Debug($"[OptionsMenuBuilder] Found a {type.Name}<{memberInfoMetadata.ValueType}> parameter for {typeof(T)}.{memberInfoMetadata.Name}"); + + var eventArgsConstructor = typeof(ChoiceChangedEventArgs<>).MakeGenericType(memberInfoMetadata.ValueType); + + var id = (string)eventArgsConstructor.GetProperty("Id").GetValue(e); + var index = (int) eventArgsConstructor.GetProperty("Index").GetValue(e); + var value = eventArgsConstructor.GetProperty("Value").GetValue(e); + + var eventArgs = Activator.CreateInstance(eventArgsConstructor, id, index, value); + + modOptionEventFound = true; + parameters[i] = eventArgs; + } if (senderFound && eventArgsFound && modOptionEventFound) { diff --git a/Nautilus/Options/Attributes/MemberInfoMetadata.cs b/Nautilus/Options/Attributes/MemberInfoMetadata.cs index 58d12bad..d39a918b 100644 --- a/Nautilus/Options/Attributes/MemberInfoMetadata.cs +++ b/Nautilus/Options/Attributes/MemberInfoMetadata.cs @@ -116,7 +116,14 @@ public void InvokeMethod(T config, params object[] arguments) return; } - Traverse.Create(config).Method(Name, MethodParameterTypes).GetValue(arguments); + MethodInfo methodInfo = AccessTools.Method(typeof(T), Name, MethodParameterTypes); + if (methodInfo.ContainsGenericParameters) + { + methodInfo.MakeGenericMethod(ValueType).Invoke(config, arguments); + return; + } + + methodInfo.Invoke(config, arguments); } public Action GetMethodAsAction(T config) diff --git a/Nautilus/Options/Attributes/OptionsMenuBuilder.cs b/Nautilus/Options/Attributes/OptionsMenuBuilder.cs index cefc8829..d9dcecbe 100644 --- a/Nautilus/Options/Attributes/OptionsMenuBuilder.cs +++ b/Nautilus/Options/Attributes/OptionsMenuBuilder.cs @@ -4,6 +4,7 @@ using System.Linq; using Nautilus.Json; using Nautilus.Utility; +using Newtonsoft.Json.Linq; using UnityEngine; namespace Nautilus.Options.Attributes; @@ -74,6 +75,7 @@ private void RouteEventHandler(object sender, EventArgs e) else if (e.GetType().IsGenericType && e.GetType().GetGenericTypeDefinition() == typeof(ChoiceChangedEventArgs<>)) { var genericParam = e.GetType().GetGenericArguments()[0]; + InternalLogger.Debug($"Generic param: {genericParam}"); var genericType = typeof(ChoiceChangedEventArgs<>).MakeGenericType(genericParam); var typedEvent = Convert.ChangeType(e, genericType); var methodInfo = ConfigFileMetadata.GetType().GetMethod(nameof(ConfigFileMetadata.HandleChoiceChanged)); @@ -186,21 +188,53 @@ private void BuildModChoiceOption(string id, string label, { if (memberInfoMetadata.ValueType.IsEnum && (choiceAttribute.Options == null || !choiceAttribute.Options.Any())) { - // Enum-based choice where the values are parsed from the enum type - string[] options = Enum.GetNames(memberInfoMetadata.ValueType); - string value = memberInfoMetadata.GetValue(ConfigFileMetadata.Config).ToString(); - if(!AddItem(ModChoiceOption.Create(id, label, options, value))) - InternalLogger.Warn($"Failed to add ModChoiceOption with id {id} to {Name}"); + try + { + // Enum-based choice where the values are parsed from the enum type + var options = Enum.GetValues(memberInfoMetadata.ValueType); + var value = memberInfoMetadata.GetValue(ConfigFileMetadata.Config); + Type type = memberInfoMetadata.ValueType; + Type arrayType = type.MakeArrayType(); + // make generic constructor for ModChoiceOption + var genericType = typeof(ModChoiceOption<>).MakeGenericType(memberInfoMetadata.ValueType); // get the Create method + var methodInfo = genericType.GetMethod("Create", new Type[] { typeof(string), typeof(string), arrayType, type, typeof(string) }); + OptionItem optionItem = (OptionItem) methodInfo.Invoke(null, new object[] { id, label, options, value, choiceAttribute.Tooltip }); + if (!AddItem(optionItem)) + InternalLogger.Warn($"Failed to add ModChoiceOption with id {id} to {Name}"); + } + catch (Exception e) + { + InternalLogger.Error($"Failed to add ModChoiceOption with id {id} to {Name} due to an error parsing the options: {e}"); + } } else if (memberInfoMetadata.ValueType.IsEnum) { - // Enum-based choice where the values are defined as custom strings - string[] options = choiceAttribute.Options; - string name = memberInfoMetadata.GetValue(ConfigFileMetadata.Config).ToString(); - int index = Math.Max(Array.IndexOf(Enum.GetNames(memberInfoMetadata.ValueType), name), 0); - if(!AddItem(ModChoiceOption.Create(id, label, options, index))) - InternalLogger.Warn($"Failed to add ModChoiceOption with id {id} to {Name}"); + try + { + // Enum-based choice where the values are defined as custom strings + var options = Array.CreateInstance(memberInfoMetadata.ValueType, choiceAttribute.Options.Length); + for (int i = 0; i < choiceAttribute.Options.Length; i++) + { + options.SetValue(Enum.Parse(memberInfoMetadata.ValueType, choiceAttribute.Options[i], true), i); + } + var option = memberInfoMetadata.GetValue(ConfigFileMetadata.Config); + int index = Array.IndexOf(options, option); + if (index < 0) + index = 0; + Type type = memberInfoMetadata.ValueType; + Type arrayType = type.MakeArrayType(); + // make generic constructor for ModChoiceOption + var genericType = typeof(ModChoiceOption<>).MakeGenericType(memberInfoMetadata.ValueType); // get the Create method + var methodInfo = genericType.GetMethod("Create", new Type[] { typeof(string), typeof(string), arrayType, typeof(int), typeof(string) }); + OptionItem optionItem = (OptionItem) methodInfo.Invoke(null, new object[] { id, label, options, index, choiceAttribute.Tooltip }); + if (!AddItem(optionItem)) + InternalLogger.Warn($"Failed to add ModChoiceOption with id {id} to {Name}"); + } + catch (Exception e) + { + InternalLogger.Error($"Failed to add ModChoiceOption with id {id} to {Name} due to an error parsing the options: {e}"); + } } else if (memberInfoMetadata.ValueType == typeof(string)) { @@ -212,11 +246,22 @@ private void BuildModChoiceOption(string id, string label, } else if (memberInfoMetadata.ValueType == typeof(int)) { - // index-based choice value - string[] options = choiceAttribute.Options; - int index = memberInfoMetadata.GetValue(ConfigFileMetadata.Config); - if(!AddItem(ModChoiceOption.Create(id, label, options, index))) - InternalLogger.Warn($"Failed to add ModChoiceOption with id {id} to {Name}"); + try + { + // index-based choice value + int[] options = new int[choiceAttribute.Options.Length]; + for (int i = 0; i < choiceAttribute.Options.Length; i++) + { + options[i] = int.Parse(choiceAttribute.Options[i]); + } + int index = memberInfoMetadata.GetValue(ConfigFileMetadata.Config); + if (!AddItem(ModChoiceOption.Create(id, label, options, index))) + InternalLogger.Warn($"Failed to add ModChoiceOption with id {id} to {Name}"); + } + catch (Exception e) + { + InternalLogger.Error($"Failed to add ModChoiceOption with id {id} to {Name} due to an error parsing the options: {e}"); + } } } diff --git a/Nautilus/Options/ModChoiceOption.cs b/Nautilus/Options/ModChoiceOption.cs index aea1c86c..03a001cf 100644 --- a/Nautilus/Options/ModChoiceOption.cs +++ b/Nautilus/Options/ModChoiceOption.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using Nautilus.Options.Utility; +using Nautilus.Utility; using UnityEngine; using UnityEngine.Events; @@ -18,7 +19,6 @@ public class ChoiceChangedEventArgs : ConfigOptionEventArgs /// public int Index { get; } - /// /// Constructs a new . /// @@ -79,6 +79,9 @@ public override void AddToPanel(uGUI_TabbedControlsPanel panel, int tabIndex) private ModChoiceOption(string id, string label, T[] options, int index, string tooltip) : base(label, id, options[index]) { + // log type of T + InternalLogger.Debug($"ModChoiceOption<{typeof(T)}> - {id} - {label} - {options} - {index} - {tooltip}"); + Options = options; List optionStrings = new List(); foreach(var option in options)