- 경직 면역 이상상태와 시전 시작 효과를 추가해 철벽, 방어 태세, 치유, 광역 치유, 보호막에 데이터 기반 시전 보호를 연결 - AbnormalityManager와 HitReactionController가 경직 면역 상태를 존중하도록 보강해 일반 피격 반응으로 인한 즉시 취소를 줄임 - SkillData에 castStartEffects를 추가하고 SkillController가 시전 시작 효과를 실행하도록 확장 - 드로그전 재검증에서 철벽, 치유, 광역 치유가 실제 전투 중 취소 없이 완료되는 것을 확인하고 보호막의 후속 피격 체감을 추가 점검 대상으로 정리 - HUD/문서 반영 과정에서 필요한 TMP_MaruBuri, TMP_SuseongBatang 아틀라스 갱신을 함께 포함
693 lines
23 KiB
C#
693 lines
23 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using Unity.Netcode;
|
|
using Colosseum.Stats;
|
|
using Colosseum.Player;
|
|
using Colosseum.Skills;
|
|
|
|
namespace Colosseum.Abnormalities
|
|
{
|
|
/// <summary>
|
|
/// 캐릭터에 부착되어 이상 상태를 관리하는 컴포넌트
|
|
/// 버프/디버프의 적용, 제거, 주기적 효과를 처리합니다.
|
|
/// </summary>
|
|
public class AbnormalityManager : NetworkBehaviour
|
|
{
|
|
[Header("References")]
|
|
[Tooltip("CharacterStats 컴포넌트 (없으면 자동 검색)")]
|
|
[SerializeField] private CharacterStats characterStats;
|
|
|
|
[Tooltip("PlayerNetworkController 컴포넌트 (HP/MP 관리용)")]
|
|
[SerializeField] private PlayerNetworkController networkController;
|
|
|
|
[Tooltip("스킬 실행 관리자 (강제 취소 처리용)")]
|
|
[SerializeField] private SkillController skillController;
|
|
|
|
// 활성화된 이상 상태 목록
|
|
private readonly List<ActiveAbnormality> activeAbnormalities = new List<ActiveAbnormality>();
|
|
|
|
// 제어 효과 상태
|
|
private int stunCount;
|
|
private int silenceCount;
|
|
private int invincibleCount;
|
|
private int hitReactionImmuneCount;
|
|
private float slowMultiplier = 1f;
|
|
private float incomingDamageMultiplier = 1f;
|
|
|
|
// 클라이언트 판정용 제어 효과 동기화 변수
|
|
private NetworkVariable<int> syncedStunCount = new NetworkVariable<int>(0);
|
|
private NetworkVariable<int> syncedSilenceCount = new NetworkVariable<int>(0);
|
|
private NetworkVariable<int> syncedInvincibleCount = new NetworkVariable<int>(0);
|
|
private NetworkVariable<int> syncedHitReactionImmuneCount = new NetworkVariable<int>(0);
|
|
private NetworkVariable<float> syncedSlowMultiplier = new NetworkVariable<float>(1f);
|
|
|
|
// 네트워크 동기화용 데이터
|
|
private NetworkList<AbnormalitySyncData> syncedAbnormalities;
|
|
|
|
/// <summary>
|
|
/// 기절 상태 여부
|
|
/// </summary>
|
|
public bool IsStunned => GetCurrentStunCount() > 0;
|
|
|
|
/// <summary>
|
|
/// 침묵 상태 여부
|
|
/// </summary>
|
|
public bool IsSilenced => GetCurrentSilenceCount() > 0;
|
|
|
|
/// <summary>
|
|
/// 무적 상태 여부
|
|
/// </summary>
|
|
public bool IsInvincible => GetCurrentInvincibleCount() > 0;
|
|
|
|
/// <summary>
|
|
/// 일반 피격 반응 무시 상태 여부
|
|
/// </summary>
|
|
public bool IsHitReactionImmune => GetCurrentHitReactionImmuneCount() > 0;
|
|
|
|
/// <summary>
|
|
/// 이동 속도 배율 (1.0 = 기본, 0.5 = 50% 감소)
|
|
/// </summary>
|
|
public float MoveSpeedMultiplier => GetCurrentSlowMultiplier();
|
|
|
|
/// <summary>
|
|
/// 받는 피해 배율 (1.0 = 기본, 1.1 = 10% 증가)
|
|
/// </summary>
|
|
public float IncomingDamageMultiplier => incomingDamageMultiplier;
|
|
|
|
/// <summary>
|
|
/// 행동 가능 여부 (기절이 아닐 때)
|
|
/// </summary>
|
|
public bool CanAct => !IsStunned;
|
|
|
|
/// <summary>
|
|
/// 스킬 사용 가능 여부
|
|
/// </summary>
|
|
public bool CanUseSkills => !IsStunned && !IsSilenced;
|
|
|
|
/// <summary>
|
|
/// 활성화된 이상 상태 목록 (읽기 전용)
|
|
/// </summary>
|
|
public IReadOnlyList<ActiveAbnormality> ActiveAbnormalities => activeAbnormalities;
|
|
|
|
// 이벤트
|
|
public event Action<ActiveAbnormality> OnAbnormalityAdded;
|
|
public event Action<ActiveAbnormality> OnAbnormalityRemoved;
|
|
public event Action OnAbnormalitiesChanged;
|
|
|
|
/// <summary>
|
|
/// 네트워크 동기화용 이상 상태 데이터 구조체
|
|
/// </summary>
|
|
private struct AbnormalitySyncData : INetworkSerializable, IEquatable<AbnormalitySyncData>
|
|
{
|
|
public int AbnormalityId;
|
|
public float RemainingDuration;
|
|
public ulong SourceClientId;
|
|
|
|
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
|
{
|
|
serializer.SerializeValue(ref AbnormalityId);
|
|
serializer.SerializeValue(ref RemainingDuration);
|
|
serializer.SerializeValue(ref SourceClientId);
|
|
}
|
|
|
|
public bool Equals(AbnormalitySyncData other)
|
|
{
|
|
return AbnormalityId == other.AbnormalityId;
|
|
}
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
if (characterStats == null)
|
|
characterStats = GetComponent<CharacterStats>();
|
|
|
|
if (networkController == null)
|
|
networkController = GetComponent<PlayerNetworkController>();
|
|
|
|
if (skillController == null)
|
|
skillController = GetComponent<SkillController>();
|
|
|
|
syncedAbnormalities = new NetworkList<AbnormalitySyncData>();
|
|
}
|
|
|
|
public override void OnNetworkSpawn()
|
|
{
|
|
syncedAbnormalities.OnListChanged += OnSyncedAbnormalitiesChanged;
|
|
|
|
syncedStunCount.OnValueChanged += HandleSyncedStunChanged;
|
|
syncedSilenceCount.OnValueChanged += HandleSyncedSilenceChanged;
|
|
syncedInvincibleCount.OnValueChanged += HandleSyncedInvincibleChanged;
|
|
syncedHitReactionImmuneCount.OnValueChanged += HandleSyncedHitReactionImmuneChanged;
|
|
syncedSlowMultiplier.OnValueChanged += HandleSyncedSlowChanged;
|
|
|
|
if (networkController != null)
|
|
{
|
|
networkController.OnDeathStateChanged += HandleDeathStateChanged;
|
|
}
|
|
|
|
if (IsServer)
|
|
{
|
|
SyncControlEffects();
|
|
}
|
|
}
|
|
|
|
public override void OnNetworkDespawn()
|
|
{
|
|
syncedAbnormalities.OnListChanged -= OnSyncedAbnormalitiesChanged;
|
|
syncedStunCount.OnValueChanged -= HandleSyncedStunChanged;
|
|
syncedSilenceCount.OnValueChanged -= HandleSyncedSilenceChanged;
|
|
syncedInvincibleCount.OnValueChanged -= HandleSyncedInvincibleChanged;
|
|
syncedHitReactionImmuneCount.OnValueChanged -= HandleSyncedHitReactionImmuneChanged;
|
|
syncedSlowMultiplier.OnValueChanged -= HandleSyncedSlowChanged;
|
|
|
|
if (networkController != null)
|
|
{
|
|
networkController.OnDeathStateChanged -= HandleDeathStateChanged;
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!IsServer) return;
|
|
|
|
UpdateAbnormalities(Time.deltaTime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 이상 상태 적용
|
|
/// </summary>
|
|
/// <param name="data">적용할 이상 상태 데이터</param>
|
|
/// <param name="source">효과 시전자</param>
|
|
public void ApplyAbnormality(AbnormalityData data, GameObject source)
|
|
{
|
|
if (data == null)
|
|
{
|
|
Debug.LogWarning("[Abnormality] ApplyAbnormality called with null data");
|
|
return;
|
|
}
|
|
|
|
if (networkController != null && networkController.IsDead)
|
|
{
|
|
Debug.Log($"[Abnormality] Ignored {data.abnormalityName} because {gameObject.name} is dead");
|
|
return;
|
|
}
|
|
|
|
if (IsServer)
|
|
{
|
|
ApplyAbnormalityInternal(data, source);
|
|
}
|
|
else
|
|
{
|
|
var sourceNetId = source != null && source.TryGetComponent<NetworkObject>(out var netObj) ? netObj.NetworkObjectId : 0UL;
|
|
ApplyAbnormalityServerRpc(data.GetInstanceID(), sourceNetId);
|
|
}
|
|
}
|
|
|
|
[Rpc(SendTo.Server)]
|
|
private void ApplyAbnormalityServerRpc(int dataId, ulong sourceNetworkId)
|
|
{
|
|
var data = FindAbnormalityDataById(dataId);
|
|
if (data == null)
|
|
{
|
|
Debug.LogWarning($"[Abnormality] Could not find data with ID: {dataId}");
|
|
return;
|
|
}
|
|
|
|
GameObject source = null;
|
|
if (sourceNetworkId != 0UL && NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(sourceNetworkId, out var netObj))
|
|
{
|
|
source = netObj.gameObject;
|
|
}
|
|
|
|
ApplyAbnormalityInternal(data, source);
|
|
}
|
|
|
|
private void ApplyAbnormalityInternal(AbnormalityData data, GameObject source)
|
|
{
|
|
var existing = FindExistingAbnormality(data);
|
|
|
|
if (existing != null)
|
|
{
|
|
if (existing.Data == data)
|
|
{
|
|
existing.RefreshDuration();
|
|
UpdateSyncedAbnormalityDuration(existing);
|
|
Debug.Log($"[Abnormality] Refreshed {data.abnormalityName} on {gameObject.name}");
|
|
return;
|
|
}
|
|
|
|
if (data.level > existing.Data.level)
|
|
{
|
|
RemoveAbnormalityInternal(existing);
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"[Abnormality] Ignored {data.abnormalityName} (level {data.level}) - existing level {existing.Data.level} is higher or equal");
|
|
return;
|
|
}
|
|
}
|
|
|
|
var newAbnormality = new ActiveAbnormality(data, source);
|
|
activeAbnormalities.Add(newAbnormality);
|
|
|
|
ApplyStatModifiers(newAbnormality);
|
|
ApplyControlEffect(data);
|
|
RecalculateIncomingDamageMultiplier();
|
|
SyncAbnormalityAdd(newAbnormality, source);
|
|
|
|
OnAbnormalityAdded?.Invoke(newAbnormality);
|
|
OnAbnormalitiesChanged?.Invoke();
|
|
|
|
Debug.Log($"[Abnormality] Applied {data.abnormalityName} (level {data.level}) to {gameObject.name} for {data.duration}s");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 이상 상태 제거
|
|
/// </summary>
|
|
/// <param name="data">제거할 이상 상태 데이터</param>
|
|
public void RemoveAbnormality(AbnormalityData data)
|
|
{
|
|
if (data == null) return;
|
|
|
|
if (IsServer)
|
|
{
|
|
var abnormality = FindExistingAbnormality(data);
|
|
if (abnormality != null)
|
|
{
|
|
RemoveAbnormalityInternal(abnormality);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RemoveAbnormalityServerRpc(data.GetInstanceID());
|
|
}
|
|
}
|
|
|
|
[Rpc(SendTo.Server)]
|
|
private void RemoveAbnormalityServerRpc(int dataId)
|
|
{
|
|
var abnormality = activeAbnormalities.Find(a => a.Data.GetInstanceID() == dataId);
|
|
if (abnormality != null)
|
|
{
|
|
RemoveAbnormalityInternal(abnormality);
|
|
}
|
|
}
|
|
|
|
private void RemoveAbnormalityInternal(ActiveAbnormality abnormality)
|
|
{
|
|
RemoveStatModifiers(abnormality);
|
|
RemoveControlEffect(abnormality.Data);
|
|
RecalculateIncomingDamageMultiplier();
|
|
SyncAbnormalityRemove(abnormality);
|
|
activeAbnormalities.Remove(abnormality);
|
|
|
|
OnAbnormalityRemoved?.Invoke(abnormality);
|
|
OnAbnormalitiesChanged?.Invoke();
|
|
|
|
Debug.Log($"[Abnormality] Removed {abnormality.Data.abnormalityName} from {gameObject.name}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 모든 이상 상태 제거
|
|
/// </summary>
|
|
public void RemoveAllAbnormalities()
|
|
{
|
|
if (!IsServer)
|
|
{
|
|
RemoveAllAbnormalitiesServerRpc();
|
|
return;
|
|
}
|
|
|
|
while (activeAbnormalities.Count > 0)
|
|
{
|
|
RemoveAbnormalityInternal(activeAbnormalities[0]);
|
|
}
|
|
}
|
|
|
|
[Rpc(SendTo.Server)]
|
|
private void RemoveAllAbnormalitiesServerRpc()
|
|
{
|
|
RemoveAllAbnormalities();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 특정 출처의 모든 이상 상태 제거
|
|
/// </summary>
|
|
public void RemoveAbnormalitiesFromSource(GameObject source)
|
|
{
|
|
if (!IsServer)
|
|
{
|
|
var sourceNetId = source != null && source.TryGetComponent<NetworkObject>(out var netObj) ? netObj.NetworkObjectId : 0UL;
|
|
RemoveAbnormalitiesFromSourceServerRpc(sourceNetId);
|
|
return;
|
|
}
|
|
|
|
for (int i = activeAbnormalities.Count - 1; i >= 0; i--)
|
|
{
|
|
if (activeAbnormalities[i].Source == source)
|
|
{
|
|
RemoveAbnormalityInternal(activeAbnormalities[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Rpc(SendTo.Server)]
|
|
private void RemoveAbnormalitiesFromSourceServerRpc(ulong sourceNetworkId)
|
|
{
|
|
for (int i = activeAbnormalities.Count - 1; i >= 0; i--)
|
|
{
|
|
var abnormality = activeAbnormalities[i];
|
|
var sourceNetId = abnormality.Source != null && abnormality.Source.TryGetComponent<NetworkObject>(out var netObj) ? netObj.NetworkObjectId : 0UL;
|
|
if (sourceNetId == sourceNetworkId)
|
|
{
|
|
RemoveAbnormalityInternal(abnormality);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateAbnormalities(float deltaTime)
|
|
{
|
|
for (int i = activeAbnormalities.Count - 1; i >= 0; i--)
|
|
{
|
|
var abnormality = activeAbnormalities[i];
|
|
|
|
if (abnormality.CanTriggerPeriodic())
|
|
{
|
|
TriggerPeriodicEffect(abnormality);
|
|
}
|
|
|
|
if (abnormality.Tick(deltaTime))
|
|
{
|
|
RemoveAbnormalityInternal(abnormality);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void TriggerPeriodicEffect(ActiveAbnormality abnormality)
|
|
{
|
|
if (networkController == null) return;
|
|
|
|
float value = abnormality.Data.periodicValue;
|
|
|
|
if (value > 0)
|
|
{
|
|
networkController.RestoreHealthRpc(value);
|
|
Debug.Log($"[Abnormality] Periodic heal: +{value} HP from {abnormality.Data.abnormalityName}");
|
|
}
|
|
else if (value < 0)
|
|
{
|
|
networkController.TakeDamageRpc(-value);
|
|
Debug.Log($"[Abnormality] Periodic damage: {-value} HP from {abnormality.Data.abnormalityName}");
|
|
}
|
|
}
|
|
|
|
private ActiveAbnormality FindExistingAbnormality(AbnormalityData data)
|
|
{
|
|
return activeAbnormalities.Find(a => a.Data.abnormalityName == data.abnormalityName);
|
|
}
|
|
|
|
private void ApplyStatModifiers(ActiveAbnormality abnormality)
|
|
{
|
|
if (characterStats == null) return;
|
|
|
|
foreach (var entry in abnormality.Data.statModifiers)
|
|
{
|
|
var stat = characterStats.GetStat(entry.statType);
|
|
if (stat != null)
|
|
{
|
|
var modifier = new StatModifier(entry.value, entry.modType, abnormality);
|
|
abnormality.AppliedModifiers.Add(modifier);
|
|
stat.AddModifier(modifier);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RemoveStatModifiers(ActiveAbnormality abnormality)
|
|
{
|
|
if (characterStats == null) return;
|
|
|
|
foreach (StatType statType in Enum.GetValues(typeof(StatType)))
|
|
{
|
|
var stat = characterStats.GetStat(statType);
|
|
stat?.RemoveAllModifiersFromSource(abnormality);
|
|
}
|
|
|
|
abnormality.AppliedModifiers.Clear();
|
|
}
|
|
|
|
private void ApplyControlEffect(AbnormalityData data)
|
|
{
|
|
bool enteredStun = false;
|
|
|
|
if (data.ignoreHitReaction)
|
|
{
|
|
hitReactionImmuneCount++;
|
|
}
|
|
|
|
switch (data.controlType)
|
|
{
|
|
case ControlType.Stun:
|
|
enteredStun = stunCount == 0;
|
|
stunCount++;
|
|
break;
|
|
|
|
case ControlType.Silence:
|
|
silenceCount++;
|
|
break;
|
|
|
|
case ControlType.Slow:
|
|
slowMultiplier = Mathf.Min(slowMultiplier, data.slowMultiplier);
|
|
break;
|
|
|
|
case ControlType.Invincible:
|
|
invincibleCount++;
|
|
break;
|
|
}
|
|
|
|
SyncControlEffects();
|
|
|
|
if (enteredStun)
|
|
{
|
|
TryCancelCurrentSkill(SkillCancelReason.Stun, data.abnormalityName);
|
|
}
|
|
}
|
|
|
|
private void RemoveControlEffect(AbnormalityData data)
|
|
{
|
|
if (data.ignoreHitReaction)
|
|
{
|
|
hitReactionImmuneCount = Mathf.Max(0, hitReactionImmuneCount - 1);
|
|
}
|
|
|
|
switch (data.controlType)
|
|
{
|
|
case ControlType.Stun:
|
|
stunCount = Mathf.Max(0, stunCount - 1);
|
|
break;
|
|
|
|
case ControlType.Silence:
|
|
silenceCount = Mathf.Max(0, silenceCount - 1);
|
|
break;
|
|
|
|
case ControlType.Slow:
|
|
RecalculateSlowMultiplier();
|
|
break;
|
|
|
|
case ControlType.Invincible:
|
|
invincibleCount = Mathf.Max(0, invincibleCount - 1);
|
|
break;
|
|
}
|
|
|
|
SyncControlEffects();
|
|
}
|
|
|
|
private void RecalculateSlowMultiplier()
|
|
{
|
|
slowMultiplier = 1f;
|
|
|
|
foreach (var abnormality in activeAbnormalities)
|
|
{
|
|
if (abnormality.Data.controlType == ControlType.Slow)
|
|
{
|
|
slowMultiplier = Mathf.Min(slowMultiplier, abnormality.Data.slowMultiplier);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RecalculateIncomingDamageMultiplier()
|
|
{
|
|
incomingDamageMultiplier = 1f;
|
|
|
|
for (int i = 0; i < activeAbnormalities.Count; i++)
|
|
{
|
|
AbnormalityData data = activeAbnormalities[i].Data;
|
|
if (data == null || !data.HasIncomingDamageModifier)
|
|
continue;
|
|
|
|
incomingDamageMultiplier *= Mathf.Max(0f, data.incomingDamageMultiplier);
|
|
}
|
|
}
|
|
|
|
private int GetCurrentStunCount() => IsServer ? stunCount : syncedStunCount.Value;
|
|
|
|
private int GetCurrentSilenceCount() => IsServer ? silenceCount : syncedSilenceCount.Value;
|
|
|
|
private int GetCurrentInvincibleCount() => IsServer ? invincibleCount : syncedInvincibleCount.Value;
|
|
|
|
private int GetCurrentHitReactionImmuneCount() => IsServer ? hitReactionImmuneCount : syncedHitReactionImmuneCount.Value;
|
|
|
|
private float GetCurrentSlowMultiplier() => IsServer ? slowMultiplier : syncedSlowMultiplier.Value;
|
|
|
|
private void SyncControlEffects()
|
|
{
|
|
if (!IsServer)
|
|
return;
|
|
|
|
syncedStunCount.Value = stunCount;
|
|
syncedSilenceCount.Value = silenceCount;
|
|
syncedInvincibleCount.Value = invincibleCount;
|
|
syncedHitReactionImmuneCount.Value = hitReactionImmuneCount;
|
|
syncedSlowMultiplier.Value = slowMultiplier;
|
|
}
|
|
|
|
private void SyncAbnormalityAdd(ActiveAbnormality abnormality, GameObject source)
|
|
{
|
|
var sourceClientId = source != null && source.TryGetComponent<NetworkObject>(out var netObj) ? netObj.OwnerClientId : 0UL;
|
|
|
|
var syncData = new AbnormalitySyncData
|
|
{
|
|
AbnormalityId = abnormality.Data.GetInstanceID(),
|
|
RemainingDuration = abnormality.RemainingDuration,
|
|
SourceClientId = sourceClientId
|
|
};
|
|
|
|
syncedAbnormalities.Add(syncData);
|
|
}
|
|
|
|
private void UpdateSyncedAbnormalityDuration(ActiveAbnormality abnormality)
|
|
{
|
|
for (int i = 0; i < syncedAbnormalities.Count; i++)
|
|
{
|
|
if (syncedAbnormalities[i].AbnormalityId == abnormality.Data.GetInstanceID())
|
|
{
|
|
var syncData = syncedAbnormalities[i];
|
|
syncData.RemainingDuration = abnormality.RemainingDuration;
|
|
syncedAbnormalities[i] = syncData;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SyncAbnormalityRemove(ActiveAbnormality abnormality)
|
|
{
|
|
for (int i = 0; i < syncedAbnormalities.Count; i++)
|
|
{
|
|
if (syncedAbnormalities[i].AbnormalityId == abnormality.Data.GetInstanceID())
|
|
{
|
|
syncedAbnormalities.RemoveAt(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnSyncedAbnormalitiesChanged(NetworkListEvent<AbnormalitySyncData> changeEvent)
|
|
{
|
|
OnAbnormalitiesChanged?.Invoke();
|
|
}
|
|
|
|
private void HandleSyncedStunChanged(int oldValue, int newValue)
|
|
{
|
|
if (oldValue == newValue)
|
|
return;
|
|
|
|
OnAbnormalitiesChanged?.Invoke();
|
|
}
|
|
|
|
private void HandleSyncedSilenceChanged(int oldValue, int newValue)
|
|
{
|
|
if (oldValue == newValue)
|
|
return;
|
|
|
|
OnAbnormalitiesChanged?.Invoke();
|
|
}
|
|
|
|
private void HandleSyncedSlowChanged(float oldValue, float newValue)
|
|
{
|
|
if (Mathf.Approximately(oldValue, newValue))
|
|
return;
|
|
|
|
OnAbnormalitiesChanged?.Invoke();
|
|
}
|
|
|
|
private void HandleSyncedInvincibleChanged(int oldValue, int newValue)
|
|
{
|
|
if (oldValue == newValue)
|
|
return;
|
|
|
|
OnAbnormalitiesChanged?.Invoke();
|
|
}
|
|
|
|
private void HandleSyncedHitReactionImmuneChanged(int oldValue, int newValue)
|
|
{
|
|
if (oldValue == newValue)
|
|
return;
|
|
|
|
OnAbnormalitiesChanged?.Invoke();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 사망 시 활성 이상 상태를 모두 제거합니다.
|
|
/// </summary>
|
|
private void HandleDeathStateChanged(bool dead)
|
|
{
|
|
if (!dead || !IsServer)
|
|
return;
|
|
|
|
if (activeAbnormalities.Count == 0)
|
|
return;
|
|
|
|
RemoveAllAbnormalities();
|
|
Debug.Log($"[Abnormality] Cleared all abnormalities on death: {gameObject.name}");
|
|
}
|
|
|
|
private void TryCancelCurrentSkill(SkillCancelReason reason, string sourceName)
|
|
{
|
|
if (!IsServer || skillController == null || !skillController.IsPlayingAnimation)
|
|
return;
|
|
|
|
if (skillController.CancelSkill(reason))
|
|
{
|
|
Debug.Log($"[Abnormality] Cancelled skill because '{sourceName}' applied to {gameObject.name}");
|
|
}
|
|
}
|
|
|
|
private AbnormalityData FindAbnormalityDataById(int instanceId)
|
|
{
|
|
var allData = Resources.FindObjectsOfTypeAll<AbnormalityData>();
|
|
foreach (var data in allData)
|
|
{
|
|
if (data.GetInstanceID() == instanceId)
|
|
return data;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 특정 이름의 이상 상태가 활성화되어 있는지 확인
|
|
/// </summary>
|
|
public bool HasAbnormality(string name)
|
|
{
|
|
return activeAbnormalities.Exists(a => a.Data.abnormalityName == name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 특정 데이터의 이상 상태가 활성화되어 있는지 확인
|
|
/// </summary>
|
|
public bool HasAbnormality(AbnormalityData data)
|
|
{
|
|
return activeAbnormalities.Exists(a => a.Data.abnormalityName == data.abnormalityName);
|
|
}
|
|
}
|
|
}
|