using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Events; public class StatusEffectHandler : MonoBehaviour { [Header("Set by code:")] public Taggable taggable; //list of ALL active effects regardless of type public List activeEffects = new List(); //explanation: slow => list of slows from each source public Dictionary> activeEffectsByType = new Dictionary>(); //explanation: entityA => list of every effect type applied by entityA //public Dictionary> activeEffectsBySource = new Dictionary>(); // Temporary list to avoid modifying collection during iteration protected List effectsToRemove = new List(); protected virtual void Awake() { taggable = GetComponentInParent(); } public virtual void ApplyEffect(StatusEffect effect, Taggable user, Taggable target, BaseAbility sourceAbility, int numberOfStacks) { if (numberOfStacks <= 0) { Debug.LogWarning($"Attempted to apply {effect.name} with {numberOfStacks} stacks"); return; } if (TryGetExistingEffect(effect, user, out RuntimeEffectInstance existingEffect)) { RefreshEffect(existingEffect, numberOfStacks); } else { ApplyNewEffect(effect, user, target, sourceAbility, numberOfStacks); } } protected bool TryGetExistingEffect(StatusEffect effect, Taggable user, out RuntimeEffectInstance existingEffect) { if (activeEffectsByType.TryGetValue(effect, out var effectList)) { existingEffect = effectList.FirstOrDefault(e => e.user == user); return existingEffect != null; } existingEffect = null; return false; } protected virtual void ApplyNewEffect(StatusEffect effect, Taggable user, Taggable target, BaseAbility sourceAbility, int numberOfStacks) { RuntimeEffectInstance runtimeEffect = CreateRuntimeEffect(effect, user, target, sourceAbility); // Initialize stacks based on stacking rule switch (effect.stackingRule) { case StackingRule.StackUnlimited: runtimeEffect.numberOfStacks = numberOfStacks; break; case StackingRule.Refresh_And_ReplaceIfHigher: runtimeEffect.numberOfStacks = 1; break; } UpdateCollections(effect, runtimeEffect); runtimeEffect.OnEffectFirstApplied(numberOfStacks); } protected virtual void RefreshEffect(RuntimeEffectInstance runtimeEffect, int numberOfStacks) { switch (runtimeEffect.sourceEffect.stackingRule) { case StackingRule.StackUnlimited: runtimeEffect.numberOfStacks += numberOfStacks; runtimeEffect.endEffectTime = Time.time + runtimeEffect.duration; runtimeEffect.OnEffectRefreshed(numberOfStacks); break; case StackingRule.Refresh_And_ReplaceIfHigher: // Keep stacks at 1, just refresh duration runtimeEffect.endEffectTime = Time.time + runtimeEffect.duration; runtimeEffect.OnEffectRefreshed(0); // 0 stacks added, just refreshed break; } } protected virtual RuntimeEffectInstance CreateRuntimeEffect(StatusEffect effect, Taggable user, Taggable target, BaseAbility sourceAbility) { RuntimeEffectInstance runtimeEffect = effect.CreateEffectInstance(); runtimeEffect.user = user; runtimeEffect.target = target; runtimeEffect.sourceBroker = user.Broker; runtimeEffect.targetBroker = target.Broker; runtimeEffect.sourceEffect = effect; runtimeEffect.sourceAbility = sourceAbility; runtimeEffect.duration = effect.duration; runtimeEffect.endEffectTime = Time.time + effect.duration; return runtimeEffect; } protected virtual void UpdateCollections(StatusEffect effect, RuntimeEffectInstance runtimeEffect) { // Add to type-specific dictionary if (!activeEffectsByType.ContainsKey(effect)) { activeEffectsByType[effect] = new List(); } activeEffectsByType[effect].Add(runtimeEffect); // Add to global list activeEffects.Add(runtimeEffect); } protected virtual void Update() { UpdateEffects(Time.deltaTime); CheckExpiredEffects(); } // Hook for child classes to add custom update logic protected virtual void UpdateEffects(float deltaTime) { // Base class does nothing - child classes can override for ticking } protected virtual void CheckExpiredEffects() { effectsToRemove.Clear(); float currentTime = Time.time; // Collect expired effects for (int i = 0; i < activeEffects.Count; i++) { if (currentTime >= activeEffects[i].endEffectTime) { effectsToRemove.Add(activeEffects[i]); } } // Remove all expired effects for (int i = 0; i < effectsToRemove.Count; i++) { RemoveEffect(effectsToRemove[i]); } } public void RemoveEffect(RuntimeEffectInstance runtimeEffect) { runtimeEffect.OnEffectRemoved(); RemoveFromCollections(runtimeEffect); CObjectPool.Release(runtimeEffect); } protected virtual void RemoveFromCollections(RuntimeEffectInstance runtimeEffect) { // Remove from global list activeEffects.Remove(runtimeEffect); // Remove from type-specific dictionary if (activeEffectsByType.TryGetValue(runtimeEffect.sourceEffect, out var effectList)) { effectList.Remove(runtimeEffect); // Clean up empty lists to avoid memory bloat if (effectList.Count == 0) { activeEffectsByType.Remove(runtimeEffect.sourceEffect); } } } public bool HasEffect(StatusEffect effect) { return activeEffectsByType.ContainsKey(effect) && activeEffectsByType[effect].Count > 0; } }