diff --git a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs index 8db98c7..86feece 100644 --- a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs +++ b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs @@ -3,6 +3,7 @@ using MiraAPI.Example.Options.Roles; using MiraAPI.Example.Roles; using MiraAPI.GameOptions; using MiraAPI.Hud; +using MiraAPI.Modifiers; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using UnityEngine; diff --git a/MiraAPI.Example/Buttons/MeetingButton.cs b/MiraAPI.Example/Buttons/MeetingButton.cs index 85b8ff1..742c7e0 100644 --- a/MiraAPI.Example/Buttons/MeetingButton.cs +++ b/MiraAPI.Example/Buttons/MeetingButton.cs @@ -1,6 +1,6 @@ using MiraAPI.Example.Modifiers; using MiraAPI.Hud; -using MiraAPI.Utilities; +using MiraAPI.Modifiers; using MiraAPI.Utilities.Assets; using UnityEngine; diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs index e917cdc..80bd497 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.Linq; using System.Text; using Il2CppInterop.Runtime.InteropTypes.Arrays; diff --git a/MiraAPI/Modifiers/ModifierExtensions.cs b/MiraAPI/Modifiers/ModifierExtensions.cs new file mode 100644 index 0000000..83dfd4c --- /dev/null +++ b/MiraAPI/Modifiers/ModifierExtensions.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using MiraAPI.Networking; +using Reactor.Networking.Attributes; +using Reactor.Utilities; + +namespace MiraAPI.Modifiers; + +/// +/// Extension methods for modifiers. +/// +public static class ModifierExtensions +{ + internal static Dictionary ModifierComponents { get; } = []; + + /// + /// Gets the ModifierComponent for a player. + /// + /// The PlayerControl object. + /// A ModifierComponent if there is one, null otherwise. + public static ModifierComponent? GetModifierComponent(this PlayerControl player) + { + if (ModifierComponents.TryGetValue(player, out var component)) + { + return component; + } + + component = player.GetComponent(); + if (component == null) + { + return null; + } + + ModifierComponents[player] = component; + return component; + } + + /// + /// Gets a modifier by its type, or null if the player doesn't have it. + /// + /// The PlayerControl object. + /// The Type of the Modifier. + /// The Modifier if it is found, null otherwise. + public static T? GetModifier(this PlayerControl? player) where T : BaseModifier + { + return player?.GetModifierComponent()?.ActiveModifiers.Find(x => x is T) as T; + } + + /// + /// Checks if a player has a modifier. + /// + /// The PlayerControl object. + /// The Type of the Modifier. + /// True if the Modifier is present, false otherwise. + public static bool HasModifier(this PlayerControl? player) where T : BaseModifier + { + return player?.GetModifierComponent() != null && + player.GetModifierComponent()!.HasModifier(); + } + + /// + /// Checks if a player has a modifier by its ID. + /// + /// The PlayerControl object. + /// The Modifier ID. + /// True if the Modifier is present, false otherwise. + public static bool HasModifier(this PlayerControl? player, uint id) + { + return player?.GetModifierComponent() != null && + player.GetModifierComponent()!.HasModifier(id); + } + + /// + /// Remote Procedure Call to remove a modifier from a player. + /// + /// The player to remove the modifier from. + /// The ID of the modifier. + [MethodRpc((uint)MiraRpc.RemoveModifier)] + public static void RpcRemoveModifier(this PlayerControl target, uint modifierId) + { + target.GetModifierComponent()?.RemoveModifier(modifierId); + } + + /// + /// Remote Procedure Call to remove a modifier from a player. + /// + /// The player to remove the modifier from. + /// The Type of the Modifier. + public static void RpcRemoveModifier(this PlayerControl player) where T : BaseModifier + { + var id = ModifierManager.GetModifierId(typeof(T)); + + if (id == null) + { + Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered."); + return; + } + + player.RpcRemoveModifier(id.Value); + } + + /// + /// Remote Procedure Call to add a modifier to a player. + /// + /// The player to add the modifier to. + /// The modifier ID. + [MethodRpc((uint)MiraRpc.AddModifier)] + public static void RpcAddModifier(this PlayerControl target, uint modifierId, params object[] args) + { + var type = ModifierManager.GetModifierType(modifierId); + if (type == null) + { + Logger.Error($"Cannot add modifier with id {modifierId} because it is not registered."); + return; + } + + var modifier = ModifierFactory.CreateInstance(type, args); + target.GetModifierComponent()?.AddModifier(modifier); + } + + /// + /// Remote Procedure Call to add a modifier to a player. + /// + /// The player to add the modifier to. + /// The arguments to initialize the modifier constructor with. + /// The modifier Type. + public static void RpcAddModifier(this PlayerControl player, params object[] args) where T : BaseModifier + { + var id = ModifierManager.GetModifierId(typeof(T)); + if (id == null) + { + Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered."); + return; + } + + var modifier = ModifierFactory.CreateInstance(args); + player.GetModifierComponent()!.AddModifier(modifier); + } +} diff --git a/MiraAPI/Modifiers/ModifierFactory.cs b/MiraAPI/Modifiers/ModifierFactory.cs new file mode 100644 index 0000000..e1b018c --- /dev/null +++ b/MiraAPI/Modifiers/ModifierFactory.cs @@ -0,0 +1,83 @@ +using System; +using System.Linq; +using System.Linq.Expressions; + +namespace MiraAPI.Modifiers; + +public static class ModifierFactory +{ + private static Func? _constructor; + + private static Func CreateConstructor(Type type) + { + var constructorInfo = type.GetConstructors() + .OrderByDescending(c => c.GetParameters().Length) + .FirstOrDefault() ?? throw new InvalidOperationException($"Type {type} does not have a public constructor."); + var parameters = constructorInfo.GetParameters(); + + var argsParam = Expression.Parameter(typeof(object[]), "args"); + var constructorParams = new Expression[parameters.Length]; + + for (var i = 0; i < parameters.Length; i++) + { + var paramType = parameters[i].ParameterType; + var paramAccess = Expression.ArrayIndex(argsParam, Expression.Constant(i)); + var convertedParam = Expression.Convert(paramAccess, paramType); + constructorParams[i] = convertedParam; + } + + var newExpr = Expression.New(constructorInfo, constructorParams); + var lambda = Expression.Lambda>(newExpr, argsParam); + + return lambda.Compile(); + } + + public static BaseModifier CreateInstance(Type type, params object[] args) + { + _constructor ??= CreateConstructor(type); + return _constructor(args); + } +} + +/// +/// Factory for creating instances of a modifier. More efficient than using reflection. +/// +/// The modifier type. +public static class ModifierFactory where T : BaseModifier +{ + private static readonly Func Constructor = CreateConstructor(); + + private static Func CreateConstructor() + { + var constructorInfo = typeof(T).GetConstructors() + .OrderByDescending(c => c.GetParameters().Length) + .FirstOrDefault() ?? throw new InvalidOperationException($"Type {typeof(T)} does not have a public constructor."); + var parameters = constructorInfo.GetParameters(); + + var argsParam = Expression.Parameter(typeof(object[]), "args"); + var constructorParams = new Expression[parameters.Length]; + + for (var i = 0; i < parameters.Length; i++) + { + var paramType = parameters[i].ParameterType; + var paramAccess = Expression.ArrayIndex(argsParam, Expression.Constant(i)); + var convertedParam = Expression.Convert(paramAccess, paramType); + constructorParams[i] = convertedParam; + } + + var newExpr = Expression.New(constructorInfo, constructorParams); + var lambda = Expression.Lambda>(newExpr, argsParam); + + return lambda.Compile(); + } + + /// + /// Creates an instance of the modifier. + /// + /// Parameters for the constructor. + /// An instance of the modifier. + public static T CreateInstance(params object[] args) + { + return Constructor(args); + } +} diff --git a/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs b/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs index d7a99d5..17356db 100644 --- a/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs +++ b/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs @@ -1,7 +1,7 @@ using System.Linq; using HarmonyLib; +using MiraAPI.Modifiers; using MiraAPI.Modifiers.Types; -using MiraAPI.Utilities; namespace MiraAPI.Patches.Modifiers; diff --git a/MiraAPI/Patches/PlayerControlPatches.cs b/MiraAPI/Patches/PlayerControlPatches.cs index bc98a51..e0d647d 100644 --- a/MiraAPI/Patches/PlayerControlPatches.cs +++ b/MiraAPI/Patches/PlayerControlPatches.cs @@ -86,6 +86,6 @@ public static class PlayerControlPatches [HarmonyPatch(nameof(PlayerControl.OnDestroy))] public static void PlayerControlOnDestroyPrefix(PlayerControl __instance) { - Utilities.Extensions.ModifierComponents.Remove(__instance); + ModifierExtensions.ModifierComponents.Remove(__instance); } } diff --git a/MiraAPI/Patches/VentPatches.cs b/MiraAPI/Patches/VentPatches.cs index 66c9aae..a41c690 100644 --- a/MiraAPI/Patches/VentPatches.cs +++ b/MiraAPI/Patches/VentPatches.cs @@ -1,6 +1,6 @@ using HarmonyLib; +using MiraAPI.Modifiers; using MiraAPI.Roles; -using MiraAPI.Utilities; using UnityEngine; namespace MiraAPI.Patches; diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 032cff3..d177d60 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -3,10 +3,8 @@ using System.Collections.Generic; using System.Linq; using HarmonyLib; using MiraAPI.GameOptions; -using MiraAPI.Modifiers; using MiraAPI.Networking; using MiraAPI.Roles; -using Reactor.Networking.Attributes; using Reactor.Utilities; using UnityEngine; @@ -130,30 +128,6 @@ public static class Extensions opt => opt.OptionBehaviour && opt.OptionBehaviour == optionBehaviour); } - internal static Dictionary ModifierComponents { get; } = []; - - /// - /// Gets the ModifierComponent for a player. - /// - /// The PlayerControl object. - /// A ModifierComponent if there is one, null otherwise. - public static ModifierComponent? GetModifierComponent(this PlayerControl player) - { - if (ModifierComponents.TryGetValue(player, out var component)) - { - return component; - } - - component = player.GetComponent(); - if (component == null) - { - return null; - } - - ModifierComponents[player] = component; - return component; - } - /// /// Randomizes a list. /// @@ -174,105 +148,6 @@ public static class Extensions return randomizedList; } - /// - /// Gets a modifier by its type, or null if the player doesn't have it. - /// - /// The PlayerControl object. - /// The Type of the Modifier. - /// The Modifier if it is found, null otherwise. - public static T? GetModifier(this PlayerControl? player) where T : BaseModifier - { - return player?.GetModifierComponent()?.ActiveModifiers.Find(x => x is T) as T; - } - - /// - /// Checks if a player has a modifier. - /// - /// The PlayerControl object. - /// The Type of the Modifier. - /// True if the Modifier is present, false otherwise. - public static bool HasModifier(this PlayerControl? player) where T : BaseModifier - { - return player?.GetModifierComponent() != null && - player.GetModifierComponent()!.HasModifier(); - } - - /// - /// Checks if a player has a modifier by its ID. - /// - /// The PlayerControl object. - /// The Modifier ID. - /// True if the Modifier is present, false otherwise. - public static bool HasModifier(this PlayerControl? player, uint id) - { - return player?.GetModifierComponent() != null && - player.GetModifierComponent()!.HasModifier(id); - } - - /// - /// Remote Procedure Call to remove a modifier from a player. - /// - /// The player to remove the modifier from. - /// The ID of the modifier. - [MethodRpc((uint)MiraRpc.RemoveModifier)] - public static void RpcRemoveModifier(this PlayerControl target, uint modifierId) - { - target.GetModifierComponent()?.RemoveModifier(modifierId); - } - - /// - /// Remote Procedure Call to remove a modifier from a player. - /// - /// The player to remove the modifier from. - /// The Type of the Modifier. - public static void RpcRemoveModifier(this PlayerControl player) where T : BaseModifier - { - var id = ModifierManager.GetModifierId(typeof(T)); - - if (id == null) - { - Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered."); - return; - } - - player.RpcRemoveModifier(id.Value); - } - - /// - /// Remote Procedure Call to add a modifier to a player. - /// - /// The player to add the modifier to. - /// The modifier ID. - [MethodRpc((uint)MiraRpc.AddModifier)] - public static void RpcAddModifier(this PlayerControl target, uint modifierId) - { - var type = ModifierManager.GetModifierType(modifierId); - if (type == null) - { - Logger.Error($"Cannot add modifier with id {modifierId} because it is not registered."); - return; - } - - target.GetModifierComponent()?.AddModifier(type); - } - - /// - /// Remote Procedure Call to add a modifier to a player. - /// - /// The player to add the modifier to. - /// The modifier Type. - public static void RpcAddModifier(this PlayerControl player) where T : BaseModifier - { - var id = ModifierManager.GetModifierId(typeof(T)); - if (id == null) - { - Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered."); - return; - } - - player.RpcAddModifier(id.Value); - } - /// /// Darkens a color by a specified amount. ///