using System.Collections.Generic;
using UnityEngine;
using Colosseum.Abnormalities;
namespace Colosseum.Combat
{
///
/// 개별 보호막 인스턴스입니다.
/// 같은 종류의 보호막은 하나로 합쳐지고, 다른 종류의 보호막은 독립적으로 유지됩니다.
///
[System.Serializable]
public sealed class ActiveShield
{
[SerializeField] private AbnormalityData shieldType;
[SerializeField] private float remainingAmount;
[SerializeField] private float remainingDuration;
[SerializeField] private bool isPermanent;
[SerializeField] private GameObject source;
public AbnormalityData ShieldType => shieldType;
public float RemainingAmount => remainingAmount;
public float RemainingDuration => isPermanent ? 0f : remainingDuration;
public bool IsPermanent => isPermanent;
public bool IsExpired => !isPermanent && remainingDuration <= 0f;
public bool IsDepleted => remainingAmount <= 0f;
public GameObject Source => source;
public ActiveShield(AbnormalityData shieldType, float amount, float duration, GameObject source)
{
this.shieldType = shieldType;
remainingAmount = Mathf.Max(0f, amount);
isPermanent = duration <= 0f;
remainingDuration = isPermanent ? 0f : duration;
this.source = source;
}
///
/// 같은 종류 보호막이 다시 적용되었을 때 양을 더하고 지속시간을 갱신합니다.
///
public float Add(float amount, float duration, GameObject source)
{
float appliedAmount = Mathf.Max(0f, amount);
remainingAmount += appliedAmount;
if (duration <= 0f)
{
isPermanent = true;
remainingDuration = 0f;
}
else if (!isPermanent)
{
remainingDuration = duration;
}
if (source != null)
{
this.source = source;
}
return appliedAmount;
}
///
/// 지속시간을 감소시킵니다.
///
public bool Tick(float deltaTime)
{
if (isPermanent || deltaTime <= 0f)
return false;
remainingDuration = Mathf.Max(0f, remainingDuration - deltaTime);
return IsExpired;
}
///
/// 들어오는 피해를 흡수합니다.
///
public float Consume(float incomingDamage)
{
if (incomingDamage <= 0f || remainingAmount <= 0f)
return 0f;
float absorbed = Mathf.Min(remainingAmount, incomingDamage);
remainingAmount = Mathf.Max(0f, remainingAmount - absorbed);
return absorbed;
}
}
///
/// 대상이 가진 보호막 인스턴스를 관리합니다.
///
[System.Serializable]
public sealed class ShieldCollection
{
private readonly List activeShields = new List();
public IReadOnlyList ActiveShields => activeShields;
public float TotalAmount
{
get
{
float total = 0f;
for (int i = 0; i < activeShields.Count; i++)
{
total += activeShields[i].RemainingAmount;
}
return total;
}
}
///
/// 보호막을 적용합니다. 같은 종류는 자기 자신만 합산 및 갱신합니다.
///
public float ApplyShield(AbnormalityData shieldType, float amount, float duration, GameObject source)
{
if (amount <= 0f)
return 0f;
ActiveShield existingShield = FindShield(shieldType);
if (existingShield != null)
{
return existingShield.Add(amount, duration, source);
}
activeShields.Add(new ActiveShield(shieldType, amount, duration, source));
return amount;
}
///
/// 보호막이 적용된 순서대로 피해를 흡수하고 남은 피해를 반환합니다.
///
public float ConsumeDamage(float incomingDamage)
{
if (incomingDamage <= 0f || activeShields.Count == 0)
return incomingDamage;
float remainingDamage = incomingDamage;
for (int i = 0; i < activeShields.Count && remainingDamage > 0f; i++)
{
remainingDamage -= activeShields[i].Consume(remainingDamage);
}
CleanupInactiveShields();
return Mathf.Max(0f, remainingDamage);
}
///
/// 지속시간 경과를 처리합니다.
///
public bool Tick(float deltaTime)
{
bool changed = false;
for (int i = activeShields.Count - 1; i >= 0; i--)
{
ActiveShield shield = activeShields[i];
bool expired = shield.Tick(deltaTime);
if (!expired && !shield.IsDepleted)
continue;
activeShields.RemoveAt(i);
changed = true;
}
return changed;
}
///
/// 모든 보호막을 제거합니다.
///
public void Clear()
{
activeShields.Clear();
}
private ActiveShield FindShield(AbnormalityData shieldType)
{
for (int i = 0; i < activeShields.Count; i++)
{
if (activeShields[i].ShieldType == shieldType)
return activeShields[i];
}
return null;
}
private void CleanupInactiveShields()
{
for (int i = activeShields.Count - 1; i >= 0; i--)
{
if (!activeShields[i].IsExpired && !activeShields[i].IsDepleted)
continue;
activeShields.RemoveAt(i);
}
}
}
///
/// 보호막 수치와 보호막 이상상태를 동기화하는 유틸리티입니다.
///
public static class ShieldAbnormalityUtility
{
///
/// 활성 보호막 목록에 맞춰 보호막 이상상태를 적용하거나 제거합니다.
///
public static void SyncShieldAbnormalities(
AbnormalityManager abnormalityManager,
IReadOnlyList activeShields,
GameObject defaultSource)
{
if (abnormalityManager == null)
return;
HashSet desiredShieldStates = new HashSet();
Dictionary shieldSources = new Dictionary();
if (activeShields != null)
{
for (int i = 0; i < activeShields.Count; i++)
{
ActiveShield shield = activeShields[i];
if (shield == null || shield.RemainingAmount <= 0f || shield.ShieldType == null)
continue;
desiredShieldStates.Add(shield.ShieldType);
if (!shieldSources.ContainsKey(shield.ShieldType))
{
shieldSources.Add(shield.ShieldType, shield.Source != null ? shield.Source : defaultSource);
}
}
}
foreach (AbnormalityData shieldState in desiredShieldStates)
{
if (abnormalityManager.HasAbnormality(shieldState))
continue;
GameObject source = shieldSources.TryGetValue(shieldState, out GameObject resolvedSource)
? resolvedSource
: defaultSource;
abnormalityManager.ApplyAbnormality(shieldState, source);
}
IReadOnlyList activeAbnormalities = abnormalityManager.ActiveAbnormalities;
List removeList = new List();
for (int i = 0; i < activeAbnormalities.Count; i++)
{
ActiveAbnormality activeAbnormality = activeAbnormalities[i];
if (activeAbnormality?.Data == null || !activeAbnormality.Data.isShieldState)
continue;
if (desiredShieldStates.Contains(activeAbnormality.Data))
continue;
removeList.Add(activeAbnormality.Data);
}
for (int i = 0; i < removeList.Count; i++)
{
abnormalityManager.RemoveAbnormality(removeList[i]);
}
}
}
}