using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.Modifiers.Types;
using MiraAPI.Utilities;
using Reactor.Utilities;
using Reactor.Utilities.Attributes;
using TMPro;
using UnityEngine;
namespace MiraAPI.Modifiers;
///
/// The component for handling modifiers.
///
[RegisterInIl2Cpp]
public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
{
///
/// Gets the active modifiers on the player.
///
[HideFromIl2Cpp]
public ImmutableList ActiveModifiers { get; private set; } = ImmutableList.Empty;
[HideFromIl2Cpp]
private List Modifiers { get; set; } = [];
private PlayerControl _player = null!;
private TextMeshPro _modifierText = null!;
private readonly List _toRemove = [];
private readonly List _toAdd = [];
internal void ClearModifiers()
{
_toRemove.AddRange(Modifiers);
}
private void Awake()
{
_player = GetComponent();
if (_player == null)
{
Logger.Error("ModifierComponent could not find PlayerControl on the same GameObject.");
Destroy(this);
return;
}
ModifierExtensions.ModifierComponents.Add(_player, this);
Modifiers = [];
if (!_player.AmOwner)
{
return;
}
_modifierText = Helpers.CreateTextLabel("ModifierText", HudManager.Instance.transform, AspectPosition.EdgeAlignments.RightTop, new Vector3(10.1f, 3.5f, 0), textAlignment: TextAlignmentOptions.Right);
_modifierText.verticalAlignment = VerticalAlignmentOptions.Top;
}
private void FixedUpdate()
{
var removed = _toRemove.Count > 0;
foreach (var modifier in _toRemove.ToArray())
{
_toRemove.Remove(modifier);
modifier.OnDeactivate();
Modifiers.Remove(modifier);
}
var added = _toAdd.Count > 0;
foreach (var modifier in _toAdd.ToArray())
{
_toAdd.Remove(modifier);
Modifiers.Add(modifier);
modifier.Initialized = true;
modifier.OnActivate();
if (modifier is TimedModifier { AutoStart: true } timer)
{
timer.StartTimer();
}
}
if (removed || added)
{
if (_player.AmOwner && HudManager.InstanceExists)
{
HudManager.Instance.SetHudActive(_player, _player.Data.Role, HudManager.Instance.TaskPanel.isActiveAndEnabled);
}
ActiveModifiers = Modifiers.ToImmutableList();
}
foreach (var modifier in ActiveModifiers)
{
modifier.FixedUpdate();
}
}
private void Update()
{
foreach (var modifier in ActiveModifiers)
{
modifier.Update();
}
if (!_modifierText || !_player.AmOwner)
{
return;
}
if (MeetingHud.Instance)
{
return;
}
var showText = false;
var builder = new StringBuilder("Modifiers:\n");
foreach (var modifier in ActiveModifiers)
{
if (modifier.HideOnUi)
{
continue;
}
showText = true;
builder.AppendLine(modifier.GetHudString());
}
_modifierText.text = showText ? builder.ToString() : string.Empty;
}
private void OnDestroy()
{
// Remove this component from the global list when destroyed
ModifierExtensions.ModifierComponents.Remove(_player);
}
///
/// Gets a collection of modifiers by type.
///
/// The predicate to check the modifier by.
/// The Type of the Modifier.
/// A collection of modifiers.
[HideFromIl2Cpp]
public IEnumerable GetModifiers(Func? predicate=null) where T : BaseModifier
{
return ActiveModifiers.OfType().Where(x => predicate == null || predicate(x));
}
///
/// Gets a collection of modifiers by type.
///
/// The modifier type.
/// The predicate to check the modifier by.
/// A collection of modifiers.
[HideFromIl2Cpp]
public IEnumerable GetModifiers(Type type, Func? predicate=null)
{
return ActiveModifiers.Where(x => x.GetType() == type && (predicate == null || predicate(x)));
}
///
/// Gets a collection of modifiers by type ID.
///
/// The modifier's type ID.
/// The predicate to check the modifier by.
/// A collection of modifiers.
[HideFromIl2Cpp]
public IEnumerable GetModifiers(uint id, Func? predicate=null)
{
var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
$"Cannot get modifier with id {id} because it is not registered.");
return GetModifiers(type, predicate);
}
///
/// Tries to get a modifier by its type.
///
/// The modifier or null.
/// The predicate to check the modifier by.
/// The Type of the Modifier.
/// True if the modifier was found, false otherwise.
[HideFromIl2Cpp]
public bool TryGetModifier([NotNullWhen(true)] out T? modifier, Func? predicate = null) where T : BaseModifier
{
modifier = GetModifier(predicate);
return modifier != null;
}
///
/// Tries to get a modifier by its type.
///
/// The modifier type.
/// The modifier or null.
/// The predicate to check the modifier by.
/// True if the modifier was found, false otherwise.
[HideFromIl2Cpp]
public bool TryGetModifier(Type type, [NotNullWhen(true)] out BaseModifier? modifier, Func? predicate = null)
{
modifier = GetModifier(type, predicate);
return modifier != null;
}
///
/// Tries to get a modifier by its type ID.
///
/// The modifier type ID.
/// The modifier or null.
/// The predicate to check the modifier by.
/// True if the modifier was found, false otherwise.
[HideFromIl2Cpp]
public bool TryGetModifier(uint id, [NotNullWhen(true)] out BaseModifier? modifier, Func? predicate = null)
{
modifier = GetModifier(id, predicate);
return modifier != null;
}
///
/// Tries to get a modifier by its unique ID.
///
/// The modifier unique ID.
/// The modifier or null.
/// True if the modifier was found, false otherwise.
[HideFromIl2Cpp]
public bool TryGetModifier(Guid modifierGuid, [NotNullWhen(true)] out BaseModifier? modifier)
{
modifier = GetModifier(modifierGuid);
return modifier != null;
}
///
/// Gets a modifier by its type.
///
/// The predicate to check the modifier by.
/// The Type of the Modifier.
/// The Modifier if it is found, null otherwise.
[HideFromIl2Cpp]
public T? GetModifier(Func? predicate = null) where T : BaseModifier
{
return GetModifiers(predicate).FirstOrDefault();
}
///
/// Gets a modifier by its type.
///
/// The modifier type.
/// The predicate to check the modifier by.
/// The Modifier if it is found, null otherwise.
[HideFromIl2Cpp]
public BaseModifier? GetModifier(Type type, Func? predicate = null)
{
return GetModifiers(type).FirstOrDefault(predicate ?? (_ => true));
}
///
/// Gets a modifier by its type ID.
///
/// The modifier ID.
/// The predicate to check the modifier by.
/// The Modifier if it is found, null otherwise.
[HideFromIl2Cpp]
public BaseModifier? GetModifier(uint id, Func? predicate = null)
{
var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
$"Cannot get modifier with id {id} because it is not registered.");
return GetModifier(type, predicate);
}
///
/// Gets a modifier by unique ID.
///
/// The modifier's unique ID.
/// The modifier if it is found, or null.
[HideFromIl2Cpp]
public BaseModifier? GetModifier(Guid modifierGuid)
{
return ActiveModifiers.Find(x => x.UniqueId == modifierGuid);
}
///
/// Removes a modifier from the player.
///
/// The modifier type.
/// The predicate to check the modifier by.
[HideFromIl2Cpp]
public void RemoveModifier(Func? predicate = null) where T : BaseModifier
{
RemoveModifier(typeof(T), x => predicate == null || predicate((T)x));
}
///
/// Removes a modifier from the player.
///
/// The modifier type.
/// The predicate to check the modifier by.
[HideFromIl2Cpp]
public void RemoveModifier(Type type, Func? predicate = null)
{
var modifiers = ActiveModifiers.Where(x => x.GetType() == type && (predicate == null || predicate(x))).ToList();
if (modifiers.Count > 1)
{
throw new InvalidOperationException($"Cannot remove modifier {type.Name} because there are multiple instances of that modifier.");
}
var modifier = modifiers.FirstOrDefault();
if (modifier is null)
{
Logger.Error($"Cannot remove modifier {type.Name} because it is not active.");
return;
}
RemoveModifier(modifier);
}
///
/// Removes a modifier from the player.
///
/// The modifier object.
[HideFromIl2Cpp]
public void RemoveModifier(BaseModifier modifier)
{
if (!ActiveModifiers.Contains(modifier))
{
Logger.Error($"Cannot remove modifier {modifier.ModifierName} because it is not active on this player.");
return;
}
_toRemove.Add(modifier);
}
///
/// Removes a modifier from the player.
///
/// The modifier's type ID.
/// The predicate to check the modifier by.
[HideFromIl2Cpp]
public void RemoveModifier(uint typeId, Func? predicate = null)
{
var type = ModifierManager.GetModifierType(typeId) ?? throw new InvalidOperationException(
$"Cannot remove modifier with id {typeId} because it is not registered.");
RemoveModifier(type, predicate);
}
///
/// Removes a modifier from the player.
///
/// The modifier's unique ID.
[HideFromIl2Cpp]
public void RemoveModifier(Guid uniqueId)
{
var modifier = GetModifier(uniqueId);
if (modifier == null)
{
Logger.Error($"Cannot remove modifier with unique id {uniqueId} because it is not active.");
return;
}
RemoveModifier(modifier);
}
///
/// Adds a modifier to the player.
///
/// The arguments to initialize the modifier constructor with.
/// The Type of the modifier.
/// The new modifier.
[HideFromIl2Cpp]
public T? AddModifier(params object[] args) where T : BaseModifier
{
return AddModifier(typeof(T), args) as T;
}
///
/// Adds a modifier to the player.
///
/// The modifier to add.
/// The modifier that was added.
[HideFromIl2Cpp]
public BaseModifier? AddModifier(BaseModifier modifier)
{
// TODO: Make a proper synchronization system.
if (LobbyBehaviour.Instance)
{
Logger.Warning($"Modifiers added in the lobby won't sync to new players!");
}
var id = modifier.TypeId;
if (modifier.Unique && ActiveModifiers.Find(x => x.TypeId == id) != null)
{
Logger.Error($"Player already has modifier with id {id}!");
return null;
}
if (ActiveModifiers.Contains(modifier))
{
Logger.Error($"Player already has this modifier!");
return null;
}
_toAdd.Add(modifier);
modifier.Player = _player;
modifier.ModifierComponent = this;
if (modifier.UniqueId == Guid.Empty)
{
modifier.UniqueId = Guid.NewGuid();
}
return modifier;
}
///
/// Adds a modifier to the player.
///
/// The modifier type.
/// The arguments to initialize the modifier constructor with.
/// The modifier that was added.
[HideFromIl2Cpp]
public BaseModifier? AddModifier(Type type, params object[] args)
{
BaseModifier? modifier;
if (args.Length > 0)
{
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.");
}
}
return AddModifier(modifier);
}
///
/// Adds a modifier to the player.
///
/// The ID of the modifier.
/// The arguments to initialize the modifier constructor with.
/// The modifier if it was created, or null if it failed.
[HideFromIl2Cpp]
public BaseModifier? AddModifier(uint id, params object[] args)
{
var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
$"Cannot add modifier with id {id} because it is not registered.");
return AddModifier(type, args);
}
///
/// Checks if a player has an active modifier by its type.
///
/// The predicate to check the modifier.
/// The Type of the Modifier.
/// True if the Modifier is present, false otherwise.
[HideFromIl2Cpp]
public bool HasModifier(Func? predicate=null) where T : BaseModifier
{
return ActiveModifiers.Exists(x => x is T modifier && (predicate == null || predicate(modifier)));
}
///
/// Checks if a player has an active modifier by its type.
///
/// The modifier type.
/// The predicate to check the modifier.
/// True if the Modifier is present, false otherwise.
[HideFromIl2Cpp]
public bool HasModifier(Type type, Func? predicate=null)
{
return ActiveModifiers.Exists(x => x.GetType() == type && (predicate == null || predicate(x)));
}
///
/// Checks if a player has an active modifier by its type ID.
///
/// The modifier's type ID.
/// The predicate to check the modifier.
/// True if the modifier is present, false otherwise.
[HideFromIl2Cpp]
public bool HasModifier(uint id, Func? predicate=null)
{
var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
$"Cannot get modifier with id {id} because it is not registered.");
return HasModifier(type, predicate);
}
///
/// Checks if a player has an active modifier by its unique ID.
///
/// The modifier's guid.
/// True if the modifier is present, false otherwise.
[HideFromIl2Cpp]
public bool HasModifier(Guid id)
{
return ActiveModifiers.Exists(x => x.UniqueId == id);
}
///
/// Checks if a player has an active modifier by its type.
///
/// Whether to check inactive modifiers (those pending to be added).
/// The predicate to check the modifier.
/// The Type of the Modifier.
/// True if the Modifier is present, false otherwise.
[HideFromIl2Cpp]
public bool HasModifier(bool checkInactive, Func? predicate=null) where T : BaseModifier
{
return ActiveModifiers.Exists(MatchExpr) || (checkInactive && _toAdd.Exists(MatchExpr));
bool MatchExpr(BaseModifier bm) => bm is T modifier && (predicate == null || predicate(modifier));
}
///
/// Checks if a player has an active modifier by its type.
///
/// The modifier type.
/// Whether to check inactive modifiers (those pending to be added).
/// The predicate to check the modifier.
/// True if the Modifier is present, false otherwise.
[HideFromIl2Cpp]
public bool HasModifier(Type type, bool checkInactive, Func? predicate=null)
{
return ActiveModifiers.Exists(MatchExpr) || (checkInactive && _toAdd.Exists(MatchExpr));
bool MatchExpr(BaseModifier bm) => bm.GetType() == type && (predicate == null || predicate(bm));
}
///
/// Checks if a player has an active modifier by its type ID.
///
/// The modifier's type ID.
/// Whether to check inactive modifiers (those pending to be added).
/// The predicate to check the modifier.
/// True if the modifier is present, false otherwise.
[HideFromIl2Cpp]
public bool HasModifier(uint id, bool checkInactive, Func? predicate=null)
{
var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
$"Cannot get modifier with id {id} because it is not registered.");
return HasModifier(type, checkInactive, predicate);
}
///
/// Checks if a player has an active modifier by its unique ID.
///
/// The modifier's guid.
/// Whether to check inactive modifiers (those pending to be added).
/// True if the modifier is present, false otherwise.
[HideFromIl2Cpp]
public bool HasModifier(Guid id, bool checkInactive)
{
return ActiveModifiers.Exists(MatchExpr) || (checkInactive && _toAdd.Exists(MatchExpr));
bool MatchExpr(BaseModifier bm) => bm.UniqueId == id;
}
}