From 9485099ab94510e66b6051a7cb6537d874a964f1 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sun, 2 Feb 2025 16:09:06 -0500 Subject: [PATCH 01/14] RPC add modifier with arguments --- .../Buttons/Freezer/FreezeButton.cs | 1 + MiraAPI.Example/Buttons/MeetingButton.cs | 2 +- .../OptionTypes/ModdedEnumOption.cs | 1 - MiraAPI/Modifiers/ModifierExtensions.cs | 139 ++++++++++++++++++ MiraAPI/Modifiers/ModifierFactory.cs | 83 +++++++++++ .../Patches/Modifiers/EndGameDidWinPatch.cs | 2 +- MiraAPI/Patches/PlayerControlPatches.cs | 2 +- MiraAPI/Patches/VentPatches.cs | 2 +- MiraAPI/Utilities/Extensions.cs | 125 ---------------- 9 files changed, 227 insertions(+), 130 deletions(-) create mode 100644 MiraAPI/Modifiers/ModifierExtensions.cs create mode 100644 MiraAPI/Modifiers/ModifierFactory.cs diff --git a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs index 8db98c7..86feece 100644 --- a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs +++ b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs @@ -3,6 +3,7 @@ using MiraAPI.Example.Options.Roles; using MiraAPI.Example.Roles; using MiraAPI.GameOptions; using MiraAPI.Hud; +using MiraAPI.Modifiers; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using UnityEngine; diff --git a/MiraAPI.Example/Buttons/MeetingButton.cs b/MiraAPI.Example/Buttons/MeetingButton.cs index 85b8ff1..742c7e0 100644 --- a/MiraAPI.Example/Buttons/MeetingButton.cs +++ b/MiraAPI.Example/Buttons/MeetingButton.cs @@ -1,6 +1,6 @@ using MiraAPI.Example.Modifiers; using MiraAPI.Hud; -using MiraAPI.Utilities; +using MiraAPI.Modifiers; using MiraAPI.Utilities.Assets; using UnityEngine; diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs index e917cdc..80bd497 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.Linq; using System.Text; using Il2CppInterop.Runtime.InteropTypes.Arrays; diff --git a/MiraAPI/Modifiers/ModifierExtensions.cs b/MiraAPI/Modifiers/ModifierExtensions.cs new file mode 100644 index 0000000..83dfd4c --- /dev/null +++ b/MiraAPI/Modifiers/ModifierExtensions.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using MiraAPI.Networking; +using Reactor.Networking.Attributes; +using Reactor.Utilities; + +namespace MiraAPI.Modifiers; + +/// +/// Extension methods for modifiers. +/// +public static class ModifierExtensions +{ + internal static Dictionary ModifierComponents { get; } = []; + + /// + /// Gets the ModifierComponent for a player. + /// + /// The PlayerControl object. + /// A ModifierComponent if there is one, null otherwise. + public static ModifierComponent? GetModifierComponent(this PlayerControl player) + { + if (ModifierComponents.TryGetValue(player, out var component)) + { + return component; + } + + component = player.GetComponent(); + if (component == null) + { + return null; + } + + ModifierComponents[player] = component; + return component; + } + + /// + /// Gets a modifier by its type, or null if the player doesn't have it. + /// + /// The PlayerControl object. + /// The Type of the Modifier. + /// The Modifier if it is found, null otherwise. + public static T? GetModifier(this PlayerControl? player) where T : BaseModifier + { + return player?.GetModifierComponent()?.ActiveModifiers.Find(x => x is T) as T; + } + + /// + /// Checks if a player has a modifier. + /// + /// The PlayerControl object. + /// The Type of the Modifier. + /// True if the Modifier is present, false otherwise. + public static bool HasModifier(this PlayerControl? player) where T : BaseModifier + { + return player?.GetModifierComponent() != null && + player.GetModifierComponent()!.HasModifier(); + } + + /// + /// Checks if a player has a modifier by its ID. + /// + /// The PlayerControl object. + /// The Modifier ID. + /// True if the Modifier is present, false otherwise. + public static bool HasModifier(this PlayerControl? player, uint id) + { + return player?.GetModifierComponent() != null && + player.GetModifierComponent()!.HasModifier(id); + } + + /// + /// Remote Procedure Call to remove a modifier from a player. + /// + /// The player to remove the modifier from. + /// The ID of the modifier. + [MethodRpc((uint)MiraRpc.RemoveModifier)] + public static void RpcRemoveModifier(this PlayerControl target, uint modifierId) + { + target.GetModifierComponent()?.RemoveModifier(modifierId); + } + + /// + /// Remote Procedure Call to remove a modifier from a player. + /// + /// The player to remove the modifier from. + /// The Type of the Modifier. + public static void RpcRemoveModifier(this PlayerControl player) where T : BaseModifier + { + var id = ModifierManager.GetModifierId(typeof(T)); + + if (id == null) + { + Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered."); + return; + } + + player.RpcRemoveModifier(id.Value); + } + + /// + /// Remote Procedure Call to add a modifier to a player. + /// + /// The player to add the modifier to. + /// The modifier ID. + [MethodRpc((uint)MiraRpc.AddModifier)] + public static void RpcAddModifier(this PlayerControl target, uint modifierId, params object[] args) + { + var type = ModifierManager.GetModifierType(modifierId); + if (type == null) + { + Logger.Error($"Cannot add modifier with id {modifierId} because it is not registered."); + return; + } + + var modifier = ModifierFactory.CreateInstance(type, args); + target.GetModifierComponent()?.AddModifier(modifier); + } + + /// + /// Remote Procedure Call to add a modifier to a player. + /// + /// The player to add the modifier to. + /// The arguments to initialize the modifier constructor with. + /// The modifier Type. + public static void RpcAddModifier(this PlayerControl player, params object[] args) where T : BaseModifier + { + var id = ModifierManager.GetModifierId(typeof(T)); + if (id == null) + { + Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered."); + return; + } + + var modifier = ModifierFactory.CreateInstance(args); + player.GetModifierComponent()!.AddModifier(modifier); + } +} diff --git a/MiraAPI/Modifiers/ModifierFactory.cs b/MiraAPI/Modifiers/ModifierFactory.cs new file mode 100644 index 0000000..e1b018c --- /dev/null +++ b/MiraAPI/Modifiers/ModifierFactory.cs @@ -0,0 +1,83 @@ +using System; +using System.Linq; +using System.Linq.Expressions; + +namespace MiraAPI.Modifiers; + +public static class ModifierFactory +{ + private static Func? _constructor; + + private static Func CreateConstructor(Type type) + { + var constructorInfo = type.GetConstructors() + .OrderByDescending(c => c.GetParameters().Length) + .FirstOrDefault() ?? throw new InvalidOperationException($"Type {type} does not have a public constructor."); + var parameters = constructorInfo.GetParameters(); + + var argsParam = Expression.Parameter(typeof(object[]), "args"); + var constructorParams = new Expression[parameters.Length]; + + for (var i = 0; i < parameters.Length; i++) + { + var paramType = parameters[i].ParameterType; + var paramAccess = Expression.ArrayIndex(argsParam, Expression.Constant(i)); + var convertedParam = Expression.Convert(paramAccess, paramType); + constructorParams[i] = convertedParam; + } + + var newExpr = Expression.New(constructorInfo, constructorParams); + var lambda = Expression.Lambda>(newExpr, argsParam); + + return lambda.Compile(); + } + + public static BaseModifier CreateInstance(Type type, params object[] args) + { + _constructor ??= CreateConstructor(type); + return _constructor(args); + } +} + +/// +/// Factory for creating instances of a modifier. More efficient than using reflection. +/// +/// The modifier type. +public static class ModifierFactory where T : BaseModifier +{ + private static readonly Func Constructor = CreateConstructor(); + + private static Func CreateConstructor() + { + var constructorInfo = typeof(T).GetConstructors() + .OrderByDescending(c => c.GetParameters().Length) + .FirstOrDefault() ?? throw new InvalidOperationException($"Type {typeof(T)} does not have a public constructor."); + var parameters = constructorInfo.GetParameters(); + + var argsParam = Expression.Parameter(typeof(object[]), "args"); + var constructorParams = new Expression[parameters.Length]; + + for (var i = 0; i < parameters.Length; i++) + { + var paramType = parameters[i].ParameterType; + var paramAccess = Expression.ArrayIndex(argsParam, Expression.Constant(i)); + var convertedParam = Expression.Convert(paramAccess, paramType); + constructorParams[i] = convertedParam; + } + + var newExpr = Expression.New(constructorInfo, constructorParams); + var lambda = Expression.Lambda>(newExpr, argsParam); + + return lambda.Compile(); + } + + /// + /// Creates an instance of the modifier. + /// + /// Parameters for the constructor. + /// An instance of the modifier. + public static T CreateInstance(params object[] args) + { + return Constructor(args); + } +} diff --git a/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs b/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs index d7a99d5..17356db 100644 --- a/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs +++ b/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs @@ -1,7 +1,7 @@ using System.Linq; using HarmonyLib; +using MiraAPI.Modifiers; using MiraAPI.Modifiers.Types; -using MiraAPI.Utilities; namespace MiraAPI.Patches.Modifiers; diff --git a/MiraAPI/Patches/PlayerControlPatches.cs b/MiraAPI/Patches/PlayerControlPatches.cs index bc98a51..e0d647d 100644 --- a/MiraAPI/Patches/PlayerControlPatches.cs +++ b/MiraAPI/Patches/PlayerControlPatches.cs @@ -86,6 +86,6 @@ public static class PlayerControlPatches [HarmonyPatch(nameof(PlayerControl.OnDestroy))] public static void PlayerControlOnDestroyPrefix(PlayerControl __instance) { - Utilities.Extensions.ModifierComponents.Remove(__instance); + ModifierExtensions.ModifierComponents.Remove(__instance); } } diff --git a/MiraAPI/Patches/VentPatches.cs b/MiraAPI/Patches/VentPatches.cs index 66c9aae..a41c690 100644 --- a/MiraAPI/Patches/VentPatches.cs +++ b/MiraAPI/Patches/VentPatches.cs @@ -1,6 +1,6 @@ using HarmonyLib; +using MiraAPI.Modifiers; using MiraAPI.Roles; -using MiraAPI.Utilities; using UnityEngine; namespace MiraAPI.Patches; diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 032cff3..d177d60 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -3,10 +3,8 @@ using System.Collections.Generic; using System.Linq; using HarmonyLib; using MiraAPI.GameOptions; -using MiraAPI.Modifiers; using MiraAPI.Networking; using MiraAPI.Roles; -using Reactor.Networking.Attributes; using Reactor.Utilities; using UnityEngine; @@ -130,30 +128,6 @@ public static class Extensions opt => opt.OptionBehaviour && opt.OptionBehaviour == optionBehaviour); } - internal static Dictionary ModifierComponents { get; } = []; - - /// - /// Gets the ModifierComponent for a player. - /// - /// The PlayerControl object. - /// A ModifierComponent if there is one, null otherwise. - public static ModifierComponent? GetModifierComponent(this PlayerControl player) - { - if (ModifierComponents.TryGetValue(player, out var component)) - { - return component; - } - - component = player.GetComponent(); - if (component == null) - { - return null; - } - - ModifierComponents[player] = component; - return component; - } - /// /// Randomizes a list. /// @@ -174,105 +148,6 @@ public static class Extensions return randomizedList; } - /// - /// Gets a modifier by its type, or null if the player doesn't have it. - /// - /// The PlayerControl object. - /// The Type of the Modifier. - /// The Modifier if it is found, null otherwise. - public static T? GetModifier(this PlayerControl? player) where T : BaseModifier - { - return player?.GetModifierComponent()?.ActiveModifiers.Find(x => x is T) as T; - } - - /// - /// Checks if a player has a modifier. - /// - /// The PlayerControl object. - /// The Type of the Modifier. - /// True if the Modifier is present, false otherwise. - public static bool HasModifier(this PlayerControl? player) where T : BaseModifier - { - return player?.GetModifierComponent() != null && - player.GetModifierComponent()!.HasModifier(); - } - - /// - /// Checks if a player has a modifier by its ID. - /// - /// The PlayerControl object. - /// The Modifier ID. - /// True if the Modifier is present, false otherwise. - public static bool HasModifier(this PlayerControl? player, uint id) - { - return player?.GetModifierComponent() != null && - player.GetModifierComponent()!.HasModifier(id); - } - - /// - /// Remote Procedure Call to remove a modifier from a player. - /// - /// The player to remove the modifier from. - /// The ID of the modifier. - [MethodRpc((uint)MiraRpc.RemoveModifier)] - public static void RpcRemoveModifier(this PlayerControl target, uint modifierId) - { - target.GetModifierComponent()?.RemoveModifier(modifierId); - } - - /// - /// Remote Procedure Call to remove a modifier from a player. - /// - /// The player to remove the modifier from. - /// The Type of the Modifier. - public static void RpcRemoveModifier(this PlayerControl player) where T : BaseModifier - { - var id = ModifierManager.GetModifierId(typeof(T)); - - if (id == null) - { - Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered."); - return; - } - - player.RpcRemoveModifier(id.Value); - } - - /// - /// Remote Procedure Call to add a modifier to a player. - /// - /// The player to add the modifier to. - /// The modifier ID. - [MethodRpc((uint)MiraRpc.AddModifier)] - public static void RpcAddModifier(this PlayerControl target, uint modifierId) - { - var type = ModifierManager.GetModifierType(modifierId); - if (type == null) - { - Logger.Error($"Cannot add modifier with id {modifierId} because it is not registered."); - return; - } - - target.GetModifierComponent()?.AddModifier(type); - } - - /// - /// Remote Procedure Call to add a modifier to a player. - /// - /// The player to add the modifier to. - /// The modifier Type. - public static void RpcAddModifier(this PlayerControl player) where T : BaseModifier - { - var id = ModifierManager.GetModifierId(typeof(T)); - if (id == null) - { - Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered."); - return; - } - - player.RpcAddModifier(id.Value); - } - /// /// Darkens a color by a specified amount. /// From ce81d8c1ff8be72e41ce26415f0667364cd63e03 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sun, 2 Feb 2025 20:36:14 -0500 Subject: [PATCH 02/14] it works? --- MiraAPI/Modifiers/ModifierData.cs | 21 +++++++++ MiraAPI/Modifiers/ModifierExtensions.cs | 9 ++-- MiraAPI/Networking/AddModifierRpc.cs | 61 +++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 MiraAPI/Modifiers/ModifierData.cs create mode 100644 MiraAPI/Networking/AddModifierRpc.cs diff --git a/MiraAPI/Modifiers/ModifierData.cs b/MiraAPI/Modifiers/ModifierData.cs new file mode 100644 index 0000000..425e063 --- /dev/null +++ b/MiraAPI/Modifiers/ModifierData.cs @@ -0,0 +1,21 @@ +using System; + +namespace MiraAPI.Modifiers; + +/// +/// Modifier data for networking. +/// +/// The Type of the modifier. +/// Parameters for constructor. +public readonly struct ModifierData(Type type, object[] args) +{ + /// + /// Gets the type of the modifier. + /// + public Type Type { get; } = type; + + /// + /// Gets the parameters for the constructor. + /// + public object[] Args { get; } = args; +} diff --git a/MiraAPI/Modifiers/ModifierExtensions.cs b/MiraAPI/Modifiers/ModifierExtensions.cs index 83dfd4c..d6ce5ff 100644 --- a/MiraAPI/Modifiers/ModifierExtensions.cs +++ b/MiraAPI/Modifiers/ModifierExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using MiraAPI.Networking; using Reactor.Networking.Attributes; +using Reactor.Networking.Rpc; using Reactor.Utilities; namespace MiraAPI.Modifiers; @@ -104,7 +105,7 @@ public static class ModifierExtensions /// /// The player to add the modifier to. /// The modifier ID. - [MethodRpc((uint)MiraRpc.AddModifier)] + /// The arguments to initialize the modifier constructor with. public static void RpcAddModifier(this PlayerControl target, uint modifierId, params object[] args) { var type = ModifierManager.GetModifierType(modifierId); @@ -114,8 +115,7 @@ public static class ModifierExtensions return; } - var modifier = ModifierFactory.CreateInstance(type, args); - target.GetModifierComponent()?.AddModifier(modifier); + Rpc.Instance.Send(target, new ModifierData(type, args)); } /// @@ -133,7 +133,6 @@ public static class ModifierExtensions return; } - var modifier = ModifierFactory.CreateInstance(args); - player.GetModifierComponent()!.AddModifier(modifier); + player.RpcAddModifier(id.Value, args); } } diff --git a/MiraAPI/Networking/AddModifierRpc.cs b/MiraAPI/Networking/AddModifierRpc.cs new file mode 100644 index 0000000..3546d4f --- /dev/null +++ b/MiraAPI/Networking/AddModifierRpc.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Hazel; +using MiraAPI.Modifiers; +using Reactor.Networking.Attributes; +using Reactor.Networking.Rpc; +using Reactor.Networking.Serialization; + +namespace MiraAPI.Networking; + +/// +/// 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) +{ + 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); + } + + /// + public override ModifierData Read(MessageReader reader) + { + var modId = reader.ReadPackedUInt32(); + var modifier = ModifierManager.GetModifierType(modId)!; + + if (!ParameterCache.TryGetValue(modifier, out var paramTypes)) + { + paramTypes = modifier.GetConstructors().OrderBy(x => x.GetParameters().Length).First().GetParameters(); + ParameterCache[modifier] = paramTypes; + } + + var objects = new object[paramTypes.Length]; + foreach (var paramType in paramTypes) + { + objects[paramType.Position] = reader.Deserialize(paramType.ParameterType); + } + + return new ModifierData(modifier, objects); + } + + /// + public override void Handle(PlayerControl innerNetObject, ModifierData data) + { + var modifier = ModifierFactory.CreateInstance(data.Type, data.Args); + innerNetObject.GetModifierComponent()?.AddModifier(modifier); + } +} From 8e2535ada25339845470623541afe08386bfc326 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sun, 2 Feb 2025 20:42:23 -0500 Subject: [PATCH 03/14] yes it works --- MiraAPI.Example/Buttons/TestButton.cs | 27 +++++++++++++++++++++ MiraAPI.Example/Modifiers/ModifierParams.cs | 16 ++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 MiraAPI.Example/Buttons/TestButton.cs create mode 100644 MiraAPI.Example/Modifiers/ModifierParams.cs diff --git a/MiraAPI.Example/Buttons/TestButton.cs b/MiraAPI.Example/Buttons/TestButton.cs new file mode 100644 index 0000000..8062c81 --- /dev/null +++ b/MiraAPI.Example/Buttons/TestButton.cs @@ -0,0 +1,27 @@ +using MiraAPI.Example.Modifiers; +using MiraAPI.Hud; +using MiraAPI.Modifiers; +using MiraAPI.Utilities.Assets; +using Reactor.Utilities.Extensions; +using UnityEngine; + +namespace MiraAPI.Example.Buttons; + +[RegisterButton] +public class TestButton : CustomActionButton +{ + public override string Name => "Test Button"; + public override float Cooldown => 0f; + public override LoadableAsset Sprite => ExampleAssets.ExampleButton; + + protected override void OnClick() + { + var randomPlayer = PlayerControl.AllPlayerControls.ToArray().Random(); + PlayerControl.LocalPlayer.RpcAddModifier("test", 1, randomPlayer); + } + + public override bool Enabled(RoleBehaviour? role) + { + return true; + } +} diff --git a/MiraAPI.Example/Modifiers/ModifierParams.cs b/MiraAPI.Example/Modifiers/ModifierParams.cs new file mode 100644 index 0000000..cde7d3b --- /dev/null +++ b/MiraAPI.Example/Modifiers/ModifierParams.cs @@ -0,0 +1,16 @@ +using MiraAPI.Modifiers; +using Reactor.Utilities; + +namespace MiraAPI.Example.Modifiers; + +[RegisterModifier] +public class ModifierParams : BaseModifier +{ + public override string ModifierName => "ModifierParams"; + + public ModifierParams(string param1, int param2, PlayerControl playerControl) + { + Logger.Error($"Param1: {param1}, Param2: {param2}"); + Logger.Error($"Player: {playerControl.PlayerId}"); + } +} From 0ebec6d0f1ba220950db3932ee93b3d7a1df42d0 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Sun, 23 Feb 2025 20:28:24 -0500 Subject: [PATCH 04/14] fix testing thing --- MiraAPI.Example/Buttons/TestButton.cs | 1 - MiraAPI.Example/Modifiers/ModifierParams.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/MiraAPI.Example/Buttons/TestButton.cs b/MiraAPI.Example/Buttons/TestButton.cs index 8062c81..feb7992 100644 --- a/MiraAPI.Example/Buttons/TestButton.cs +++ b/MiraAPI.Example/Buttons/TestButton.cs @@ -7,7 +7,6 @@ using UnityEngine; namespace MiraAPI.Example.Buttons; -[RegisterButton] public class TestButton : CustomActionButton { public override string Name => "Test Button"; diff --git a/MiraAPI.Example/Modifiers/ModifierParams.cs b/MiraAPI.Example/Modifiers/ModifierParams.cs index cde7d3b..e92bbda 100644 --- a/MiraAPI.Example/Modifiers/ModifierParams.cs +++ b/MiraAPI.Example/Modifiers/ModifierParams.cs @@ -3,7 +3,6 @@ using Reactor.Utilities; namespace MiraAPI.Example.Modifiers; -[RegisterModifier] public class ModifierParams : BaseModifier { public override string ModifierName => "ModifierParams"; 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 05/14] 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); - } -} From 9ae9094a85e2443e98725e8cff0603b2f114f88a Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Mon, 24 Feb 2025 21:52:06 -0500 Subject: [PATCH 06/14] oops fix that --- MiraAPI/Patches/VentPatches.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MiraAPI/Patches/VentPatches.cs b/MiraAPI/Patches/VentPatches.cs index 621abbc..8aa174b 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; From 4a8fb38380402e2745c080103bed37155de1aa15 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 25 Feb 2025 17:17:40 -0500 Subject: [PATCH 07/14] improvements to timed modifier --- MiraAPI/Modifiers/Types/TimedModifier.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MiraAPI/Modifiers/Types/TimedModifier.cs b/MiraAPI/Modifiers/Types/TimedModifier.cs index 038fa6d..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. From caa27642db35486f326ac6e81a8f2e4ab539faed Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 25 Feb 2025 17:22:00 -0500 Subject: [PATCH 08/14] further modifier improvements (massive changes) --- MiraAPI.Example/Buttons/TestButton.cs | 6 + .../Events/Vanilla/Usables/PlayerUseEvent.cs | 2 +- MiraAPI/Modifiers/BaseModifier.cs | 13 +- MiraAPI/Modifiers/ModifierComponent.cs | 155 +++++++++----- MiraAPI/Modifiers/ModifierExtensions.cs | 191 ++++++++++++------ MiraAPI/Modifiers/ModifierManager.cs | 24 +-- .../{ => Modifiers}/AddModifierRpc.cs | 2 +- .../{ => Modifiers}/SyncModifiersRpc.cs | 2 +- .../Patches/Events/KillAnimationPatches.cs | 2 +- MiraAPI/Patches/Events/UsePatches.cs | 2 +- .../Patches/Freeplay/TaskAddButtonPatches.cs | 2 +- MiraAPI/Patches/Freeplay/TaskAdderPatches.cs | 8 +- .../Patches/Modifiers/EndGameDidWinPatch.cs | 2 +- MiraAPI/Patches/VentPatches.cs | 4 +- 14 files changed, 276 insertions(+), 139 deletions(-) rename MiraAPI/Networking/{ => Modifiers}/AddModifierRpc.cs (97%) rename MiraAPI/Networking/{ => Modifiers}/SyncModifiersRpc.cs (97%) diff --git a/MiraAPI.Example/Buttons/TestButton.cs b/MiraAPI.Example/Buttons/TestButton.cs index feb7992..776dc40 100644 --- a/MiraAPI.Example/Buttons/TestButton.cs +++ b/MiraAPI.Example/Buttons/TestButton.cs @@ -15,6 +15,12 @@ public class TestButton : CustomActionButton protected override void OnClick() { + if (PlayerControl.LocalPlayer.HasModifier()) + { + PlayerControl.LocalPlayer.RpcRemoveModifier(); + return; + } + var randomPlayer = PlayerControl.AllPlayerControls.ToArray().Random(); PlayerControl.LocalPlayer.RpcAddModifier("test", 1, randomPlayer); } 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 8834454..21ce064 100644 --- a/MiraAPI/Modifiers/BaseModifier.cs +++ b/MiraAPI/Modifiers/BaseModifier.cs @@ -43,13 +43,14 @@ public abstract class BaseModifier public bool Initialized { get; internal set; } /// - /// Get the ID of the modifier. + /// Gets the unique ID of the modifier. /// - /// The ID of the modifier. - public uint GetId() - { - return ModifierManager.GetModifierId(GetType()) ?? throw new InvalidOperationException("Modifier is not registered."); - } + public uint UniqueId { get; internal set; } + + /// + /// Gets the type ID of the modifier. + /// + public uint TypeId => ModifierManager.GetModifierTypeId(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 6cd3d5e..94ab3db 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -37,6 +37,14 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) private readonly List _toAdd = []; + private uint _nextUniqueId; + + private uint GetNextUniqueId() + { + _nextUniqueId++; + return _nextUniqueId; + } + internal void ClearModifiers() { _toRemove.AddRange(Modifiers); @@ -104,14 +112,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()}"); } @@ -128,11 +134,11 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) /// /// The predicate to check the modifier by. /// The Type of the Modifier. - /// The Modifier if it is found, null otherwise. + /// A collection of modifiers. [HideFromIl2Cpp] - public IEnumerable GetModifiersByType(Func? predicate=null) where T : BaseModifier + public IEnumerable GetModifiers(Func? predicate=null) where T : BaseModifier { - return ActiveModifiers.OfType().Where(x => predicate == null || predicate(x)); + return Modifiers.OfType().Where(x => predicate == null || predicate(x)); } /// @@ -140,26 +146,26 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) /// /// The modifier type. /// The predicate to check the modifier by. - /// The Modifier if it is found, null otherwise. + /// A collection of modifiers. [HideFromIl2Cpp] - public IEnumerable GetModifiersByType(Type type, Func? predicate=null) + public IEnumerable GetModifiers(Type type, Func? predicate=null) { - return ActiveModifiers.Where(x => x.GetType() == type && (predicate == null || predicate(x))); + return Modifiers.Where(x => x.GetType() == type && (predicate == null || predicate(x))); } /// - /// Gets a collection of modifiers by ID. + /// Gets a collection of modifiers by type ID. /// - /// The Modifier ID. + /// The modifier's type ID. /// The predicate to check the modifier by. - /// The Modifier if it is found, null otherwise. + /// A collection of modifiers. [HideFromIl2Cpp] - public IEnumerable GetModifiersByType(uint id, Func? predicate=null) + 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 GetModifiersByType(type, predicate); + return GetModifiers(type, predicate); } /// @@ -191,23 +197,34 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) } /// - /// Tries to get a modifier by its ID. + /// Tries to get a modifier by its type ID. /// - /// The modifier 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) + public bool TryGetModifierByTypeId(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); + modifier = GetModifierByTypeId(id, predicate); + return modifier != null; } /// - /// Gets a modifier by its type, or null if the player doesn't have it. + /// 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 TryGetModifierByUniqueId(uint id, out BaseModifier? modifier) + { + modifier = GetModifierByUniqueId(id); + return modifier != null; + } + + /// + /// Gets a modifier by its type. /// /// The predicate to check the modifier by. /// The Type of the Modifier. @@ -215,11 +232,11 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) [HideFromIl2Cpp] public T? GetModifier(Func? predicate = null) where T : BaseModifier { - return GetModifiersByType(predicate).FirstOrDefault(); + return GetModifiers(predicate).FirstOrDefault(); } /// - /// Gets a modifier by its type, or null if the player doesn't have it. + /// Gets a modifier by its type. /// /// The modifier type. /// The predicate to check the modifier by. @@ -227,17 +244,17 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) [HideFromIl2Cpp] public BaseModifier? GetModifier(Type type, Func? predicate = null) { - return GetModifiersByType(type).FirstOrDefault(predicate ?? (_ => true)); + return GetModifiers(type).FirstOrDefault(predicate ?? (_ => true)); } /// - /// Gets a modifier by its type, or null if the player doesn't have it. + /// 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) + public BaseModifier? GetModifierByTypeId(uint id, Func? predicate = null) { var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException( $"Cannot get modifier with id {id} because it is not registered."); @@ -245,6 +262,17 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) 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? GetModifierByUniqueId(uint id) + { + return Modifiers.Find(x => x.UniqueId == id); + } + /// /// Removes a modifier from the player. /// @@ -280,19 +308,6 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) RemoveModifier(modifier); } - /// - /// Removes a modifier from the player. - /// - /// The modifier ID. - /// The predicate to check the modifier by. - [HideFromIl2Cpp] - public void RemoveModifier(uint modifierId, Func? predicate = null) - { - var type = ModifierManager.GetModifierType(modifierId) ?? throw new InvalidOperationException( - $"Cannot remove modifier with id {modifierId} because it is not registered."); - RemoveModifier(type, predicate); - } - /// /// Removes a modifier from the player. /// @@ -309,6 +324,36 @@ 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 RemoveModifierByTypeId(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 RemoveModifierByUniqueId(uint uniqueId) + { + var modifier = GetModifierByUniqueId(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. /// @@ -329,8 +374,8 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) [HideFromIl2Cpp] public BaseModifier? AddModifier(BaseModifier modifier) { - var id = modifier.GetId(); - if (modifier.Unique && Modifiers.Find(x => x.GetId() == id) != null) + 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; @@ -345,6 +390,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) _toAdd.Add(modifier); modifier.Player = _player; modifier.ModifierComponent = this; + modifier.UniqueId = GetNextUniqueId(); return modifier; } @@ -398,7 +444,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) [HideFromIl2Cpp] public bool HasModifier(Func? predicate=null) where T : BaseModifier { - return ActiveModifiers.Exists(x => x is T modifier && (predicate == null || predicate(modifier))); + return Modifiers.Exists(x => x is T modifier && (predicate == null || predicate(modifier))); } /// @@ -410,21 +456,32 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) [HideFromIl2Cpp] public bool HasModifier(Type type, Func? predicate=null) { - return ActiveModifiers.Exists(x => x.GetType() == type && (predicate == null || predicate(x))); + return Modifiers.Exists(x => x.GetType() == type && (predicate == null || predicate(x))); } /// - /// Checks if a player has an active modifier by its ID. + /// Checks if a player has an active modifier by its type ID /// - /// The Modifier ID. + /// The Modifier 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) + public bool HasModifierByTypeId(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 type ID. + /// True if the Modifier is present, false otherwise. + [HideFromIl2Cpp] + public bool HasModifierByUniqueId(uint id) + { + return Modifiers.Exists(x => x.UniqueId == id); + } } diff --git a/MiraAPI/Modifiers/ModifierExtensions.cs b/MiraAPI/Modifiers/ModifierExtensions.cs index 226b16b..5b42c6b 100644 --- a/MiraAPI/Modifiers/ModifierExtensions.cs +++ b/MiraAPI/Modifiers/ModifierExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using MiraAPI.Networking; +using MiraAPI.Networking.Modifiers; using Reactor.Networking.Attributes; using Reactor.Networking.Rpc; using Reactor.Utilities; @@ -21,14 +22,14 @@ public static class ModifierExtensions /// Remote Procedure Call to add a modifier to a player. /// /// The player to add the modifier to. - /// The modifier ID. + /// The modifier type ID. /// The arguments to initialize the modifier constructor with. - public static void RpcAddModifier(this PlayerControl target, uint modifierId, params object[] args) + public static void RpcAddModifier(this PlayerControl target, uint typeId, params object[] args) { - _ = ModifierManager.GetModifierType(modifierId) ?? throw new InvalidOperationException( - $"Modifier with ID {modifierId} is not registered."); + _ = ModifierManager.GetModifierType(typeId) ?? throw new InvalidOperationException( + $"Modifier with ID {typeId} is not registered."); - Rpc.Instance.Send(target, new ModifierData(modifierId, args)); + Rpc.Instance.Send(target, new ModifierData(typeId, args)); } /// @@ -39,7 +40,7 @@ public static class ModifierExtensions /// 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( + var id = ModifierManager.GetModifierTypeId(type) ?? throw new InvalidOperationException( $"Modifier {type.Name} is not registered."); player.RpcAddModifier(id, args); @@ -60,28 +61,61 @@ public static class ModifierExtensions /// Remote Procedure Call to remove a modifier from a player. /// /// The player to remove the modifier from. - /// The ID of the modifier. + /// The unique ID of the modifier. [MethodRpc((uint)MiraRpc.RemoveModifier)] - public static void RpcRemoveModifier(this PlayerControl target, uint modifierId) + public static void RpcRemoveModifierByUniqueId(this PlayerControl target, uint uniqueId) { - target.RemoveModifier(modifierId); + target.RemoveModifierByUniqueId(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 RpcRemoveModifierByTypeId( + this PlayerControl target, + uint typeId, + Func? predicate = null) + { + var modifier = target.GetModifierByTypeId(typeId, predicate); + if (modifier is null) + { + Logger.Error($"Player {target.PlayerId} does not have modifier with type ID {typeId}."); + return; + } + + target.RpcRemoveModifierByUniqueId(modifier.UniqueId); } /// /// 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 + /// 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.GetModifierId(typeof(T)); - if (id == null) - { - Logger.Error($"Cannot add modifier {typeof(T).Name} because it is not registered."); - return; - } + var id = ModifierManager.GetModifierTypeId(type) ?? throw new InvalidOperationException( + $"Modifier {type.Name} is not registered."); - player.RpcRemoveModifier(id.Value); + player.RpcRemoveModifierByTypeId(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); } /// @@ -89,7 +123,7 @@ public static class ModifierExtensions /// /// The PlayerControl object. /// A ModifierComponent if there is one, null otherwise. - public static ModifierComponent? GetModifierComponent(this PlayerControl player) + public static ModifierComponent GetModifierComponent(this PlayerControl player) { if (ModifierComponents.TryGetValue(player, out var component)) { @@ -97,9 +131,9 @@ public static class ModifierExtensions } component = player.GetComponent(); - if (component == null) + if (!component) { - return null; + throw new InvalidOperationException("ModifierComponent is not attached to the player."); } ModifierComponents[player] = component; @@ -115,7 +149,7 @@ public static class ModifierExtensions /// 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); + return player.GetModifierComponent().HasModifier(predicate); } /// @@ -127,19 +161,33 @@ public static class ModifierExtensions /// 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); + return player.GetModifierComponent().HasModifier(type, predicate); } /// - /// Checks if the player has a specific modifier by ID. + /// Checks if the player has a specific modifier by type ID. /// /// The PlayerControl instance. - /// The ID of the modifier. + /// 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 id, Func? predicate = null) + public static bool HasModifierByTypeId( + this PlayerControl player, + uint id, + Func? predicate = null) { - return player.GetModifierComponent()!.HasModifier(id, predicate); + return player.GetModifierComponent().HasModifierByTypeId(id, predicate); + } + + /// + /// Checks if the player has a specific modifier by unique ID. + /// + /// The PlayerControl instance. + /// The unique ID of the modifier. + /// True if the player has the modifier, false otherwise. + public static bool HasModifierByUniqueId(this PlayerControl player, uint id) + { + return player.GetModifierComponent().HasModifierByUniqueId(id); } /// @@ -151,7 +199,7 @@ public static class ModifierExtensions /// The modifier if found, null otherwise. public static T? GetModifier(this PlayerControl player, Func? predicate = null) where T : BaseModifier { - return player.GetModifierComponent()!.GetModifier(predicate); + return player.GetModifierComponent().GetModifier(predicate); } /// @@ -166,7 +214,22 @@ public static class ModifierExtensions Type type, Func? predicate = null) { - return player.GetModifierComponent()!.GetModifier(type, predicate); + return player.GetModifierComponent().GetModifier(type, predicate); + } + + /// + /// Gets a specific modifier from the player by 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? GetModifierByTypeId( + this PlayerControl player, + uint id, + Func? predicate = null) + { + return player.GetModifierComponent().GetModifierByTypeId(id, predicate); } /// @@ -174,14 +237,12 @@ public static class ModifierExtensions /// /// The PlayerControl instance. /// The ID of the modifier. - /// Optional predicate to filter the modifiers. /// The modifier if found, null otherwise. - public static BaseModifier? GetModifier( + public static BaseModifier? GetModifierByUniqueId( this PlayerControl player, - uint id, - Func? predicate = null) + uint id) { - return player.GetModifierComponent()!.GetModifier(id, predicate); + return player.GetModifierComponent().GetModifierByUniqueId(id); } /// @@ -191,10 +252,10 @@ public static class ModifierExtensions /// The PlayerControl instance. /// Optional predicate to filter the modifiers. /// An enumerable of modifiers. - public static IEnumerable GetModifiersByType(this PlayerControl player, Func? predicate = null) + public static IEnumerable GetModifiers(this PlayerControl player, Func? predicate = null) where T : BaseModifier { - return player.GetModifierComponent()!.GetModifiersByType(predicate); + return player.GetModifierComponent().GetModifiers(predicate); } /// @@ -204,27 +265,37 @@ public static class ModifierExtensions /// The type of the modifiers. /// Optional predicate to filter the modifiers. /// An enumerable of modifiers. - public static IEnumerable GetModifiersByType( + public static IEnumerable GetModifiers( this PlayerControl player, Type type, Func? predicate = null) { - return player.GetModifierComponent()!.GetModifiersByType(type, predicate); + return player.GetModifierComponent().GetModifiers(type, predicate); } /// - /// Gets all modifiers of a specific type from the player by ID. + /// Gets all modifiers of a specific type from the player by type ID. /// /// The PlayerControl instance. - /// The ID of the modifiers. + /// The type ID of the modifiers. /// Optional predicate to filter the modifiers. /// An enumerable of modifiers. - public static IEnumerable GetModifiersByType( + public static IEnumerable GetModifiers( this PlayerControl player, uint id, Func? predicate = null) { - return player.GetModifierComponent()!.GetModifiersByType(id, predicate); + return player.GetModifierComponent().GetModifiers(id, 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); } /// @@ -236,7 +307,7 @@ public static class ModifierExtensions public static void RemoveModifier(this PlayerControl player, Func? predicate = null) where T : BaseModifier { - player.GetModifierComponent()!.RemoveModifier(predicate); + player.GetModifierComponent().RemoveModifier(predicate); } /// @@ -247,31 +318,33 @@ public static class ModifierExtensions /// Optional predicate to filter the modifiers. public static void RemoveModifier(this PlayerControl player, Type type, Func? predicate = null) { - player.GetModifierComponent()!.RemoveModifier(type, predicate); + player.GetModifierComponent().RemoveModifier(type, predicate); } /// /// Removes a specific modifier from the player by ID. /// /// The PlayerControl instance. - /// The ID of the modifier. + /// The ID of the modifier. /// Optional predicate to filter the modifiers. - public static void RemoveModifier( + public static void RemoveModifierByTypeId( this PlayerControl player, - uint modifierId, + uint typeId, Func? predicate = null) { - player.GetModifierComponent()!.RemoveModifier(modifierId, predicate); + player.GetModifierComponent().RemoveModifierByTypeId(typeId, predicate); } /// - /// Removes a specific modifier from the player. + /// Removes a specific modifier from the player by ID. /// /// The PlayerControl instance. - /// The modifier to remove. - public static void RemoveModifier(this PlayerControl player, BaseModifier modifier) + /// The ID of the modifier. + public static void RemoveModifierByUniqueId( + this PlayerControl player, + uint uniqueId) { - player.GetModifierComponent()!.RemoveModifier(modifier); + player.GetModifierComponent().RemoveModifierByUniqueId(uniqueId); } /// @@ -283,7 +356,7 @@ public static class ModifierExtensions /// The added modifier. public static T? AddModifier(this PlayerControl player, params object[] args) where T : BaseModifier { - return player.GetModifierComponent()!.AddModifier(args); + return player.GetModifierComponent().AddModifier(args); } /// @@ -294,7 +367,7 @@ public static class ModifierExtensions /// The added modifier. public static BaseModifier? AddModifier(this PlayerControl player, BaseModifier modifier) { - return player.GetModifierComponent()!.AddModifier(modifier); + return player.GetModifierComponent().AddModifier(modifier); } /// @@ -306,18 +379,18 @@ public static class ModifierExtensions /// The added modifier. public static BaseModifier? AddModifier(this PlayerControl player, Type type, params object[] args) { - return player.GetModifierComponent()!.AddModifier(type, args); + return player.GetModifierComponent().AddModifier(type, args); } /// - /// Adds a specific modifier to the player by ID. + /// Adds a specific modifier to the player by type ID. /// /// The PlayerControl instance. - /// The ID of the modifier. + /// 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 id, params object[] args) + public static BaseModifier? AddModifier(this PlayerControl player, uint typeId, params object[] args) { - return player.GetModifierComponent()!.AddModifier(id, args); + return player.GetModifierComponent().AddModifier(typeId, args); } } diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs index 65341a0..209423e 100644 --- a/MiraAPI/Modifiers/ModifierManager.cs +++ b/MiraAPI/Modifiers/ModifierManager.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Runtime.Serialization; using MiraAPI.Modifiers.Types; using MiraAPI.Networking; +using MiraAPI.Networking.Modifiers; using MiraAPI.PluginLoading; using MiraAPI.Roles; using MiraAPI.Utilities; @@ -23,16 +24,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,7 +47,7 @@ public static class ModifierManager /// /// The Type. /// The ID of the modifier. - public static uint? GetModifierId(Type type) + public static uint? GetModifierTypeId(Type type) { if (!TypeToIdModifierMap.TryGetValue(type, out var id)) { @@ -63,8 +64,8 @@ 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 info.Modifiers.Add(bm); @@ -81,7 +82,7 @@ public static class ModifierManager PrioritiesToIdsMap[priority] = list = []; } - list.Add(_nextId); + list.Add(_nextTypeId); return true; } @@ -143,7 +144,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"); } @@ -158,8 +159,7 @@ 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); + modifier.IsModifierValidOn(player.Data.Role) && !player.HasModifierByTypeId(modifierId); } internal static void SyncAllModifiers(int targetId = -1) diff --git a/MiraAPI/Networking/AddModifierRpc.cs b/MiraAPI/Networking/Modifiers/AddModifierRpc.cs similarity index 97% rename from MiraAPI/Networking/AddModifierRpc.cs rename to MiraAPI/Networking/Modifiers/AddModifierRpc.cs index fdc37a9..5727ba5 100644 --- a/MiraAPI/Networking/AddModifierRpc.cs +++ b/MiraAPI/Networking/Modifiers/AddModifierRpc.cs @@ -5,7 +5,7 @@ using Reactor.Networking.Attributes; using Reactor.Networking.Rpc; using Reactor.Networking.Serialization; -namespace MiraAPI.Networking; +namespace MiraAPI.Networking.Modifiers; /// /// Remote procedure call for adding a modifier. diff --git a/MiraAPI/Networking/SyncModifiersRpc.cs b/MiraAPI/Networking/Modifiers/SyncModifiersRpc.cs similarity index 97% rename from MiraAPI/Networking/SyncModifiersRpc.cs rename to MiraAPI/Networking/Modifiers/SyncModifiersRpc.cs index 288e2c1..0571198 100644 --- a/MiraAPI/Networking/SyncModifiersRpc.cs +++ b/MiraAPI/Networking/Modifiers/SyncModifiersRpc.cs @@ -3,7 +3,7 @@ using MiraAPI.Modifiers; using Reactor.Networking.Attributes; using Reactor.Networking.Rpc; -namespace MiraAPI.Networking; +namespace MiraAPI.Networking.Modifiers; [RegisterCustomRpc((uint)MiraRpc.SyncModifiers)] internal sealed class SyncModifiersRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc(plugin, id) 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 184bafb..0ee6da4 100644 --- a/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs +++ b/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs @@ -16,7 +16,7 @@ internal static class TaskAddButtonPatches if (uint.TryParse(__instance.name, out var result)) { __instance.Overlay.sprite = __instance.CheckImage; - __instance.Overlay.enabled = PlayerControl.LocalPlayer.HasModifier(result); + __instance.Overlay.enabled = PlayerControl.LocalPlayer.HasModifierByTypeId(result); return false; } diff --git a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs index b634c91..0c35670 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.GetId().ToString(NumberFormatInfo.InvariantInfo); + taskAddButton.name = modifier.TypeId.ToString(NumberFormatInfo.InvariantInfo); taskAddButton.role = null; taskAddButton.MyTask = null; taskAddButton.SafePositionWorld = instance.SafePositionWorld; @@ -336,10 +336,10 @@ internal static class TaskAdderPatches taskAddButton.Button.OnClick = new Button.ButtonClickedEvent(); taskAddButton.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { - var id = modifier.GetId(); - if (PlayerControl.LocalPlayer.HasModifier(id)) + var id = modifier.TypeId; + if (PlayerControl.LocalPlayer.HasModifierByTypeId(id)) { - PlayerControl.LocalPlayer.RemoveModifier(id); + PlayerControl.LocalPlayer.RemoveModifierByTypeId(id); taskAddButton.Overlay.enabled = false; } else diff --git a/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs b/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs index 17356db..a86383d 100644 --- a/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs +++ b/MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs @@ -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/VentPatches.cs b/MiraAPI/Patches/VentPatches.cs index 8aa174b..2f8991d 100644 --- a/MiraAPI/Patches/VentPatches.cs +++ b/MiraAPI/Patches/VentPatches.cs @@ -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) { From 7e4a71739c0ba89fdf2312e89c668d4751052619 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 25 Feb 2025 21:57:38 -0500 Subject: [PATCH 09/14] better modifier unique IDs --- MiraAPI/Modifiers/BaseModifier.cs | 2 +- MiraAPI/Modifiers/ModifierComponent.cs | 53 ++++++------ MiraAPI/Modifiers/ModifierData.cs | 19 ----- MiraAPI/Modifiers/ModifierExtensions.cs | 80 +++++++++---------- MiraAPI/Modifiers/ModifierManager.cs | 69 +--------------- MiraAPI/Networking/GuidConverter.cs | 32 ++++++++ .../Networking/Modifiers/AddModifierRpc.cs | 23 +++++- MiraAPI/Networking/Modifiers/ModifierData.cs | 11 +++ .../Networking/Modifiers/SyncModifiersRpc.cs | 52 ------------ MiraAPI/Patches/AmongUsClientSyncPatch.cs | 6 +- .../Patches/Freeplay/TaskAddButtonPatches.cs | 2 +- MiraAPI/Patches/Freeplay/TaskAdderPatches.cs | 4 +- 12 files changed, 134 insertions(+), 219 deletions(-) delete mode 100644 MiraAPI/Modifiers/ModifierData.cs create mode 100644 MiraAPI/Networking/GuidConverter.cs create mode 100644 MiraAPI/Networking/Modifiers/ModifierData.cs delete mode 100644 MiraAPI/Networking/Modifiers/SyncModifiersRpc.cs diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs index 21ce064..52a2bb0 100644 --- a/MiraAPI/Modifiers/BaseModifier.cs +++ b/MiraAPI/Modifiers/BaseModifier.cs @@ -45,7 +45,7 @@ public abstract class BaseModifier /// /// Gets the unique ID of the modifier. /// - public uint UniqueId { get; internal set; } + public Guid UniqueId { get; internal set; } = Guid.Empty; /// /// Gets the type ID of the modifier. diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index 94ab3db..57abd8c 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -37,14 +37,6 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) private readonly List _toAdd = []; - private uint _nextUniqueId; - - private uint GetNextUniqueId() - { - _nextUniqueId++; - return _nextUniqueId; - } - internal void ClearModifiers() { _toRemove.AddRange(Modifiers); @@ -204,22 +196,22 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) /// The predicate to check the modifier by. /// True if the modifier was found, false otherwise. [HideFromIl2Cpp] - public bool TryGetModifierByTypeId(uint id, out BaseModifier? modifier, Func? predicate = null) + public bool TryGetModifier(uint id, out BaseModifier? modifier, Func? predicate = null) { - modifier = GetModifierByTypeId(id, predicate); + modifier = GetModifier(id, predicate); return modifier != null; } /// /// Tries to get a modifier by its unique ID. /// - /// The modifier unique ID. + /// The modifier unique ID. /// The modifier or null. /// True if the modifier was found, false otherwise. [HideFromIl2Cpp] - public bool TryGetModifierByUniqueId(uint id, out BaseModifier? modifier) + public bool TryGetModifier(Guid modifierGuid, out BaseModifier? modifier) { - modifier = GetModifierByUniqueId(id); + modifier = GetModifier(modifierGuid); return modifier != null; } @@ -254,7 +246,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) /// The predicate to check the modifier by. /// The Modifier if it is found, null otherwise. [HideFromIl2Cpp] - public BaseModifier? GetModifierByTypeId(uint id, Func? predicate = null) + 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."); @@ -265,12 +257,12 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) /// /// Gets a modifier by unique ID. /// - /// The modifier's unique ID. + /// The modifier's unique ID. /// The modifier if it is found, or null. [HideFromIl2Cpp] - public BaseModifier? GetModifierByUniqueId(uint id) + public BaseModifier? GetModifier(Guid modifierGuid) { - return Modifiers.Find(x => x.UniqueId == id); + return Modifiers.Find(x => x.UniqueId == modifierGuid); } /// @@ -330,7 +322,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) /// The modifier's type ID. /// The predicate to check the modifier by. [HideFromIl2Cpp] - public void RemoveModifierByTypeId(uint typeId, Func? predicate = null) + 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."); @@ -342,9 +334,9 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) /// /// The modifier's unique ID. [HideFromIl2Cpp] - public void RemoveModifierByUniqueId(uint uniqueId) + public void RemoveModifier(Guid uniqueId) { - var modifier = GetModifierByUniqueId(uniqueId); + var modifier = GetModifier(uniqueId); if (modifier == null) { Logger.Error($"Cannot remove modifier with unique id {uniqueId} because it is not active."); @@ -390,7 +382,10 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) _toAdd.Add(modifier); modifier.Player = _player; modifier.ModifierComponent = this; - modifier.UniqueId = GetNextUniqueId(); + if (modifier.UniqueId == Guid.Empty) + { + modifier.UniqueId = Guid.NewGuid(); + } return modifier; } @@ -460,13 +455,13 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) } /// - /// Checks if a player has an active modifier by its type ID + /// Checks if a player has an active modifier by its type ID. /// - /// The Modifier type ID. + /// The modifier's type ID. /// The predicate to check the modifier. - /// True if the Modifier is present, false otherwise. + /// True if the modifier is present, false otherwise. [HideFromIl2Cpp] - public bool HasModifierByTypeId(uint id, Func? predicate=null) + 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."); @@ -475,12 +470,12 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) } /// - /// Checks if a player has an active modifier by its unique ID + /// Checks if a player has an active modifier by its unique ID. /// - /// The Modifier type ID. - /// True if the Modifier is present, false otherwise. + /// The modifier's guid. + /// True if the modifier is present, false otherwise. [HideFromIl2Cpp] - public bool HasModifierByUniqueId(uint id) + public bool HasModifier(Guid id) { return Modifiers.Exists(x => x.UniqueId == id); } diff --git a/MiraAPI/Modifiers/ModifierData.cs b/MiraAPI/Modifiers/ModifierData.cs deleted file mode 100644 index 7260834..0000000 --- a/MiraAPI/Modifiers/ModifierData.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace MiraAPI.Modifiers; - -/// -/// Modifier data for networking. -/// -/// The ID of the modifier. -/// Parameters for constructor. -public readonly struct ModifierData(uint id, object[] args) -{ - /// - /// Gets the type of the modifier. - /// - public uint Id { get; } = id; - - /// - /// Gets the parameters for the constructor. - /// - public object[] Args { get; } = args; -} diff --git a/MiraAPI/Modifiers/ModifierExtensions.cs b/MiraAPI/Modifiers/ModifierExtensions.cs index 5b42c6b..5d05d69 100644 --- a/MiraAPI/Modifiers/ModifierExtensions.cs +++ b/MiraAPI/Modifiers/ModifierExtensions.cs @@ -29,7 +29,7 @@ public static class ModifierExtensions _ = ModifierManager.GetModifierType(typeId) ?? throw new InvalidOperationException( $"Modifier with ID {typeId} is not registered."); - Rpc.Instance.Send(target, new ModifierData(typeId, args)); + Rpc.Instance.Send(target, new ModifierData(typeId, Guid.NewGuid(), args)); } /// @@ -63,9 +63,9 @@ public static class ModifierExtensions /// The player to remove the modifier from. /// The unique ID of the modifier. [MethodRpc((uint)MiraRpc.RemoveModifier)] - public static void RpcRemoveModifierByUniqueId(this PlayerControl target, uint uniqueId) + public static void RpcRemoveModifier(this PlayerControl target, Guid uniqueId) { - target.RemoveModifierByUniqueId(uniqueId); + target.RemoveModifier(uniqueId); } /// @@ -74,19 +74,19 @@ public static class ModifierExtensions /// The player to remove the modifier from. /// The type ID of the modifier. /// Optional predicate to filter the modifiers. - public static void RpcRemoveModifierByTypeId( + public static void RpcRemoveModifier( this PlayerControl target, uint typeId, Func? predicate = null) { - var modifier = target.GetModifierByTypeId(typeId, predicate); + 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.RpcRemoveModifierByUniqueId(modifier.UniqueId); + target.RpcRemoveModifier(modifier.UniqueId); } /// @@ -103,7 +103,7 @@ public static class ModifierExtensions var id = ModifierManager.GetModifierTypeId(type) ?? throw new InvalidOperationException( $"Modifier {type.Name} is not registered."); - player.RpcRemoveModifierByTypeId(id, predicate); + player.RpcRemoveModifier(id, predicate); } /// @@ -168,26 +168,26 @@ public static class ModifierExtensions /// Checks if the player has a specific modifier by type ID. /// /// The PlayerControl instance. - /// The type ID of the modifier. + /// The type ID of the modifier. /// Optional predicate to filter the modifiers. /// True if the player has the modifier, false otherwise. - public static bool HasModifierByTypeId( + public static bool HasModifier( this PlayerControl player, - uint id, + uint typeId, Func? predicate = null) { - return player.GetModifierComponent().HasModifierByTypeId(id, predicate); + return player.GetModifierComponent().HasModifier(typeId, predicate); } /// - /// Checks if the player has a specific modifier by unique ID. + /// Checks if the player has a specific modifier by its GUID. /// /// The PlayerControl instance. - /// The unique ID of the modifier. + /// The unique ID of the modifier. /// True if the player has the modifier, false otherwise. - public static bool HasModifierByUniqueId(this PlayerControl player, uint id) + public static bool HasModifier(this PlayerControl player, Guid uniqueId) { - return player.GetModifierComponent().HasModifierByUniqueId(id); + return player.GetModifierComponent().HasModifier(uniqueId); } /// @@ -218,31 +218,31 @@ public static class ModifierExtensions } /// - /// Gets a specific modifier from the player by type ID. + /// Gets a specific modifier from the player by its type ID. /// /// The PlayerControl instance. - /// The type ID of the modifier. + /// The type ID of the modifier. /// Optional predicate to filter the modifiers. /// The modifier if found, null otherwise. - public static BaseModifier? GetModifierByTypeId( + public static BaseModifier? GetModifier( this PlayerControl player, - uint id, + uint typeId, Func? predicate = null) { - return player.GetModifierComponent().GetModifierByTypeId(id, predicate); + return player.GetModifierComponent().GetModifier(typeId, predicate); } /// - /// Gets a specific modifier from the player by ID. + /// Gets a specific modifier from the player by its GUID. /// /// The PlayerControl instance. - /// The ID of the modifier. + /// The GUID of the modifier. /// The modifier if found, null otherwise. - public static BaseModifier? GetModifierByUniqueId( + public static BaseModifier? GetModifier( this PlayerControl player, - uint id) + Guid uniqueId) { - return player.GetModifierComponent().GetModifierByUniqueId(id); + return player.GetModifierComponent().GetModifier(uniqueId); } /// @@ -251,7 +251,7 @@ public static class ModifierExtensions /// The type of the modifiers. /// The PlayerControl instance. /// Optional predicate to filter the modifiers. - /// An enumerable of modifiers. + /// A collection of modifiers. public static IEnumerable GetModifiers(this PlayerControl player, Func? predicate = null) where T : BaseModifier { @@ -264,7 +264,7 @@ public static class ModifierExtensions /// The PlayerControl instance. /// The type of the modifiers. /// Optional predicate to filter the modifiers. - /// An enumerable of modifiers. + /// A collection of modifiers. public static IEnumerable GetModifiers( this PlayerControl player, Type type, @@ -277,15 +277,15 @@ public static class ModifierExtensions /// Gets all modifiers of a specific type from the player by type ID. /// /// The PlayerControl instance. - /// The type ID of the modifiers. + /// The type ID of the modifiers. /// Optional predicate to filter the modifiers. - /// An enumerable of modifiers. + /// A collection of modifiers. public static IEnumerable GetModifiers( this PlayerControl player, - uint id, + uint typeId, Func? predicate = null) { - return player.GetModifierComponent().GetModifiers(id, predicate); + return player.GetModifierComponent().GetModifiers(typeId, predicate); } /// @@ -322,29 +322,29 @@ public static class ModifierExtensions } /// - /// Removes a specific modifier from the player by ID. + /// Removes a specific modifier from the player by its type ID. /// /// The PlayerControl instance. - /// The ID of the modifier. + /// The type ID of the modifier. /// Optional predicate to filter the modifiers. - public static void RemoveModifierByTypeId( + public static void RemoveModifier( this PlayerControl player, uint typeId, Func? predicate = null) { - player.GetModifierComponent().RemoveModifierByTypeId(typeId, predicate); + player.GetModifierComponent().RemoveModifier(typeId, predicate); } /// - /// Removes a specific modifier from the player by ID. + /// Removes a specific modifier from the player by its GUID. /// /// The PlayerControl instance. - /// The ID of the modifier. - public static void RemoveModifierByUniqueId( + /// The GUID of the modifier. + public static void RemoveModifier( this PlayerControl player, - uint uniqueId) + Guid uniqueId) { - player.GetModifierComponent().RemoveModifierByUniqueId(uniqueId); + player.GetModifierComponent().RemoveModifier(uniqueId); } /// diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs index 209423e..4285a73 100644 --- a/MiraAPI/Modifiers/ModifierManager.cs +++ b/MiraAPI/Modifiers/ModifierManager.cs @@ -3,12 +3,9 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using MiraAPI.Modifiers.Types; -using MiraAPI.Networking; -using MiraAPI.Networking.Modifiers; using MiraAPI.PluginLoading; using MiraAPI.Roles; using MiraAPI.Utilities; -using Reactor.Networking.Rpc; using Reactor.Utilities; using Reactor.Utilities.Extensions; using Random = System.Random; @@ -159,70 +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.HasModifierByTypeId(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/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 index 5727ba5..2e7b19e 100644 --- a/MiraAPI/Networking/Modifiers/AddModifierRpc.cs +++ b/MiraAPI/Networking/Modifiers/AddModifierRpc.cs @@ -21,7 +21,8 @@ public class AddModifierRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc public override void Write(MessageWriter writer, ModifierData data) { - writer.WritePacked(data.Id); + writer.WritePacked(data.TypeId); + writer.WriteBytesAndSize(data.UniqueId.ToByteArray()); writer.WritePacked(data.Args.Length); foreach (var arg in data.Args) { @@ -34,6 +35,7 @@ public class AddModifierRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc public override void Handle(PlayerControl player, ModifierData data) { - player.AddModifier(data.Id, data.Args); + 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/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/Modifiers/SyncModifiersRpc.cs b/MiraAPI/Networking/Modifiers/SyncModifiersRpc.cs deleted file mode 100644 index 0571198..0000000 --- a/MiraAPI/Networking/Modifiers/SyncModifiersRpc.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Hazel; -using MiraAPI.Modifiers; -using Reactor.Networking.Attributes; -using Reactor.Networking.Rpc; - -namespace MiraAPI.Networking.Modifiers; - -[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/Freeplay/TaskAddButtonPatches.cs b/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs index 0ee6da4..184bafb 100644 --- a/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs +++ b/MiraAPI/Patches/Freeplay/TaskAddButtonPatches.cs @@ -16,7 +16,7 @@ internal static class TaskAddButtonPatches if (uint.TryParse(__instance.name, out var result)) { __instance.Overlay.sprite = __instance.CheckImage; - __instance.Overlay.enabled = PlayerControl.LocalPlayer.HasModifierByTypeId(result); + __instance.Overlay.enabled = PlayerControl.LocalPlayer.HasModifier(result); return false; } diff --git a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs index 0c35670..666a1ea 100644 --- a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs +++ b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs @@ -337,9 +337,9 @@ internal static class TaskAdderPatches taskAddButton.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { var id = modifier.TypeId; - if (PlayerControl.LocalPlayer.HasModifierByTypeId(id)) + if (PlayerControl.LocalPlayer.HasModifier(id)) { - PlayerControl.LocalPlayer.RemoveModifierByTypeId(id); + PlayerControl.LocalPlayer.RemoveModifier(id); taskAddButton.Overlay.enabled = false; } else From 0e2eeea0691b9f89ff7ea41c600c8a5f310ffa1f Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 25 Feb 2025 22:01:52 -0500 Subject: [PATCH 10/14] add check to prevent adding modifiers before a game has started --- MiraAPI/Modifiers/ModifierComponent.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index 57abd8c..43f3a94 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -366,6 +366,12 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) [HideFromIl2Cpp] public BaseModifier? AddModifier(BaseModifier modifier) { + if (!AmongUsClient.Instance.IsGameStarted) + { + Logger.Error("Cannot add modifier because the game has not started yet."); + return null; + } + var id = modifier.TypeId; if (modifier.Unique && Modifiers.Find(x => x.TypeId == id) != null) { From 81834a7f1aeebde6e255001a2f081d620c7f1429 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Wed, 26 Feb 2025 15:38:25 -0500 Subject: [PATCH 11/14] allow modifier in lobby with error --- MiraAPI/Modifiers/ModifierComponent.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index 43f3a94..57850b4 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -366,10 +366,9 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) [HideFromIl2Cpp] public BaseModifier? AddModifier(BaseModifier modifier) { - if (!AmongUsClient.Instance.IsGameStarted) + if (LobbyBehaviour.Instance) { - Logger.Error("Cannot add modifier because the game has not started yet."); - return null; + Logger.Warning($"Modifiers added in the lobby won't sync to new players!"); } var id = modifier.TypeId; From a619a975d7382681ba09978417946e7a43050dd7 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Wed, 26 Feb 2025 17:24:05 -0500 Subject: [PATCH 12/14] fix modifier factory issue --- MiraAPI/Modifiers/ModifierFactory.cs | 38 ++++++++---------- MiraAPI/Utilities/Extensions.cs | 59 ++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 21 deletions(-) diff --git a/MiraAPI/Modifiers/ModifierFactory.cs b/MiraAPI/Modifiers/ModifierFactory.cs index d145b82..8f8d005 100644 --- a/MiraAPI/Modifiers/ModifierFactory.cs +++ b/MiraAPI/Modifiers/ModifierFactory.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; +using MiraAPI.Utilities; namespace MiraAPI.Modifiers; @@ -8,19 +11,11 @@ namespace MiraAPI.Modifiers; /// public static class ModifierFactory { - private static Func? _constructor; + private static readonly Dictionary<(Type, Type[]), Func> _constructorCache = []; private static Func CreateConstructor(Type type, params object[] args) { - 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( + var constructorInfo = type.GetBestConstructor(args) ?? throw new InvalidOperationException( $"Could not find a constructor for type {type} with the specified arguments."); var parameters = constructorInfo.GetParameters(); @@ -50,8 +45,16 @@ public static class ModifierFactory /// An instance of the modifier. public static BaseModifier CreateInstance(Type type, params object[] args) { - _constructor ??= CreateConstructor(type, args); - return _constructor(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); } } @@ -65,15 +68,8 @@ public static class ModifierFactory where T : BaseModifier private static Func CreateConstructor(params object[] args) { - 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 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(); diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 8d95294..1faeb94 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -6,6 +6,7 @@ using Reactor.Utilities; using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using TMPro; using UnityEngine; @@ -38,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. /// From 6443b5932b0fc8936c1e39079eb8883b307ce2b9 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Wed, 26 Feb 2025 17:24:41 -0500 Subject: [PATCH 13/14] modifier converter + cleanup --- MiraAPI.Example/Buttons/TestButton.cs | 32 --------------- MiraAPI.Example/Modifiers/ModifierParams.cs | 15 ------- MiraAPI/Modifiers/ModifierComponent.cs | 1 + .../Networking/Modifiers/ModifierConverter.cs | 40 +++++++++++++++++++ 4 files changed, 41 insertions(+), 47 deletions(-) delete mode 100644 MiraAPI.Example/Buttons/TestButton.cs delete mode 100644 MiraAPI.Example/Modifiers/ModifierParams.cs create mode 100644 MiraAPI/Networking/Modifiers/ModifierConverter.cs diff --git a/MiraAPI.Example/Buttons/TestButton.cs b/MiraAPI.Example/Buttons/TestButton.cs deleted file mode 100644 index 776dc40..0000000 --- a/MiraAPI.Example/Buttons/TestButton.cs +++ /dev/null @@ -1,32 +0,0 @@ -using MiraAPI.Example.Modifiers; -using MiraAPI.Hud; -using MiraAPI.Modifiers; -using MiraAPI.Utilities.Assets; -using Reactor.Utilities.Extensions; -using UnityEngine; - -namespace MiraAPI.Example.Buttons; - -public class TestButton : CustomActionButton -{ - public override string Name => "Test Button"; - public override float Cooldown => 0f; - public override LoadableAsset Sprite => ExampleAssets.ExampleButton; - - protected override void OnClick() - { - if (PlayerControl.LocalPlayer.HasModifier()) - { - PlayerControl.LocalPlayer.RpcRemoveModifier(); - return; - } - - var randomPlayer = PlayerControl.AllPlayerControls.ToArray().Random(); - PlayerControl.LocalPlayer.RpcAddModifier("test", 1, randomPlayer); - } - - public override bool Enabled(RoleBehaviour? role) - { - return true; - } -} diff --git a/MiraAPI.Example/Modifiers/ModifierParams.cs b/MiraAPI.Example/Modifiers/ModifierParams.cs deleted file mode 100644 index e92bbda..0000000 --- a/MiraAPI.Example/Modifiers/ModifierParams.cs +++ /dev/null @@ -1,15 +0,0 @@ -using MiraAPI.Modifiers; -using Reactor.Utilities; - -namespace MiraAPI.Example.Modifiers; - -public class ModifierParams : BaseModifier -{ - public override string ModifierName => "ModifierParams"; - - public ModifierParams(string param1, int param2, PlayerControl playerControl) - { - Logger.Error($"Param1: {param1}, Param2: {param2}"); - Logger.Error($"Player: {playerControl.PlayerId}"); - } -} diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index 57850b4..e7c6991 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -366,6 +366,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) [HideFromIl2Cpp] public BaseModifier? AddModifier(BaseModifier modifier) { + // TODO: Make a proper synchronization system. if (LobbyBehaviour.Instance) { Logger.Warning($"Modifiers added in the lobby won't sync to new players!"); 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}."); + } +} From 26b94b01278ac58f1adc1d26689d495adc0a19b9 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Wed, 26 Feb 2025 17:29:46 -0500 Subject: [PATCH 14/14] fix ordering of properties in BaseModifier.cs --- MiraAPI/Modifiers/BaseModifier.cs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs index 52a2bb0..473c78f 100644 --- a/MiraAPI/Modifiers/BaseModifier.cs +++ b/MiraAPI/Modifiers/BaseModifier.cs @@ -17,6 +17,21 @@ public abstract class BaseModifier /// public ModifierComponent? ModifierComponent { get; internal set; } + /// + /// Gets a value indicating whether the modifier has been initialized. + /// + 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. /// @@ -37,21 +52,6 @@ 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; } - - /// - /// 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 HUD information for this modifier. Defaults to the modifier name. Does nothing if is true. ///