191 lines
6.4 KiB
C#

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<RuntimeEffectInstance> activeEffects = new List<RuntimeEffectInstance>();
//explanation: slow => list of slows from each source
public Dictionary<StatusEffect, List<RuntimeEffectInstance>> activeEffectsByType = new Dictionary<StatusEffect, List<RuntimeEffectInstance>>();
//explanation: entityA => list of every effect type applied by entityA
//public Dictionary<Taggable, List<RuntimeEffectInstance>> activeEffectsBySource = new Dictionary<Taggable, List<RuntimeEffectInstance>>();
// Temporary list to avoid modifying collection during iteration
protected List<RuntimeEffectInstance> effectsToRemove = new List<RuntimeEffectInstance>();
protected virtual void Awake()
{
taggable = GetComponentInParent<Taggable>();
}
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<RuntimeEffectInstance>();
}
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<RuntimeEffectInstance>.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;
}
}