major modifier overhaul

This commit is contained in:
XtraCube committed 2025-02-24 21:51:02 -05:00
1 parent 0ebec6d0f1
commit 1c6fa9a84d
13 files changed
+531 -447

No files matched your search

+17 -6
View File
@@ -1,4 +1,6 @@
namespace MiraAPI.Modifiers;
using System;
namespace MiraAPI.Modifiers;
/// <summary>
/// Base class for all modifiers.
@@ -15,11 +17,6 @@ public abstract class BaseModifier
/// </summary>
public ModifierComponent? ModifierComponent { get; internal set; }
/// <summary>
/// Gets the modifier id.
/// </summary>
public uint ModifierId { get; internal set; }
/// <summary>
/// Gets the modifier name.
/// </summary>
@@ -40,6 +37,20 @@ public abstract class BaseModifier
/// </summary>
public virtual bool Unique => true;
/// <summary>
/// Gets a value indicating whether the modifier has been initialized.
/// </summary>
public bool Initialized { get; internal set; }
/// <summary>
/// Get the ID of the modifier.
/// </summary>
/// <returns>The ID of the modifier.</returns>
public uint GetId()
{
return ModifierManager.GetModifierId(GetType()) ?? throw new InvalidOperationException("Modifier is not registered.");
}
/// <summary>
/// Gets the HUD information for this modifier. Defaults to the modifier name. Does nothing if <see cref="HideOnUi"/> is true.
/// </summary>
+193 -77
View File
@@ -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)
}
/// <summary>
/// Gets a collection of modifiers by their type, or null if the player doesn't have one.
/// Gets a collection of modifiers by type.
/// </summary>
/// <param name="predicate">The predicate to check the modifier by.</param>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
/// <returns>The Modifier if it is found, null otherwise.</returns>
public IEnumerable<T> GetModifiersByType<T>() where T : BaseModifier
[HideFromIl2Cpp]
public IEnumerable<T> GetModifiersByType<T>(Func<T, bool>? predicate=null) where T : BaseModifier
{
return ActiveModifiers.OfType<T>();
return ActiveModifiers.OfType<T>().Where(x => predicate == null || predicate(x));
}
/// <summary>
/// Gets a modifier by its type, or null if the player doesn't have it.
/// Gets a collection of modifiers by type.
/// </summary>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
/// <param name="type">The modifier type.</param>
/// <param name="predicate">The predicate to check the modifier by.</param>
/// <returns>The Modifier if it is found, null otherwise.</returns>
public T? GetModifier<T>() where T : BaseModifier
[HideFromIl2Cpp]
public IEnumerable<BaseModifier> GetModifiersByType(Type type, Func<BaseModifier, bool>? predicate=null)
{
return GetModifiersByType<T>().FirstOrDefault();
return ActiveModifiers.Where(x => x.GetType() == type && (predicate == null || predicate(x)));
}
/// <summary>
/// Gets a collection of modifiers by ID.
/// </summary>
/// <param name="id">The Modifier ID.</param>
/// <param name="predicate">The predicate to check the modifier by.</param>
/// <returns>The Modifier if it is found, null otherwise.</returns>
[HideFromIl2Cpp]
public IEnumerable<BaseModifier> GetModifiersByType(uint id, Func<BaseModifier, bool>? 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);
}
/// <summary>
/// Tries to get a modifier by its type.
/// </summary>
/// <param name="modifier">The modifier or null.</param>
/// <param name="predicate">The predicate to check the modifier by.</param>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
/// <returns>True if the modifier was found, false otherwise.</returns>
public bool TryGetModifier<T>(out T? modifier) where T : BaseModifier
[HideFromIl2Cpp]
public bool TryGetModifier<T>(out T? modifier, Func<T, bool>? predicate = null) where T : BaseModifier
{
modifier = GetModifier<T>();
modifier = GetModifier(predicate);
return modifier != null;
}
/// <summary>
/// Tries to get a modifier by its type.
/// </summary>
/// <param name="type">The modifier type.</param>
/// <param name="modifier">The modifier or null.</param>
/// <param name="predicate">The predicate to check the modifier by.</param>
/// <returns>True if the modifier was found, false otherwise.</returns>
[HideFromIl2Cpp]
public bool TryGetModifier(Type type, out BaseModifier? modifier, Func<BaseModifier, bool>? predicate = null)
{
modifier = GetModifier(type, predicate);
return modifier != null;
}
/// <summary>
/// Tries to get a modifier by its ID.
/// </summary>
/// <param name="id">The modifier ID.</param>
/// <param name="modifier">The modifier or null.</param>
/// <param name="predicate">The predicate to check the modifier by.</param>
/// <returns>True if the modifier was found, false otherwise.</returns>
[HideFromIl2Cpp]
public bool TryGetModifier(uint id, out BaseModifier? modifier, Func<BaseModifier, bool>? 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);
}
/// <summary>
/// Gets a modifier by its type, or null if the player doesn't have it.
/// </summary>
/// <param name="predicate">The predicate to check the modifier by.</param>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
/// <returns>The Modifier if it is found, null otherwise.</returns>
[HideFromIl2Cpp]
public T? GetModifier<T>(Func<T, bool>? predicate = null) where T : BaseModifier
{
return GetModifiersByType(predicate).FirstOrDefault();
}
/// <summary>
/// Gets a modifier by its type, or null if the player doesn't have it.
/// </summary>
/// <param name="type">The modifier type.</param>
/// <param name="predicate">The predicate to check the modifier by.</param>
/// <returns>The Modifier if it is found, null otherwise.</returns>
[HideFromIl2Cpp]
public BaseModifier? GetModifier(Type type, Func<BaseModifier, bool>? predicate = null)
{
return GetModifiersByType(type).FirstOrDefault(predicate ?? (_ => true));
}
/// <summary>
/// Gets a modifier by its type, or null if the player doesn't have it.
/// </summary>
/// <param name="id">The modifier ID.</param>
/// <param name="predicate">The predicate to check the modifier by.</param>
/// <returns>The Modifier if it is found, null otherwise.</returns>
[HideFromIl2Cpp]
public BaseModifier? GetModifier(uint id, Func<BaseModifier, bool>? 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);
}
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <typeparam name="T">The modifier type.</typeparam>
/// <param name="predicate">The predicate to check the modifier by.</param>
[HideFromIl2Cpp]
public void RemoveModifier<T>(Func<T, bool>? predicate = null) where T : BaseModifier
{
RemoveModifier(typeof(T), x => predicate == null || predicate((T)x));
}
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <param name="type">The modifier type.</param>
/// <param name="predicate">The predicate to check the modifier by.</param>
[HideFromIl2Cpp]
public void RemoveModifier(Type type)
public void RemoveModifier(Type type, Func<BaseModifier, bool>? 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<MiraApiPlugin>.Error($"Cannot remove modifier {type.Name} because it is not active.");
@@ -172,32 +280,17 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
RemoveModifier(modifier);
}
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <typeparam name="T">The modifier type.</typeparam>
[HideFromIl2Cpp]
public void RemoveModifier<T>() where T : BaseModifier
{
RemoveModifier(typeof(T));
}
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <param name="modifierId">The modifier ID.</param>
/// <param name="predicate">The predicate to check the modifier by.</param>
[HideFromIl2Cpp]
public void RemoveModifier(uint modifierId)
public void RemoveModifier(uint modifierId, Func<BaseModifier, bool>? predicate = null)
{
var modifier = Modifiers.Find(x => x.ModifierId == modifierId);
if (modifier is null)
{
Logger<MiraApiPlugin>.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);
}
/// <summary>
@@ -216,6 +309,18 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
_toRemove.Add(modifier);
}
/// <summary>
/// Adds a modifier to the player.
/// </summary>
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
/// <typeparam name="T">The Type of the modifier.</typeparam>
/// <returns>The new modifier.</returns>
[HideFromIl2Cpp]
public T? AddModifier<T>(params object[] args) where T : BaseModifier
{
return AddModifier(typeof(T), args) as T;
}
/// <summary>
/// Adds a modifier to the player.
/// </summary>
@@ -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<MiraApiPlugin>.Error($"Player already has modifier with id {modifier.ModifierId}!");
Logger<MiraApiPlugin>.Error($"Player already has modifier with id {id}!");
return null;
}
var modifierId = ModifierManager.GetModifierId(modifier.GetType());
if (modifierId == null)
if (Modifiers.Contains(modifier))
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {modifier.GetType().Name} because it has no ID!");
Logger<MiraApiPlugin>.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.
/// </summary>
/// <param name="type">The modifier type.</param>
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
/// <returns>The modifier that was added.</returns>
[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<MiraApiPlugin>.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<MiraApiPlugin>.Error($"Player already has modifier with id {modifierId}!");
return null;
}
if (Activator.CreateInstance(type) is not BaseModifier modifier)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {type.Name} because it is null.");
return null;
}
AddModifier(modifier);
return modifier;
return AddModifier(modifier);
}
/// <summary>
/// Adds a modifier to the player.
/// </summary>
/// <typeparam name="T">The Type of the modifier.</typeparam>
/// <returns>The new modifier.</returns>
/// <param name="id">The ID of the modifier.</param>
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
/// <returns>The modifier if it was created, or null if it failed.</returns>
[HideFromIl2Cpp]
public T? AddModifier<T>() 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);
}
/// <summary>
/// Checks if a player has an active or queued modifier by its ID.
/// </summary>
/// <param name="id">The Modifier ID.</param>
/// <param name="predicate">The predicate to check the modifier.</param>
/// <returns>True if the Modifier is present, false otherwise.</returns>
public bool HasModifier(uint id, Func<BaseModifier, bool>? predicate=null)
{
return ActiveModifiers.Exists(x => x.ModifierId == id && (predicate == null || predicate(x))) ||
_toAdd.Exists(x => x.ModifierId == id && (predicate == null || predicate(x)));
}
/// <summary>
/// Checks if a player has an active or queued modifier by its type.
/// Checks if a player has an active modifier by its type.
/// </summary>
/// <param name="predicate">The predicate to check the modifier.</param>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
/// <returns>True if the Modifier is present, false otherwise.</returns>
[HideFromIl2Cpp]
public bool HasModifier<T>(Func<T, bool>? predicate=null) where T : BaseModifier
{
return ModifierManager.GetModifierId(typeof(T)) is { } id &&
HasModifier(id, (Func<BaseModifier, bool>?)predicate);
return ActiveModifiers.Exists(x => x is T modifier && (predicate == null || predicate(modifier)));
}
/// <summary>
/// Checks if a player has an active modifier by its type.
/// </summary>
/// <param name="type">The modifier type.</param>
/// <param name="predicate">The predicate to check the modifier.</param>
/// <returns>True if the Modifier is present, false otherwise.</returns>
[HideFromIl2Cpp]
public bool HasModifier(Type type, Func<BaseModifier, bool>? predicate=null)
{
return ActiveModifiers.Exists(x => x.GetType() == type && (predicate == null || predicate(x)));
}
/// <summary>
/// Checks if a player has an active modifier by its ID.
/// </summary>
/// <param name="id">The Modifier ID.</param>
/// <param name="predicate">The predicate to check the modifier.</param>
/// <returns>True if the Modifier is present, false otherwise.</returns>
[HideFromIl2Cpp]
public bool HasModifier(uint id, Func<BaseModifier, bool>? 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);
}
}
+4 -6
View File
@@ -1,18 +1,16 @@
using System;
namespace MiraAPI.Modifiers;
namespace MiraAPI.Modifiers;
/// <summary>
/// Modifier data for networking.
/// </summary>
/// <param name="type">The Type of the modifier.</param>
/// <param name="id">The ID of the modifier.</param>
/// <param name="args">Parameters for constructor.</param>
public readonly struct ModifierData(Type type, object[] args)
public readonly struct ModifierData(uint id, object[] args)
{
/// <summary>
/// Gets the type of the modifier.
/// </summary>
public Type Type { get; } = type;
public uint Id { get; } = id;
/// <summary>
/// Gets the parameters for the constructor.
+249 -64
View File
@@ -8,11 +8,81 @@ using Reactor.Utilities;
namespace MiraAPI.Modifiers;
/// <summary>
/// Extension methods for modifiers.
/// Extensions for modifiers.
/// </summary>
public static class ModifierExtensions
{
internal static Dictionary<PlayerControl, ModifierComponent> ModifierComponents { get; } = [];
/// <summary>
/// Gets the dictionary to cache ModifierComponents.
/// </summary>
public static Dictionary<PlayerControl, ModifierComponent> ModifierComponents { get; } = [];
/// <summary>
/// Remote Procedure Call to add a modifier to a player.
/// </summary>
/// <param name="target">The player to add the modifier to.</param>
/// <param name="modifierId">The modifier ID.</param>
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
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<AddModifierRpc>.Instance.Send(target, new ModifierData(modifierId, args));
}
/// <summary>
/// Remote Procedure Call to add a modifier to a player.
/// </summary>
/// <param name="player">The player to add the modifier to.</param>
/// <param name="type">The type of the modifier.</param>
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
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);
}
/// <summary>
/// Remote Procedure Call to add a modifier to a player.
/// </summary>
/// <param name="player">The player to add the modifier to.</param>
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
/// <typeparam name="T">The modifier Type.</typeparam>
public static void RpcAddModifier<T>(this PlayerControl player, params object[] args) where T : BaseModifier
{
player.RpcAddModifier(typeof(T), args);
}
/// <summary>
/// Remote Procedure Call to remove a modifier from a player.
/// </summary>
/// <param name="target">The player to remove the modifier from.</param>
/// <param name="modifierId">The ID of the modifier.</param>
[MethodRpc((uint)MiraRpc.RemoveModifier)]
public static void RpcRemoveModifier(this PlayerControl target, uint modifierId)
{
target.RemoveModifier(modifierId);
}
/// <summary>
/// Remote Procedure Call to remove a modifier from a player.
/// </summary>
/// <param name="player">The player to remove the modifier from.</param>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
public static void RpcRemoveModifier<T>(this PlayerControl player) where T : BaseModifier
{
var id = ModifierManager.GetModifierId(typeof(T));
if (id == null)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
return;
}
player.RpcRemoveModifier(id.Value);
}
/// <summary>
/// Gets the ModifierComponent for a player.
@@ -37,102 +107,217 @@ public static class ModifierExtensions
}
/// <summary>
/// Gets a modifier by its type, or null if the player doesn't have it.
/// Checks if the player has a specific modifier.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
/// <returns>The Modifier if it is found, null otherwise.</returns>
public static T? GetModifier<T>(this PlayerControl? player) where T : BaseModifier
/// <typeparam name="T">The type of the modifier.</typeparam>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
/// <returns>True if the player has the modifier, false otherwise.</returns>
public static bool HasModifier<T>(this PlayerControl player, Func<T, bool>? predicate = null) where T : BaseModifier
{
return player?.GetModifierComponent()?.ActiveModifiers.Find(x => x is T) as T;
return player.GetModifierComponent()!.HasModifier(predicate);
}
/// <summary>
/// Checks if a player has a modifier.
/// Checks if the player has a specific modifier by type.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
/// <returns>True if the Modifier is present, false otherwise.</returns>
public static bool HasModifier<T>(this PlayerControl? player) where T : BaseModifier
/// <param name="player">The PlayerControl instance.</param>
/// <param name="type">The type of the modifier.</param>
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
/// <returns>True if the player has the modifier, false otherwise.</returns>
public static bool HasModifier(this PlayerControl player, Type type, Func<BaseModifier, bool>? predicate = null)
{
return player?.GetModifierComponent() != null &&
player.GetModifierComponent()!.HasModifier<T>();
return player.GetModifierComponent()!.HasModifier(type, predicate);
}
/// <summary>
/// Checks if a player has a modifier by its ID.
/// Checks if the player has a specific modifier by ID.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <param name="id">The Modifier ID.</param>
/// <returns>True if the Modifier is present, false otherwise.</returns>
public static bool HasModifier(this PlayerControl? player, uint id)
/// <param name="player">The PlayerControl instance.</param>
/// <param name="id">The ID of the modifier.</param>
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
/// <returns>True if the player has the modifier, false otherwise.</returns>
public static bool HasModifier(this PlayerControl player, uint id, Func<BaseModifier, bool>? predicate = null)
{
return player?.GetModifierComponent() != null &&
player.GetModifierComponent()!.HasModifier(id);
return player.GetModifierComponent()!.HasModifier(id, predicate);
}
/// <summary>
/// Remote Procedure Call to remove a modifier from a player.
/// Gets a specific modifier from the player.
/// </summary>
/// <param name="target">The player to remove the modifier from.</param>
/// <typeparam name="T">The type of the modifier.</typeparam>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
/// <returns>The modifier if found, null otherwise.</returns>
public static T? GetModifier<T>(this PlayerControl player, Func<T, bool>? predicate = null) where T : BaseModifier
{
return player.GetModifierComponent()!.GetModifier(predicate);
}
/// <summary>
/// Gets a specific modifier from the player by type.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="type">The type of the modifier.</param>
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
/// <returns>The modifier if found, null otherwise.</returns>
public static BaseModifier? GetModifier(
this PlayerControl player,
Type type,
Func<BaseModifier, bool>? predicate = null)
{
return player.GetModifierComponent()!.GetModifier(type, predicate);
}
/// <summary>
/// Gets a specific modifier from the player by ID.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="id">The ID of the modifier.</param>
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
/// <returns>The modifier if found, null otherwise.</returns>
public static BaseModifier? GetModifier(
this PlayerControl player,
uint id,
Func<BaseModifier, bool>? predicate = null)
{
return player.GetModifierComponent()!.GetModifier(id, predicate);
}
/// <summary>
/// Gets all modifiers of a specific type from the player.
/// </summary>
/// <typeparam name="T">The type of the modifiers.</typeparam>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
/// <returns>An enumerable of modifiers.</returns>
public static IEnumerable<T> GetModifiersByType<T>(this PlayerControl player, Func<T, bool>? predicate = null)
where T : BaseModifier
{
return player.GetModifierComponent()!.GetModifiersByType(predicate);
}
/// <summary>
/// Gets all modifiers of a specific type from the player.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="type">The type of the modifiers.</param>
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
/// <returns>An enumerable of modifiers.</returns>
public static IEnumerable<BaseModifier> GetModifiersByType(
this PlayerControl player,
Type type,
Func<BaseModifier, bool>? predicate = null)
{
return player.GetModifierComponent()!.GetModifiersByType(type, predicate);
}
/// <summary>
/// Gets all modifiers of a specific type from the player by ID.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="id">The ID of the modifiers.</param>
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
/// <returns>An enumerable of modifiers.</returns>
public static IEnumerable<BaseModifier> GetModifiersByType(
this PlayerControl player,
uint id,
Func<BaseModifier, bool>? predicate = null)
{
return player.GetModifierComponent()!.GetModifiersByType(id, predicate);
}
/// <summary>
/// Removes a specific modifier from the player.
/// </summary>
/// <typeparam name="T">The type of the modifier.</typeparam>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
public static void RemoveModifier<T>(this PlayerControl player, Func<T, bool>? predicate = null)
where T : BaseModifier
{
player.GetModifierComponent()!.RemoveModifier(predicate);
}
/// <summary>
/// Removes a specific modifier from the player by type.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="type">The type of the modifier.</param>
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
public static void RemoveModifier(this PlayerControl player, Type type, Func<BaseModifier, bool>? predicate = null)
{
player.GetModifierComponent()!.RemoveModifier(type, predicate);
}
/// <summary>
/// Removes a specific modifier from the player by ID.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="modifierId">The ID of the modifier.</param>
[MethodRpc((uint)MiraRpc.RemoveModifier)]
public static void RpcRemoveModifier(this PlayerControl target, uint modifierId)
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
public static void RemoveModifier(
this PlayerControl player,
uint modifierId,
Func<BaseModifier, bool>? predicate = null)
{
target.GetModifierComponent()?.RemoveModifier(modifierId);
player.GetModifierComponent()!.RemoveModifier(modifierId, predicate);
}
/// <summary>
/// Remote Procedure Call to remove a modifier from a player.
/// Removes a specific modifier from the player.
/// </summary>
/// <param name="player">The player to remove the modifier from.</param>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
public static void RpcRemoveModifier<T>(this PlayerControl player) where T : BaseModifier
/// <param name="player">The PlayerControl instance.</param>
/// <param name="modifier">The modifier to remove.</param>
public static void RemoveModifier(this PlayerControl player, BaseModifier modifier)
{
var id = ModifierManager.GetModifierId(typeof(T));
if (id == null)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
return;
}
player.RpcRemoveModifier(id.Value);
player.GetModifierComponent()!.RemoveModifier(modifier);
}
/// <summary>
/// Remote Procedure Call to add a modifier to a player.
/// Adds a specific modifier to the player.
/// </summary>
/// <param name="target">The player to add the modifier to.</param>
/// <param name="modifierId">The modifier ID.</param>
/// <typeparam name="T">The type of the modifier.</typeparam>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
public static void RpcAddModifier(this PlayerControl target, uint modifierId, params object[] args)
/// <returns>The added modifier.</returns>
public static T? AddModifier<T>(this PlayerControl player, params object[] args) where T : BaseModifier
{
var type = ModifierManager.GetModifierType(modifierId);
if (type == null)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier with id {modifierId} because it is not registered.");
return;
}
Rpc<AddModifierRpc>.Instance.Send(target, new ModifierData(type, args));
return player.GetModifierComponent()!.AddModifier<T>(args);
}
/// <summary>
/// Remote Procedure Call to add a modifier to a player.
/// Adds a specific modifier to the player.
/// </summary>
/// <param name="player">The player to add the modifier to.</param>
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
/// <typeparam name="T">The modifier Type.</typeparam>
public static void RpcAddModifier<T>(this PlayerControl player, params object[] args) where T : BaseModifier
/// <param name="player">The PlayerControl instance.</param>
/// <param name="modifier">The modifier to add.</param>
/// <returns>The added modifier.</returns>
public static BaseModifier? AddModifier(this PlayerControl player, BaseModifier modifier)
{
var id = ModifierManager.GetModifierId(typeof(T));
if (id == null)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
return;
}
return player.GetModifierComponent()!.AddModifier(modifier);
}
player.RpcAddModifier(id.Value, args);
/// <summary>
/// Adds a specific modifier to the player by type.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="type">The type of the modifier.</param>
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
/// <returns>The added modifier.</returns>
public static BaseModifier? AddModifier(this PlayerControl player, Type type, params object[] args)
{
return player.GetModifierComponent()!.AddModifier(type, args);
}
/// <summary>
/// Adds a specific modifier to the player by ID.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="id">The ID of the modifier.</param>
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
/// <returns>The added modifier.</returns>
public static BaseModifier? AddModifier(this PlayerControl player, uint id, params object[] args)
{
return player.GetModifierComponent()!.AddModifier(id, args);
}
}
+33 -10
View File
@@ -1,18 +1,28 @@
using System;
using System.Linq;
using System.Linq.Expressions;
namespace MiraAPI.Modifiers;
/// <summary>
/// Factory for creating instances of a modifier. More efficient than using reflection.
/// </summary>
public static class ModifierFactory
{
private static Func<object[], BaseModifier>? _constructor;
private static Func<object[], BaseModifier> CreateConstructor(Type type)
private static Func<object[], BaseModifier> 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();
}
/// <summary>
/// Creates a modifier with the specified type and arguments.
/// </summary>
/// <param name="type">Modifier type.</param>
/// <param name="args">Arguments.</param>
/// <returns>An instance of the modifier.</returns>
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<T> where T : BaseModifier
{
private static readonly Func<object[], T> Constructor = CreateConstructor();
private static Func<object[], T> CreateConstructor()
private static Func<object[], T> 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");
+6 -2
View File
@@ -48,7 +48,12 @@ public static class ModifierManager
/// <returns>The ID of the modifier.</returns>
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))
+1 -1
View File
@@ -102,7 +102,7 @@ public abstract class TimedModifier : BaseModifier
if (RemoveOnComplete)
{
ModifierComponent?.RemoveModifier(ModifierId);
ModifierComponent?.RemoveModifier(this);
}
}
+20 -22
View File
@@ -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<MiraApiPlugin, ModifierData>(plugin, id)
{
private static readonly Dictionary<Type, ParameterInfo[]> ParameterCache = [];
/// <inheritdoc />
public override RpcLocalHandling LocalHandling => RpcLocalHandling.Before;
/// <inheritdoc />
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);
}
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
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);
}
}
@@ -1,6 +1,6 @@
using HarmonyLib;
using MiraAPI.Modifiers;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Patches.Freeplay;
+5 -4
View File
@@ -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;
}
}));
+1 -1
View File
@@ -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);
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ using UnityEngine;
namespace MiraAPI.Patches.Roles;
[HarmonyPatch(typeof(TaskPanelBehaviour))]
public static class TaskPanelPatch
internal static class TaskPanelPatch
{
/// <summary>
/// This patch is to override the automatic updating of the y position on the tab (which is in base game)
-252
View File
@@ -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;
/// <summary>
/// Extensions for modifiers.
/// </summary>
public static class ModifierExtensions
{
/// <summary>
/// Gets the dictionary to cache ModifierComponents.
/// </summary>
public static Dictionary<PlayerControl, ModifierComponent> ModifierComponents { get; } = [];
/// <summary>
/// Remote Procedure Call to add a modifier to a player.
/// </summary>
/// <param name="target">The player to add the modifier to.</param>
/// <param name="modifierId">The modifier ID.</param>
[MethodRpc((uint)MiraRpc.AddModifier)]
public static void RpcAddModifier(this PlayerControl target, uint modifierId)
{
var type = ModifierManager.GetModifierType(modifierId);
if (type == null)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier with id {modifierId} because it is not registered.");
return;
}
target.AddModifier(type);
}
/// <summary>
/// Remote Procedure Call to add a modifier to a player.
/// </summary>
/// <param name="player">The player to add the modifier to.</param>
/// <typeparam name="T">The modifier Type.</typeparam>
public static void RpcAddModifier<T>(this PlayerControl player) where T : BaseModifier
{
var id = ModifierManager.GetModifierId(typeof(T));
if (id == null)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
return;
}
player.RpcAddModifier(id.Value);
}
/// <summary>
/// Remote Procedure Call to remove a modifier from a player.
/// </summary>
/// <param name="target">The player to remove the modifier from.</param>
/// <param name="modifierId">The ID of the modifier.</param>
[MethodRpc((uint)MiraRpc.RemoveModifier)]
public static void RpcRemoveModifier(this PlayerControl target, uint modifierId)
{
target.RemoveModifier(modifierId);
}
/// <summary>
/// Remote Procedure Call to remove a modifier from a player.
/// </summary>
/// <param name="player">The player to remove the modifier from.</param>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
public static void RpcRemoveModifier<T>(this PlayerControl player) where T : BaseModifier
{
var id = ModifierManager.GetModifierId(typeof(T));
if (id == null)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
return;
}
player.RpcRemoveModifier(id.Value);
}
/// <summary>
/// Gets the ModifierComponent for a player.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <returns>A ModifierComponent if there is one, null otherwise.</returns>
public static ModifierComponent? GetModifierComponent(this PlayerControl player)
{
if (ModifierComponents.TryGetValue(player, out var component))
{
return component;
}
component = player.GetComponent<ModifierComponent>();
if (!component)
{
return null;
}
ModifierComponents[player] = component;
return component;
}
/// <summary>
/// Gets the ModifierComponent for a player.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <param name="component">The ModifierComponent if it is found.</param>
/// <returns>A ModifierComponent if there is one, null otherwise.</returns>
public static bool TryGetModifierComponent(this PlayerControl player, out ModifierComponent? component)
{
if (ModifierComponents.TryGetValue(player, out component))
{
return component;
}
component = player.GetComponent<ModifierComponent>();
return component;
}
/// <summary>
/// Gets a collection of modifiers by their type, or null if the player doesn't have one.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
/// <returns>The Modifier if it is found, null otherwise.</returns>
public static IEnumerable<T> GetModifiersByType<T>(this PlayerControl player) where T : BaseModifier
{
return player.GetModifierComponent()!.GetModifiersByType<T>();
}
/// <summary>
/// Gets a modifier by its type, or null if the player doesn't have it.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
/// <returns>The Modifier if it is found, null otherwise.</returns>
public static T? GetModifier<T>(this PlayerControl player) where T : BaseModifier
{
return player.GetModifierComponent()!.GetModifier<T>();
}
/// <summary>
/// Tries to get a modifier by its type.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <param name="modifier">The modifier or null.</param>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
/// <returns>True if the modifier was found, false otherwise.</returns>
public static bool TryGetModifier<T>(this PlayerControl player, out T? modifier) where T : BaseModifier
{
return player.GetModifierComponent()!.TryGetModifier(out modifier);
}
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <param name="type">The modifier type.</param>
public static void RemoveModifier(this PlayerControl player, Type type)
{
player.GetModifierComponent()!.RemoveModifier(type);
}
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <typeparam name="T">The modifier type.</typeparam>
public static void RemoveModifier<T>(this PlayerControl player) where T : BaseModifier
{
player.GetModifierComponent()!.RemoveModifier<T>();
}
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <param name="modifierId">The modifier ID.</param>
public static void RemoveModifier(this PlayerControl player, uint modifierId)
{
player.GetModifierComponent()!.RemoveModifier(modifierId);
}
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <param name="modifier">The modifier object.</param>
public static void RemoveModifier(this PlayerControl player, BaseModifier modifier)
{
player.GetModifierComponent()!.RemoveModifier(modifier);
}
/// <summary>
/// Adds a modifier to the player.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <param name="modifier">The modifier to add.</param>
/// <returns>The modifier that was added.</returns>
public static BaseModifier? AddModifier(this PlayerControl player, BaseModifier modifier)
{
return player.GetModifierComponent()!.AddModifier(modifier);
}
/// <summary>
/// Adds a modifier to the player.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <param name="type">The modifier type.</param>
/// <returns>The modifier that was added.</returns>
public static BaseModifier? AddModifier(this PlayerControl player, Type type)
{
return player.GetModifierComponent()!.AddModifier(type);
}
/// <summary>
/// Adds a modifier to the player.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <typeparam name="T">The Type of the modifier.</typeparam>
/// <returns>The new modifier.</returns>
public static T? AddModifier<T>(this PlayerControl player) where T : BaseModifier
{
return player.GetModifierComponent()!.AddModifier<T>();
}
/// <summary>
/// Checks if a player has an active or queued modifier by its ID.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <param name="id">The Modifier ID.</param>
/// <param name="predicate">The predicate to check the modifier.</param>
/// <returns>True if the Modifier is present, false otherwise.</returns>
public static bool HasModifier(this PlayerControl player, uint id, Func<BaseModifier, bool>? predicate=null)
{
return player.GetModifierComponent()!.HasModifier(id, predicate);
}
/// <summary>
/// Checks if a player has an active or queued modifier by its type.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <param name="predicate">The predicate to check the modifier.</param>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
/// <returns>True if the Modifier is present, false otherwise.</returns>
public static bool HasModifier<T>(this PlayerControl player, Func<T, bool>? predicate=null) where T : BaseModifier
{
return player.GetModifierComponent()!.HasModifier(predicate);
}
}