Merge pull request #19 from All-Of-Us-Mods/rpc-modifier-arguments

Modifier Rework
This commit is contained in:
XtraCube authored and GitHub committed 2025-02-27 18:33:35 -05:00
commit 6f4eff7b38
25 files changed
+1024 -489

No files matched your search

@@ -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;
+1 -1
View File
@@ -1,6 +1,6 @@
using MiraAPI.Example.Modifiers;
using MiraAPI.Hud;
using MiraAPI.Utilities;
using MiraAPI.Modifiers;
using MiraAPI.Utilities.Assets;
using UnityEngine;
@@ -1,4 +1,4 @@
namespace MiraAPI.Events.Vanilla;
namespace MiraAPI.Events.Vanilla.Usables;
/// <summary>
/// Event for <see cref="PlayerControl"/> using a <see cref="IUsable"/> from Vanilla Among Us. Will always be ran locally.
+15 -3
View File
@@ -1,4 +1,6 @@
namespace MiraAPI.Modifiers;
using System;
namespace MiraAPI.Modifiers;
/// <summary>
/// Base class for all modifiers.
@@ -16,9 +18,19 @@ public abstract class BaseModifier
public ModifierComponent? ModifierComponent { get; internal set; }
/// <summary>
/// Gets the modifier id.
/// Gets a value indicating whether the modifier has been initialized.
/// </summary>
public uint ModifierId { get; internal set; }
public bool Initialized { get; internal set; }
/// <summary>
/// Gets the unique ID of the modifier.
/// </summary>
public Guid UniqueId { get; internal set; } = Guid.Empty;
/// <summary>
/// Gets the type ID of the modifier.
/// </summary>
public uint TypeId => ModifierManager.GetModifierTypeId(GetType()) ?? throw new InvalidOperationException("Modifier is not registered.");
/// <summary>
/// Gets the modifier name.
+251 -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)
@@ -103,14 +104,12 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
return;
}
var filteredModifiers = Modifiers.Where(mod => !mod.HideOnUi);
var filteredModifiers = Modifiers.Where(mod => !mod.HideOnUi).ToArray();
var baseModifiers = filteredModifiers as BaseModifier[] ?? filteredModifiers.ToArray();
if (baseModifiers.Length != 0 && !MeetingHud.Instance)
if (filteredModifiers.Length != 0 && !MeetingHud.Instance)
{
var stringBuild = new StringBuilder();
foreach (var mod in baseModifiers)
foreach (var mod in filteredModifiers)
{
stringBuild.Append(CultureInfo.InvariantCulture, $"\n{mod.GetHudString()}");
}
@@ -123,77 +122,178 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
}
/// <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
/// <returns>A collection of modifiers.</returns>
[HideFromIl2Cpp]
public IEnumerable<T> GetModifiers<T>(Func<T, bool>? predicate=null) where T : BaseModifier
{
return ActiveModifiers.OfType<T>();
return Modifiers.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>
/// <returns>The Modifier if it is found, null otherwise.</returns>
public T? GetModifier<T>() where T : BaseModifier
/// <param name="type">The modifier type.</param>
/// <param name="predicate">The predicate to check the modifier by.</param>
/// <returns>A collection of modifiers.</returns>
[HideFromIl2Cpp]
public IEnumerable<BaseModifier> GetModifiers(Type type, Func<BaseModifier, bool>? predicate=null)
{
return GetModifiersByType<T>().FirstOrDefault();
return Modifiers.Where(x => x.GetType() == type && (predicate == null || predicate(x)));
}
/// <summary>
/// Gets a collection of modifiers by type ID.
/// </summary>
/// <param name="id">The modifier's type ID.</param>
/// <param name="predicate">The predicate to check the modifier by.</param>
/// <returns>A collection of modifiers.</returns>
[HideFromIl2Cpp]
public IEnumerable<BaseModifier> GetModifiers(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 GetModifiers(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>
/// Removes a modifier from the player.
/// 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 void RemoveModifier(Type type)
public bool TryGetModifier(Type type, out BaseModifier? modifier, Func<BaseModifier, bool>? predicate = null)
{
var modifier = Modifiers.Find(x => x.GetType() == type);
modifier = GetModifier(type, predicate);
return modifier != null;
}
if (modifier is null)
{
Logger<MiraApiPlugin>.Error($"Cannot remove modifier {type.Name} because it is not active.");
return;
}
/// <summary>
/// Tries to get a modifier by its type ID.
/// </summary>
/// <param name="id">The modifier type 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)
{
modifier = GetModifier(id, predicate);
return modifier != null;
}
RemoveModifier(modifier);
/// <summary>
/// Tries to get a modifier by its unique ID.
/// </summary>
/// <param name="modifierGuid">The modifier unique ID.</param>
/// <param name="modifier">The modifier or null.</param>
/// <returns>True if the modifier was found, false otherwise.</returns>
[HideFromIl2Cpp]
public bool TryGetModifier(Guid modifierGuid, out BaseModifier? modifier)
{
modifier = GetModifier(modifierGuid);
return modifier != null;
}
/// <summary>
/// Gets a modifier by its 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>
[HideFromIl2Cpp]
public T? GetModifier<T>(Func<T, bool>? predicate = null) where T : BaseModifier
{
return GetModifiers(predicate).FirstOrDefault();
}
/// <summary>
/// Gets a modifier by its type.
/// </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 GetModifiers(type).FirstOrDefault(predicate ?? (_ => true));
}
/// <summary>
/// Gets a modifier by its type 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 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>
/// Gets a modifier by unique ID.
/// </summary>
/// <param name="modifierGuid">The modifier's unique ID.</param>
/// <returns>The modifier if it is found, or null.</returns>
[HideFromIl2Cpp]
public BaseModifier? GetModifier(Guid modifierGuid)
{
return Modifiers.Find(x => x.UniqueId == modifierGuid);
}
/// <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>() where T : BaseModifier
public void RemoveModifier<T>(Func<T, bool>? predicate = null) where T : BaseModifier
{
RemoveModifier(typeof(T));
RemoveModifier(typeof(T), x => predicate == null || predicate((T)x));
}
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <param name="modifierId">The modifier ID.</param>
/// <param name="type">The modifier type.</param>
/// <param name="predicate">The predicate to check the modifier by.</param>
[HideFromIl2Cpp]
public void RemoveModifier(uint modifierId)
public void RemoveModifier(Type type, Func<BaseModifier, bool>? predicate = null)
{
var modifier = Modifiers.Find(x => x.ModifierId == modifierId);
var modifiers = Modifiers.Where(x => x.GetType() == type && (predicate == null || predicate(x))).ToList();
if (modifiers.Count > 1)
{
throw new InvalidOperationException($"Cannot remove modifier {type.Name} because there are multiple instances of that modifier.");
}
var modifier = modifiers.FirstOrDefault();
if (modifier is null)
{
Logger<MiraApiPlugin>.Error($"Cannot remove modifier with id {modifierId} because it is not active.");
Logger<MiraApiPlugin>.Error($"Cannot remove modifier {type.Name} because it is not active.");
return;
}
@@ -216,6 +316,48 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
_toRemove.Add(modifier);
}
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <param name="typeId">The modifier's type ID.</param>
/// <param name="predicate">The predicate to check the modifier by.</param>
[HideFromIl2Cpp]
public void RemoveModifier(uint typeId, Func<BaseModifier, bool>? predicate = null)
{
var type = ModifierManager.GetModifierType(typeId) ?? throw new InvalidOperationException(
$"Cannot remove modifier with id {typeId} because it is not registered.");
RemoveModifier(type, predicate);
}
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <param name="uniqueId">The modifier's unique ID.</param>
[HideFromIl2Cpp]
public void RemoveModifier(Guid uniqueId)
{
var modifier = GetModifier(uniqueId);
if (modifier == null)
{
Logger<MiraApiPlugin>.Error($"Cannot remove modifier with unique id {uniqueId} because it is not active.");
return;
}
RemoveModifier(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 +366,32 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
[HideFromIl2Cpp]
public BaseModifier? AddModifier(BaseModifier modifier)
{
if (Modifiers.Contains(modifier))
// TODO: Make a proper synchronization system.
if (LobbyBehaviour.Instance)
{
Logger<MiraApiPlugin>.Error($"Player already has modifier with id {modifier.ModifierId}!");
Logger<MiraApiPlugin>.Warning($"Modifiers added in the lobby won't sync to new players!");
}
var id = modifier.TypeId;
if (modifier.Unique && Modifiers.Find(x => x.TypeId == id) != null)
{
Logger<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;
if (modifier.UniqueId == Guid.Empty)
{
modifier.UniqueId = Guid.NewGuid();
}
return modifier;
}
@@ -249,66 +399,90 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
/// Adds a modifier to the player.
/// </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 Modifiers.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 Modifiers.Exists(x => x.GetType() == type && (predicate == null || predicate(x)));
}
/// <summary>
/// Checks if a player has an active modifier by its type ID.
/// </summary>
/// <param name="id">The modifier's type 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);
}
/// <summary>
/// Checks if a player has an active modifier by its unique ID.
/// </summary>
/// <param name="id">The modifier's guid.</param>
/// <returns>True if the modifier is present, false otherwise.</returns>
[HideFromIl2Cpp]
public bool HasModifier(Guid id)
{
return Modifiers.Exists(x => x.UniqueId == id);
}
}
+396
View File
@@ -0,0 +1,396 @@
using System;
using System.Collections.Generic;
using MiraAPI.Networking;
using MiraAPI.Networking.Modifiers;
using Reactor.Networking.Attributes;
using Reactor.Networking.Rpc;
using Reactor.Utilities;
namespace MiraAPI.Modifiers;
/// <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="typeId">The modifier type ID.</param>
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
public static void RpcAddModifier(this PlayerControl target, uint typeId, params object[] args)
{
_ = ModifierManager.GetModifierType(typeId) ?? throw new InvalidOperationException(
$"Modifier with ID {typeId} is not registered.");
Rpc<AddModifierRpc>.Instance.Send(target, new ModifierData(typeId, Guid.NewGuid(), 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.GetModifierTypeId(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="uniqueId">The unique ID of the modifier.</param>
[MethodRpc((uint)MiraRpc.RemoveModifier)]
public static void RpcRemoveModifier(this PlayerControl target, Guid uniqueId)
{
target.RemoveModifier(uniqueId);
}
/// <summary>
/// Remote Procedure Call to remove a modifier from a player by type ID.
/// </summary>
/// <param name="target">The player to remove the modifier from.</param>
/// <param name="typeId">The type ID of the modifier.</param>
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
public static void RpcRemoveModifier(
this PlayerControl target,
uint typeId,
Func<BaseModifier, bool>? predicate = null)
{
var modifier = target.GetModifier(typeId, predicate);
if (modifier is null)
{
Logger<MiraApiPlugin>.Error($"Player {target.PlayerId} does not have modifier with type ID {typeId}.");
return;
}
target.RpcRemoveModifier(modifier.UniqueId);
}
/// <summary>
/// Remote Procedure Call to remove a modifier from a player.
/// </summary>
/// <param name="player">The player to remove the modifier from.</param>
/// <param name="type">The type of the modifier.</param>
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
public static void RpcRemoveModifier(
this PlayerControl player,
Type type,
Func<BaseModifier, bool>? predicate = null)
{
var id = ModifierManager.GetModifierTypeId(type) ?? throw new InvalidOperationException(
$"Modifier {type.Name} is not registered.");
player.RpcRemoveModifier(id, predicate);
}
/// <summary>
/// Remote Procedure Call to remove a modifier from a player.
/// </summary>
/// <param name="player">The player to remove the modifier from.</param>
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
public static void RpcRemoveModifier<T>(this PlayerControl player, Func<BaseModifier, bool>? predicate = null)
where T : BaseModifier
{
player.RpcRemoveModifier(typeof(T), predicate);
}
/// <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)
{
throw new InvalidOperationException("ModifierComponent is not attached to the player.");
}
ModifierComponents[player] = component;
return component;
}
/// <summary>
/// Checks if the player has a specific modifier.
/// </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>
/// <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().HasModifier(predicate);
}
/// <summary>
/// Checks if the player has a specific modifier 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>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().HasModifier(type, predicate);
}
/// <summary>
/// Checks if the player has a specific modifier by type ID.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="typeId">The type 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 typeId,
Func<BaseModifier, bool>? predicate = null)
{
return player.GetModifierComponent().HasModifier(typeId, predicate);
}
/// <summary>
/// Checks if the player has a specific modifier by its GUID.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="uniqueId">The unique ID of the modifier.</param>
/// <returns>True if the player has the modifier, false otherwise.</returns>
public static bool HasModifier(this PlayerControl player, Guid uniqueId)
{
return player.GetModifierComponent().HasModifier(uniqueId);
}
/// <summary>
/// Gets 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>
/// <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 its type ID.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="typeId">The type 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 typeId,
Func<BaseModifier, bool>? predicate = null)
{
return player.GetModifierComponent().GetModifier(typeId, predicate);
}
/// <summary>
/// Gets a specific modifier from the player by its GUID.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="uniqueId">The GUID of the modifier.</param>
/// <returns>The modifier if found, null otherwise.</returns>
public static BaseModifier? GetModifier(
this PlayerControl player,
Guid uniqueId)
{
return player.GetModifierComponent().GetModifier(uniqueId);
}
/// <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>A collection of modifiers.</returns>
public static IEnumerable<T> GetModifiers<T>(this PlayerControl player, Func<T, bool>? predicate = null)
where T : BaseModifier
{
return player.GetModifierComponent().GetModifiers(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>A collection of modifiers.</returns>
public static IEnumerable<BaseModifier> GetModifiers(
this PlayerControl player,
Type type,
Func<BaseModifier, bool>? predicate = null)
{
return player.GetModifierComponent().GetModifiers(type, predicate);
}
/// <summary>
/// Gets all modifiers of a specific type from the player by type ID.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="typeId">The type ID of the modifiers.</param>
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
/// <returns>A collection of modifiers.</returns>
public static IEnumerable<BaseModifier> GetModifiers(
this PlayerControl player,
uint typeId,
Func<BaseModifier, bool>? predicate = null)
{
return player.GetModifierComponent().GetModifiers(typeId, predicate);
}
/// <summary>
/// Removes a specific modifier from the player.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="modifier">The modifier to remove.</param>
public static void RemoveModifier(this PlayerControl player, BaseModifier modifier)
{
player.GetModifierComponent().RemoveModifier(modifier);
}
/// <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 its type ID.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="typeId">The type ID of the modifier.</param>
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
public static void RemoveModifier(
this PlayerControl player,
uint typeId,
Func<BaseModifier, bool>? predicate = null)
{
player.GetModifierComponent().RemoveModifier(typeId, predicate);
}
/// <summary>
/// Removes a specific modifier from the player by its GUID.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="uniqueId">The GUID of the modifier.</param>
public static void RemoveModifier(
this PlayerControl player,
Guid uniqueId)
{
player.GetModifierComponent().RemoveModifier(uniqueId);
}
/// <summary>
/// Adds a specific modifier to the player.
/// </summary>
/// <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>
/// <returns>The added modifier.</returns>
public static T? AddModifier<T>(this PlayerControl player, params object[] args) where T : BaseModifier
{
return player.GetModifierComponent().AddModifier<T>(args);
}
/// <summary>
/// Adds a specific modifier to the player.
/// </summary>
/// <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)
{
return player.GetModifierComponent().AddModifier(modifier);
}
/// <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 type ID.
/// </summary>
/// <param name="player">The PlayerControl instance.</param>
/// <param name="typeId">The type 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 typeId, params object[] args)
{
return player.GetModifierComponent().AddModifier(typeId, args);
}
}
+102
View File
@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using MiraAPI.Utilities;
namespace MiraAPI.Modifiers;
/// <summary>
/// Factory for creating instances of a modifier. More efficient than using reflection.
/// </summary>
public static class ModifierFactory
{
private static readonly Dictionary<(Type, Type[]), Func<object[], BaseModifier>> _constructorCache = [];
private static Func<object[], BaseModifier> CreateConstructor(Type type, params object[] args)
{
var constructorInfo = type.GetBestConstructor(args) ?? throw new InvalidOperationException(
$"Could not find a constructor for type {type} with the specified arguments.");
var parameters = constructorInfo.GetParameters();
var argsParam = Expression.Parameter(typeof(object[]), "args");
var constructorParams = new Expression[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
{
var paramType = parameters[i].ParameterType;
var paramAccess = Expression.ArrayIndex(argsParam, Expression.Constant(i));
var convertedParam = Expression.Convert(paramAccess, paramType);
constructorParams[i] = convertedParam;
}
var newExpr = Expression.New(constructorInfo, constructorParams);
var lambda = Expression.Lambda<Func<object[], BaseModifier>>(newExpr, argsParam);
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)
{
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);
}
}
/// <summary>
/// Factory for creating instances of a modifier. More efficient than using reflection.
/// </summary>
/// <typeparam name="T">The modifier type.</typeparam>
public static class ModifierFactory<T> where T : BaseModifier
{
private static readonly Func<object[], T> Constructor = CreateConstructor();
private static Func<object[], T> CreateConstructor(params object[] args)
{
var constructorInfo = typeof(T).GetBestConstructor(args) ?? throw new InvalidOperationException(
$"Could not find a constructor for type {typeof(T)} with the specified arguments.");
var parameters = constructorInfo.GetParameters();
var argsParam = Expression.Parameter(typeof(object[]), "args");
var constructorParams = new Expression[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
{
var paramType = parameters[i].ParameterType;
var paramAccess = Expression.ArrayIndex(argsParam, Expression.Constant(i));
var convertedParam = Expression.Convert(paramAccess, paramType);
constructorParams[i] = convertedParam;
}
var newExpr = Expression.New(constructorInfo, constructorParams);
var lambda = Expression.Lambda<Func<object[], T>>(newExpr, argsParam);
return lambda.Compile();
}
/// <summary>
/// Creates an instance of the modifier.
/// </summary>
/// <param name="args">Parameters for the constructor.</param>
/// <returns>An instance of the modifier.</returns>
public static T CreateInstance(params object[] args)
{
return Constructor(args);
}
}
+17 -80
View File
@@ -3,11 +3,9 @@ using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using MiraAPI.Modifiers.Types;
using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using Reactor.Networking.Rpc;
using Reactor.Utilities;
using Reactor.Utilities.Extensions;
using Random = System.Random;
@@ -23,16 +21,16 @@ public static class ModifierManager
private static readonly Dictionary<Type, uint> TypeToIdModifierMap = [];
private static readonly Dictionary<int, List<uint>> PrioritiesToIdsMap = [];
private static uint _nextId;
private static uint _nextTypeId;
private static uint GetNextId()
private static uint GetNextTypeId()
{
_nextId++;
return _nextId;
_nextTypeId++;
return _nextTypeId;
}
/// <summary>
/// Gets the modifier type from the id.
/// Gets the modifier type from the type id.
/// </summary>
/// <param name="id">The ID.</param>
/// <returns>The Type of the modifier.</returns>
@@ -46,9 +44,14 @@ public static class ModifierManager
/// </summary>
/// <param name="type">The Type.</param>
/// <returns>The ID of the modifier.</returns>
public static uint? GetModifierId(Type type)
public static uint? GetModifierTypeId(Type type)
{
return TypeToIdModifierMap.GetValueOrDefault(type);
if (!TypeToIdModifierMap.TryGetValue(type, out var id))
{
return null;
}
return id;
}
internal static bool RegisterModifier(Type modifierType, MiraPluginInfo info)
@@ -58,10 +61,9 @@ public static class ModifierManager
return false;
}
IdToTypeModifierMap.Add(GetNextId(), modifierType);
TypeToIdModifierMap.Add(modifierType, _nextId);
IdToTypeModifierMap.Add(GetNextTypeId(), modifierType);
TypeToIdModifierMap.Add(modifierType, _nextTypeId);
var bm = (BaseModifier)FormatterServices.GetUninitializedObject(modifierType); // this probably isn't a great idea
bm.ModifierId = _nextId;
info.Modifiers.Add(bm);
if (!typeof(GameModifier).IsAssignableFrom(modifierType))
@@ -77,7 +79,7 @@ public static class ModifierManager
PrioritiesToIdsMap[priority] = list = [];
}
list.Add(_nextId);
list.Add(_nextTypeId);
return true;
}
@@ -139,7 +141,7 @@ public static class ModifierManager
var mod = Activator.CreateInstance(IdToTypeModifierMap[id]) as GameModifier;
var plr = plrs.Where(x => IsGameModifierValid(x, mod!, id)).Random();
if (plr == null)
if (!plr || plr == null)
{
Logger<MiraApiPlugin>.Warning($"Somehow valid players for modifier {mod!.ModifierName} disappeared");
}
@@ -154,71 +156,6 @@ public static class ModifierManager
private static bool IsGameModifierValid(PlayerControl player, GameModifier modifier, uint modifierId)
{
return (player.Data.Role is not ICustomRole role || role.IsModifierApplicable(modifier)) &&
modifier.IsModifierValidOn(player.Data.Role) &&
!player.HasModifier(modifierId);
}
internal static void SyncAllModifiers(int targetId = -1)
{
var data = new List<NetData>();
foreach (var player in PlayerControl.AllPlayerControls)
{
data.Add(GetPlayerModifiers(player));
}
Rpc<SyncModifiersRpc>.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<ModifierComponent>();
if (!modifierComponent)
{
continue;
}
modifierComponent.ClearModifiers();
foreach (var id in ids)
{
if (!IdToTypeModifierMap.TryGetValue(id, out var type))
{
Logger<MiraApiPlugin>.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<byte> bytes = [];
var modifierComponent = player.GetComponent<ModifierComponent>();
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);
}
}
+5 -3
View File
@@ -17,7 +17,7 @@ public abstract class TimedModifier : BaseModifier
/// <summary>
/// Gets a value indicating whether the timer should start automatically when added.
/// </summary>
public virtual bool AutoStart => false;
public virtual bool AutoStart => true;
/// <summary>
/// Gets a value indicating whether the modifier should be removed when the timer completes.
@@ -27,7 +27,9 @@ public abstract class TimedModifier : BaseModifier
/// <summary>
/// Called when the timer completes.
/// </summary>
public abstract void OnTimerComplete();
public virtual void OnTimerComplete()
{
}
/// <summary>
/// Gets or sets a value indicating whether the timer is active.
@@ -102,7 +104,7 @@ public abstract class TimedModifier : BaseModifier
if (RemoveOnComplete)
{
ModifierComponent?.RemoveModifier(ModifierId);
ModifierComponent?.RemoveModifier(this);
}
}
+32
View File
@@ -0,0 +1,32 @@
using System;
using Hazel;
using Reactor.Networking.Attributes;
using Reactor.Networking.Serialization;
namespace MiraAPI.Networking;
/// <summary>
/// Converter for <see cref="Guid"/>.
/// </summary>
[MessageConverter]
public class GuidConverter : MessageConverter<Guid>
{
/// <summary>
/// Writes a <see cref="Guid"/> to the writer.
/// </summary>
/// <inheritdoc />
public override void Write(MessageWriter writer, Guid value)
{
writer.WriteBytesAndSize(value.ToByteArray());
}
/// <summary>
/// Reads a <see cref="Guid"/> from the reader.
/// </summary>
/// <inheritdoc />
/// <returns>A GUID object.</returns>
public override Guid Read(MessageReader reader, Type objectType)
{
return new Guid(reader.ReadBytesAndSize());
}
}
@@ -0,0 +1,76 @@
using System;
using Hazel;
using MiraAPI.Modifiers;
using Reactor.Networking.Attributes;
using Reactor.Networking.Rpc;
using Reactor.Networking.Serialization;
namespace MiraAPI.Networking.Modifiers;
/// <summary>
/// Remote procedure call for adding a modifier.
/// </summary>
/// <param name="plugin">Mira plugin.</param>
/// <param name="id">RPC ID.</param>
[RegisterCustomRpc((uint)MiraRpc.AddModifier)]
public class AddModifierRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, ModifierData>(plugin, id)
{
/// <inheritdoc />
public override RpcLocalHandling LocalHandling => RpcLocalHandling.Before;
/// <inheritdoc />
public override void Write(MessageWriter writer, ModifierData data)
{
writer.WritePacked(data.TypeId);
writer.WriteBytesAndSize(data.UniqueId.ToByteArray());
writer.WritePacked(data.Args.Length);
foreach (var arg in data.Args)
{
writer.Write(arg.GetType().AssemblyQualifiedName);
writer.Serialize(arg);
}
}
/// <inheritdoc />
public override ModifierData Read(MessageReader reader)
{
var modId = reader.ReadPackedUInt32();
var guid = new Guid(reader.ReadBytesAndSize());
var argCount = reader.ReadPackedUInt32();
var objects = new object[argCount];
if (argCount > 0)
{
var types = new Type[argCount];
for (var i = 0; i < argCount; i++)
{
var name = reader.ReadString();
types[i] = Type.GetType(name) ?? throw new InvalidOperationException($"Type not found: {name}");
objects[i] = reader.Deserialize(types[i]);
}
}
return new ModifierData(modId, guid, objects);
}
/// <inheritdoc />
public override void Handle(PlayerControl player, ModifierData data)
{
var type = ModifierManager.GetModifierType(data.TypeId) ?? throw new InvalidOperationException($"Modifier type not found for ID {data.TypeId}.");
BaseModifier? modifier;
if (data.Args.Length > 0)
{
modifier = ModifierFactory.CreateInstance(type, data.Args);
}
else
{
modifier = Activator.CreateInstance(type) as BaseModifier;
if (modifier == null)
{
throw new InvalidOperationException($"Cannot add modifier {type.Name} because it is not a valid modifier.");
}
}
modifier.UniqueId = data.UniqueId;
player.AddModifier(modifier);
}
}
@@ -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;
/// <summary>
/// Converter for serializing and deserializing <see cref="BaseModifier"/> objects.
/// </summary>
[MessageConverter]
public class ModifierConverter : MessageConverter<BaseModifier>
{
/// <summary>
/// Writes a <see cref="BaseModifier"/> to the writer.
/// </summary>
/// <param name="writer">The writer to write to.</param>
/// <param name="value">The <see cref="BaseModifier"/> to write.</param>
public override void Write(MessageWriter writer, BaseModifier value)
{
writer.WriteNetObject(value.Player);
writer.WriteBytesAndSize(value.UniqueId.ToByteArray());
}
/// <summary>
/// Reads a <see cref="BaseModifier"/> from the reader.
/// </summary>
/// <param name="reader">The reader to read from.</param>
/// <param name="objectType">The type of the object to read.</param>
/// <returns>The <see cref="BaseModifier"/> that was read.</returns>
/// <exception cref="InvalidOperationException">Thrown if the modifier is not found.</exception>
public override BaseModifier Read(MessageReader reader, Type objectType)
{
var player = reader.ReadNetObject<PlayerControl>();
var guid = new Guid(reader.ReadBytesAndSize());
return player.GetModifier(guid) ?? throw new InvalidOperationException($"Modifier with GUID {guid} not found for player {player.NetId}.");
}
}
@@ -0,0 +1,11 @@
using System;
namespace MiraAPI.Networking.Modifiers;
/// <summary>
/// Modifier data for networking.
/// </summary>
/// <param name="TypeId">The ID of the modifier.</param>
/// <param name="UniqueId">The unique ID of the modifier.</param>
/// <param name="Args">Parameters for constructor.</param>
public record struct ModifierData(uint TypeId, Guid UniqueId, object[] Args);
-52
View File
@@ -1,52 +0,0 @@
using Hazel;
using MiraAPI.Modifiers;
using Reactor.Networking.Attributes;
using Reactor.Networking.Rpc;
namespace MiraAPI.Networking;
[RegisterCustomRpc((uint)MiraRpc.SyncModifiers)]
internal sealed class SyncModifiersRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, NetData[]>(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 ?? []);
}
}
+2 -4
View File
@@ -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;
/// <summary>
/// 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.
/// </summary>
[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]
@@ -2,7 +2,7 @@
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
namespace MiraAPI.Patches;
namespace MiraAPI.Patches.Events;
/// <summary>
/// General patches for the KillAnimation class.
+1 -1
View File
@@ -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;
@@ -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.TypeId.ToString(NumberFormatInfo.InvariantInfo);
taskAddButton.role = null;
taskAddButton.MyTask = null;
taskAddButton.SafePositionWorld = instance.SafePositionWorld;
@@ -336,14 +336,15 @@ internal static class TaskAdderPatches
taskAddButton.Button.OnClick = new Button.ButtonClickedEvent();
taskAddButton.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() =>
{
if (PlayerControl.LocalPlayer.HasModifier(modifier.ModifierId))
var id = modifier.TypeId;
if (PlayerControl.LocalPlayer.HasModifier(id))
{
PlayerControl.LocalPlayer.RemoveModifier(modifier.ModifierId);
PlayerControl.LocalPlayer.RemoveModifier(id);
taskAddButton.Overlay.enabled = false;
}
else
{
PlayerControl.LocalPlayer.AddModifier(ModifierManager.GetModifierType(modifier.ModifierId)!);
PlayerControl.LocalPlayer.AddModifier(id);
taskAddButton.Overlay.enabled = true;
}
}));
@@ -1,7 +1,7 @@
using System.Linq;
using HarmonyLib;
using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
using MiraAPI.Utilities;
namespace MiraAPI.Patches.Modifiers;
@@ -24,7 +24,7 @@ public static class EndGameDidWinPatch
var didWin = networkedPlayerInfo.Role.DidWin(gameOverReason);
var modifierWin = networkedPlayerInfo.Object?.GetModifierComponent()?.ActiveModifiers.OfType<GameModifier>()
var modifierWin = networkedPlayerInfo.Object?.GetModifierComponent().GetModifiers<GameModifier>()
.FirstOrDefault(x => x.DidWin(gameOverReason) != null)?.DidWin(gameOverReason);
if (modifierWin != null)
{
+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)
+3 -3
View File
@@ -1,8 +1,8 @@
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Usables;
using MiraAPI.Modifiers;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Patches;
@@ -35,8 +35,8 @@ public static class VentPatches
var canVent = role is ICustomRole customRole ? customRole.Configuration.CanUseVent : role.CanVent;
couldUse = canVent;
var modifiers = @object.GetModifierComponent()?.ActiveModifiers;
if (modifiers is { Count: > 0 })
var modifiers = @object.GetModifierComponent().ActiveModifiers;
if (modifiers.Count > 0)
{
switch (canVent)
{
+59 -2
View File
@@ -1,13 +1,12 @@
using HarmonyLib;
using MiraAPI.GameOptions;
using MiraAPI.Modifiers;
using MiraAPI.Networking;
using MiraAPI.Roles;
using Reactor.Networking.Attributes;
using Reactor.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using TMPro;
using UnityEngine;
@@ -40,6 +39,64 @@ public static class Extensions
BitConverter.GetBytes(count.Value).AddRangeToArray(BitConverter.GetBytes(chance.Value)));
}
/// <summary>
/// Gets the best constructor for a type based on the specified arguments.
/// </summary>
/// <param name="type">The type to get the constructor from.</param>
/// <param name="args">The arguments to pass into the constructor.</param>
/// <returns>The best constructor.</returns>
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();
}
/// <summary>
/// Gets the constructors of a type that match the specified arguments.
/// </summary>
/// <param name="type">The type to get constructors from.</param>
/// <param name="args">The arguments to pass into the constructor.</param>
/// <returns>A collection of valid constructors.</returns>
public static IEnumerable<ConstructorInfo> 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]));
});
}
/// <summary>
/// Calculates the inheritance distance from the given type to its target base type.
/// Lower values mean the type is a closer match.
/// </summary>
/// <param name="from">The derived type.</param>
/// <param name="to">The base type.</param>
/// <returns>The distance between the types.</returns>
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;
}
/// <summary>
/// Enables stencil masking on a TMP text object.
/// </summary>
-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);
}
}