diff --git a/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs
index 54680f2c..ac3582b2 100644
--- a/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs
+++ b/MiraAPI/GameOptions/Attributes/ModdedEnumOptionAttribute.cs
@@ -32,3 +32,33 @@ public override object GetValue()
: throw new InvalidOperationException($"HolderOption for option \"{Title}\" with EnumType ${enumType.FullName} is not a ModdedEnumOption");
}
}
+
+///
+/// Attribute for creating an enum option.
+///
+/// The enum type.
+[AttributeUsage(AttributeTargets.Property)]
+public class ModdedEnumOptionAttribute(string title, string[]? values = null)
+ : ModdedOptionAttribute(title) where T : Enum
+{
+ internal override IModdedOption CreateOption(object? value, PropertyInfo property)
+ {
+ var opt = new ModdedEnumOption(Title, (T)(value ?? 0), values);
+ return opt;
+ }
+
+ ///
+ public override void SetValue(object value)
+ {
+ var opt = HolderOption as ModdedEnumOption;
+ opt?.SetValue((T)value);
+ }
+
+ ///
+ public override object GetValue()
+ {
+ return HolderOption is ModdedEnumOption opt
+ ? opt.Value
+ : throw new InvalidOperationException($"HolderOption for option \"{Title}\" with EnumType ${typeof(T).FullName} is not a ModdedEnumOption");
+ }
+}
diff --git a/MiraAPI/GameOptions/Attributes/ModdedEnumOptionListAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedEnumOptionListAttribute.cs
new file mode 100644
index 00000000..c8ec4472
--- /dev/null
+++ b/MiraAPI/GameOptions/Attributes/ModdedEnumOptionListAttribute.cs
@@ -0,0 +1,67 @@
+using MiraAPI.GameOptions.OptionTypes;
+using System;
+using System.Collections;
+using System.Reflection;
+
+namespace MiraAPI.GameOptions.Attributes;
+
+///
+/// Attribute for creating a list of enum options.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class ModdedEnumOptionListAttribute(Func titler, Type enumType, string[]? values = null)
+ : ModdedOptionListAttribute(titler)
+{
+ internal override IModdedOptionList CreateOptionList(IList value, PropertyInfo property)
+ {
+ var optList = new ModdedOptionList(
+ value.Count, idx => new(Titler(idx), (int)(value[idx] ?? 0), enumType, values));
+ return optList;
+ }
+
+ ///
+ public override void SetValue(int idx, object value)
+ {
+ var opt = HolderOptionList?[idx] as ModdedEnumOption;
+ opt?.SetValue((int)value);
+ }
+
+ ///
+ public override object GetValue(int idx)
+ {
+ return HolderOptionList?[idx] is ModdedEnumOption opt
+ ? Enum.ToObject(enumType, opt.Value)
+ : throw new InvalidOperationException($"HolderOption for option \"{Titler(idx)}\" with EnumType ${enumType.FullName} is not a ModdedEnumOption");
+ }
+}
+
+///
+/// Attribute for creating a list of enum options.
+///
+/// The enum type.
+[AttributeUsage(AttributeTargets.Property)]
+public class ModdedEnumOptionListAttribute(Func titler, string[]? values = null)
+ : ModdedOptionListAttribute(titler) where T : Enum
+{
+ internal override IModdedOptionList CreateOptionList(IList value, PropertyInfo property)
+ {
+ var optList = new ModdedOptionList>(
+ value.Count, idx => new(Titler(idx), (T)(value[idx] ?? 0), values));
+ return optList;
+ }
+
+ ///
+ public override void SetValue(int idx, object value)
+ {
+ var opt = HolderOptionList?[idx] as ModdedEnumOption;
+ opt?.SetValue((T)value);
+ }
+
+ ///
+ public override object GetValue(int idx)
+ {
+ return HolderOptionList?[idx] is ModdedEnumOption opt
+ ? opt.Value
+ : throw new InvalidOperationException($"HolderOption for option \"{Titler(idx)}\" with EnumType ${typeof(T).FullName} is not a ModdedEnumOption");
+ }
+}
diff --git a/MiraAPI/GameOptions/Attributes/ModdedNumberOptionListAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedNumberOptionListAttribute.cs
new file mode 100644
index 00000000..18d7f37d
--- /dev/null
+++ b/MiraAPI/GameOptions/Attributes/ModdedNumberOptionListAttribute.cs
@@ -0,0 +1,55 @@
+using MiraAPI.GameOptions.OptionTypes;
+using MiraAPI.Utilities;
+using System;
+using System.Collections;
+using System.Reflection;
+
+namespace MiraAPI.GameOptions.Attributes;
+
+///
+/// A number option attribute for the modded options system.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class ModdedNumberOptionListAttribute(
+ Func titler,
+ float min,
+ float max,
+ float increment = 1,
+ MiraNumberSuffixes suffixType = MiraNumberSuffixes.None,
+ string? formatString = null,
+ bool zeroInfinity = false)
+ : ModdedOptionListAttribute(titler)
+{
+ internal override IModdedOptionList CreateOptionList(IList value, PropertyInfo property)
+ {
+ return new ModdedOptionList(value.Count, idx =>
+ {
+ return new(
+ Titler(idx),
+ (float)(value[idx] ?? min + increment),
+ min,
+ max,
+ increment,
+ suffixType,
+ formatString,
+ zeroInfinity);
+ });
+ }
+
+ ///
+ public override void SetValue(int idx, object value)
+ {
+ var opt = HolderOptionList?[idx] as ModdedNumberOption;
+ opt?.SetValue((float)value);
+ }
+
+ ///
+ public override object GetValue(int idx)
+ {
+ if (HolderOptionList?[idx] is ModdedNumberOption opt)
+ {
+ return opt.Value;
+ }
+ throw new InvalidOperationException($"HolderOption for option \"{Titler(idx)}\" is not a ModdedNumberOption");
+ }
+}
diff --git a/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs
index 702d75e3..62ce43b6 100644
--- a/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs
+++ b/MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs
@@ -9,31 +9,19 @@ namespace MiraAPI.GameOptions.Attributes;
/// The option title.
/// Optional parameter to specify a role Type.
[AttributeUsage(AttributeTargets.Property)]
-public abstract class ModdedOptionAttribute(string title, Type? roleType = null) : Attribute
+public abstract class ModdedOptionAttribute(string title, Type? roleType = null) : PropertyOptionAttribute
{
internal IModdedOption? HolderOption { get; set; }
///
/// Gets the title of the option.
///
- public string Title { get; private set; } = title;
+ public string Title => title;
///
/// Gets the role type of the option.
///
- protected Type? RoleType { get; private set; } = roleType;
-
- ///
- /// Sets the value of the option.
- ///
- /// The new value as an object.
- public abstract void SetValue(object value);
-
- ///
- /// Gets the value of the option.
- ///
- /// The value of the option as an object.
- public abstract object GetValue();
+ protected Type? RoleType => roleType;
internal abstract IModdedOption? CreateOption(object? value, PropertyInfo property);
}
diff --git a/MiraAPI/GameOptions/Attributes/ModdedOptionListAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedOptionListAttribute.cs
new file mode 100644
index 00000000..74615ddd
--- /dev/null
+++ b/MiraAPI/GameOptions/Attributes/ModdedOptionListAttribute.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections;
+using System.Reflection;
+
+namespace MiraAPI.GameOptions.Attributes;
+
+///
+/// Represents an attribute that is used to define a list of modded options.
+///
+/// A function to title of the options.
+[AttributeUsage(AttributeTargets.Property)]
+public abstract class ModdedOptionListAttribute(Func titler) : PropertyOptionAttribute
+{
+ internal IModdedOptionList? HolderOptionList { get; set; }
+
+ internal object? Value { get; set; }
+
+ ///
+ /// Gets the function to title of the options.
+ ///
+ public Func Titler => titler;
+
+ ///
+ /// Sets the value of all the options.
+ ///
+ /// The new values as an object.
+ public override void SetValue(object value)
+ {
+ Value = value;
+ var list = (IList)value;
+ if (list.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++)
+ {
+ SetValue(i, list[i]!);
+ }
+ }
+
+ ///
+ /// Sets the value of the specific option.
+ ///
+ /// The option to set.
+ /// The new value as an object.
+ public abstract void SetValue(int idx, object value);
+
+ ///
+ /// Gets the value of all the options.
+ ///
+ /// The value of the options as an object.
+ public override object GetValue()
+ {
+ return Value!;
+ }
+
+ ///
+ /// Gets the value of the specific option.
+ ///
+ /// The option to set.
+ /// The value of the option as an object.
+ public abstract object GetValue(int idx);
+
+ internal abstract IModdedOptionList? CreateOptionList(IList value, PropertyInfo property);
+}
diff --git a/MiraAPI/GameOptions/Attributes/ModdedToggleOptionListAttribute.cs b/MiraAPI/GameOptions/Attributes/ModdedToggleOptionListAttribute.cs
new file mode 100644
index 00000000..7e34080a
--- /dev/null
+++ b/MiraAPI/GameOptions/Attributes/ModdedToggleOptionListAttribute.cs
@@ -0,0 +1,38 @@
+using MiraAPI.GameOptions.OptionTypes;
+using System;
+using System.Collections;
+using System.Reflection;
+
+namespace MiraAPI.GameOptions.Attributes;
+
+///
+/// Attribute for a list of toggle options.
+///
+/// The function to get each option's title.
+[AttributeUsage(AttributeTargets.Property)]
+public class ModdedToggleOptionListAttribute(Func titler) : ModdedOptionListAttribute(titler)
+{
+ internal override IModdedOptionList CreateOptionList(IList value, PropertyInfo property)
+ {
+ var optList = new ModdedOptionList(
+ value.Count, idx => new(Titler(idx), (bool)(value[idx] ?? false)));
+ return optList;
+ }
+
+ ///
+ public override void SetValue(int idx, object value)
+ {
+ var opt = HolderOptionList?[idx] as ModdedToggleOption;
+ opt?.SetValue((bool)value);
+ }
+
+ ///
+ public override object GetValue(int idx)
+ {
+ if (HolderOptionList?[idx] is ModdedToggleOption opt)
+ {
+ return opt.Value;
+ }
+ throw new InvalidOperationException($"Holder option for {Titler(idx)} is not a ModdedToggleOption.");
+ }
+}
diff --git a/MiraAPI/GameOptions/Attributes/PropertyOptionAttribute.cs b/MiraAPI/GameOptions/Attributes/PropertyOptionAttribute.cs
new file mode 100644
index 00000000..643d7b7e
--- /dev/null
+++ b/MiraAPI/GameOptions/Attributes/PropertyOptionAttribute.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace MiraAPI.GameOptions.Attributes;
+
+///
+/// Represents an attribute that is used to intercept a property's getter and setter.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public abstract class PropertyOptionAttribute : Attribute
+{
+ ///
+ /// Sets the value of the option.
+ ///
+ /// The new value as an object.
+ public abstract void SetValue(object value);
+
+ ///
+ /// Gets the value of the option.
+ ///
+ /// The value of the option as an object.
+ public abstract object GetValue();
+}
diff --git a/MiraAPI/GameOptions/IModdedOption.cs b/MiraAPI/GameOptions/IModdedOption.cs
index 5a3c3f8f..e21c3b29 100644
--- a/MiraAPI/GameOptions/IModdedOption.cs
+++ b/MiraAPI/GameOptions/IModdedOption.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using BepInEx.Configuration;
using MiraAPI.Networking;
using MiraAPI.PluginLoading;
@@ -104,3 +105,10 @@ public interface IModdedOption
/// The ConfigFile representing the preset configuration.
void LoadFromPreset(ConfigFile presetConfig);
}
+
+///
+/// Interface for list of modded options.
+///
+public interface IModdedOptionList : IReadOnlyList
+{
+}
diff --git a/MiraAPI/GameOptions/ModdedOptionsManager.cs b/MiraAPI/GameOptions/ModdedOptionsManager.cs
index 3b85a73c..ffb85e30 100644
--- a/MiraAPI/GameOptions/ModdedOptionsManager.cs
+++ b/MiraAPI/GameOptions/ModdedOptionsManager.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -20,7 +21,7 @@ namespace MiraAPI.GameOptions;
///
public static class ModdedOptionsManager
{
- private static readonly Dictionary OptionAttributes = [];
+ private static readonly Dictionary OptionAttributes = [];
private static readonly Dictionary TypeToGroup = [];
internal static readonly Dictionary CreatedPlayerOptions = [];
@@ -113,6 +114,94 @@ internal static void RegisterAttributeOption(
RegisterOption(option, group, property.Name, pluginInfo);
}
+ internal static void RegisterPropertyOptionList(Type type, PropertyInfo property, MiraPluginInfo pluginInfo)
+ {
+ if (!TypeToGroup.TryGetValue(type, out var group))
+ {
+ Error($"Failed to get group for {type.Name}");
+ return;
+ }
+
+ if (property.GetValue(group) is not IModdedOptionList optionList)
+ {
+ Error($"Failed to get option list for {property.Name}");
+ return;
+ }
+
+ for (int i = 0; i < optionList.Count; i++)
+ {
+ RegisterOption(optionList[i], group, property.Name + i, pluginInfo);
+ }
+ }
+
+ internal static void RegisterAttributeOptionList(
+ Type type,
+ ModdedOptionListAttribute attribute,
+ PropertyInfo property,
+ MiraPluginInfo pluginInfo)
+ {
+ if (OptionAttributes.ContainsKey(property))
+ {
+ Error($"Property {property.Name} already has an attribute registered.");
+ return;
+ }
+
+ if (!TypeToGroup.TryGetValue(type, out var group))
+ {
+ Error($"Failed to get group for {type.Name}");
+ return;
+ }
+
+ var propertyVal = property.GetValue(group);
+
+ if (propertyVal == null)
+ {
+ Error("Cannot initialize option list with null value.");
+ return;
+ }
+
+ var propertyList = (IList)propertyVal;
+ var optionList = attribute.CreateOptionList(propertyList, property);
+
+ if (optionList == null)
+ {
+ Error($"Failed to get option for {property.Name}");
+ return;
+ }
+ if (propertyList.Count != optionList.Count)
+ {
+ Error("Mismatch in count between values and created options.");
+ return;
+ }
+
+ var setterOriginal = property.GetSetMethod();
+ var setterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertySetterPatch));
+ PluginSingleton.Instance.Harmony.Patch(setterOriginal, postfix: new HarmonyMethod(setterPatch));
+
+ var getterOriginal = property.GetGetMethod();
+ var getterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyGetterPatch));
+ PluginSingleton.Instance.Harmony.Patch(getterOriginal, prefix: new HarmonyMethod(getterPatch));
+
+ var listIndex = propertyList.GetType().GetProperty("Item")!;
+
+ var listSetterOriginal = listIndex.GetSetMethod();
+ var listSetterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyListSetterPatch));
+ PluginSingleton.Instance.Harmony.Patch(listSetterOriginal, postfix: new HarmonyMethod(listSetterPatch));
+
+ var listGetterOriginal = listIndex.GetSetMethod();
+ var listGetterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyListGetterPatch));
+ PluginSingleton.Instance.Harmony.Patch(listGetterOriginal, prefix: new HarmonyMethod(listGetterPatch));
+
+ OptionAttributes.Add(property, attribute);
+ attribute.HolderOptionList = optionList;
+ attribute.Value = propertyVal;
+
+ for (int i = 0; i < optionList.Count; i++)
+ {
+ RegisterOption(optionList[i], group, property.Name + i, pluginInfo);
+ }
+ }
+
internal static void RegisterOption(
IModdedOption option,
AbstractOptionGroup group,
@@ -202,4 +291,38 @@ public static bool PropertyGetterPatch(MethodBase __originalMethod, ref object _
__result = attribute.GetValue();
return false;
}
+
+ ///
+ /// Patches the setter of a list property to update the value of the option.
+ ///
+ /// The list's instance.
+ /// The index to find in the list.
+ /// The new object value.
+#pragma warning disable CA1707
+ [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony naming convention")]
+ public static void PropertyListSetterPatch(object __instance, int index, object value)
+#pragma warning restore CA1707
+ {
+ var attribute = (ModdedOptionListAttribute)OptionAttributes.First(
+ pair => pair.Value is ModdedOptionListAttribute list && ReferenceEquals(list.Value, __instance)).Value;
+ attribute.SetValue(index, value);
+ }
+
+ ///
+ /// Patches the getter of a list property to return the value of the option.
+ ///
+ /// The list's instance.
+ /// The index to find in the list.
+ /// The result of the property getter.
+ /// False so the original getter gets skipped.
+#pragma warning disable CA1707
+ [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony naming convention")]
+ public static bool PropertyListGetterPatch(object __instance, int index, ref object __result)
+#pragma warning restore CA1707
+ {
+ var attribute = (ModdedOptionListAttribute)OptionAttributes.First(
+ pair => pair.Value is ModdedOptionListAttribute list && ReferenceEquals(list.Value, __instance)).Value;
+ __result = attribute.GetValue(index);
+ return false;
+ }
}
diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs
index 206894dc..644c87c8 100644
--- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs
+++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs
@@ -15,8 +15,6 @@ namespace MiraAPI.GameOptions.OptionTypes;
/// The value type.
public abstract class ModdedOption : IModdedOption
{
- private IMiraPlugin? _parentMod;
-
///
public uint Id { get; }
@@ -32,13 +30,13 @@ public abstract class ModdedOption : IModdedOption
///
public IMiraPlugin? ParentMod
{
- get => _parentMod;
+ get;
set
{
- if (_parentMod != null || value == null) return;
- _parentMod = value;
+ if (field != null || value == null) return;
+ field = value;
- var entry = _parentMod.GetConfigFile().Bind(ConfigDefinition, DefaultValue);
+ var entry = field.GetConfigFile().Bind(ConfigDefinition, DefaultValue);
Value = entry.Value;
}
}
@@ -54,9 +52,9 @@ public IMiraPlugin? ParentMod
public T DefaultValue { get; }
///
- /// Gets or sets the event that is invoked when the value of the option changes.
+ /// Gets the event that is invoked when the value of the option changes.
///
- public Action? ChangedEvent { get; set; }
+ public Action? ChangedEvent { get; init; }
///
public Func Visible { get; set; }
diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOptionList.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOptionList.cs
new file mode 100644
index 00000000..5a764b21
--- /dev/null
+++ b/MiraAPI/GameOptions/OptionTypes/ModdedOptionList.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MiraAPI.GameOptions.OptionTypes;
+
+///
+/// Represents a modded option list.
+///
+/// The type of options.
+public class ModdedOptionList : IModdedOptionList where T : IModdedOption
+{
+ ///
+ public int Count { get; }
+
+ ///
+ /// Gets the list of options.
+ ///
+ public IReadOnlyList Options { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The option list's length.
+ /// The option factory to instantiate the options from.
+ public ModdedOptionList(int count, Func optionFactory)
+ {
+ Count = count;
+ Options = Enumerable.Range(0, Count).Select(optionFactory).ToArray();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The options list.
+ public ModdedOptionList(IEnumerable options)
+ {
+ Count = options.Count();
+ Options = options.ToArray();
+ }
+
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ return ((IEnumerable)Options).GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return Options.GetEnumerator();
+ }
+
+ ///
+ /// Indexes the option of type .
+ ///
+ /// The option's index.
+ /// option of type .
+ public T this[int idx] => Options[idx];
+
+ IModdedOption IReadOnlyList.this[int index] => this[index];
+}
diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs
index 0e31779c..bc65692e 100644
--- a/MiraAPI/Modifiers/BaseModifier.cs
+++ b/MiraAPI/Modifiers/BaseModifier.cs
@@ -128,4 +128,12 @@ public virtual void OnMeetingStart()
///
/// True if the player can vent, false otherwise. Null for no effect.
public virtual bool? CanVent() => null;
+
+ ///
+ /// Removes this modifier instance from the player.
+ ///
+ public void RemoveSelf()
+ {
+ ModifierComponent?.RemoveModifier(this);
+ }
}
diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs
index d64f3662..a152d0b7 100644
--- a/MiraAPI/Modifiers/ModifierComponent.cs
+++ b/MiraAPI/Modifiers/ModifierComponent.cs
@@ -203,6 +203,18 @@ public IEnumerable GetModifiers(uint id, Func?
return GetModifiers(type, predicate);
}
+ ///
+ /// Gets a collection of modifiers by type, if the type is an interface.
+ ///
+ /// The predicate to check the modifier by.
+ /// The Type of the interface of the Modifier.
+ /// A collection of modifiers.
+ [HideFromIl2Cpp]
+ public IEnumerable GetModifiersOfType(Func? predicate = null) where T : class
+ {
+ return ActiveModifiers.OfType().Where(x => predicate == null || predicate(x));
+ }
+
///
/// Tries to get a modifier by its type.
///
@@ -258,6 +270,20 @@ public bool TryGetModifier(Guid modifierGuid, [NotNullWhen(true)] out BaseModifi
return modifier != null;
}
+ ///
+ /// Tries to get a modifier by its type, if the type is an interface.
+ ///
+ /// The modifier or null.
+ /// The predicate to check the modifier by.
+ /// The Type of the interface of the Modifier.
+ /// True if the modifier was found, false otherwise.
+ [HideFromIl2Cpp]
+ public bool TryGetModifierOfType([NotNullWhen(true)] out T? modifier, Func? predicate = null) where T : class
+ {
+ modifier = GetModifierOfType(predicate);
+ return modifier != null;
+ }
+
///
/// Gets a modifier by its type.
///
@@ -308,6 +334,18 @@ public bool TryGetModifier(Guid modifierGuid, [NotNullWhen(true)] out BaseModifi
return ActiveModifiers.Find(x => x.UniqueId == modifierGuid);
}
+ ///
+ /// Gets a modifier by its type, if the type is an interface.
+ ///
+ /// The predicate to check the modifier by.
+ /// The Type of the interface of the Modifier.
+ /// The Modifier if it is found, null otherwise.
+ [HideFromIl2Cpp]
+ public T? GetModifierOfType(Func? predicate = null) where T : class
+ {
+ return GetModifiersOfType(predicate).FirstOrDefault();
+ }
+
///
/// Removes a modifier from the player.
///
@@ -389,6 +427,77 @@ public void RemoveModifier(Guid uniqueId)
RemoveModifier(modifier);
}
+ ///
+ /// Tries to remove a modifier from the player.
+ ///
+ /// The modifier type.
+ /// The predicate to check the modifier by.
+ /// if the modifier is not active on this player, or there are multiple instances;
+ /// else .
+ [HideFromIl2Cpp]
+ public bool TryRemoveModifier(Func? predicate = null) where T : BaseModifier
+ {
+ return TryGetModifier(out var modifier, predicate) &&
+ TryRemoveModifier(modifier);
+ }
+
+ ///
+ /// Tries to remove a modifier from the player.
+ ///
+ /// The modifier type.
+ /// The predicate to check the modifier by.
+ /// if the modifier is not active on this player, or there are multiple instances;
+ /// else .
+ [HideFromIl2Cpp]
+ public bool TryRemoveModifier(Type type, Func? predicate = null)
+ {
+ return TryGetModifier(type, out var modifier, predicate) &&
+ TryRemoveModifier(modifier);
+ }
+
+ ///
+ /// Tries to remove a modifier from the player.
+ ///
+ /// The modifier object.
+ /// if the modifier is not active on this player, else .
+ [HideFromIl2Cpp]
+ public bool TryRemoveModifier(BaseModifier modifier)
+ {
+ if (!ActiveModifiers.Contains(modifier))
+ {
+ return false;
+ }
+
+ _toRemove.Add(modifier);
+ return true;
+ }
+
+ ///
+ /// Tries to remove a modifier from the player.
+ ///
+ /// The modifier's type ID.
+ /// The predicate to check the modifier by.
+ /// if the modifier is not active on this player, or there are multiple instances;
+ /// else .
+ [HideFromIl2Cpp]
+ public bool TryRemoveModifier(uint typeId, Func? predicate = null)
+ {
+ return TryGetModifier(typeId, out var modifier, predicate) &&
+ TryRemoveModifier(modifier);
+ }
+
+ ///
+ /// Tries to remove a modifier from the player.
+ ///
+ /// The modifier's unique ID.
+ /// if the modifier is not active on this player, else .
+ [HideFromIl2Cpp]
+ public bool TryRemoveModifier(Guid uniqueId)
+ {
+ return TryGetModifier(uniqueId, out var modifier) &&
+ TryRemoveModifier(modifier);
+ }
+
///
/// Adds a modifier to the player.
///
@@ -585,4 +694,30 @@ public bool HasModifier(Guid id, bool checkInactive)
return ActiveModifiers.Exists(MatchExpr) || (checkInactive && _toAdd.Exists(MatchExpr));
bool MatchExpr(BaseModifier bm) => bm.UniqueId == id;
}
+
+ ///
+ /// Checks if a player has an active modifier by its type, if the type is an interface.
+ ///
+ /// The predicate to check the modifier.
+ /// The Type of the interface of the Modifier.
+ /// True if the Modifier is present, false otherwise.
+ [HideFromIl2Cpp]
+ public bool HasModifierOfType(Func? predicate=null) where T : class
+ {
+ return ActiveModifiers.Exists(x => x is T modifier && (predicate == null || predicate(modifier)));
+ }
+
+ ///
+ /// Checks if a player has an active modifier by its type, if the type is an interface.
+ ///
+ /// Whether to check inactive modifiers (those pending to be added).
+ /// The predicate to check the modifier.
+ /// The Type of the interface of the Modifier.
+ /// True if the Modifier is present, false otherwise.
+ [HideFromIl2Cpp]
+ public bool HasModifierOfType(bool checkInactive, Func? predicate=null) where T : class
+ {
+ return ActiveModifiers.Exists(MatchExpr) || (checkInactive && _toAdd.Exists(MatchExpr));
+ bool MatchExpr(BaseModifier bm) => bm is T modifier && (predicate == null || predicate(modifier));
+ }
}
diff --git a/MiraAPI/Modifiers/ModifierExtensions.cs b/MiraAPI/Modifiers/ModifierExtensions.cs
index 7a0ef23f..cccba45a 100644
--- a/MiraAPI/Modifiers/ModifierExtensions.cs
+++ b/MiraAPI/Modifiers/ModifierExtensions.cs
@@ -191,6 +191,18 @@ public static bool HasModifier(this PlayerControl player, Guid uniqueId)
return player.GetModifierComponent().HasModifier(uniqueId);
}
+ ///
+ /// Checks if the player has a specific modifier, if the type is an interface.
+ ///
+ /// The type of the interface of the modifier.
+ /// The PlayerControl instance.
+ /// Optional predicate to filter the modifiers.
+ /// True if the player has the modifier, false otherwise.
+ public static bool HasModifierOfType(this PlayerControl player, Func? predicate = null) where T : class
+ {
+ return player.GetModifierComponent().HasModifierOfType(predicate);
+ }
+
///
/// Clears all modifiers from a player.
///
@@ -266,6 +278,19 @@ public static bool TryGetModifier(this PlayerControl player, Guid modifierGuid,
return player.GetModifierComponent().TryGetModifier(modifierGuid, out modifier);
}
+ ///
+ /// Tries to get a modifier by its type, if the type is an interface.
+ ///
+ /// The PlayerControl instance.
+ /// The modifier or null.
+ /// The predicate to check the modifier by.
+ /// The Type of the interface of the Modifier.
+ /// True if the modifier was found, false otherwise.
+ public static bool TryGetModifierOfType(this PlayerControl player, [NotNullWhen(true)] out T? modifier, Func? predicate = null) where T : class
+ {
+ return player.GetModifierComponent().TryGetModifierOfType(out modifier, predicate);
+ }
+
///
/// Gets a specific modifier from the player.
///
@@ -321,6 +346,18 @@ public static bool TryGetModifier(this PlayerControl player, Guid modifierGuid,
return player.GetModifierComponent().GetModifier(uniqueId);
}
+ ///
+ /// Gets a specific modifier from the player, if the type is an interface.
+ ///
+ /// The type of the interface of the modifier.
+ /// The PlayerControl instance.
+ /// Optional predicate to filter the modifiers.
+ /// The modifier if found, null otherwise.
+ public static T? GetModifierOfType(this PlayerControl player, Func? predicate = null) where T : class
+ {
+ return player.GetModifierComponent().GetModifierOfType(predicate);
+ }
+
///
/// Gets all modifiers of a specific type from the player.
///
@@ -364,6 +401,19 @@ public static IEnumerable GetModifiers(
return player.GetModifierComponent().GetModifiers(typeId, predicate);
}
+ ///
+ /// Gets all modifiers of a specific type from the player, if the type is an interface.
+ ///
+ /// The type of the interface of the modifiers.
+ /// The PlayerControl instance.
+ /// Optional predicate to filter the modifiers.
+ /// A collection of modifiers.
+ public static IEnumerable GetModifiersOfType(this PlayerControl player, Func? predicate = null)
+ where T : class
+ {
+ return player.GetModifierComponent().GetModifiersOfType(predicate);
+ }
+
///
/// Removes a specific modifier from the player.
///
@@ -423,6 +473,73 @@ public static void RemoveModifier(
player.GetModifierComponent().RemoveModifier(uniqueId);
}
+ ///
+ /// Tries to remove a specific modifier from the player.
+ ///
+ /// The PlayerControl instance.
+ /// The modifier to remove.
+ /// if the modifier is not active on this player, else .
+ public static bool TryRemoveModifier(this PlayerControl player, BaseModifier modifier)
+ {
+ return player.GetModifierComponent().TryRemoveModifier(modifier);
+ }
+
+ ///
+ /// Tries to remove a specific modifier from the player.
+ ///
+ /// The type of the modifier.
+ /// The PlayerControl instance.
+ /// Optional predicate to filter the modifiers.
+ /// if the modifier is not active on this player, or there are multiple instances;
+ /// else .
+ public static bool TryRemoveModifier(this PlayerControl player, Func? predicate = null)
+ where T : BaseModifier
+ {
+ return player.GetModifierComponent().TryRemoveModifier(predicate);
+ }
+
+ ///
+ /// Tries to remove a specific modifier from the player by type.
+ ///
+ /// The PlayerControl instance.
+ /// The type of the modifier.
+ /// Optional predicate to filter the modifiers.
+ /// if the modifier is not active on this player, or there are multiple instances;
+ /// else .
+ public static bool TryRemoveModifier(this PlayerControl player, Type type, Func? predicate = null)
+ {
+ return player.GetModifierComponent().TryRemoveModifier(type, predicate);
+ }
+
+ ///
+ /// Tries to remove a specific modifier from the player by its type ID.
+ ///
+ /// The PlayerControl instance.
+ /// The type ID of the modifier.
+ /// Optional predicate to filter the modifiers.
+ /// if the modifier is not active on this player, or there are multiple instances;
+ /// else .
+ public static bool TryRemoveModifier(
+ this PlayerControl player,
+ uint typeId,
+ Func? predicate = null)
+ {
+ return player.GetModifierComponent().TryRemoveModifier(typeId, predicate);
+ }
+
+ ///
+ /// Tries to remove a specific modifier from the player by its GUID.
+ ///
+ /// The PlayerControl instance.
+ /// The GUID of the modifier.
+ /// if the modifier is not active on this player, else .
+ public static bool TryRemoveModifier(
+ this PlayerControl player,
+ Guid uniqueId)
+ {
+ return player.GetModifierComponent().TryRemoveModifier(uniqueId);
+ }
+
///
/// Adds a specific modifier to the player.
///
diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs
index 26a881f4..bb4e6cc7 100644
--- a/MiraAPI/PluginLoading/MiraPluginManager.cs
+++ b/MiraAPI/PluginLoading/MiraPluginManager.cs
@@ -201,15 +201,26 @@ private static bool RegisterOptions(Type type, MiraPluginInfo pluginInfo)
}
var attribute = property.GetCustomAttribute();
- if (attribute == null)
+ if (attribute != null)
{
+ ModdedOptionsManager.RegisterAttributeOption(type, attribute, property, pluginInfo);
continue;
}
- ModdedOptionsManager.RegisterAttributeOption(type, attribute, property, pluginInfo);
+ if (property.PropertyType.IsAssignableTo(typeof(IModdedOptionList)))
+ {
+ ModdedOptionsManager.RegisterPropertyOptionList(type, property, pluginInfo);
+ }
+
+ var listAttr = property.GetCustomAttribute();
+ if (listAttr != null)
+ {
+ ModdedOptionsManager.RegisterAttributeOptionList(type, listAttr, property, pluginInfo);
+ }
}
- foreach (var field in type.GetFields().Where(f => f.FieldType.IsAssignableTo(typeof(IModdedOption))))
+ foreach (var field in type.GetFields()
+ .Where(f => f.FieldType.IsAssignableTo(typeof(IModdedOption)) || f.FieldType.IsAssignableTo(typeof(IModdedOptionList))))
{
Error($"{field.Name} is a field, not a property. Use properties for options.");
}