git is confusing

This commit is contained in:
Az committed 2026-09-07 15:33:11 -04:00
1 parent 875107e337
commit 2ce04690a9
26 files changed
+1083 -32

No files matched your search

+3
View File
@@ -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
+16
View File
@@ -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
+2 -2
View File
@@ -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();
}
+8
View File
@@ -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>
{
}
+139 -2
View File
@@ -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];
}
+1 -1
View File
@@ -644,7 +644,7 @@ public abstract class CustomActionButton<T> : CustomActionButton where T : MonoB
/// </summary>
public virtual void ResetTarget()
{
Target = null;
SetOutline(false);
Target = null;
}
}
+8
View File
@@ -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);
}
}
+138
View File
@@ -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));
}
}
}
+117
View File
@@ -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;
}
+2
View File
@@ -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)
{
+14 -3
View File
@@ -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
View File
@@ -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>
+1 -1
View File
@@ -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)));
}