diff --git a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs index e26405e..3b7363a 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 fed4e1f..c4d59e3 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/Events/Vanilla/Usables/PlayerUseEvent.cs b/MiraAPI/Events/Vanilla/Usables/PlayerUseEvent.cs index 655a46c..5f05676 100644 --- a/MiraAPI/Events/Vanilla/Usables/PlayerUseEvent.cs +++ b/MiraAPI/Events/Vanilla/Usables/PlayerUseEvent.cs @@ -1,4 +1,4 @@ -namespace MiraAPI.Events.Vanilla; +namespace MiraAPI.Events.Vanilla.Usables; /// /// Event for using a from Vanilla Among Us. Will always be ran locally. diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs index 9f6aa93..473c78f 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. @@ -16,9 +18,19 @@ public abstract class BaseModifier public ModifierComponent? ModifierComponent { get; internal set; } /// - /// Gets the modifier id. + /// Gets a value indicating whether the modifier has been initialized. /// - public uint ModifierId { get; internal set; } + public bool Initialized { get; internal set; } + + /// + /// Gets the unique ID of the modifier. + /// + public Guid UniqueId { get; internal set; } = Guid.Empty; + + /// + /// Gets the type ID of the modifier. + /// + public uint TypeId => ModifierManager.GetModifierTypeId(GetType()) ?? throw new InvalidOperationException("Modifier is not registered."); /// /// Gets the modifier name. diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index 2b00652..e7c6991 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) @@ -103,14 +104,12 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) return; } - var filteredModifiers = Modifiers.Where(mod => !mod.HideOnUi); + var filteredModifiers = Modifiers.Where(mod => !mod.HideOnUi).ToArray(); - var baseModifiers = filteredModifiers as BaseModifier[] ?? filteredModifiers.ToArray(); - - if (baseModifiers.Length != 0 && !MeetingHud.Instance) + if (filteredModifiers.Length != 0 && !MeetingHud.Instance) { var stringBuild = new StringBuilder(); - foreach (var mod in baseModifiers) + foreach (var mod in filteredModifiers) { stringBuild.Append(CultureInfo.InvariantCulture, $"\n{mod.GetHudString()}"); } @@ -123,77 +122,178 @@ 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 + /// A collection of modifiers. + [HideFromIl2Cpp] + public IEnumerable GetModifiers(Func? predicate=null) where T : BaseModifier { - return ActiveModifiers.OfType(); + return Modifiers.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 if it is found, null otherwise. - public T? GetModifier() where T : BaseModifier + /// The modifier type. + /// The predicate to check the modifier by. + /// A collection of modifiers. + [HideFromIl2Cpp] + public IEnumerable GetModifiers(Type type, Func? predicate=null) { - return GetModifiersByType().FirstOrDefault(); + return Modifiers.Where(x => x.GetType() == type && (predicate == null || predicate(x))); + } + + /// + /// Gets a collection of modifiers by type ID. + /// + /// The modifier's type ID. + /// The predicate to check the modifier by. + /// A collection of modifiers. + [HideFromIl2Cpp] + public IEnumerable GetModifiers(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 GetModifiers(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; } /// - /// Removes a modifier from the player. + /// 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 void RemoveModifier(Type type) + public bool TryGetModifier(Type type, out BaseModifier? modifier, Func? predicate = null) { - var modifier = Modifiers.Find(x => x.GetType() == type); + modifier = GetModifier(type, predicate); + return modifier != null; + } - if (modifier is null) - { - Logger.Error($"Cannot remove modifier {type.Name} because it is not active."); - return; - } + /// + /// Tries to get a modifier by its type ID. + /// + /// The modifier type 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) + { + modifier = GetModifier(id, predicate); + return modifier != null; + } - RemoveModifier(modifier); + /// + /// Tries to get a modifier by its unique ID. + /// + /// The modifier unique ID. + /// The modifier or null. + /// True if the modifier was found, false otherwise. + [HideFromIl2Cpp] + public bool TryGetModifier(Guid modifierGuid, out BaseModifier? modifier) + { + modifier = GetModifier(modifierGuid); + return modifier != null; + } + + /// + /// Gets a modifier by its type. + /// + /// 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 GetModifiers(predicate).FirstOrDefault(); + } + + /// + /// Gets a modifier by its type. + /// + /// 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 GetModifiers(type).FirstOrDefault(predicate ?? (_ => true)); + } + + /// + /// Gets a modifier by its type ID. + /// + /// 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); + } + + /// + /// Gets a modifier by unique ID. + /// + /// The modifier's unique ID. + /// The modifier if it is found, or null. + [HideFromIl2Cpp] + public BaseModifier? GetModifier(Guid modifierGuid) + { + return Modifiers.Find(x => x.UniqueId == modifierGuid); } /// /// Removes a modifier from the player. /// /// The modifier type. + /// The predicate to check the modifier by. [HideFromIl2Cpp] - public void RemoveModifier() where T : BaseModifier + public void RemoveModifier(Func? predicate = null) where T : BaseModifier { - RemoveModifier(typeof(T)); + RemoveModifier(typeof(T), x => predicate == null || predicate((T)x)); } /// /// Removes a modifier from the player. /// - /// The modifier ID. + /// The modifier type. + /// The predicate to check the modifier by. [HideFromIl2Cpp] - public void RemoveModifier(uint modifierId) + public void RemoveModifier(Type type, Func? predicate = null) { - var modifier = Modifiers.Find(x => x.ModifierId == modifierId); + 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 with id {modifierId} because it is not active."); + Logger.Error($"Cannot remove modifier {type.Name} because it is not active."); return; } @@ -216,6 +316,48 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) _toRemove.Add(modifier); } + /// + /// Removes a modifier from the player. + /// + /// The modifier's type ID. + /// The predicate to check the modifier by. + [HideFromIl2Cpp] + public void RemoveModifier(uint typeId, Func? predicate = null) + { + var type = ModifierManager.GetModifierType(typeId) ?? throw new InvalidOperationException( + $"Cannot remove modifier with id {typeId} because it is not registered."); + RemoveModifier(type, predicate); + } + + /// + /// Removes a modifier from the player. + /// + /// The modifier's unique ID. + [HideFromIl2Cpp] + public void RemoveModifier(Guid uniqueId) + { + var modifier = GetModifier(uniqueId); + if (modifier == null) + { + Logger.Error($"Cannot remove modifier with unique id {uniqueId} because it is not active."); + return; + } + + RemoveModifier(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 +366,32 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) [HideFromIl2Cpp] public BaseModifier? AddModifier(BaseModifier modifier) { - if (Modifiers.Contains(modifier)) + // TODO: Make a proper synchronization system. + if (LobbyBehaviour.Instance) { - Logger.Error($"Player already has modifier with id {modifier.ModifierId}!"); + Logger.Warning($"Modifiers added in the lobby won't sync to new players!"); + } + + var id = modifier.TypeId; + if (modifier.Unique && Modifiers.Find(x => x.TypeId == id) != null) + { + 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; + if (modifier.UniqueId == Guid.Empty) + { + modifier.UniqueId = Guid.NewGuid(); + } return modifier; } @@ -249,66 +399,90 @@ 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 Modifiers.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 Modifiers.Exists(x => x.GetType() == type && (predicate == null || predicate(x))); + } + + /// + /// Checks if a player has an active modifier by its type ID. + /// + /// The modifier's type 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); + } + + /// + /// Checks if a player has an active modifier by its unique ID. + /// + /// The modifier's guid. + /// True if the modifier is present, false otherwise. + [HideFromIl2Cpp] + public bool HasModifier(Guid id) + { + return Modifiers.Exists(x => x.UniqueId == id); } } diff --git a/MiraAPI/Modifiers/ModifierExtensions.cs b/MiraAPI/Modifiers/ModifierExtensions.cs new file mode 100644 index 0000000..5d05d69 --- /dev/null +++ b/MiraAPI/Modifiers/ModifierExtensions.cs @@ -0,0 +1,396 @@ +using System; +using System.Collections.Generic; +using MiraAPI.Networking; +using MiraAPI.Networking.Modifiers; +using Reactor.Networking.Attributes; +using Reactor.Networking.Rpc; +using Reactor.Utilities; + +namespace MiraAPI.Modifiers; + +/// +/// 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 type ID. + /// The arguments to initialize the modifier constructor with. + public static void RpcAddModifier(this PlayerControl target, uint typeId, params object[] args) + { + _ = ModifierManager.GetModifierType(typeId) ?? throw new InvalidOperationException( + $"Modifier with ID {typeId} is not registered."); + + Rpc.Instance.Send(target, new ModifierData(typeId, Guid.NewGuid(), 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.GetModifierTypeId(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 unique ID of the modifier. + [MethodRpc((uint)MiraRpc.RemoveModifier)] + public static void RpcRemoveModifier(this PlayerControl target, Guid uniqueId) + { + target.RemoveModifier(uniqueId); + } + + /// + /// Remote Procedure Call to remove a modifier from a player by type ID. + /// + /// The player to remove the modifier from. + /// The type ID of the modifier. + /// Optional predicate to filter the modifiers. + public static void RpcRemoveModifier( + this PlayerControl target, + uint typeId, + Func? predicate = null) + { + var modifier = target.GetModifier(typeId, predicate); + if (modifier is null) + { + Logger.Error($"Player {target.PlayerId} does not have modifier with type ID {typeId}."); + return; + } + + target.RpcRemoveModifier(modifier.UniqueId); + } + + /// + /// Remote Procedure Call to remove a modifier from a player. + /// + /// The player to remove the modifier from. + /// The type of the modifier. + /// Optional predicate to filter the modifiers. + public static void RpcRemoveModifier( + this PlayerControl player, + Type type, + Func? predicate = null) + { + var id = ModifierManager.GetModifierTypeId(type) ?? throw new InvalidOperationException( + $"Modifier {type.Name} is not registered."); + + player.RpcRemoveModifier(id, predicate); + } + + /// + /// Remote Procedure Call to remove a modifier from a player. + /// + /// The player to remove the modifier from. + /// Optional predicate to filter the modifiers. + /// The Type of the Modifier. + public static void RpcRemoveModifier(this PlayerControl player, Func? predicate = null) + where T : BaseModifier + { + player.RpcRemoveModifier(typeof(T), predicate); + } + + /// + /// 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) + { + throw new InvalidOperationException("ModifierComponent is not attached to the player."); + } + + ModifierComponents[player] = component; + return component; + } + + /// + /// Checks if the player has a specific modifier. + /// + /// 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().HasModifier(predicate); + } + + /// + /// Checks if the player has a specific modifier by type. + /// + /// 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().HasModifier(type, predicate); + } + + /// + /// Checks if the player has a specific modifier by type ID. + /// + /// The PlayerControl instance. + /// The type 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 typeId, + Func? predicate = null) + { + return player.GetModifierComponent().HasModifier(typeId, predicate); + } + + /// + /// Checks if the player has a specific modifier by its GUID. + /// + /// The PlayerControl instance. + /// The unique ID of the modifier. + /// True if the player has the modifier, false otherwise. + public static bool HasModifier(this PlayerControl player, Guid uniqueId) + { + return player.GetModifierComponent().HasModifier(uniqueId); + } + + /// + /// Gets a specific modifier from the player. + /// + /// 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 its type ID. + /// + /// The PlayerControl instance. + /// The type ID of the modifier. + /// Optional predicate to filter the modifiers. + /// The modifier if found, null otherwise. + public static BaseModifier? GetModifier( + this PlayerControl player, + uint typeId, + Func? predicate = null) + { + return player.GetModifierComponent().GetModifier(typeId, predicate); + } + + /// + /// Gets a specific modifier from the player by its GUID. + /// + /// The PlayerControl instance. + /// The GUID of the modifier. + /// The modifier if found, null otherwise. + public static BaseModifier? GetModifier( + this PlayerControl player, + Guid uniqueId) + { + return player.GetModifierComponent().GetModifier(uniqueId); + } + + /// + /// Gets all modifiers of a specific type from the player. + /// + /// The type of the modifiers. + /// The PlayerControl instance. + /// Optional predicate to filter the modifiers. + /// A collection of modifiers. + public static IEnumerable GetModifiers(this PlayerControl player, Func? predicate = null) + where T : BaseModifier + { + return player.GetModifierComponent().GetModifiers(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. + /// A collection of modifiers. + public static IEnumerable GetModifiers( + this PlayerControl player, + Type type, + Func? predicate = null) + { + return player.GetModifierComponent().GetModifiers(type, predicate); + } + + /// + /// Gets all modifiers of a specific type from the player by type ID. + /// + /// The PlayerControl instance. + /// The type ID of the modifiers. + /// Optional predicate to filter the modifiers. + /// A collection of modifiers. + public static IEnumerable GetModifiers( + this PlayerControl player, + uint typeId, + Func? predicate = null) + { + return player.GetModifierComponent().GetModifiers(typeId, predicate); + } + + /// + /// Removes a specific modifier from the player. + /// + /// The PlayerControl instance. + /// The modifier to remove. + public static void RemoveModifier(this PlayerControl player, BaseModifier modifier) + { + player.GetModifierComponent().RemoveModifier(modifier); + } + + /// + /// 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 its type ID. + /// + /// The PlayerControl instance. + /// The type ID of the modifier. + /// Optional predicate to filter the modifiers. + public static void RemoveModifier( + this PlayerControl player, + uint typeId, + Func? predicate = null) + { + player.GetModifierComponent().RemoveModifier(typeId, predicate); + } + + /// + /// Removes a specific modifier from the player by its GUID. + /// + /// The PlayerControl instance. + /// The GUID of the modifier. + public static void RemoveModifier( + this PlayerControl player, + Guid uniqueId) + { + player.GetModifierComponent().RemoveModifier(uniqueId); + } + + /// + /// Adds a specific modifier to the player. + /// + /// The type of the modifier. + /// The PlayerControl instance. + /// The arguments to initialize the modifier constructor with. + /// The added modifier. + public static T? AddModifier(this PlayerControl player, params object[] args) where T : BaseModifier + { + return player.GetModifierComponent().AddModifier(args); + } + + /// + /// Adds a specific modifier to the player. + /// + /// The PlayerControl instance. + /// The modifier to add. + /// The added modifier. + public static BaseModifier? AddModifier(this PlayerControl player, BaseModifier modifier) + { + return player.GetModifierComponent().AddModifier(modifier); + } + + /// + /// 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 type ID. + /// + /// The PlayerControl instance. + /// The type ID of the modifier. + /// The arguments to initialize the modifier constructor with. + /// The added modifier. + public static BaseModifier? AddModifier(this PlayerControl player, uint typeId, params object[] args) + { + return player.GetModifierComponent().AddModifier(typeId, args); + } +} diff --git a/MiraAPI/Modifiers/ModifierFactory.cs b/MiraAPI/Modifiers/ModifierFactory.cs new file mode 100644 index 0000000..8f8d005 --- /dev/null +++ b/MiraAPI/Modifiers/ModifierFactory.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using MiraAPI.Utilities; + +namespace MiraAPI.Modifiers; + +/// +/// Factory for creating instances of a modifier. More efficient than using reflection. +/// +public static class ModifierFactory +{ + private static readonly Dictionary<(Type, Type[]), Func> _constructorCache = []; + + private static Func CreateConstructor(Type type, params object[] args) + { + var constructorInfo = type.GetBestConstructor(args) ?? 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"); + 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 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) + { + var argTypes = args.Select(arg => arg?.GetType() ?? typeof(object)).ToArray(); + var key = (type, argTypes); + + if (!_constructorCache.TryGetValue(key, out var constructor)) + { + constructor = CreateConstructor(type, args); + _constructorCache[key] = constructor; + } + + 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(params object[] args) + { + var constructorInfo = typeof(T).GetBestConstructor(args) ?? 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"); + 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/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs index d4d4004..4285a73 100644 --- a/MiraAPI/Modifiers/ModifierManager.cs +++ b/MiraAPI/Modifiers/ModifierManager.cs @@ -3,11 +3,9 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using MiraAPI.Modifiers.Types; -using MiraAPI.Networking; using MiraAPI.PluginLoading; using MiraAPI.Roles; using MiraAPI.Utilities; -using Reactor.Networking.Rpc; using Reactor.Utilities; using Reactor.Utilities.Extensions; using Random = System.Random; @@ -23,16 +21,16 @@ public static class ModifierManager private static readonly Dictionary TypeToIdModifierMap = []; private static readonly Dictionary> PrioritiesToIdsMap = []; - private static uint _nextId; + private static uint _nextTypeId; - private static uint GetNextId() + private static uint GetNextTypeId() { - _nextId++; - return _nextId; + _nextTypeId++; + return _nextTypeId; } /// - /// Gets the modifier type from the id. + /// Gets the modifier type from the type id. /// /// The ID. /// The Type of the modifier. @@ -46,9 +44,14 @@ public static class ModifierManager /// /// The Type. /// The ID of the modifier. - public static uint? GetModifierId(Type type) + public static uint? GetModifierTypeId(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) @@ -58,10 +61,9 @@ public static class ModifierManager return false; } - IdToTypeModifierMap.Add(GetNextId(), modifierType); - TypeToIdModifierMap.Add(modifierType, _nextId); + IdToTypeModifierMap.Add(GetNextTypeId(), modifierType); + TypeToIdModifierMap.Add(modifierType, _nextTypeId); 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)) @@ -77,7 +79,7 @@ public static class ModifierManager PrioritiesToIdsMap[priority] = list = []; } - list.Add(_nextId); + list.Add(_nextTypeId); return true; } @@ -139,7 +141,7 @@ public static class ModifierManager var mod = Activator.CreateInstance(IdToTypeModifierMap[id]) as GameModifier; var plr = plrs.Where(x => IsGameModifierValid(x, mod!, id)).Random(); - if (plr == null) + if (!plr || plr == null) { Logger.Warning($"Somehow valid players for modifier {mod!.ModifierName} disappeared"); } @@ -154,71 +156,6 @@ public static class ModifierManager private static bool IsGameModifierValid(PlayerControl player, GameModifier modifier, uint modifierId) { return (player.Data.Role is not ICustomRole role || role.IsModifierApplicable(modifier)) && - modifier.IsModifierValidOn(player.Data.Role) && - !player.HasModifier(modifierId); - } - - internal static void SyncAllModifiers(int targetId = -1) - { - var data = new List(); - - foreach (var player in PlayerControl.AllPlayerControls) - { - data.Add(GetPlayerModifiers(player)); - } - - Rpc.Instance.SendTo(PlayerControl.LocalPlayer, targetId, [.. data]); - } - - internal static void HandleSyncModifiers(NetData[] data) - { - foreach (var netData in data) - { - var ids = new uint[netData.Data.Length / 4]; - Buffer.BlockCopy(netData.Data, 0, ids, 0, netData.Data.Length); - - var plr = GameData.Instance.GetPlayerById((byte)netData.Id).Object; - var modifierComponent = plr.GetComponent(); - - if (!modifierComponent) - { - continue; - } - - modifierComponent.ClearModifiers(); - - foreach (var id in ids) - { - if (!IdToTypeModifierMap.TryGetValue(id, out var type)) - { - Logger.Error($"Cannot add modifier with id {id} because it is not registered."); - continue; - } - - modifierComponent.AddModifier(type); - } - } - } - - private static NetData GetPlayerModifiers(PlayerControl? player) - { - if (player == null || !player) - { - return new NetData(0, []); - } - - List bytes = []; - var modifierComponent = player.GetComponent(); - if (modifierComponent == null || !modifierComponent) - { - return new NetData(player.PlayerId, []); - } - - foreach (var modifier in modifierComponent.ActiveModifiers) - { - bytes.AddRange(BitConverter.GetBytes(TypeToIdModifierMap[modifier.GetType()])); - } - - return new NetData(player.PlayerId, [.. bytes]); + modifier.IsModifierValidOn(player.Data.Role) && !player.HasModifier(modifierId); } } diff --git a/MiraAPI/Modifiers/Types/TimedModifier.cs b/MiraAPI/Modifiers/Types/TimedModifier.cs index 286b80a..ebb0457 100644 --- a/MiraAPI/Modifiers/Types/TimedModifier.cs +++ b/MiraAPI/Modifiers/Types/TimedModifier.cs @@ -17,7 +17,7 @@ public abstract class TimedModifier : BaseModifier /// /// Gets a value indicating whether the timer should start automatically when added. /// - public virtual bool AutoStart => false; + public virtual bool AutoStart => true; /// /// Gets a value indicating whether the modifier should be removed when the timer completes. @@ -27,7 +27,9 @@ public abstract class TimedModifier : BaseModifier /// /// Called when the timer completes. /// - public abstract void OnTimerComplete(); + public virtual void OnTimerComplete() + { + } /// /// Gets or sets a value indicating whether the timer is active. @@ -102,7 +104,7 @@ public abstract class TimedModifier : BaseModifier if (RemoveOnComplete) { - ModifierComponent?.RemoveModifier(ModifierId); + ModifierComponent?.RemoveModifier(this); } } diff --git a/MiraAPI/Networking/GuidConverter.cs b/MiraAPI/Networking/GuidConverter.cs new file mode 100644 index 0000000..c00d759 --- /dev/null +++ b/MiraAPI/Networking/GuidConverter.cs @@ -0,0 +1,32 @@ +using System; +using Hazel; +using Reactor.Networking.Attributes; +using Reactor.Networking.Serialization; + +namespace MiraAPI.Networking; + +/// +/// Converter for . +/// +[MessageConverter] +public class GuidConverter : MessageConverter +{ + /// + /// Writes a to the writer. + /// + /// + public override void Write(MessageWriter writer, Guid value) + { + writer.WriteBytesAndSize(value.ToByteArray()); + } + + /// + /// Reads a from the reader. + /// + /// + /// A GUID object. + public override Guid Read(MessageReader reader, Type objectType) + { + return new Guid(reader.ReadBytesAndSize()); + } +} diff --git a/MiraAPI/Networking/Modifiers/AddModifierRpc.cs b/MiraAPI/Networking/Modifiers/AddModifierRpc.cs new file mode 100644 index 0000000..2e7b19e --- /dev/null +++ b/MiraAPI/Networking/Modifiers/AddModifierRpc.cs @@ -0,0 +1,76 @@ +using System; +using Hazel; +using MiraAPI.Modifiers; +using Reactor.Networking.Attributes; +using Reactor.Networking.Rpc; +using Reactor.Networking.Serialization; + +namespace MiraAPI.Networking.Modifiers; + +/// +/// Remote procedure call for adding a modifier. +/// +/// Mira plugin. +/// RPC ID. +[RegisterCustomRpc((uint)MiraRpc.AddModifier)] +public class AddModifierRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc(plugin, id) +{ + /// + public override RpcLocalHandling LocalHandling => RpcLocalHandling.Before; + + /// + public override void Write(MessageWriter writer, ModifierData data) + { + writer.WritePacked(data.TypeId); + writer.WriteBytesAndSize(data.UniqueId.ToByteArray()); + 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 guid = new Guid(reader.ReadBytesAndSize()); + var argCount = reader.ReadPackedUInt32(); + var objects = new object[argCount]; + + if (argCount > 0) + { + 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]); + } + } + + return new ModifierData(modId, guid, objects); + } + + /// + public override void Handle(PlayerControl player, ModifierData data) + { + var type = ModifierManager.GetModifierType(data.TypeId) ?? throw new InvalidOperationException($"Modifier type not found for ID {data.TypeId}."); + BaseModifier? modifier; + if (data.Args.Length > 0) + { + modifier = ModifierFactory.CreateInstance(type, data.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."); + } + } + modifier.UniqueId = data.UniqueId; + player.AddModifier(modifier); + } +} diff --git a/MiraAPI/Networking/Modifiers/ModifierConverter.cs b/MiraAPI/Networking/Modifiers/ModifierConverter.cs new file mode 100644 index 0000000..e8afa1b --- /dev/null +++ b/MiraAPI/Networking/Modifiers/ModifierConverter.cs @@ -0,0 +1,40 @@ +using System; +using Hazel; +using InnerNet; +using MiraAPI.Modifiers; +using Reactor.Networking.Attributes; +using Reactor.Networking.Serialization; + +namespace MiraAPI.Networking.Modifiers; + +/// +/// Converter for serializing and deserializing objects. +/// +[MessageConverter] +public class ModifierConverter : MessageConverter +{ + /// + /// Writes a to the writer. + /// + /// The writer to write to. + /// The to write. + public override void Write(MessageWriter writer, BaseModifier value) + { + writer.WriteNetObject(value.Player); + writer.WriteBytesAndSize(value.UniqueId.ToByteArray()); + } + + /// + /// Reads a from the reader. + /// + /// The reader to read from. + /// The type of the object to read. + /// The that was read. + /// Thrown if the modifier is not found. + public override BaseModifier Read(MessageReader reader, Type objectType) + { + var player = reader.ReadNetObject(); + var guid = new Guid(reader.ReadBytesAndSize()); + return player.GetModifier(guid) ?? throw new InvalidOperationException($"Modifier with GUID {guid} not found for player {player.NetId}."); + } +} diff --git a/MiraAPI/Networking/Modifiers/ModifierData.cs b/MiraAPI/Networking/Modifiers/ModifierData.cs new file mode 100644 index 0000000..3d3f158 --- /dev/null +++ b/MiraAPI/Networking/Modifiers/ModifierData.cs @@ -0,0 +1,11 @@ +using System; + +namespace MiraAPI.Networking.Modifiers; + +/// +/// Modifier data for networking. +/// +/// The ID of the modifier. +/// The unique ID of the modifier. +/// Parameters for constructor. +public record struct ModifierData(uint TypeId, Guid UniqueId, object[] Args); diff --git a/MiraAPI/Networking/SyncModifiersRpc.cs b/MiraAPI/Networking/SyncModifiersRpc.cs deleted file mode 100644 index 288e2c1..0000000 --- a/MiraAPI/Networking/SyncModifiersRpc.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Hazel; -using MiraAPI.Modifiers; -using Reactor.Networking.Attributes; -using Reactor.Networking.Rpc; - -namespace MiraAPI.Networking; - -[RegisterCustomRpc((uint)MiraRpc.SyncModifiers)] -internal sealed class SyncModifiersRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc(plugin, id) -{ - public override RpcLocalHandling LocalHandling => RpcLocalHandling.None; - - public override void Write(MessageWriter writer, NetData[]? data) - { - if (data == null) - { - writer.WritePacked(0U); - return; - } - - writer.WritePacked((uint)data.Length); - foreach (var netData in data) - { - writer.WritePacked(netData.Id); - writer.WriteBytesAndSize(netData.Data); - } - } - - public override NetData[] Read(MessageReader reader) - { - var length = reader.ReadPackedUInt32(); - var data = new NetData[length]; - for (var i = 0; i < length; i++) - { - var dataId = reader.ReadPackedUInt32(); - var bytes = reader.ReadBytesAndSize(); - data[i] = new NetData(dataId, bytes); - } - - return data; - } - - public override void Handle(PlayerControl playerControl, NetData[]? data) - { - if (AmongUsClient.Instance.HostId != playerControl.OwnerId) - { - return; - } - - ModifierManager.HandleSyncModifiers(data ?? []); - } -} diff --git a/MiraAPI/Patches/AmongUsClientSyncPatch.cs b/MiraAPI/Patches/AmongUsClientSyncPatch.cs index 4a76283..6af91c1 100644 --- a/MiraAPI/Patches/AmongUsClientSyncPatch.cs +++ b/MiraAPI/Patches/AmongUsClientSyncPatch.cs @@ -3,16 +3,15 @@ using InnerNet; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Player; using MiraAPI.GameOptions; -using MiraAPI.Modifiers; using MiraAPI.Roles; namespace MiraAPI.Patches; /// -/// Sync all options, role settings, and modifiers to the player when they join the game. +/// Sync all settings to the player when they join the game. /// [HarmonyPatch(typeof(AmongUsClient))] -public static class AmongUsClientSyncPatch +internal static class AmongUsClientSyncPatch { [HarmonyPostfix] [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CreatePlayer))] @@ -33,7 +32,6 @@ public static class AmongUsClientSyncPatch ModdedOptionsManager.SyncAllOptions(clientData.Id); CustomRoleManager.SyncAllRoleSettings(clientData.Id); - ModifierManager.SyncAllModifiers(clientData.Id); } [HarmonyPostfix] diff --git a/MiraAPI/Patches/Events/KillAnimationPatches.cs b/MiraAPI/Patches/Events/KillAnimationPatches.cs index 13b4cff..3572ff8 100644 --- a/MiraAPI/Patches/Events/KillAnimationPatches.cs +++ b/MiraAPI/Patches/Events/KillAnimationPatches.cs @@ -2,7 +2,7 @@ using MiraAPI.Events; using MiraAPI.Events.Vanilla.Gameplay; -namespace MiraAPI.Patches; +namespace MiraAPI.Patches.Events; /// /// General patches for the KillAnimation class. diff --git a/MiraAPI/Patches/Events/UsePatches.cs b/MiraAPI/Patches/Events/UsePatches.cs index beb1f69..0571cd6 100644 --- a/MiraAPI/Patches/Events/UsePatches.cs +++ b/MiraAPI/Patches/Events/UsePatches.cs @@ -1,8 +1,8 @@ using HarmonyLib; using MiraAPI.Events; -using MiraAPI.Events.Vanilla; using System.Collections.Generic; using System.Reflection; +using MiraAPI.Events.Vanilla.Usables; namespace MiraAPI.Patches.Events; 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..666a1ea 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.TypeId.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.TypeId; + 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/Modifiers/EndGameDidWinPatch.cs b/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs index d7a99d5..a86383d 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; @@ -24,7 +24,7 @@ public static class EndGameDidWinPatch var didWin = networkedPlayerInfo.Role.DidWin(gameOverReason); - var modifierWin = networkedPlayerInfo.Object?.GetModifierComponent()?.ActiveModifiers.OfType() + var modifierWin = networkedPlayerInfo.Object?.GetModifierComponent().GetModifiers() .FirstOrDefault(x => x.DidWin(gameOverReason) != null)?.DidWin(gameOverReason); if (modifierWin != null) { 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/Patches/VentPatches.cs b/MiraAPI/Patches/VentPatches.cs index 621abbc..2f8991d 100644 --- a/MiraAPI/Patches/VentPatches.cs +++ b/MiraAPI/Patches/VentPatches.cs @@ -1,8 +1,8 @@ using HarmonyLib; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Usables; +using MiraAPI.Modifiers; using MiraAPI.Roles; -using MiraAPI.Utilities; using UnityEngine; namespace MiraAPI.Patches; @@ -35,8 +35,8 @@ public static class VentPatches var canVent = role is ICustomRole customRole ? customRole.Configuration.CanUseVent : role.CanVent; couldUse = canVent; - var modifiers = @object.GetModifierComponent()?.ActiveModifiers; - if (modifiers is { Count: > 0 }) + var modifiers = @object.GetModifierComponent().ActiveModifiers; + if (modifiers.Count > 0) { switch (canVent) { diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 9316ba8..7ebaecc 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -1,13 +1,12 @@ using HarmonyLib; using MiraAPI.GameOptions; -using MiraAPI.Modifiers; using MiraAPI.Networking; using MiraAPI.Roles; -using Reactor.Networking.Attributes; using Reactor.Utilities; using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using TMPro; using UnityEngine; @@ -40,6 +39,64 @@ public static class Extensions BitConverter.GetBytes(count.Value).AddRangeToArray(BitConverter.GetBytes(chance.Value))); } + /// + /// Gets the best constructor for a type based on the specified arguments. + /// + /// The type to get the constructor from. + /// The arguments to pass into the constructor. + /// The best constructor. + public static ConstructorInfo? GetBestConstructor(this Type type, params object[] args) + { + return type.GetValidConstructors(args) + .OrderBy( + ctor => ctor.GetParameters() + .Select((p, i) => GetInheritanceDistance(args[i].GetType(), p.ParameterType)) + .Sum()) + .FirstOrDefault(); + } + + /// + /// Gets the constructors of a type that match the specified arguments. + /// + /// The type to get constructors from. + /// The arguments to pass into the constructor. + /// A collection of valid constructors. + public static IEnumerable GetValidConstructors(this Type type, params object[] args) + { + return type.GetConstructors().Where( + x => + { + var parameters = x.GetParameters(); + return parameters.Length == args.Length && Array.TrueForAll( + parameters, + t => t.ParameterType.IsInstanceOfType(args[t.Position])); + }); + } + + /// + /// Calculates the inheritance distance from the given type to its target base type. + /// Lower values mean the type is a closer match. + /// + /// The derived type. + /// The base type. + /// The distance between the types. + public static int GetInheritanceDistance(Type from, Type to) + { + if (!from.IsAssignableFrom(to)) + { + return int.MaxValue; + } + + var type = from; + var distance = 0; + while (type != null && type != to) + { + type = type.BaseType; + distance++; + } + return type == to ? distance : int.MaxValue; + } + /// /// Enables stencil masking on a TMP text object. /// 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); - } -}