using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Globalization; 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 => Modifiers.ToImmutableList(); [HideFromIl2Cpp] private List Modifiers { get; set; } = []; private PlayerControl? _player; private TextMeshPro? _modifierText; private readonly List _toRemove = []; private readonly List _toAdd = []; internal void ClearModifiers() { _toRemove.AddRange(Modifiers); } private void Start() { _player = GetComponent(); 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() { foreach (var modifier in _toRemove) { modifier.OnDeactivate(); Modifiers.Remove(modifier); } foreach (var modifier in _toAdd) { Modifiers.Add(modifier); modifier.Initialized = true; modifier.OnActivate(); if (modifier is TimedModifier { AutoStart: true } timer) { timer.StartTimer(); } } if (_toAdd.Count > 0 || _toRemove.Count > 0) { if (_player?.AmOwner == true) { HudManager.Instance?.SetHudActive(_player, _player.Data.Role, HudManager.Instance.TaskPanel.isActiveAndEnabled); } _toAdd.Clear(); _toRemove.Clear(); } foreach (var modifier in Modifiers) { modifier.FixedUpdate(); } } private void Update() { foreach (var modifier in Modifiers) { modifier.Update(); } if (!_modifierText || _player?.AmOwner == false) { return; } var filteredModifiers = Modifiers.Where(mod => !mod.HideOnUi).ToArray(); if (filteredModifiers.Length != 0 && !MeetingHud.Instance) { var stringBuild = new StringBuilder(); foreach (var mod in filteredModifiers) { stringBuild.Append(CultureInfo.InvariantCulture, $"\n{mod.GetHudString()}"); } _modifierText!.text = $"Modifiers:{stringBuild}"; } else if (_modifierText!.text != string.Empty) { _modifierText.text = string.Empty; } } /// /// 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 Modifiers.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 Modifiers.Where(x => x.GetType() == type && (predicate == null || predicate(x))); } /// /// Gets a collection of modifiers by type ID. /// /// The modifier's type ID. /// The predicate to check the modifier by. /// A collection of modifiers. [HideFromIl2Cpp] public IEnumerable GetModifiers(uint id, Func? predicate=null) { var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException( $"Cannot get modifier with id {id} because it is not registered."); return GetModifiers(type, predicate); } /// /// Tries to get a modifier by its type. /// /// The modifier or null. /// The predicate to check the modifier by. /// The Type of the Modifier. /// True if the modifier was found, false otherwise. [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 Modifiers.Find(x => x.UniqueId == modifierGuid); } /// /// Removes a modifier from the player. /// /// The modifier type. /// The predicate to check the modifier by. [HideFromIl2Cpp] public void RemoveModifier(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 = Modifiers.Where(x => x.GetType() == type && (predicate == null || predicate(x))).ToList(); if (modifiers.Count > 1) { throw new InvalidOperationException($"Cannot remove modifier {type.Name} because there are multiple instances of that modifier."); } var modifier = modifiers.FirstOrDefault(); if (modifier is null) { Logger.Error($"Cannot remove modifier {type.Name} because it is not active."); return; } RemoveModifier(modifier); } /// /// Removes a modifier from the player. /// /// The modifier object. [HideFromIl2Cpp] public void RemoveModifier(BaseModifier modifier) { if (!Modifiers.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 && Modifiers.Find(x => x.TypeId == id) != null) { Logger.Error($"Player already has modifier with id {id}!"); return null; } if (Modifiers.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 Modifiers.Exists(x => x is T modifier && (predicate == null || predicate(modifier))); } /// /// Checks if a player has an active modifier by its type. /// /// The modifier type. /// The predicate to check the modifier. /// True if the Modifier is present, false otherwise. [HideFromIl2Cpp] public bool HasModifier(Type type, Func? predicate=null) { return Modifiers.Exists(x => x.GetType() == type && (predicate == null || predicate(x))); } /// /// Checks if a player has an active modifier by its type ID. /// /// The modifier's type ID. /// The predicate to check the modifier. /// True if the modifier is present, false otherwise. [HideFromIl2Cpp] public bool HasModifier(uint id, Func? predicate=null) { var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException( $"Cannot get modifier with id {id} because it is not registered."); return HasModifier(type, predicate); } /// /// Checks if a player has an active modifier by its unique ID. /// /// The modifier's guid. /// True if the modifier is present, false otherwise. [HideFromIl2Cpp] public bool HasModifier(Guid id) { return Modifiers.Exists(x => x.UniqueId == id); } }