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)