mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-19 15:51:48 +00:00
git is confusing
This commit is contained in:
26 files changed
+1083
-32
No files matched your search
@@ -35,3 +35,6 @@ dotnet_diagnostic.S2696.severity = none
|
||||
|
||||
# S2245: Pseudorandom number generators (PRNGs) should not be used in security contexts
|
||||
dotnet_diagnostic.S2245.severity = none
|
||||
|
||||
# IDE0130: Namespace does not match folder structure
|
||||
dotnet_diagnostic.IDE0130.severity = none
|
||||
@@ -1,6 +1,7 @@
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.GameOptions.Attributes;
|
||||
using MiraAPI.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Options;
|
||||
@@ -16,11 +17,19 @@ public class ExampleOptions : AbstractOptionGroup
|
||||
[ModdedToggleOption("options.exampleOptions1.toggleOpt1")]
|
||||
public bool ToggleOpt2 { get; set; } = true;
|
||||
|
||||
[ModdedToggleOption("Toggle Opt 3")]
|
||||
[ModdedOptionVisibility(nameof(ToggleOpt2))] // ToggleOpt3 will be visible only when ToggleOpt2 is true
|
||||
public bool ToggleOpt3 { get; set; } = true;
|
||||
|
||||
[ModdedNumberOption("options.exampleOptions1.numberOpt", min: 0, max: 10, increment: .25f, formatString: "0.00", suffixType: MiraNumberSuffixes.Percent)]
|
||||
public float NumberOpt { get; set; } = 4f;
|
||||
|
||||
[ModdedEnumOption("options.exampleOptions1.bestApi", typeof(BestApi), ["Mira API", "Mitochondria", "Reactor"])]
|
||||
public BestApi Opt { get; set; } = BestApi.MiraAPI;
|
||||
|
||||
[ModdedEnumOptionList<ValueState>("Value State {0}", ["None", "Primary", "Secondary"])]
|
||||
public List<ValueState> ValueStates { get; } =
|
||||
[ValueState.Primary, ValueState.Secondary, ValueState.None, ValueState.None, ValueState.None];
|
||||
}
|
||||
|
||||
public enum BestApi
|
||||
@@ -29,3 +38,10 @@ public enum BestApi
|
||||
Mitochondria,
|
||||
Reactor,
|
||||
}
|
||||
|
||||
public enum ValueState
|
||||
{
|
||||
None,
|
||||
Primary,
|
||||
Secondary,
|
||||
}
|
||||
@@ -16,6 +16,9 @@ public class ExampleOptions2 : AbstractOptionGroup
|
||||
public ModdedPlayerOption PlayerOption { get; } = new("options.exampleOptions2.ehWhatever");
|
||||
public ModdedToggleOption ToggleOpt1 { get; } = new("options.exampleOptions2.toggleOption1", false);
|
||||
|
||||
public ModdedOptionList<ModdedNumberOption> PlayerNums { get; } = new(5, i =>
|
||||
new($"PlayerNum{i}", 2, 0, 5, 1, Utilities.MiraNumberSuffixes.None));
|
||||
|
||||
public ModdedToggleOption ToggleOpt2 { get; } = new("options.exampleOptions2.toggleOption2", false)
|
||||
{
|
||||
Visible = () => OptionGroupSingleton<ExampleOptions2>.Instance.ToggleOpt1, // implicit cast from ModdedToggleOption to bool
|
||||
|
||||
@@ -17,7 +17,7 @@ public static class MiraEventManager
|
||||
/// <param name="eventInstance">The <typeparamref name="T"/> instance.</param>
|
||||
/// <typeparam name="T">Type of Event.</typeparam>
|
||||
/// <returns>If there was an event handler invoked for this event, return <see langword="true"/>. Otherwise, return <see langword="false"/>.</returns>
|
||||
public static bool InvokeEvent<T>(T eventInstance) where T : MiraEvent
|
||||
public static bool InvokeEvent<T>(this T eventInstance) where T : MiraEvent
|
||||
{
|
||||
EventWrappers.TryGetValue(typeof(T), out var handlers);
|
||||
if (handlers == null || handlers.Count == 0)
|
||||
@@ -46,7 +46,7 @@ public static class MiraEventManager
|
||||
/// <param name="eventInstance">The <see cref="MiraEvent"/> instance.</param>
|
||||
/// <param name="type">The type to use for handler lookup.</param>
|
||||
/// <returns>If there was an event handler invoked for this event, return <see langword="true"/>. Otherwise, return <see langword="false"/>.</returns>
|
||||
public static bool InvokeEvent(MiraEvent eventInstance, Type type)
|
||||
public static bool InvokeEvent(this MiraEvent eventInstance, Type type)
|
||||
{
|
||||
EventWrappers.TryGetValue(type, out var handlers);
|
||||
if (handlers == null || handlers.Count == 0)
|
||||
|
||||
@@ -36,3 +36,33 @@ public class ModdedEnumOptionAttribute(string title, Type enumType, string[]? va
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{Title}\" with EnumType ${enumType.FullName} is not a ModdedEnumOption");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for creating an enum option.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The enum type.</typeparam>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ModdedEnumOptionAttribute<T>(string title, string[]? values = null)
|
||||
: ModdedOptionAttribute(title) where T : Enum
|
||||
{
|
||||
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
|
||||
{
|
||||
var opt = new ModdedEnumOption<T>(Title, (T)(value ?? 0), values);
|
||||
return opt;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetValue(object value)
|
||||
{
|
||||
var opt = HolderOption as ModdedEnumOption<T>;
|
||||
opt?.SetValue((T)value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue()
|
||||
{
|
||||
return HolderOption is ModdedEnumOption<T> opt
|
||||
? opt.Value
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{Title}\" with EnumType ${typeof(T).FullName} is not a ModdedEnumOption");
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,14 @@ namespace MiraAPI.GameOptions.Attributes;
|
||||
/// <param name="roleType">Optional parameter to specify a role Type.</param>
|
||||
/// <param name="modeType">Optional parameter to specify a game mode Type.</param>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public abstract class ModdedOptionAttribute(string title, Type? roleType = null, Type? modeType = null) : Attribute
|
||||
public abstract class ModdedOptionAttribute(string title, Type? roleType = null, Type? modeType = null) : PropertyOptionAttribute
|
||||
{
|
||||
internal IModdedOption? HolderOption { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the title of the option.
|
||||
/// </summary>
|
||||
public string Title { get; private set; } = title;
|
||||
public string Title => title;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the role type of the option.
|
||||
@@ -29,17 +29,5 @@ public abstract class ModdedOptionAttribute(string title, Type? roleType = null,
|
||||
/// </summary>
|
||||
protected Type? ModeType { get; private set; } = modeType;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the option.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value as an <see langword="object"/>.</param>
|
||||
public abstract void SetValue(object value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the option.
|
||||
/// </summary>
|
||||
/// <returns>The value of the option as an <see langword="object"/>.</returns>
|
||||
public abstract object GetValue();
|
||||
|
||||
internal abstract IModdedOption? CreateOption(object? value, PropertyInfo property);
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MiraAPI.GameOptions.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Attribute to modify a property with a <see cref="ModdedOptionAttribute"/> or <see cref="ModdedOptionListAttribute"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Initializes a new instance of the <see cref="ModdedOptionVisibilityAttribute"/> class.
|
||||
/// </remarks>
|
||||
/// <param name="holderType">The type the member is in.</param>
|
||||
/// <param name="memberName">The member to get the visibility function from.</param>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public sealed class ModdedOptionVisibilityAttribute(Type? holderType = null, string? memberName = null) : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedOptionVisibilityAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="memberName">The member to get the visibility function from.</param>
|
||||
public ModdedOptionVisibilityAttribute(string memberName)
|
||||
: this(null, memberName)
|
||||
{
|
||||
}
|
||||
|
||||
internal Func<bool>? GetVisibility(AbstractOptionGroup group, PropertyInfo property)
|
||||
{
|
||||
if (GetVisibilityMember(group, property, out Type type, out var instanceGet) is not { } member)
|
||||
{
|
||||
Error($"Member {memberName} does not exist in {type.FullName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (member is PropertyInfo vProperty)
|
||||
{
|
||||
if (!vProperty.PropertyType.IsAssignableTo(typeof(bool)) &&
|
||||
!vProperty.PropertyType.IsAssignableTo(typeof(ModdedOption<bool>)))
|
||||
{
|
||||
Error($"Property {memberName} is not a bool or a bool ModdedOption.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var indexParams = vProperty.GetIndexParameters();
|
||||
if (indexParams.Length > 0)
|
||||
{
|
||||
Error($"Property {memberName} cannot be an indexer.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return () => (bool)vProperty.GetValue(instanceGet?.Invoke())!;
|
||||
}
|
||||
if (member is MethodInfo vMethod)
|
||||
{
|
||||
if (vMethod.ReturnType != typeof(bool))
|
||||
{
|
||||
Error($"Method {memberName} does not return a bool.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var paramList = vMethod.GetParameters();
|
||||
if (paramList.Length > 0)
|
||||
{
|
||||
Error($"Method {memberName} has too many parameters.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return () => (bool)vMethod.Invoke(instanceGet?.Invoke(), null)!;
|
||||
}
|
||||
|
||||
Error($"Member {memberName} of {type.FullName} is not valid for Visible function.");
|
||||
return null;
|
||||
}
|
||||
|
||||
internal Func<int, bool>? GetListVisibility(AbstractOptionGroup group, PropertyInfo property)
|
||||
{
|
||||
if (GetVisibilityMember(group, property, out Type type, out var instanceGet) is not { } member)
|
||||
{
|
||||
Error($"Member {memberName} does not exist in {type.FullName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (member is PropertyInfo vProperty)
|
||||
{
|
||||
if (!vProperty.PropertyType.IsAssignableTo(typeof(bool)) &&
|
||||
!vProperty.PropertyType.IsAssignableTo(typeof(ModdedOption<bool>)) &&
|
||||
!vProperty.PropertyType.IsAssignableTo(typeof(IList<bool>)))
|
||||
{
|
||||
Error($"Property {memberName} is not a bool value or indexer, a list of bools, or a bool ModdedOption.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var indexParams = vProperty.GetIndexParameters();
|
||||
if (indexParams.Length > 1)
|
||||
{
|
||||
Error($"Indexer {memberName} has too many parameters.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (vProperty.PropertyType.IsAssignableTo(typeof(IList<bool>)))
|
||||
{
|
||||
if (indexParams.Length != 0)
|
||||
{
|
||||
Error($"Property {memberName} cannot be an indexer of lists of bools.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return i => ((IList<bool>)vProperty.GetValue(instanceGet?.Invoke())!)[i];
|
||||
}
|
||||
|
||||
if (indexParams.Length == 0)
|
||||
{
|
||||
return _ => (bool)vProperty.GetValue(instanceGet?.Invoke())!;
|
||||
}
|
||||
if (indexParams[0].ParameterType != typeof(int))
|
||||
{
|
||||
Error($"Indexer {memberName}'s parameter is not an int.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return i => (bool)vProperty.GetValue(instanceGet?.Invoke(), [i])!;
|
||||
}
|
||||
if (member is MethodInfo vMethod)
|
||||
{
|
||||
if (vMethod.ReturnType != typeof(bool))
|
||||
{
|
||||
Error($"Method {memberName} does not return a bool.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var paramList = vMethod.GetParameters();
|
||||
if (paramList.Length > 1)
|
||||
{
|
||||
Error($"Method {memberName} has too many parameters.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (paramList.Length == 0)
|
||||
{
|
||||
return _ => (bool)vMethod.Invoke(instanceGet?.Invoke(), null)!;
|
||||
}
|
||||
if (paramList[0].ParameterType != typeof(int))
|
||||
{
|
||||
Error($"Method {memberName}'s parameter is not an int.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return i => (bool)vMethod.Invoke(instanceGet?.Invoke(), [i])!;
|
||||
}
|
||||
|
||||
Error($"Member {memberName} of {type.FullName} is not valid for Visible function.");
|
||||
return null;
|
||||
}
|
||||
|
||||
#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
|
||||
private MemberInfo? GetVisibilityMember(AbstractOptionGroup group, PropertyInfo property, out Type type, out Func<object>? instanceGet)
|
||||
{
|
||||
Type groupType = group.GetType();
|
||||
BindingFlags flags = BindingFlags.Static | BindingFlags.Public;
|
||||
|
||||
instanceGet = null;
|
||||
type = holderType ?? groupType;
|
||||
memberName ??= $"{property.Name}Visible";
|
||||
if (type.IsAssignableTo(typeof(AbstractOptionGroup)))
|
||||
{
|
||||
flags |= BindingFlags.Instance;
|
||||
|
||||
if (type == groupType)
|
||||
{
|
||||
flags |= BindingFlags.NonPublic;
|
||||
instanceGet = () => group;
|
||||
}
|
||||
else
|
||||
{
|
||||
var instanceProp = typeof(OptionGroupSingleton<>).MakeGenericType(type)
|
||||
.GetProperty(nameof(OptionGroupSingleton<>.Instance))!;
|
||||
instanceGet = () => instanceProp.GetValue(null)!;
|
||||
}
|
||||
}
|
||||
MemberInfo? member = type.GetMember(memberName, flags).FirstOrDefault();
|
||||
if (instanceGet != null)
|
||||
{
|
||||
MethodInfo? method = (member as PropertyInfo)?.GetGetMethod()
|
||||
?? member as MethodInfo;
|
||||
if (method != null && method.IsStatic)
|
||||
{
|
||||
instanceGet = null;
|
||||
}
|
||||
}
|
||||
return member;
|
||||
}
|
||||
#pragma warning restore S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MiraAPI.GameOptions.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for creating a list of enum options.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ModdedEnumOptionListAttribute(string title, Type enumType, string[]? values = null)
|
||||
: ModdedOptionListAttribute(title)
|
||||
{
|
||||
internal override IModdedOptionList CreateOptionList(IList value, PropertyInfo property)
|
||||
{
|
||||
var optList = new ModdedOptionList<ModdedEnumOption>(
|
||||
value.Count, idx => new(GetFormattedTitle(idx), (int)(value[idx] ?? 0), enumType, values));
|
||||
return optList;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetValue(int idx, object value)
|
||||
{
|
||||
var opt = HolderOptionList?[idx] as ModdedEnumOption;
|
||||
opt?.SetValue((int)value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue(int idx)
|
||||
{
|
||||
return HolderOptionList?[idx] is ModdedEnumOption opt
|
||||
? Enum.ToObject(enumType, opt.Value)
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" with EnumType ${enumType.FullName} is not a ModdedEnumOption");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for creating a list of enum options.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The enum type.</typeparam>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ModdedEnumOptionListAttribute<T>(string title, string[]? values = null)
|
||||
: ModdedOptionListAttribute(title) where T : Enum
|
||||
{
|
||||
internal override IModdedOptionList CreateOptionList(IList value, PropertyInfo property)
|
||||
{
|
||||
var optList = new ModdedOptionList<ModdedEnumOption<T>>(
|
||||
value.Count, idx => new(GetFormattedTitle(idx), (T)(value[idx] ?? 0), values));
|
||||
return optList;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetValue(int idx, object value)
|
||||
{
|
||||
var opt = HolderOptionList?[idx] as ModdedEnumOption<T>;
|
||||
opt?.SetValue((T)value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue(int idx)
|
||||
{
|
||||
return HolderOptionList?[idx] is ModdedEnumOption<T> opt
|
||||
? opt.Value
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" with EnumType ${typeof(T).FullName} is not a ModdedEnumOption");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
using MiraAPI.Utilities;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MiraAPI.GameOptions.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// A number option attribute for the modded options system.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ModdedNumberOptionListAttribute(
|
||||
string title,
|
||||
float min,
|
||||
float max,
|
||||
float increment = 1,
|
||||
MiraNumberSuffixes suffixType = MiraNumberSuffixes.None,
|
||||
string? formatString = null,
|
||||
bool zeroInfinity = false)
|
||||
: ModdedOptionListAttribute(title)
|
||||
{
|
||||
internal override IModdedOptionList CreateOptionList(IList value, PropertyInfo property)
|
||||
{
|
||||
return new ModdedOptionList<ModdedNumberOption>(value.Count, idx =>
|
||||
{
|
||||
return new(
|
||||
GetFormattedTitle(idx),
|
||||
(float)(value[idx] ?? min + increment),
|
||||
min,
|
||||
max,
|
||||
increment,
|
||||
suffixType,
|
||||
formatString,
|
||||
zeroInfinity);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetValue(int idx, object value)
|
||||
{
|
||||
var opt = HolderOptionList?[idx] as ModdedNumberOption;
|
||||
opt?.SetValue((float)value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue(int idx)
|
||||
{
|
||||
return HolderOptionList?[idx] is ModdedNumberOption opt
|
||||
? (object)opt.Value
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{GetFormattedTitle(idx)}\" is not a ModdedNumberOption");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace MiraAPI.GameOptions.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an attribute that is used to define a list of modded options.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the options.</param>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public abstract class ModdedOptionListAttribute(string title) : PropertyOptionAttribute
|
||||
{
|
||||
internal IModdedOptionList? HolderOptionList { get; set; }
|
||||
|
||||
internal object? Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the title of the options.
|
||||
/// If the string contains a format parameter, e.g., <c>{0}</c>, then the option's index will be given to it.
|
||||
/// The string cannot contain more than one parameter.
|
||||
/// If no parameter is given, the title will simply append the index to the end of it.
|
||||
/// </summary>
|
||||
public string Title => title;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of all the options.
|
||||
/// </summary>
|
||||
/// <param name="value">The new values as an object.</param>
|
||||
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]!);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the specific option.
|
||||
/// </summary>
|
||||
/// <param name="idx">The option to set.</param>
|
||||
/// <param name="value">The new value as an object.</param>
|
||||
public abstract void SetValue(int idx, object value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of all the options.
|
||||
/// </summary>
|
||||
/// <returns>The value of the options as an object.</returns>
|
||||
public override object GetValue()
|
||||
{
|
||||
return Value!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the specific option.
|
||||
/// </summary>
|
||||
/// <param name="idx">The option to set.</param>
|
||||
/// <returns>The value of the option as an object.</returns>
|
||||
public abstract object GetValue(int idx);
|
||||
|
||||
internal abstract IModdedOptionList? CreateOptionList(IList value, PropertyInfo property);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Title"/> formatted with the option's index.
|
||||
/// </summary>
|
||||
/// <param name="index">The option's index.</param>
|
||||
/// <returns>The formatted option's title.</returns>
|
||||
[SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "I don't even know if this is worth it.")]
|
||||
protected string GetFormattedTitle(int index)
|
||||
{
|
||||
Match match = Regex.Match(Title, @"\{(\d+)(?::[^}]+)?\}", default, Regex.InfiniteMatchTimeout);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
if (match.Groups.Count == 1 && match.Groups[0].Value == "0")
|
||||
{
|
||||
return string.Format(Title, index);
|
||||
}
|
||||
Error("ModdedOptionList Title cannot contain more than the first parameter for formatting.");
|
||||
}
|
||||
return Title + index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MiraAPI.GameOptions.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for a list of toggle options.
|
||||
/// </summary>
|
||||
/// <param name="title">The option's title.</param>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ModdedToggleOptionListAttribute(string title) : ModdedOptionListAttribute(title)
|
||||
{
|
||||
internal override IModdedOptionList CreateOptionList(IList value, PropertyInfo property)
|
||||
{
|
||||
var optList = new ModdedOptionList<ModdedToggleOption>(
|
||||
value.Count, idx => new(GetFormattedTitle(idx), (bool)(value[idx] ?? false)));
|
||||
return optList;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetValue(int idx, object value)
|
||||
{
|
||||
var opt = HolderOptionList?[idx] as ModdedToggleOption;
|
||||
opt?.SetValue((bool)value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue(int idx)
|
||||
{
|
||||
return HolderOptionList?[idx] is ModdedToggleOption opt
|
||||
? (object)opt.Value
|
||||
: throw new InvalidOperationException($"Holder option for {GetFormattedTitle(idx)} is not a ModdedToggleOption.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace MiraAPI.GameOptions.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an attribute that is used to intercept a property's getter and setter.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public abstract class PropertyOptionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the value of the option.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value as an object.</param>
|
||||
public abstract void SetValue(object value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the option.
|
||||
/// </summary>
|
||||
/// <returns>The value of the option as an object.</returns>
|
||||
public abstract object GetValue();
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BepInEx.Configuration;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.PluginLoading;
|
||||
@@ -114,3 +115,10 @@ public interface IModdedOption
|
||||
/// <param name="presetConfig">The <see cref="ConfigFile"/> representing the preset configuration.</param>
|
||||
void LoadFromPreset(ConfigFile presetConfig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for list of modded options.
|
||||
/// </summary>
|
||||
public interface IModdedOptionList : IReadOnlyList<IModdedOption>
|
||||
{
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
@@ -23,7 +24,7 @@ namespace MiraAPI.GameOptions;
|
||||
/// </summary>
|
||||
public static class ModdedOptionsManager
|
||||
{
|
||||
private static readonly Dictionary<PropertyInfo, ModdedOptionAttribute> OptionAttributes = [];
|
||||
private static readonly Dictionary<PropertyInfo, PropertyOptionAttribute> OptionAttributes = [];
|
||||
private static readonly Dictionary<Type, AbstractOptionGroup> TypeToGroup = [];
|
||||
|
||||
internal static readonly Dictionary<OptionBehaviour, ModdedPlayerOption> CreatedPlayerOptions = [];
|
||||
@@ -183,10 +184,112 @@ public static class ModdedOptionsManager
|
||||
OptionAttributes.Add(property, attribute);
|
||||
attribute.HolderOption = option;
|
||||
|
||||
var visibilityAttr = property.GetCustomAttribute<ModdedOptionVisibilityAttribute>();
|
||||
var visibilityFunc = visibilityAttr?.GetVisibility(group, property);
|
||||
if (visibilityFunc != null)
|
||||
{
|
||||
option.Visible = visibilityFunc;
|
||||
}
|
||||
|
||||
RegisterOption(option, group, property.Name, pluginInfo);
|
||||
}
|
||||
|
||||
private static void RegisterOption(
|
||||
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<MiraApiPlugin>.Instance.Harmony.Patch(setterOriginal, postfix: new HarmonyMethod(setterPatch));
|
||||
|
||||
var getterOriginal = property.GetGetMethod();
|
||||
var getterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyGetterPatch));
|
||||
PluginSingleton<MiraApiPlugin>.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<MiraApiPlugin>.Instance.Harmony.Patch(listSetterOriginal, postfix: new HarmonyMethod(listSetterPatch));
|
||||
|
||||
var listGetterOriginal = listIndex.GetSetMethod();
|
||||
var listGetterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyListGetterPatch));
|
||||
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(listGetterOriginal, prefix: new HarmonyMethod(listGetterPatch));
|
||||
|
||||
OptionAttributes.Add(property, attribute);
|
||||
attribute.HolderOptionList = optionList;
|
||||
attribute.Value = propertyVal;
|
||||
|
||||
var visibilityAttr = property.GetCustomAttribute<ModdedOptionVisibilityAttribute>();
|
||||
var visibilityFunc = visibilityAttr?.GetListVisibility(group, property);
|
||||
for (int i = 0; i < optionList.Count; i++)
|
||||
{
|
||||
var option = optionList[i];
|
||||
if (visibilityFunc != null)
|
||||
{
|
||||
option.Visible = () => visibilityFunc(i);
|
||||
}
|
||||
RegisterOption(option, group, property.Name + i, pluginInfo);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RegisterOption(
|
||||
IModdedOption option,
|
||||
AbstractOptionGroup group,
|
||||
string propertyName,
|
||||
@@ -274,4 +377,38 @@ public static class ModdedOptionsManager
|
||||
__result = attribute.GetValue();
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patches the setter of a list property to update the value of the option.
|
||||
/// </summary>
|
||||
/// <param name="__instance">The list's instance.</param>
|
||||
/// <param name="index">The index to find in the list.</param>
|
||||
/// <param name="value">The new object value.</param>
|
||||
#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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patches the getter of a list property to return the value of the option.
|
||||
/// </summary>
|
||||
/// <param name="__instance">The list's instance.</param>
|
||||
/// <param name="index">The index to find in the list.</param>
|
||||
/// <param name="__result">The result of the property getter.</param>
|
||||
/// <returns>False so the original getter gets skipped.</returns>
|
||||
#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;
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,6 @@ namespace MiraAPI.GameOptions.OptionTypes;
|
||||
/// <typeparam name="T">The value type.</typeparam>
|
||||
public abstract class ModdedOption<T> : IModdedOption
|
||||
{
|
||||
private IMiraPlugin? _parentMod;
|
||||
|
||||
/// <inheritdoc />
|
||||
public uint Id { get; }
|
||||
|
||||
@@ -31,13 +29,13 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.GameOptions.OptionTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a modded option list.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of options.</typeparam>
|
||||
public class ModdedOptionList<T> : IModdedOptionList where T : IModdedOption
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public int Count { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of options.
|
||||
/// </summary>
|
||||
public IReadOnlyList<T> Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedOptionList{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="count">The option list's length.</param>
|
||||
/// <param name="optionFactory">The option factory to instantiate the options from.</param>
|
||||
public ModdedOptionList(int count, Func<int, T> optionFactory)
|
||||
{
|
||||
Count = count;
|
||||
Options = [.. Enumerable.Range(0, Count).Select(optionFactory)];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedOptionList{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options">The options list.</param>
|
||||
public ModdedOptionList(IEnumerable<T> options)
|
||||
{
|
||||
Count = options.Count();
|
||||
Options = [.. options];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<IModdedOption> GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<IModdedOption>)Options).GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return Options.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indexes the option of type <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <param name="idx">The option's index.</param>
|
||||
/// <returns>option of type <typeparamref name="T"/>.</returns>
|
||||
public T this[int idx] => Options[idx];
|
||||
|
||||
IModdedOption IReadOnlyList<IModdedOption>.this[int index] => this[index];
|
||||
}
|
||||
@@ -644,7 +644,7 @@ public abstract class CustomActionButton<T> : CustomActionButton where T : MonoB
|
||||
/// </summary>
|
||||
public virtual void ResetTarget()
|
||||
{
|
||||
Target = null;
|
||||
SetOutline(false);
|
||||
Target = null;
|
||||
}
|
||||
}
|
||||
@@ -134,4 +134,12 @@ public abstract class BaseModifier : IOptionable
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes this modifier instance from the player.
|
||||
/// </summary>
|
||||
public void RemoveSelf()
|
||||
{
|
||||
ModifierComponent?.RemoveModifier(this);
|
||||
}
|
||||
}
|
||||
@@ -203,6 +203,18 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
return GetModifiers(type, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of <see cref="BaseModifier"/>s by type, if the type is an interface.
|
||||
/// </summary>
|
||||
/// <param name="predicate">The predicate to check the <see cref="BaseModifier"/> by.</param>
|
||||
/// <typeparam name="T">The Type of the interface of the <see cref="BaseModifier"/>.</typeparam>
|
||||
/// <returns>An <see cref="IEnumerable{T}"/> of <see cref="BaseModifier"/>s of type <typeparamref name="T"/>.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public IEnumerable<T> GetModifiersOfType<T>(Func<T, bool>? predicate = null) where T : class
|
||||
{
|
||||
return ActiveModifiers.OfType<T>().Where(x => predicate == null || predicate(x));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
@@ -258,6 +270,20 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
return modifier != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a <see cref="BaseModifier"/> by its type, if the type is an interface.
|
||||
/// </summary>
|
||||
/// <param name="modifier">The <see cref="BaseModifier"/> of type <typeparamref name="T"/> or null.</param>
|
||||
/// <param name="predicate">The predicate to check the <see cref="BaseModifier"/> by.</param>
|
||||
/// <typeparam name="T">The Type of the interface of the <see cref="BaseModifier"/>.</typeparam>
|
||||
/// <returns><see langword="true"/> if the <see cref="BaseModifier"/> of type <typeparamref name="T"/> was found, <see langword="false"/> otherwise.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool TryGetModifierOfType<T>([NotNullWhen(true)] out T? modifier, Func<T, bool>? predicate = null) where T : class
|
||||
{
|
||||
modifier = GetModifierOfType(predicate);
|
||||
return modifier != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
@@ -308,6 +334,18 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
return ActiveModifiers.Find(x => x.UniqueId == modifierGuid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="BaseModifier"/> by its type, if the type is an interface.
|
||||
/// </summary>
|
||||
/// <param name="predicate">The predicate to check the <see cref="BaseModifier"/> by.</param>
|
||||
/// <typeparam name="T">The Type of the interface of the <see cref="BaseModifier"/>.</typeparam>
|
||||
/// <returns>The <see cref="BaseModifier"/> of type <typeparamref name="T"/> if it is found, <see langword="null"/> otherwise.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public T? GetModifierOfType<T>(Func<T, bool>? predicate = null) where T : class
|
||||
{
|
||||
return GetModifiersOfType(predicate).FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a <typeparamref name="T"/> from the player.
|
||||
/// </summary>
|
||||
@@ -389,6 +427,77 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
RemoveModifier(modifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to remove a <typeparamref name="T"/> from the player.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The <see cref="BaseModifier"/> type.</typeparam>
|
||||
/// <param name="predicate">The predicate to check the <see cref="BaseModifier"/> by.</param>
|
||||
/// <returns><see langword="false"/> if the <typeparamref name="T"/> is not active on this player, or there are multiple instances;
|
||||
/// else <see langword="true"/>.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool TryRemoveModifier<T>(Func<T, bool>? predicate = null) where T : BaseModifier
|
||||
{
|
||||
return TryGetModifier(out var modifier, predicate) &&
|
||||
TryRemoveModifier(modifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to remove a <see cref="BaseModifier"/> from the player.
|
||||
/// </summary>
|
||||
/// <param name="type">The <see cref="BaseModifier"/> type.</param>
|
||||
/// <param name="predicate">The predicate to check the modifier by.</param>
|
||||
/// <returns><see langword="false"/> if the <see cref="BaseModifier"/> is not active on this player, or there are multiple instances;
|
||||
/// else <see langword="true"/>.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool TryRemoveModifier(Type type, Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
return TryGetModifier(type, out var modifier, predicate) &&
|
||||
TryRemoveModifier(modifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to remove a <see cref="BaseModifier"/> from the player.
|
||||
/// </summary>
|
||||
/// <param name="modifier">The <see cref="BaseModifier"/> object.</param>
|
||||
/// <returns><see langword="false"/> if the <see cref="BaseModifier"/> is not active on this player, else <see langword="true"/>.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool TryRemoveModifier(BaseModifier modifier)
|
||||
{
|
||||
if (!ActiveModifiers.Contains(modifier))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_toRemove.Add(modifier);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to remove a <see cref="BaseModifier"/> from the player.
|
||||
/// </summary>
|
||||
/// <param name="typeId">The <see cref="BaseModifier"/>'s type ID.</param>
|
||||
/// <param name="predicate">The predicate to check the <see cref="BaseModifier"/> by.</param>
|
||||
/// <returns><see langword="false"/> if the <see cref="BaseModifier"/> is not active on this player, or there are multiple instances;
|
||||
/// else <see langword="true"/>.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool TryRemoveModifier(uint typeId, Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
return TryGetModifier(typeId, out var modifier, predicate) &&
|
||||
TryRemoveModifier(modifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to remove a <see cref="BaseModifier"/> from the player.
|
||||
/// </summary>
|
||||
/// <param name="uniqueId">The <see cref="BaseModifier"/>'s unique ID.</param>
|
||||
/// <returns><see langword="false"/> if the <see cref="BaseModifier"/> is not active on this player, else <see langword="true"/>.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool TryRemoveModifier(Guid uniqueId)
|
||||
{
|
||||
return TryGetModifier(uniqueId, out var modifier) &&
|
||||
TryRemoveModifier(modifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <typeparamref name="T"/> to the player.
|
||||
/// </summary>
|
||||
@@ -584,4 +693,33 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
return bm.UniqueId == id;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a player has an active modifier by its type, if the type is an interface.
|
||||
/// </summary>
|
||||
/// <param name="predicate">The predicate to check the modifier.</param>
|
||||
/// <typeparam name="T">The Type of the interface of the Modifier.</typeparam>
|
||||
/// <returns>True if the Modifier is present, false otherwise.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool HasModifierOfType<T>(Func<T, bool>? predicate = null) where T : class
|
||||
{
|
||||
return ActiveModifiers.Exists(x => x is T modifier && (predicate == null || predicate(modifier)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a player has an active modifier by its type, if the type is an interface.
|
||||
/// </summary>
|
||||
/// <param name="checkInactive">Whether to check inactive modifiers (those pending to be added).</param>
|
||||
/// <param name="predicate">The predicate to check the modifier.</param>
|
||||
/// <typeparam name="T">The Type of the interface of the Modifier.</typeparam>
|
||||
/// <returns>True if the Modifier is present, false otherwise.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool HasModifierOfType<T>(bool checkInactive, Func<T, bool>? predicate = null) where T : class
|
||||
{
|
||||
return ActiveModifiers.Exists(MatchExpr) || (checkInactive && _toAdd.Exists(MatchExpr));
|
||||
bool MatchExpr(BaseModifier bm)
|
||||
{
|
||||
return bm is T modifier && (predicate == null || predicate(modifier));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,6 +190,18 @@ public static class ModifierExtensions
|
||||
return player.GetModifierComponent().HasModifier(uniqueId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the <see cref="PlayerControl"/> has a specific <see cref="BaseModifier"/>, if the type is an interface.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the interface of the <see cref="BaseModifier"/>.</typeparam>
|
||||
/// <param name="player">The <see cref="PlayerControl"/> instance.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the <see cref="BaseModifier"/>s.</param>
|
||||
/// <returns><see langword="true"/> if the <see cref="PlayerControl"/> has the <see cref="BaseModifier"/> of type <typeparamref name="T"/>, <see langword="false"/> otherwise.</returns>
|
||||
public static bool HasModifierOfType<T>(this PlayerControl player, Func<T, bool>? predicate = null) where T : class
|
||||
{
|
||||
return player.GetModifierComponent().HasModifierOfType(predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all <see cref="BaseModifier"/>s from the <see cref="PlayerControl"/>.
|
||||
/// </summary>
|
||||
@@ -265,6 +277,19 @@ public static class ModifierExtensions
|
||||
return player.GetModifierComponent().TryGetModifier(modifierGuid, out modifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a <see cref="BaseModifier"/> by its type, if the type is an interface.
|
||||
/// </summary>
|
||||
/// <param name="player">The <see cref="PlayerControl"/> instance.</param>
|
||||
/// <param name="modifier">The <see cref="BaseModifier"/> of type <typeparamref name="T"/> or null.</param>
|
||||
/// <param name="predicate">The predicate to check the <see cref="BaseModifier"/> of type <typeparamref name="T"/> by.</param>
|
||||
/// <typeparam name="T">The Type of the interface of the <see cref="BaseModifier"/>.</typeparam>
|
||||
/// <returns><see langword="true"/> if the <see cref="BaseModifier"/> of type <typeparamref name="T"/> was found, <see langword="false"/> otherwise.</returns>
|
||||
public static bool TryGetModifierOfType<T>(this PlayerControl player, [NotNullWhen(true)] out T? modifier, Func<T, bool>? predicate = null) where T : class
|
||||
{
|
||||
return player.GetModifierComponent().TryGetModifierOfType(out modifier, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a specific <typeparamref name="T"/> from the <see cref="PlayerControl"/>.
|
||||
/// </summary>
|
||||
@@ -320,6 +345,18 @@ public static class ModifierExtensions
|
||||
return player.GetModifierComponent().GetModifier(uniqueId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a specific <see cref="BaseModifier"/> of type <typeparamref name="T"/> from the <see cref="PlayerControl"/>, if the type is an interface.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the interface of the modifier.</typeparam>
|
||||
/// <param name="player">The <see cref="PlayerControl"/> instance.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the <see cref="BaseModifier"/>s.</param>
|
||||
/// <returns>The <see cref="BaseModifier"/> of type <typeparamref name="T"/> if found, <see langword="null"/> otherwise.</returns>
|
||||
public static T? GetModifierOfType<T>(this PlayerControl player, Func<T, bool>? predicate = null) where T : class
|
||||
{
|
||||
return player.GetModifierComponent().GetModifierOfType(predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all <typeparamref name="T"/>s from the <see cref="PlayerControl"/>.
|
||||
/// </summary>
|
||||
@@ -363,6 +400,19 @@ public static class ModifierExtensions
|
||||
return player.GetModifierComponent().GetModifiers(typeId, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all <see cref="BaseModifier"/>s of a specific type from the <see cref="PlayerControl"/>, if the type is an interface.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the interface of the <see cref="BaseModifier"/>s.</typeparam>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the <see cref="BaseModifier"/>s.</param>
|
||||
/// <returns>An <see cref="IEnumerable{T}"/> of <see cref="BaseModifier"/>s of type <typeparamref name="T"/>.</returns>
|
||||
public static IEnumerable<T> GetModifiersOfType<T>(this PlayerControl player, Func<T, bool>? predicate = null)
|
||||
where T : class
|
||||
{
|
||||
return player.GetModifierComponent().GetModifiersOfType(predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a specific <see cref="BaseModifier"/> from the <see cref="PlayerControl"/>.
|
||||
/// </summary>
|
||||
@@ -422,6 +472,73 @@ public static class ModifierExtensions
|
||||
player.GetModifierComponent().RemoveModifier(uniqueId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to remove a specific <see cref="BaseModifier"/> from the <see cref="PlayerControl"/>.
|
||||
/// </summary>
|
||||
/// <param name="player">The <see cref="PlayerControl"/> instance.</param>
|
||||
/// <param name="modifier">The <see cref="BaseModifier"/> to remove.</param>
|
||||
/// <returns><see langword="false"/> if the <see cref="BaseModifier"/> is not active on this <see cref="PlayerControl"/>, else <see langword="true"/>.</returns>
|
||||
public static bool TryRemoveModifier(this PlayerControl player, BaseModifier modifier)
|
||||
{
|
||||
return player.GetModifierComponent().TryRemoveModifier(modifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to remove a specific <see cref="BaseModifier"/> from the <see cref="PlayerControl"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the <see cref="BaseModifier"/>.</typeparam>
|
||||
/// <param name="player">The <see cref="PlayerControl"/> instance.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the <see cref="BaseModifier"/>s.</param>
|
||||
/// <returns><see langword="false"/> if the <see cref="BaseModifier"/> is not active on this <see cref="PlayerControl"/>, or there are multiple instances;
|
||||
/// else <see langword="true"/>.</returns>
|
||||
public static bool TryRemoveModifier<T>(this PlayerControl player, Func<T, bool>? predicate = null)
|
||||
where T : BaseModifier
|
||||
{
|
||||
return player.GetModifierComponent().TryRemoveModifier(predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to remove a specific <see cref="BaseModifier"/> from the <see cref="PlayerControl"/> by type.
|
||||
/// </summary>
|
||||
/// <param name="player">The <see cref="PlayerControl"/> instance.</param>
|
||||
/// <param name="type">The type of the <see cref="BaseModifier"/>.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the <see cref="BaseModifier"/>s.</param>
|
||||
/// <returns><see langword="false"/> if the <see cref="BaseModifier"/> is not active on this <see cref="PlayerControl"/>, or there are multiple instances;
|
||||
/// else <see langword="true"/>.</returns>
|
||||
public static bool TryRemoveModifier(this PlayerControl player, Type type, Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
return player.GetModifierComponent().TryRemoveModifier(type, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to remove a specific <see cref="BaseModifier"/> from the <see cref="PlayerControl"/> by its type ID.
|
||||
/// </summary>
|
||||
/// <param name="player">The <see cref="PlayerControl"/> instance.</param>
|
||||
/// <param name="typeId">The type ID of the <see cref="BaseModifier"/>.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the <see cref="BaseModifier"/>s.</param>
|
||||
/// <returns><see langword="false"/> if the <see cref="BaseModifier"/> is not active on this <see cref="PlayerControl"/>, or there are multiple instances;
|
||||
/// else <see langword="true"/>.</returns>
|
||||
public static bool TryRemoveModifier(
|
||||
this PlayerControl player,
|
||||
uint typeId,
|
||||
Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
return player.GetModifierComponent().TryRemoveModifier(typeId, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to remove a specific <see cref="BaseModifier"/> from the <see cref="PlayerControl"/> by its GUID.
|
||||
/// </summary>
|
||||
/// <param name="player">The <see cref="PlayerControl"/> instance.</param>
|
||||
/// <param name="uniqueId">The GUID of the <see cref="BaseModifier"/>.</param>
|
||||
/// <returns><see langword="false"/> if the <see cref="BaseModifier"/> is not active on this <see cref="PlayerControl"/>, else <see langword="true"/>.</returns>
|
||||
public static bool TryRemoveModifier(
|
||||
this PlayerControl player,
|
||||
Guid uniqueId)
|
||||
{
|
||||
return player.GetModifierComponent().TryRemoveModifier(uniqueId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a specific <typeparamref name="T"/> to the <see cref="PlayerControl"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -451,7 +451,7 @@ public static class LobbyViewPanePatches
|
||||
|
||||
foreach (var grouping in sortedRoleGroups)
|
||||
{
|
||||
if (!grouping.Any() || grouping.All(x => x.Configuration.HideSettings || !x.VisibleInSettings()))
|
||||
if (!grouping.Any() || grouping.All(x => x.Configuration.HideSettings || !x.VisibleInSettings() || !x.Configuration.AssociatedGameMode.IsInstanceOfType(CustomGameModeManager.ActiveMode)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -477,7 +477,7 @@ public static class LobbyViewPanePatches
|
||||
|
||||
foreach (var customRole in grouping)
|
||||
{
|
||||
if (customRole.Configuration.HideSettings || !customRole.VisibleInSettings())
|
||||
if (customRole.Configuration.HideSettings || !customRole.VisibleInSettings() || !customRole.Configuration.AssociatedGameMode.IsInstanceOfType(CustomGameModeManager.ActiveMode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -230,7 +230,9 @@ public static class RoleGuidePatches
|
||||
__instance.CoLoadAssetAsync<NamePlateViewData>(
|
||||
namePlate.GetAssetReference(),
|
||||
(Action<NamePlateViewData>?)LoadNameplate));
|
||||
#pragma warning disable S1751 // Loops with at most one iteration should be refactored (Justification: No idea why it's behaving this way.)
|
||||
continue;
|
||||
#pragma warning restore S1751 // Loops with at most one iteration should be refactored
|
||||
|
||||
void LoadNameplate(NamePlateViewData viewdata)
|
||||
{
|
||||
|
||||
@@ -326,15 +326,26 @@ public sealed class MiraPluginManager
|
||||
}
|
||||
|
||||
var attribute = property.GetCustomAttribute<ModdedOptionAttribute>();
|
||||
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<ModdedOptionListAttribute>();
|
||||
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.");
|
||||
}
|
||||
|
||||
+57
-1
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using AmongUs.GameOptions;
|
||||
using System;
|
||||
|
||||
namespace MiraAPI.Roles;
|
||||
|
||||
@@ -32,4 +33,59 @@ public static class RoleId
|
||||
? throw new InvalidOperationException($"Role {type} is not registered")
|
||||
: roleId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of a role.
|
||||
/// </summary>
|
||||
/// <param name="role">The <see cref="ICustomRole"/> role.</param>
|
||||
/// <returns>The ID as a ushort.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown when the role is not registered.</exception>
|
||||
public static ushort Get(ICustomRole role)
|
||||
{
|
||||
return Get(role.GetType());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of an <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the role.</typeparam>
|
||||
/// <returns>The role ID as a <see cref="RoleTypes"/>.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown when the role is not registered.</exception>
|
||||
public static RoleTypes GetType<T>() where T : ICustomRole
|
||||
{
|
||||
return (RoleTypes)Get<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of a role.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the role.</param>
|
||||
/// <returns>The ID as a <see cref="RoleTypes"/>.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown when the role is not registered.</exception>
|
||||
public static RoleTypes GetType(Type type)
|
||||
{
|
||||
return (RoleTypes)Get(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <typeparamref name="T"/> role.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the role.</typeparam>
|
||||
/// <returns>The <see cref="RoleBehaviour"/> object.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown when the role is not registered.</exception>
|
||||
public static RoleBehaviour GetRole<T>() where T : ICustomRole
|
||||
{
|
||||
return RoleManager.Instance.GetRole(GetType<T>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the role from its type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the role.</param>
|
||||
/// <returns>The <see cref="RoleBehaviour"/> object.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown when the role is not registered.</exception>
|
||||
public static RoleBehaviour GetRole(Type type)
|
||||
{
|
||||
return RoleManager.Instance.GetRole(GetType(type));
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,15 @@ public abstract class LoadableAsset<T> where T : UnityEngine.Object
|
||||
/// <returns>The loaded asset.</returns>
|
||||
public abstract T LoadAsset();
|
||||
|
||||
/// <summary>
|
||||
/// Converts the <see cref="LoadableAsset{T}"/> into its <typeparamref name="T"/> by loading the asset.
|
||||
/// </summary>
|
||||
/// <param name="loadable">The <see cref="LoadableAsset{T}"/> to get the asset from.</param>
|
||||
public static implicit operator T(LoadableAsset<T> loadable)
|
||||
{
|
||||
return loadable.LoadAsset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unloads an asset.
|
||||
/// </summary>
|
||||
|
||||
@@ -44,7 +44,7 @@ public static class Extensions
|
||||
}
|
||||
|
||||
return new NetData(
|
||||
RoleId.Get(role.GetType()),
|
||||
RoleId.Get(role),
|
||||
BitConverter.GetBytes(count.Value).AddRangeToArray(BitConverter.GetBytes(chance.Value)));
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user