more stuff

This commit is contained in:
Az committed 2026-10-15 20:46:41 -04:00
1 parent b074fdc330
commit 04b7701b79
23 files changed
+283 -124

No files matched your search

+7 -4
View File
@@ -27,19 +27,22 @@ public class ExampleOptions : AbstractOptionGroup
[ModdedOptionVisibility(nameof(NumberOptVisibility))] // Can also target static methods [ModdedOptionVisibility(nameof(NumberOptVisibility))] // Can also target static methods
public float NumberOpt { get; set; } = 4f; 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"])] [ModdedEnumOption("options.exampleOptions1.bestApi", typeof(Api), ["Mira API", "Mitochondria", "Reactor"])]
public Api BestApi { get; set; } = Api.MiraAPI;
[ModdedEnumOption<Api>("options.exampleOptions1.worstApi", ["Mira API", "Mitochondria", "Reactor"])]
// These three can target both instance and static methods // These three can target both instance and static methods
[ModdedOptionGetter(nameof(GetApi))] // Can also automatically resolve to {Option Name}Getter [ModdedOptionGetter(nameof(GetApi))] // Can also automatically resolve to {Option Name}Getter
[ModdedOptionSetter(nameof(SetApi))] // Can also automatically resolve to {Option Name}Setter [ModdedOptionSetter(nameof(SetApi))] // Can also automatically resolve to {Option Name}Setter
[ModdedOptionChanged(nameof(ApiChanged))] // Can also automatically resolve to {Option Name}Changed [ModdedOptionChanged(nameof(ApiChanged))] // Can also automatically resolve to {Option Name}Changed
public Api BestApi { get; set; } = Api.MiraAPI;
[ModdedEnumOption<Api>("options.exampleOptions1.worstApi", ["Mira API", "Mitochondria", "Reactor"])]
public Api WorstApi { get; set; } = Api.MiraAPI; public Api WorstApi { get; set; } = Api.MiraAPI;
[ModdedEnumOptionList<ValueState>("Value State {0}", ["None", "Primary", "Secondary"])] [ModdedEnumOptionList<ValueState>("Value State {0}", ["None", "Primary", "Secondary"])]
public List<ValueState> ValueStates { get; } = public List<ValueState> ValueStates { get; set; } =
[ValueState.Primary, ValueState.Secondary, ValueState.None, ValueState.None, ValueState.None]; [ValueState.Primary, ValueState.Secondary, ValueState.None, ValueState.None, ValueState.None];
public bool ToggleOpt1Visibility() public bool ToggleOpt1Visibility()
@@ -47,6 +47,7 @@ Eliminate every other faction.</string>
<string name="options.exampleOptions1.toggleOpt2">Toggle Opt 2</string> <string name="options.exampleOptions1.toggleOpt2">Toggle Opt 2</string>
<string name="options.exampleOptions1.numberOpt">Number Opt</string> <string name="options.exampleOptions1.numberOpt">Number Opt</string>
<string name="options.exampleOptions1.bestApi">Best API</string> <string name="options.exampleOptions1.bestApi">Best API</string>
<string name="options.exampleOptions1.worstApi">Worst API</string>
<string name="options.exampleOptions2">Example Options 2</string> <string name="options.exampleOptions2">Example Options 2</string>
<string name="options.exampleOptions2.youMustChoseAPlayer">YOU MUST CHOSE A PLAYER</string> <string name="options.exampleOptions2.youMustChoseAPlayer">YOU MUST CHOSE A PLAYER</string>
<string name="options.exampleOptions2.ehWhatever">Eh whatever</string> <string name="options.exampleOptions2.ehWhatever">Eh whatever</string>
@@ -32,7 +32,7 @@ public class ModdedEnumOptionAttribute(string title, Type enumType, string[]? va
{ {
return HolderOption is ModdedEnumOption opt return HolderOption is ModdedEnumOption opt
? Enum.ToObject(enumType, opt.Value) ? 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<T>(string title, string[]? values = null)
{ {
return HolderOption is ModdedEnumOption<T> opt return HolderOption is ModdedEnumOption<T> opt
? opt.Value ? 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}>");
} }
} }
@@ -28,9 +28,16 @@ public class ModdedNumberOptionAttribute(
{ {
internal override IModdedOption CreateOption(object? value, PropertyInfo property) internal override IModdedOption CreateOption(object? value, PropertyInfo property)
{ {
var final = value switch
{
null => min + increment,
int integer => integer,
_ => (float)value,
};
return new ModdedNumberOption( return new ModdedNumberOption(
Title, Title,
(float)(value ?? min + increment), final,
min, min,
max, max,
increment, increment,
@@ -42,15 +49,28 @@ public class ModdedNumberOptionAttribute(
/// <inheritdoc /> /// <inheritdoc />
public override void SetValue(object value) public override void SetValue(object value)
{ {
var opt = HolderOption as ModdedNumberOption; if (HolderOption is not ModdedNumberOption opt)
opt?.SetValue((float)value); return;
var final = value switch
{
int integer => integer,
_ => (float)value,
};
opt.SetValue(final);
} }
/// <inheritdoc /> /// <inheritdoc />
public override object GetValue() public override object GetValue()
{ {
return HolderOption is ModdedNumberOption opt if (HolderOption is not ModdedNumberOption opt)
? (object)opt.Value throw new InvalidOperationException($"HolderOption for option \"{Title}\" is not a ModdedNumberOption");
: 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;
} }
} }
@@ -13,6 +13,7 @@ namespace MiraAPI.GameOptions.Attributes;
public abstract class ModdedOptionAttribute(string title, Type? roleType = null, Type? modeType = null) : PropertyOptionAttribute public abstract class ModdedOptionAttribute(string title, Type? roleType = null, Type? modeType = null) : PropertyOptionAttribute
{ {
internal IModdedOption? HolderOption { get; set; } internal IModdedOption? HolderOption { get; set; }
internal Type ValueType { get; set; }
/// <summary> /// <summary>
/// Gets the title of the option. /// Gets the title of the option.
@@ -0,0 +1,49 @@
using System;
using System.Reflection;
using MiraAPI.GameOptions.OptionTypes;
namespace MiraAPI.GameOptions.Attributes;
/// <summary>
/// Attribute for a <see cref="ModdedPlayerOption"/>.
/// </summary>
/// <param name="title">The title of the option.</param>
/// <param name="allowNone">Whether the option includes a none option. If None is selected, value will return -1.</param>
[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;
}
/// <inheritdoc />
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!;
}
/// <inheritdoc />
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);
}
}
@@ -25,7 +25,7 @@ public class ModdedStringOptionAttribute(
public override object GetValue() public override object GetValue()
{ {
return HolderOption is ModdedStringOption opt return HolderOption is ModdedStringOption opt
? (object)opt.Value ? opt.Value
: throw new InvalidOperationException($"Holder option for {Title} is not a ModdedStringOption."); : throw new InvalidOperationException($"Holder option for {Title} is not a ModdedStringOption.");
} }
@@ -28,7 +28,7 @@ public class ModdedToggleOptionAttribute(string title) : ModdedOptionAttribute(t
public override object GetValue() public override object GetValue()
{ {
return HolderOption is ModdedToggleOption opt return HolderOption is ModdedToggleOption opt
? (object)opt.Value ? opt.Value
: throw new InvalidOperationException($"Holder option for {Title} is not a ModdedToggleOption."); : throw new InvalidOperationException($"Holder option for {Title} is not a ModdedToggleOption.");
} }
} }
@@ -34,7 +34,7 @@ public class ModdedEnumOptionListAttribute(string title, Type enumType, string[]
{ {
return HolderOptionList?[idx] is ModdedEnumOption opt return HolderOptionList?[idx] is ModdedEnumOption opt
? Enum.ToObject(enumType, opt.Value) ? 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<T>(string title, string[]? values = n
{ {
return HolderOptionList?[idx] is ModdedEnumOption<T> opt return HolderOptionList?[idx] is ModdedEnumOption<T> opt
? opt.Value ? 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}>");
} }
} }
@@ -31,9 +31,16 @@ public class ModdedNumberOptionListAttribute(
{ {
return new ModdedOptionList<ModdedNumberOption>(value.Count, idx => return new ModdedOptionList<ModdedNumberOption>(value.Count, idx =>
{ {
var final = value[idx] switch
{
null => min + increment,
int integer => integer,
_ => (float)value[idx]!,
};
return new( return new(
GetFormattedTitle(idx), GetFormattedTitle(idx),
(float)(value[idx] ?? min + increment), final,
min, min,
max, max,
increment, increment,
@@ -46,15 +53,28 @@ public class ModdedNumberOptionListAttribute(
/// <inheritdoc /> /// <inheritdoc />
public override void SetValue(int idx, object value) public override void SetValue(int idx, object value)
{ {
var opt = HolderOptionList?[idx] as ModdedNumberOption; if (HolderOptionList?[idx] is not ModdedNumberOption opt)
opt?.SetValue((float)value); return;
var final = value switch
{
int integer => integer,
_ => (float)value,
};
opt.SetValue(final);
} }
/// <inheritdoc /> /// <inheritdoc />
public override object GetValue(int idx) public override object GetValue(int idx)
{ {
return HolderOptionList?[idx] is ModdedNumberOption opt if (HolderOptionList?[idx] is not ModdedNumberOption opt)
? (object)opt.Value throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" is not a ModdedNumberOption");
: 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;
} }
} }
@@ -3,6 +3,7 @@ using System.Collections;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using MiraAPI.Translation;
namespace MiraAPI.GameOptions.Attributes; namespace MiraAPI.GameOptions.Attributes;
@@ -14,8 +15,8 @@ namespace MiraAPI.GameOptions.Attributes;
public abstract class ModdedOptionListAttribute(string title) : PropertyOptionAttribute public abstract class ModdedOptionListAttribute(string title) : PropertyOptionAttribute
{ {
internal IModdedOptionList? HolderOptionList { get; set; } internal IModdedOptionList? HolderOptionList { get; set; }
internal IList? Value { get; set; }
internal object? Value { get; set; } internal Type ValueType { get; set; }
/// <summary> /// <summary>
/// Gets the title of the options. /// Gets the title of the options.
@@ -31,16 +32,16 @@ public abstract class ModdedOptionListAttribute(string title) : PropertyOptionAt
/// <param name="value">The new values as an object.</param> /// <param name="value">The new values as an object.</param>
public override void SetValue(object value) public override void SetValue(object value)
{ {
Value = value; Value = (IList)value;
var list = (IList)value;
if (list.Count != HolderOptionList!.Count) if (Value.Count != HolderOptionList!.Count)
{ {
throw new InvalidOperationException($"Value set to option list cannot change the list's length."); 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.")] [SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "I don't even know if this is worth it.")]
protected string GetFormattedTitle(int index) 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 Title + index;
{
return string.Format(Title, index);
}
Error("ModdedOptionList Title cannot contain more than the first parameter for formatting.");
} }
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; return Title + index;
} }
} }
@@ -0,0 +1,54 @@
using System;
using System.Collections;
using System.Reflection;
using MiraAPI.GameOptions.OptionTypes;
namespace MiraAPI.GameOptions.Attributes;
/// <summary>
/// Attribute for creating a list of player options.
/// </summary>
/// <param name="title">The option title.</param>
/// <param name="allowNone">Whether the option includes a none option. If None is selected, value will return -1.</param>
[AttributeUsage(AttributeTargets.Property)]
public class ModdedPlayerOptionListAttribute(
string title,
bool allowNone = true)
: ModdedOptionListAttribute(title)
{
internal override IModdedOptionList CreateOptionList(IList value, PropertyInfo property)
{
return new ModdedOptionList<ModdedPlayerOption>(value.Count, idx =>
{
return new(GetFormattedTitle(idx), allowNone);
});
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
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;
}
}
@@ -36,7 +36,7 @@ public class ModdedStringOptionListAttribute(
public override object GetValue(int idx) public override object GetValue(int idx)
{ {
return HolderOptionList?[idx] is ModdedStringOption opt return HolderOptionList?[idx] is ModdedStringOption opt
? (object)opt.Value ? opt.Value
: throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" is not a ModdedStringOption"); : throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" is not a ModdedStringOption");
} }
} }
@@ -30,7 +30,7 @@ public class ModdedToggleOptionListAttribute(string title) : ModdedOptionListAtt
public override object GetValue(int idx) public override object GetValue(int idx)
{ {
return HolderOptionList?[idx] is ModdedToggleOption opt return HolderOptionList?[idx] is ModdedToggleOption opt
? (object)opt.Value ? opt.Value
: throw new InvalidOperationException($"Holder option for {GetFormattedTitle(idx)} is not a ModdedToggleOption."); : throw new InvalidOperationException($"Holder option for {GetFormattedTitle(idx)} is not a ModdedToggleOption.");
} }
} }
+38 -24
View File
@@ -62,14 +62,14 @@ public static class ModdedOptionsManager
internal static bool RegisterGroup(Type type) 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; return false;
} }
if (TypeToGroup.ContainsKey(type)) if (Activator.CreateInstance(type) is not AbstractOptionGroup group)
{ {
Logger<MiraApiPlugin>.Error($"Group {type.Name} already exists.");
return false; return false;
} }
@@ -79,17 +79,17 @@ public static class ModdedOptionsManager
internal static bool RegisterGroup(Type type, MiraPluginInfo pluginInfo) internal static bool RegisterGroup(Type type, MiraPluginInfo pluginInfo)
{ {
if (Activator.CreateInstance(type) is not AbstractOptionGroup group)
{
return false;
}
if (TypeToGroup.ContainsKey(type)) if (TypeToGroup.ContainsKey(type))
{ {
Error($"Group {type.Name} already exists."); Error($"Group {type.Name} already exists.");
return false; return false;
} }
if (Activator.CreateInstance(type) is not AbstractOptionGroup group)
{
return false;
}
if (typeof(AbstractGameMode).IsAssignableFrom(group.OptionableType)) if (typeof(AbstractGameMode).IsAssignableFrom(group.OptionableType))
{ {
if (GameModeOptionGroups.TryGetValue(group.OptionableType, out var oldList)) if (GameModeOptionGroups.TryGetValue(group.OptionableType, out var oldList))
@@ -117,7 +117,7 @@ public static class ModdedOptionsManager
Groups.Add(group); Groups.Add(group);
TypeToGroup.Add(type, group); TypeToGroup.Add(type, group);
typeof(OptionGroupSingleton<>).MakeGenericType(type) typeof(OptionGroupSingleton<>).MakeGenericType(type)
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)! // Message suppression points to here .GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
.SetValue(null, group); .SetValue(null, group);
} }
@@ -164,16 +164,21 @@ public static class ModdedOptionsManager
return; return;
} }
var setterOriginal = property.GetSetMethod(true);
var setterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertySetterPatch));
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(setterOriginal, postfix: new HarmonyMethod(setterPatch));
var getterOriginal = property.GetGetMethod(true); var getterOriginal = property.GetGetMethod(true);
var getterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyGetterPatch)); var getterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyGetterPatch));
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(getterOriginal, prefix: new HarmonyMethod(getterPatch)); PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(getterOriginal, prefix: new HarmonyMethod(getterPatch));
var setterOriginal = property.GetSetMethod(true);
if (setterOriginal != null)
{
var setterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertySetterPatch));
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(setterOriginal, postfix: new HarmonyMethod(setterPatch));
}
OptionAttributes.Add(property, attribute); OptionAttributes.Add(property, attribute);
attribute.HolderOption = option; attribute.HolderOption = option;
attribute.ValueType = property.PropertyType;
var visibilityAttr = property.GetCustomAttribute<ModdedOptionVisibilityAttribute>(); var visibilityAttr = property.GetCustomAttribute<ModdedOptionVisibilityAttribute>();
var visibilityFunc = visibilityAttr?.GetVisibility(group, property); var visibilityFunc = visibilityAttr?.GetVisibility(group, property);
@@ -220,9 +225,9 @@ public static class ModdedOptionsManager
return; return;
} }
Func<int, string> 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); RegisterOption(optionList[i], group, configName(i), pluginInfo);
} }
@@ -269,32 +274,41 @@ public static class ModdedOptionsManager
return; return;
} }
var setterOriginal = property.GetSetMethod(true);
var setterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertySetterPatch));
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(setterOriginal, postfix: new HarmonyMethod(setterPatch));
var getterOriginal = property.GetGetMethod(true); var getterOriginal = property.GetGetMethod(true);
var getterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyGetterPatch)); var getterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyGetterPatch));
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(getterOriginal, prefix: new HarmonyMethod(getterPatch)); PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(getterOriginal, prefix: new HarmonyMethod(getterPatch));
var setterOriginal = property.GetSetMethod(true);
if (setterOriginal != null)
{
var setterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertySetterPatch));
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(setterOriginal, postfix: new HarmonyMethod(setterPatch));
}
var listType = propertyList.GetType(); var listType = propertyList.GetType();
if (PatchedListTypes.Add(listType)) if (PatchedListTypes.Add(listType))
{ {
var listIndex = listType.GetProperty("Item")!; var listIndex = listType.GetProperty("Item")!;
var listSetterOriginal = listIndex.GetSetMethod(true);
var listSetterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyListSetterPatch));
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(listSetterOriginal, postfix: new HarmonyMethod(listSetterPatch));
var listGetterOriginal = listIndex.GetGetMethod(true); var listGetterOriginal = listIndex.GetGetMethod(true);
var listGetterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyListGetterPatch)); var listGetterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyListGetterPatch));
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(listGetterOriginal, prefix: new HarmonyMethod(listGetterPatch)); PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(listGetterOriginal, prefix: new HarmonyMethod(listGetterPatch));
var listSetterOriginal = listIndex.GetSetMethod(true);
if (listSetterOriginal != null)
{
var listSetterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyListSetterPatch));
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(listSetterOriginal, postfix: new HarmonyMethod(listSetterPatch));
}
} }
OptionAttributes.Add(property, attribute); OptionAttributes.Add(property, attribute);
attribute.HolderOptionList = optionList; attribute.HolderOptionList = optionList;
attribute.Value = propertyVal; attribute.Value = (IList)propertyVal;
attribute.ValueType = listType.GetGenericArguments()[0];
var valueType = property.PropertyType.GetGenericArguments()[0]; var valueType = property.PropertyType.GetGenericArguments()[0];
@@ -50,7 +50,7 @@ public class ModdedEnumOption : ModdedOption<int>
$"{ParentMod!.OptionsTitleText}.EnumOption.{TranslationController.Instance.GetString(StringName)}"; $"{ParentMod!.OptionsTitleText}.EnumOption.{TranslationController.Instance.GetString(StringName)}";
stringOption.SetUpFromData(Data, 20); stringOption.SetUpFromData(Data, 20);
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged; stringOption.OnValueChanged = (Action<OptionBehaviour>)ValueChanged;
// Changing the button text to make them more distinct from number options // Changing the button text to make them more distinct from number options
stringOption.MinusBtn.ChangeButtonText("<"); stringOption.MinusBtn.ChangeButtonText("<");
@@ -153,7 +153,7 @@ public class ModdedEnumOption<T> : ModdedOption<T>
$"{ParentMod!.OptionsTitleText}.EnumOption.{TranslationController.Instance.GetString(StringName)}"; $"{ParentMod!.OptionsTitleText}.EnumOption.{TranslationController.Instance.GetString(StringName)}";
stringOption.SetUpFromData(Data, 20); stringOption.SetUpFromData(Data, 20);
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged; stringOption.OnValueChanged = (Action<OptionBehaviour>)ValueChanged;
// Changing the button text to make them more distinct from number options // Changing the button text to make them more distinct from number options
stringOption.MinusBtn.ChangeButtonText("<"); stringOption.MinusBtn.ChangeButtonText("<");
@@ -78,8 +78,36 @@ public class ModdedNumberOption : ModdedOption<float>
string? formatString = null, string? formatString = null,
bool halfIncrements = false, bool halfIncrements = false,
bool includeInPreset = true) 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<FloatGameSetting>();
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;
} }
/// <summary> /// <summary>
@@ -104,54 +132,10 @@ public class ModdedNumberOption : ModdedOption<float>
string? formatString = null, string? formatString = null,
bool zeroInfinity = false, bool zeroInfinity = false,
bool includeInPreset = true) 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<FloatGameSetting>();
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;
}
/// <inheritdoc /> /// <inheritdoc />
public override OptionBehaviour CreateOption( public override OptionBehaviour CreateOption(
ToggleOption toggleOpt, ToggleOption toggleOpt,
@@ -29,6 +29,11 @@ public class ModdedPlayerOption : ModdedOption<int>
/// </summary> /// </summary>
public bool AllowNone { get; set; } public bool AllowNone { get; set; }
/// <summary>
/// Gets the player selected by this option. May be <c>null</c> if allowed to have <c>None</c> as an option.
/// </summary>
public NetworkedPlayerInfo? SelectedPlayer => Value == -1 ? null : Values[Value];
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ModdedPlayerOption"/> class. /// Initializes a new instance of the <see cref="ModdedPlayerOption"/> class.
/// </summary> /// </summary>
@@ -64,7 +69,7 @@ public class ModdedPlayerOption : ModdedOption<int>
Values = GetFilteredPlayers(); Values = GetFilteredPlayers();
playerOption.SetUpFromData(Data, 20); playerOption.SetUpFromData(Data, 20);
playerOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged; playerOption.OnValueChanged = (Action<OptionBehaviour>)ValueChanged;
playerOption.Title = StringName; playerOption.Title = StringName;
playerOption.TitleText.text = Title.Translate(); playerOption.TitleText.text = Title.Translate();
@@ -124,7 +124,7 @@ public class ModdedStringOption : ModdedOption<string>
$"{ParentMod!.OptionsTitleText}.StringOption.{TranslationController.Instance.GetString(StringName)}"; $"{ParentMod!.OptionsTitleText}.StringOption.{TranslationController.Instance.GetString(StringName)}";
stringOption.SetUpFromData(Data, 20); stringOption.SetUpFromData(Data, 20);
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged; stringOption.OnValueChanged = (Action<OptionBehaviour>)ValueChanged;
// Changing the button text to make them more distinct from number options // Changing the button text to make them more distinct from number options
stringOption.MinusBtn.ChangeButtonText("<"); stringOption.MinusBtn.ChangeButtonText("<");
@@ -11,6 +11,11 @@ namespace MiraAPI.GameOptions.OptionTypes;
/// </summary> /// </summary>
public class ModdedToggleOption : ModdedOption<bool> public class ModdedToggleOption : ModdedOption<bool>
{ {
/// <summary>
/// Gets or sets the delegate that dictates how a toggle option's value is displayed.
/// </summary>
public Func<bool, string> DisplayValue { get; set; } = value => value ? "On" : "Off";
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ModdedToggleOption"/> class. /// Initializes a new instance of the <see cref="ModdedToggleOption"/> class.
/// </summary> /// </summary>
@@ -39,7 +44,7 @@ public class ModdedToggleOption : ModdedOption<bool>
toggleOption.Title = StringName; toggleOption.Title = StringName;
toggleOption.TitleText.text = Title.Translate(); toggleOption.TitleText.text = Title.Translate();
toggleOption.CheckMark.enabled = Value; toggleOption.CheckMark.enabled = Value;
toggleOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged; toggleOption.OnValueChanged = (Action<OptionBehaviour>)ValueChanged;
OptionBehaviour = toggleOption; OptionBehaviour = toggleOption;
@@ -76,7 +81,7 @@ public class ModdedToggleOption : ModdedOption<bool>
ModdedOptionsManager.AddSettingsChangeMessage( ModdedOptionsManager.AddSettingsChangeMessage(
HudManager.Instance.Notifier, HudManager.Instance.Notifier,
StringName, StringName,
newValue ? "On" : "Off", DisplayValue(newValue).Translate(),
Configuration.PopUpTextColor, Configuration.PopUpTextColor,
Configuration.PopUpIconTmp, Configuration.PopUpIconTmp,
false); false);
@@ -126,7 +126,7 @@ public sealed class HideAndSeekHudHelper(nint cppPtr) : MonoBehaviour(cppPtr)
currentFinalHideTime = totalFinalHideTime; currentFinalHideTime = totalFinalHideTime;
if (timerBar != null) if (timerBar != null)
{ {
Object.Destroy(timerBar); Destroy(timerBar);
} }
finalHideAlertSfx = GameManagerCreator.Instance.HideAndSeekManagerPrefab.FinalHideAlertSFX; finalHideAlertSfx = GameManagerCreator.Instance.HideAndSeekManagerPrefab.FinalHideAlertSFX;
+1 -1
View File
@@ -151,7 +151,7 @@ public sealed class MiraHudHelper(nint cppPtr) : MonoBehaviour(cppPtr)
var oldPos = settingsButton.transform.localPosition; var oldPos = settingsButton.transform.localPosition;
settingsButton.transform.localPosition = new Vector3(oldPos.x, oldPos.y, -100); settingsButton.transform.localPosition = new Vector3(oldPos.x, oldPos.y, -100);
var chatButton = instance.Chat.chatButton.gameObject; var chatButton = instance.Chat.chatButton.gameObject;
ClonedChatButton = Object.Instantiate(chatButton, chatButton.transform.parent); ClonedChatButton = Instantiate(chatButton, chatButton.transform.parent);
ClonedChatButton.SetActive(false); ClonedChatButton.SetActive(false);
instance.Chat.chatButtonAspectPosition = ClonedChatButton.GetComponent<AspectPosition>(); instance.Chat.chatButtonAspectPosition = ClonedChatButton.GetComponent<AspectPosition>();
chatButton.GetComponent<AspectPosition>().Destroy(); chatButton.GetComponent<AspectPosition>().Destroy();
+8 -7
View File
@@ -313,9 +313,9 @@ public sealed class MiraPluginManager
foreach (var property in type.GetProperties()) foreach (var property in type.GetProperties())
{ {
if (property.GetMethod?.IsStatic == true) if (property.GetMethod?.IsStatic == true && property.GetCustomAttribute<MiraIgnoreAttribute>() == 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; continue;
} }
@@ -325,6 +325,12 @@ public sealed class MiraPluginManager
continue; continue;
} }
if (property.PropertyType.IsAssignableTo(typeof(IModdedOptionList)))
{
ModdedOptionsManager.RegisterPropertyOptionList(type, property, pluginInfo);
continue;
}
var attribute = property.GetCustomAttribute<ModdedOptionAttribute>(); var attribute = property.GetCustomAttribute<ModdedOptionAttribute>();
if (attribute != null) if (attribute != null)
{ {
@@ -332,11 +338,6 @@ public sealed class MiraPluginManager
continue; continue;
} }
if (property.PropertyType.IsAssignableTo(typeof(IModdedOptionList)))
{
ModdedOptionsManager.RegisterPropertyOptionList(type, property, pluginInfo);
}
var listAttr = property.GetCustomAttribute<ModdedOptionListAttribute>(); var listAttr = property.GetCustomAttribute<ModdedOptionListAttribute>();
if (listAttr != null) if (listAttr != null)
{ {