From 1c6fa9a84dae0b057b32fcbeca83d7b1bdd9aff8 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 24 Feb 2025 21:51:02 -0500 Subject: [PATCH] major modifier overhaul --- MiraAPI/Modifiers/BaseModifier.cs | 23 +- MiraAPI/Modifiers/ModifierComponent.cs | 270 ++++++++++----- MiraAPI/Modifiers/ModifierData.cs | 10 +- MiraAPI/Modifiers/ModifierExtensions.cs | 313 ++++++++++++++---- MiraAPI/Modifiers/ModifierFactory.cs | 43 ++- MiraAPI/Modifiers/ModifierManager.cs | 8 +- MiraAPI/Modifiers/Types/TimedModifier.cs | 2 +- MiraAPI/Networking/AddModifierRpc.cs | 42 ++- .../Patches/Freeplay/TaskAddButtonPatches.cs | 2 +- MiraAPI/Patches/Freeplay/TaskAdderPatches.cs | 9 +- MiraAPI/Patches/PlayerControlPatches.cs | 2 +- MiraAPI/Patches/Roles/TaskPanelPatch.cs | 2 +- MiraAPI/Utilities/ModifierExtensions.cs | 252 -------------- 13 files changed, 531 insertions(+), 447 deletions(-) delete mode 100644 MiraAPI/Utilities/ModifierExtensions.cs diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs index 9f6aa93..8834454 100644 --- a/MiraAPI/Modifiers/BaseModifier.cs +++ b/MiraAPI/Modifiers/BaseModifier.cs @@ -1,4 +1,6 @@ -namespace MiraAPI.Modifiers; +using System; + +namespace MiraAPI.Modifiers; /// /// Base class for all modifiers. @@ -15,11 +17,6 @@ public abstract class BaseModifier /// public ModifierComponent? ModifierComponent { get; internal set; } - /// - /// Gets the modifier id. - /// - public uint ModifierId { get; internal set; } - /// /// Gets the modifier name. /// @@ -40,6 +37,20 @@ public abstract class BaseModifier /// public virtual bool Unique => true; + /// + /// Gets a value indicating whether the modifier has been initialized. + /// + public bool Initialized { get; internal set; } + + /// + /// Get the ID of the modifier. + /// + /// The ID of the modifier. + public uint GetId() + { + return ModifierManager.GetModifierId(GetType()) ?? throw new InvalidOperationException("Modifier is not registered."); + } + /// /// Gets the HUD information for this modifier. Defaults to the modifier name. Does nothing if is true. /// diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index 2b00652..6cd3d5e 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -67,6 +67,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) foreach (var modifier in _toAdd) { Modifiers.Add(modifier); + modifier.Initialized = true; modifier.OnActivate(); if (modifier is TimedModifier { AutoStart: true } timer) @@ -123,46 +124,153 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) } /// - /// Gets a collection of modifiers by their type, or null if the player doesn't have one. + /// Gets a collection of modifiers by type. /// + /// The predicate to check the modifier by. /// The Type of the Modifier. /// The Modifier if it is found, null otherwise. - public IEnumerable GetModifiersByType() where T : BaseModifier + [HideFromIl2Cpp] + public IEnumerable GetModifiersByType(Func? predicate=null) where T : BaseModifier { - return ActiveModifiers.OfType(); + return ActiveModifiers.OfType().Where(x => predicate == null || predicate(x)); } /// - /// Gets a modifier by its type, or null if the player doesn't have it. + /// Gets a collection of modifiers by type. /// - /// The Type of the Modifier. + /// The modifier type. + /// The predicate to check the modifier by. /// The Modifier if it is found, null otherwise. - public T? GetModifier() where T : BaseModifier + [HideFromIl2Cpp] + public IEnumerable GetModifiersByType(Type type, Func? predicate=null) { - return GetModifiersByType().FirstOrDefault(); + return ActiveModifiers.Where(x => x.GetType() == type && (predicate == null || predicate(x))); + } + + /// + /// Gets a collection of modifiers by ID. + /// + /// The Modifier ID. + /// The predicate to check the modifier by. + /// The Modifier if it is found, null otherwise. + [HideFromIl2Cpp] + public IEnumerable GetModifiersByType(uint id, Func? predicate=null) + { + var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException( + $"Cannot get modifier with id {id} because it is not registered."); + + return GetModifiersByType(type, predicate); } /// /// Tries to get a modifier by its type. /// /// The modifier or null. + /// The predicate to check the modifier by. /// The Type of the Modifier. /// True if the modifier was found, false otherwise. - public bool TryGetModifier(out T? modifier) where T : BaseModifier + [HideFromIl2Cpp] + public bool TryGetModifier(out T? modifier, Func? predicate = null) where T : BaseModifier { - modifier = GetModifier(); + modifier = GetModifier(predicate); return modifier != null; } + /// + /// Tries to get a modifier by its type. + /// + /// The modifier type. + /// The modifier or null. + /// The predicate to check the modifier by. + /// True if the modifier was found, false otherwise. + [HideFromIl2Cpp] + public bool TryGetModifier(Type type, out BaseModifier? modifier, Func? predicate = null) + { + modifier = GetModifier(type, predicate); + return modifier != null; + } + + /// + /// Tries to get a modifier by its ID. + /// + /// The modifier ID. + /// The modifier or null. + /// The predicate to check the modifier by. + /// True if the modifier was found, false otherwise. + [HideFromIl2Cpp] + public bool TryGetModifier(uint id, out BaseModifier? modifier, Func? predicate = null) + { + var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException( + $"Cannot get modifier with id {id} because it is not registered."); + + return TryGetModifier(type, out modifier, predicate); + } + + /// + /// Gets a modifier by its type, or null if the player doesn't have it. + /// + /// The predicate to check the modifier by. + /// The Type of the Modifier. + /// The Modifier if it is found, null otherwise. + [HideFromIl2Cpp] + public T? GetModifier(Func? predicate = null) where T : BaseModifier + { + return GetModifiersByType(predicate).FirstOrDefault(); + } + + /// + /// Gets a modifier by its type, or null if the player doesn't have it. + /// + /// The modifier type. + /// The predicate to check the modifier by. + /// The Modifier if it is found, null otherwise. + [HideFromIl2Cpp] + public BaseModifier? GetModifier(Type type, Func? predicate = null) + { + return GetModifiersByType(type).FirstOrDefault(predicate ?? (_ => true)); + } + + /// + /// Gets a modifier by its type, or null if the player doesn't have it. + /// + /// The modifier ID. + /// The predicate to check the modifier by. + /// The Modifier if it is found, null otherwise. + [HideFromIl2Cpp] + public BaseModifier? GetModifier(uint id, Func? predicate = null) + { + var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException( + $"Cannot get modifier with id {id} because it is not registered."); + + return GetModifier(type, predicate); + } + + /// + /// Removes a modifier from the player. + /// + /// The modifier type. + /// The predicate to check the modifier by. + [HideFromIl2Cpp] + public void RemoveModifier(Func? predicate = null) where T : BaseModifier + { + RemoveModifier(typeof(T), x => predicate == null || predicate((T)x)); + } + /// /// Removes a modifier from the player. /// /// The modifier type. + /// The predicate to check the modifier by. [HideFromIl2Cpp] - public void RemoveModifier(Type type) + public void RemoveModifier(Type type, Func? predicate = null) { - var modifier = Modifiers.Find(x => x.GetType() == type); + var modifiers = Modifiers.Where(x => x.GetType() == type && (predicate == null || predicate(x))).ToList(); + if (modifiers.Count > 1) + { + throw new InvalidOperationException($"Cannot remove modifier {type.Name} because there are multiple instances of that modifier."); + } + var modifier = modifiers.FirstOrDefault(); if (modifier is null) { Logger.Error($"Cannot remove modifier {type.Name} because it is not active."); @@ -172,32 +280,17 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) RemoveModifier(modifier); } - /// - /// Removes a modifier from the player. - /// - /// The modifier type. - [HideFromIl2Cpp] - public void RemoveModifier() where T : BaseModifier - { - RemoveModifier(typeof(T)); - } - /// /// Removes a modifier from the player. /// /// The modifier ID. + /// The predicate to check the modifier by. [HideFromIl2Cpp] - public void RemoveModifier(uint modifierId) + public void RemoveModifier(uint modifierId, Func? predicate = null) { - var modifier = Modifiers.Find(x => x.ModifierId == modifierId); - - if (modifier is null) - { - Logger.Error($"Cannot remove modifier with id {modifierId} because it is not active."); - return; - } - - RemoveModifier(modifier); + var type = ModifierManager.GetModifierType(modifierId) ?? throw new InvalidOperationException( + $"Cannot remove modifier with id {modifierId} because it is not registered."); + RemoveModifier(type, predicate); } /// @@ -216,6 +309,18 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) _toRemove.Add(modifier); } + /// + /// Adds a modifier to the player. + /// + /// The arguments to initialize the modifier constructor with. + /// The Type of the modifier. + /// The new modifier. + [HideFromIl2Cpp] + public T? AddModifier(params object[] args) where T : BaseModifier + { + return AddModifier(typeof(T), args) as T; + } + /// /// Adds a modifier to the player. /// @@ -224,24 +329,22 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) [HideFromIl2Cpp] public BaseModifier? AddModifier(BaseModifier modifier) { - if (Modifiers.Contains(modifier)) + var id = modifier.GetId(); + if (modifier.Unique && Modifiers.Find(x => x.GetId() == id) != null) { - Logger.Error($"Player already has modifier with id {modifier.ModifierId}!"); + Logger.Error($"Player already has modifier with id {id}!"); return null; } - var modifierId = ModifierManager.GetModifierId(modifier.GetType()); - - if (modifierId == null) + if (Modifiers.Contains(modifier)) { - Logger.Error($"Cannot add modifier {modifier.GetType().Name} because it has no ID!"); + Logger.Error($"Player already has this modifier!"); return null; } _toAdd.Add(modifier); modifier.Player = _player; modifier.ModifierComponent = this; - modifier.ModifierId = modifierId.Value; return modifier; } @@ -249,66 +352,79 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) /// Adds a modifier to the player. /// /// The modifier type. + /// The arguments to initialize the modifier constructor with. /// The modifier that was added. [HideFromIl2Cpp] - public BaseModifier? AddModifier(Type type) + public BaseModifier? AddModifier(Type type, params object[] args) { - var modifierId = ModifierManager.GetModifierId(type); - if (modifierId == null) + BaseModifier? modifier; + if (args.Length > 0) { - Logger.Error($"Cannot add modifier {type.Name} because it is not registered."); - return null; + modifier = ModifierFactory.CreateInstance(type, args); + } + else + { + modifier = Activator.CreateInstance(type) as BaseModifier; + if (modifier == null) + { + throw new InvalidOperationException($"Cannot add modifier {type.Name} because it is not a valid modifier."); + } } - if (Modifiers.Find(x => x.ModifierId == modifierId && x.Unique) != null) - { - Logger.Error($"Player already has modifier with id {modifierId}!"); - return null; - } - - if (Activator.CreateInstance(type) is not BaseModifier modifier) - { - Logger.Error($"Cannot add modifier {type.Name} because it is null."); - return null; - } - - AddModifier(modifier); - - return modifier; + return AddModifier(modifier); } /// /// Adds a modifier to the player. /// - /// The Type of the modifier. - /// The new modifier. + /// The ID of the modifier. + /// The arguments to initialize the modifier constructor with. + /// The modifier if it was created, or null if it failed. [HideFromIl2Cpp] - public T? AddModifier() where T : BaseModifier + public BaseModifier? AddModifier(uint id, params object[] args) { - return AddModifier(typeof(T)) as T; + var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException( + $"Cannot add modifier with id {id} because it is not registered."); + + return AddModifier(type, args); } /// - /// Checks if a player has an active or queued modifier by its ID. - /// - /// The Modifier ID. - /// The predicate to check the modifier. - /// True if the Modifier is present, false otherwise. - public bool HasModifier(uint id, Func? predicate=null) - { - return ActiveModifiers.Exists(x => x.ModifierId == id && (predicate == null || predicate(x))) || - _toAdd.Exists(x => x.ModifierId == id && (predicate == null || predicate(x))); - } - - /// - /// Checks if a player has an active or queued modifier by its type. + /// Checks if a player has an active modifier by its type. /// /// The predicate to check the modifier. /// The Type of the Modifier. /// True if the Modifier is present, false otherwise. + [HideFromIl2Cpp] public bool HasModifier(Func? predicate=null) where T : BaseModifier { - return ModifierManager.GetModifierId(typeof(T)) is { } id && - HasModifier(id, (Func?)predicate); + return ActiveModifiers.Exists(x => x is T modifier && (predicate == null || predicate(modifier))); + } + + /// + /// Checks if a player has an active modifier by its type. + /// + /// The modifier type. + /// The predicate to check the modifier. + /// True if the Modifier is present, false otherwise. + [HideFromIl2Cpp] + public bool HasModifier(Type type, Func? predicate=null) + { + return ActiveModifiers.Exists(x => x.GetType() == type && (predicate == null || predicate(x))); + } + + /// + /// Checks if a player has an active modifier by its ID. + /// + /// The Modifier ID. + /// The predicate to check the modifier. + /// True if the Modifier is present, false otherwise. + [HideFromIl2Cpp] + public bool HasModifier(uint id, Func? predicate=null) + { + var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException( + $"Cannot get modifier with id {id} because it is not registered."); + + return HasModifier(type, predicate); } } diff --git a/MiraAPI/Modifiers/ModifierData.cs b/MiraAPI/Modifiers/ModifierData.cs index 425e063..7260834 100644 --- a/MiraAPI/Modifiers/ModifierData.cs +++ b/MiraAPI/Modifiers/ModifierData.cs @@ -1,18 +1,16 @@ -using System; - -namespace MiraAPI.Modifiers; +namespace MiraAPI.Modifiers; /// /// Modifier data for networking. /// -/// The Type of the modifier. +/// The ID of the modifier. /// Parameters for constructor. -public readonly struct ModifierData(Type type, object[] args) +public readonly struct ModifierData(uint id, object[] args) { /// /// Gets the type of the modifier. /// - public Type Type { get; } = type; + public uint Id { get; } = id; /// /// Gets the parameters for the constructor. diff --git a/MiraAPI/Modifiers/ModifierExtensions.cs b/MiraAPI/Modifiers/ModifierExtensions.cs index d6ce5ff..226b16b 100644 --- a/MiraAPI/Modifiers/ModifierExtensions.cs +++ b/MiraAPI/Modifiers/ModifierExtensions.cs @@ -8,11 +8,81 @@ using Reactor.Utilities; namespace MiraAPI.Modifiers; /// -/// Extension methods for modifiers. +/// Extensions for modifiers. /// public static class ModifierExtensions { - internal static Dictionary ModifierComponents { get; } = []; + /// + /// Gets the dictionary to cache ModifierComponents. + /// + public static Dictionary ModifierComponents { get; } = []; + + /// + /// Remote Procedure Call to add a modifier to a player. + /// + /// The player to add the modifier to. + /// The modifier ID. + /// The arguments to initialize the modifier constructor with. + public static void RpcAddModifier(this PlayerControl target, uint modifierId, params object[] args) + { + _ = ModifierManager.GetModifierType(modifierId) ?? throw new InvalidOperationException( + $"Modifier with ID {modifierId} is not registered."); + + Rpc.Instance.Send(target, new ModifierData(modifierId, args)); + } + + /// + /// Remote Procedure Call to add a modifier to a player. + /// + /// The player to add the modifier to. + /// The type of the modifier. + /// The arguments to initialize the modifier constructor with. + public static void RpcAddModifier(this PlayerControl player, Type type, params object[] args) + { + var id = ModifierManager.GetModifierId(type) ?? throw new InvalidOperationException( + $"Modifier {type.Name} is not registered."); + + player.RpcAddModifier(id, args); + } + + /// + /// 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 + { + player.RpcAddModifier(typeof(T), args); + } + + /// + /// 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.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); + } /// /// Gets the ModifierComponent for a player. @@ -37,102 +107,217 @@ public static class ModifierExtensions } /// - /// Gets a modifier by its type, or null if the player doesn't have it. + /// Checks if the player has a specific modifier. /// - /// 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 + /// The type of the modifier. + /// The PlayerControl instance. + /// Optional predicate to filter the modifiers. + /// True if the player has the modifier, false otherwise. + public static bool HasModifier(this PlayerControl player, Func? predicate = null) where T : BaseModifier { - return player?.GetModifierComponent()?.ActiveModifiers.Find(x => x is T) as T; + return player.GetModifierComponent()!.HasModifier(predicate); } /// - /// Checks if a player has a modifier. + /// Checks if the player has a specific modifier by type. /// - /// 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 + /// The PlayerControl instance. + /// The type of the modifier. + /// Optional predicate to filter the modifiers. + /// True if the player has the modifier, false otherwise. + public static bool HasModifier(this PlayerControl player, Type type, Func? predicate = null) { - return player?.GetModifierComponent() != null && - player.GetModifierComponent()!.HasModifier(); + return player.GetModifierComponent()!.HasModifier(type, predicate); } /// - /// Checks if a player has a modifier by its ID. + /// Checks if the player has a specific modifier by ID. /// - /// The PlayerControl object. - /// The Modifier ID. - /// True if the Modifier is present, false otherwise. - public static bool HasModifier(this PlayerControl? player, uint id) + /// The PlayerControl instance. + /// The ID of the modifier. + /// Optional predicate to filter the modifiers. + /// True if the player has the modifier, false otherwise. + public static bool HasModifier(this PlayerControl player, uint id, Func? predicate = null) { - return player?.GetModifierComponent() != null && - player.GetModifierComponent()!.HasModifier(id); + return player.GetModifierComponent()!.HasModifier(id, predicate); } /// - /// Remote Procedure Call to remove a modifier from a player. + /// Gets a specific modifier from the player. /// - /// The player to remove the modifier from. + /// The type of the modifier. + /// The PlayerControl instance. + /// Optional predicate to filter the modifiers. + /// The modifier if found, null otherwise. + public static T? GetModifier(this PlayerControl player, Func? predicate = null) where T : BaseModifier + { + return player.GetModifierComponent()!.GetModifier(predicate); + } + + /// + /// Gets a specific modifier from the player by type. + /// + /// The PlayerControl instance. + /// The type of the modifier. + /// Optional predicate to filter the modifiers. + /// The modifier if found, null otherwise. + public static BaseModifier? GetModifier( + this PlayerControl player, + Type type, + Func? predicate = null) + { + return player.GetModifierComponent()!.GetModifier(type, predicate); + } + + /// + /// Gets a specific modifier from the player by ID. + /// + /// The PlayerControl instance. + /// The ID of the modifier. + /// Optional predicate to filter the modifiers. + /// The modifier if found, null otherwise. + public static BaseModifier? GetModifier( + this PlayerControl player, + uint id, + Func? predicate = null) + { + return player.GetModifierComponent()!.GetModifier(id, predicate); + } + + /// + /// Gets all modifiers of a specific type from the player. + /// + /// The type of the modifiers. + /// The PlayerControl instance. + /// Optional predicate to filter the modifiers. + /// An enumerable of modifiers. + public static IEnumerable GetModifiersByType(this PlayerControl player, Func? predicate = null) + where T : BaseModifier + { + return player.GetModifierComponent()!.GetModifiersByType(predicate); + } + + /// + /// Gets all modifiers of a specific type from the player. + /// + /// The PlayerControl instance. + /// The type of the modifiers. + /// Optional predicate to filter the modifiers. + /// An enumerable of modifiers. + public static IEnumerable GetModifiersByType( + this PlayerControl player, + Type type, + Func? predicate = null) + { + return player.GetModifierComponent()!.GetModifiersByType(type, predicate); + } + + /// + /// Gets all modifiers of a specific type from the player by ID. + /// + /// The PlayerControl instance. + /// The ID of the modifiers. + /// Optional predicate to filter the modifiers. + /// An enumerable of modifiers. + public static IEnumerable GetModifiersByType( + this PlayerControl player, + uint id, + Func? predicate = null) + { + return player.GetModifierComponent()!.GetModifiersByType(id, predicate); + } + + /// + /// Removes a specific modifier from the player. + /// + /// The type of the modifier. + /// The PlayerControl instance. + /// Optional predicate to filter the modifiers. + public static void RemoveModifier(this PlayerControl player, Func? predicate = null) + where T : BaseModifier + { + player.GetModifierComponent()!.RemoveModifier(predicate); + } + + /// + /// Removes a specific modifier from the player by type. + /// + /// The PlayerControl instance. + /// The type of the modifier. + /// Optional predicate to filter the modifiers. + public static void RemoveModifier(this PlayerControl player, Type type, Func? predicate = null) + { + player.GetModifierComponent()!.RemoveModifier(type, predicate); + } + + /// + /// Removes a specific modifier from the player by ID. + /// + /// The PlayerControl instance. /// The ID of the modifier. - [MethodRpc((uint)MiraRpc.RemoveModifier)] - public static void RpcRemoveModifier(this PlayerControl target, uint modifierId) + /// Optional predicate to filter the modifiers. + public static void RemoveModifier( + this PlayerControl player, + uint modifierId, + Func? predicate = null) { - target.GetModifierComponent()?.RemoveModifier(modifierId); + player.GetModifierComponent()!.RemoveModifier(modifierId, predicate); } /// - /// Remote Procedure Call to remove a modifier from a player. + /// Removes a specific modifier from the player. /// - /// The player to remove the modifier from. - /// The Type of the Modifier. - public static void RpcRemoveModifier(this PlayerControl player) where T : BaseModifier + /// The PlayerControl instance. + /// The modifier to remove. + public static void RemoveModifier(this PlayerControl player, BaseModifier modifier) { - 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); + player.GetModifierComponent()!.RemoveModifier(modifier); } /// - /// Remote Procedure Call to add a modifier to a player. + /// Adds a specific modifier to the player. /// - /// The player to add the modifier to. - /// The modifier ID. + /// The type of the modifier. + /// The PlayerControl instance. /// The arguments to initialize the modifier constructor with. - public static void RpcAddModifier(this PlayerControl target, uint modifierId, params object[] args) + /// The added modifier. + public static T? AddModifier(this PlayerControl player, params object[] args) where T : BaseModifier { - var type = ModifierManager.GetModifierType(modifierId); - if (type == null) - { - Logger.Error($"Cannot add modifier with id {modifierId} because it is not registered."); - return; - } - - Rpc.Instance.Send(target, new ModifierData(type, args)); + return player.GetModifierComponent()!.AddModifier(args); } /// - /// Remote Procedure Call to add a modifier to a player. + /// Adds a specific modifier to the 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 + /// The PlayerControl instance. + /// The modifier to add. + /// The added modifier. + public static BaseModifier? AddModifier(this PlayerControl player, BaseModifier modifier) { - var id = ModifierManager.GetModifierId(typeof(T)); - if (id == null) - { - Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered."); - return; - } + return player.GetModifierComponent()!.AddModifier(modifier); + } - player.RpcAddModifier(id.Value, args); + /// + /// Adds a specific modifier to the player by type. + /// + /// The PlayerControl instance. + /// The type of the modifier. + /// The arguments to initialize the modifier constructor with. + /// The added modifier. + public static BaseModifier? AddModifier(this PlayerControl player, Type type, params object[] args) + { + return player.GetModifierComponent()!.AddModifier(type, args); + } + + /// + /// Adds a specific modifier to the player by ID. + /// + /// The PlayerControl instance. + /// The ID of the modifier. + /// The arguments to initialize the modifier constructor with. + /// The added modifier. + public static BaseModifier? AddModifier(this PlayerControl player, uint id, params object[] args) + { + return player.GetModifierComponent()!.AddModifier(id, args); } } diff --git a/MiraAPI/Modifiers/ModifierFactory.cs b/MiraAPI/Modifiers/ModifierFactory.cs index e1b018c..d145b82 100644 --- a/MiraAPI/Modifiers/ModifierFactory.cs +++ b/MiraAPI/Modifiers/ModifierFactory.cs @@ -1,18 +1,28 @@ using System; -using System.Linq; using System.Linq.Expressions; namespace MiraAPI.Modifiers; +/// +/// Factory for creating instances of a modifier. More efficient than using reflection. +/// public static class ModifierFactory { private static Func? _constructor; - private static Func CreateConstructor(Type type) + private static Func CreateConstructor(Type type, params object[] args) { - var constructorInfo = type.GetConstructors() - .OrderByDescending(c => c.GetParameters().Length) - .FirstOrDefault() ?? throw new InvalidOperationException($"Type {type} does not have a public constructor."); + var constructorInfo = Array.Find( + type.GetConstructors(), + x => + { + var parameters = x.GetParameters(); + return parameters.Length == args.Length && Array.TrueForAll( + parameters, + t => t.ParameterType.IsInstanceOfType(args[t.Position])); + }) ?? throw new InvalidOperationException( + $"Could not find a constructor for type {type} with the specified arguments."); + var parameters = constructorInfo.GetParameters(); var argsParam = Expression.Parameter(typeof(object[]), "args"); @@ -32,9 +42,15 @@ public static class ModifierFactory return lambda.Compile(); } + /// + /// Creates a modifier with the specified type and arguments. + /// + /// Modifier type. + /// Arguments. + /// An instance of the modifier. public static BaseModifier CreateInstance(Type type, params object[] args) { - _constructor ??= CreateConstructor(type); + _constructor ??= CreateConstructor(type, args); return _constructor(args); } } @@ -47,11 +63,18 @@ public static class ModifierFactory where T : BaseModifier { private static readonly Func Constructor = CreateConstructor(); - private static Func CreateConstructor() + private static Func CreateConstructor(params object[] args) { - var constructorInfo = typeof(T).GetConstructors() - .OrderByDescending(c => c.GetParameters().Length) - .FirstOrDefault() ?? throw new InvalidOperationException($"Type {typeof(T)} does not have a public constructor."); + var constructorInfo = Array.Find( + typeof(T).GetConstructors(), + x => + { + var parameters = x.GetParameters(); + return parameters.Length == args.Length && Array.TrueForAll( + parameters, + t => t.ParameterType.IsInstanceOfType(args[t.Position])); + }) ?? throw new InvalidOperationException($"Could not find a constructor for type {typeof(T)} with the specified arguments."); + var parameters = constructorInfo.GetParameters(); var argsParam = Expression.Parameter(typeof(object[]), "args"); diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs index d4d4004..65341a0 100644 --- a/MiraAPI/Modifiers/ModifierManager.cs +++ b/MiraAPI/Modifiers/ModifierManager.cs @@ -48,7 +48,12 @@ public static class ModifierManager /// The ID of the modifier. public static uint? GetModifierId(Type type) { - return TypeToIdModifierMap.GetValueOrDefault(type); + if (!TypeToIdModifierMap.TryGetValue(type, out var id)) + { + return null; + } + + return id; } internal static bool RegisterModifier(Type modifierType, MiraPluginInfo info) @@ -61,7 +66,6 @@ public static class ModifierManager IdToTypeModifierMap.Add(GetNextId(), modifierType); TypeToIdModifierMap.Add(modifierType, _nextId); var bm = (BaseModifier)FormatterServices.GetUninitializedObject(modifierType); // this probably isn't a great idea - bm.ModifierId = _nextId; info.Modifiers.Add(bm); if (!typeof(GameModifier).IsAssignableFrom(modifierType)) diff --git a/MiraAPI/Modifiers/Types/TimedModifier.cs b/MiraAPI/Modifiers/Types/TimedModifier.cs index 286b80a..038fa6d 100644 --- a/MiraAPI/Modifiers/Types/TimedModifier.cs +++ b/MiraAPI/Modifiers/Types/TimedModifier.cs @@ -102,7 +102,7 @@ public abstract class TimedModifier : BaseModifier if (RemoveOnComplete) { - ModifierComponent?.RemoveModifier(ModifierId); + ModifierComponent?.RemoveModifier(this); } } diff --git a/MiraAPI/Networking/AddModifierRpc.cs b/MiraAPI/Networking/AddModifierRpc.cs index 3546d4f..fdc37a9 100644 --- a/MiraAPI/Networking/AddModifierRpc.cs +++ b/MiraAPI/Networking/AddModifierRpc.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; using Hazel; using MiraAPI.Modifiers; using Reactor.Networking.Attributes; @@ -18,44 +15,45 @@ namespace MiraAPI.Networking; [RegisterCustomRpc((uint)MiraRpc.AddModifier)] public class AddModifierRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc(plugin, id) { - private static readonly Dictionary ParameterCache = []; - /// public override RpcLocalHandling LocalHandling => RpcLocalHandling.Before; /// public override void Write(MessageWriter writer, ModifierData data) { - var modId = ModifierManager.GetModifierId(data.Type); - writer.WritePacked((uint)modId!); - MessageSerializer.Serialize(writer, data.Args); + writer.WritePacked(data.Id); + writer.WritePacked(data.Args.Length); + foreach (var arg in data.Args) + { + writer.Write(arg.GetType().AssemblyQualifiedName); + writer.Serialize(arg); + } } /// public override ModifierData Read(MessageReader reader) { var modId = reader.ReadPackedUInt32(); - var modifier = ModifierManager.GetModifierType(modId)!; + var argCount = reader.ReadPackedUInt32(); + var objects = new object[argCount]; - if (!ParameterCache.TryGetValue(modifier, out var paramTypes)) + if (argCount > 0) { - paramTypes = modifier.GetConstructors().OrderBy(x => x.GetParameters().Length).First().GetParameters(); - ParameterCache[modifier] = paramTypes; + var types = new Type[argCount]; + for (var i = 0; i < argCount; i++) + { + var name = reader.ReadString(); + types[i] = Type.GetType(name) ?? throw new InvalidOperationException($"Type not found: {name}"); + objects[i] = reader.Deserialize(types[i]); + } } - var objects = new object[paramTypes.Length]; - foreach (var paramType in paramTypes) - { - objects[paramType.Position] = reader.Deserialize(paramType.ParameterType); - } - - return new ModifierData(modifier, objects); + return new ModifierData(modId, objects); } /// - public override void Handle(PlayerControl innerNetObject, ModifierData data) + public override void Handle(PlayerControl player, ModifierData data) { - var modifier = ModifierFactory.CreateInstance(data.Type, data.Args); - innerNetObject.GetModifierComponent()?.AddModifier(modifier); + player.AddModifier(data.Id, data.Args); } } diff --git a/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs b/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs index fb57d70..184bafb 100644 --- a/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs +++ b/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs @@ -1,6 +1,6 @@ using HarmonyLib; +using MiraAPI.Modifiers; using MiraAPI.Roles; -using MiraAPI.Utilities; using UnityEngine; namespace MiraAPI.Patches.Freeplay; diff --git a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs index d1c6cb9..b634c91 100644 --- a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs +++ b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs @@ -326,7 +326,7 @@ internal static class TaskAdderPatches } var taskAddButton = Object.Instantiate(instance.RoleButton); - taskAddButton.name = modifier.ModifierId.ToString(CultureInfo.InvariantCulture); + taskAddButton.name = modifier.GetId().ToString(NumberFormatInfo.InvariantInfo); taskAddButton.role = null; taskAddButton.MyTask = null; taskAddButton.SafePositionWorld = instance.SafePositionWorld; @@ -336,14 +336,15 @@ internal static class TaskAdderPatches taskAddButton.Button.OnClick = new Button.ButtonClickedEvent(); taskAddButton.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { - if (PlayerControl.LocalPlayer.HasModifier(modifier.ModifierId)) + var id = modifier.GetId(); + if (PlayerControl.LocalPlayer.HasModifier(id)) { - PlayerControl.LocalPlayer.RemoveModifier(modifier.ModifierId); + PlayerControl.LocalPlayer.RemoveModifier(id); taskAddButton.Overlay.enabled = false; } else { - PlayerControl.LocalPlayer.AddModifier(ModifierManager.GetModifierType(modifier.ModifierId)!); + PlayerControl.LocalPlayer.AddModifier(id); taskAddButton.Overlay.enabled = true; } })); diff --git a/MiraAPI/Patches/PlayerControlPatches.cs b/MiraAPI/Patches/PlayerControlPatches.cs index fbabe1a..31fdbb8 100644 --- a/MiraAPI/Patches/PlayerControlPatches.cs +++ b/MiraAPI/Patches/PlayerControlPatches.cs @@ -126,6 +126,6 @@ public static class PlayerControlPatches [HarmonyPatch(nameof(PlayerControl.OnDestroy))] public static void PlayerControlOnDestroyPrefix(PlayerControl __instance) { - Utilities.ModifierExtensions.ModifierComponents.Remove(__instance); + ModifierExtensions.ModifierComponents.Remove(__instance); } } diff --git a/MiraAPI/Patches/Roles/TaskPanelPatch.cs b/MiraAPI/Patches/Roles/TaskPanelPatch.cs index 431d47b..a08445a 100644 --- a/MiraAPI/Patches/Roles/TaskPanelPatch.cs +++ b/MiraAPI/Patches/Roles/TaskPanelPatch.cs @@ -4,7 +4,7 @@ using UnityEngine; namespace MiraAPI.Patches.Roles; [HarmonyPatch(typeof(TaskPanelBehaviour))] -public static class TaskPanelPatch +internal static class TaskPanelPatch { /// /// This patch is to override the automatic updating of the y position on the tab (which is in base game) diff --git a/MiraAPI/Utilities/ModifierExtensions.cs b/MiraAPI/Utilities/ModifierExtensions.cs deleted file mode 100644 index dadb62c..0000000 --- a/MiraAPI/Utilities/ModifierExtensions.cs +++ /dev/null @@ -1,252 +0,0 @@ -using System; -using System.Collections.Generic; -using MiraAPI.Modifiers; -using MiraAPI.Networking; -using Reactor.Networking.Attributes; -using Reactor.Utilities; - -namespace MiraAPI.Utilities; - -/// -/// Extensions for modifiers. -/// -public static class ModifierExtensions -{ - /// - /// Gets the dictionary to cache ModifierComponents. - /// - public static Dictionary ModifierComponents { get; } = []; - - /// - /// 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.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); - } - - /// - /// 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.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); - } - - /// - /// 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) - { - return null; - } - - ModifierComponents[player] = component; - return component; - } - - /// - /// Gets the ModifierComponent for a player. - /// - /// The PlayerControl object. - /// The ModifierComponent if it is found. - /// A ModifierComponent if there is one, null otherwise. - public static bool TryGetModifierComponent(this PlayerControl player, out ModifierComponent? component) - { - if (ModifierComponents.TryGetValue(player, out component)) - { - return component; - } - - component = player.GetComponent(); - return component; - } - - /// - /// Gets a collection of modifiers by their type, or null if the player doesn't have one. - /// - /// The PlayerControl object. - /// The Type of the Modifier. - /// The Modifier if it is found, null otherwise. - public static IEnumerable GetModifiersByType(this PlayerControl player) where T : BaseModifier - { - return player.GetModifierComponent()!.GetModifiersByType(); - } - - /// - /// 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()!.GetModifier(); - } - - /// - /// Tries to get a modifier by its type. - /// - /// The PlayerControl object. - /// The modifier or null. - /// The Type of the Modifier. - /// True if the modifier was found, false otherwise. - public static bool TryGetModifier(this PlayerControl player, out T? modifier) where T : BaseModifier - { - return player.GetModifierComponent()!.TryGetModifier(out modifier); - } - - /// - /// Removes a modifier from the player. - /// - /// The PlayerControl object. - /// The modifier type. - public static void RemoveModifier(this PlayerControl player, Type type) - { - player.GetModifierComponent()!.RemoveModifier(type); - } - - /// - /// Removes a modifier from the player. - /// - /// The PlayerControl object. - /// The modifier type. - public static void RemoveModifier(this PlayerControl player) where T : BaseModifier - { - player.GetModifierComponent()!.RemoveModifier(); - } - - /// - /// Removes a modifier from the player. - /// - /// The PlayerControl object. - /// The modifier ID. - public static void RemoveModifier(this PlayerControl player, uint modifierId) - { - player.GetModifierComponent()!.RemoveModifier(modifierId); - } - - /// - /// Removes a modifier from the player. - /// - /// The PlayerControl object. - /// The modifier object. - public static void RemoveModifier(this PlayerControl player, BaseModifier modifier) - { - player.GetModifierComponent()!.RemoveModifier(modifier); - } - - /// - /// Adds a modifier to the player. - /// - /// The PlayerControl object. - /// The modifier to add. - /// The modifier that was added. - public static BaseModifier? AddModifier(this PlayerControl player, BaseModifier modifier) - { - return player.GetModifierComponent()!.AddModifier(modifier); - } - - /// - /// Adds a modifier to the player. - /// - /// The PlayerControl object. - /// The modifier type. - /// The modifier that was added. - public static BaseModifier? AddModifier(this PlayerControl player, Type type) - { - return player.GetModifierComponent()!.AddModifier(type); - } - - /// - /// Adds a modifier to the player. - /// - /// The PlayerControl object. - /// The Type of the modifier. - /// The new modifier. - public static T? AddModifier(this PlayerControl player) where T : BaseModifier - { - return player.GetModifierComponent()!.AddModifier(); - } - - /// - /// Checks if a player has an active or queued modifier by its ID. - /// - /// The PlayerControl object. - /// The Modifier ID. - /// The predicate to check the modifier. - /// True if the Modifier is present, false otherwise. - public static bool HasModifier(this PlayerControl player, uint id, Func? predicate=null) - { - return player.GetModifierComponent()!.HasModifier(id, predicate); - } - - /// - /// Checks if a player has an active or queued modifier by its type. - /// - /// The PlayerControl object. - /// The predicate to check the modifier. - /// The Type of the Modifier. - /// True if the Modifier is present, false otherwise. - public static bool HasModifier(this PlayerControl player, Func? predicate=null) where T : BaseModifier - { - return player.GetModifierComponent()!.HasModifier(predicate); - } -}