191 lines
6.4 KiB
C#
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;
|
|
}
|
|
}
|
|
|
|
|