using System.Collections.Generic;
using Colosseum.AI;
using Colosseum.Abnormalities;
using Colosseum.Skills;
using Unity.Behavior;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Serialization;
namespace Colosseum.Enemy
{
///
/// 마지막 패턴 실행 결과입니다.
///
public enum BossPatternExecutionResult
{
None,
Running,
Succeeded,
Failed,
Cancelled,
}
///
/// 보스 BT가 읽고 쓰는 런타임 전투 상태를 보관합니다.
/// 타겟, 페이즈 진행 상태, 패턴 쿨다운 같은 전투 런타임 결과를 외부 시스템과 공유합니다.
///
[DisallowMultipleComponent]
[RequireComponent(typeof(BossEnemy))]
[RequireComponent(typeof(SkillController))]
public class BossBehaviorRuntimeState : NetworkBehaviour
{
[Header("References")]
[SerializeField] protected BossEnemy bossEnemy;
[SerializeField] protected EnemyBase enemyBase;
[SerializeField] protected SkillController skillController;
[SerializeField] protected AbnormalityManager abnormalityManager;
[SerializeField] protected UnityEngine.AI.NavMeshAgent navMeshAgent;
[SerializeField] protected BehaviorGraphAgent behaviorGraphAgent;
[Header("Pattern Flow")]
[Tooltip("대형 패턴(시그니처/기동/조합) 직후 기본 패턴 최소 순환 횟수")]
[Min(0)] [SerializeField] protected int basicLoopMinCountAfterBigPattern = 2;
[Header("시그니처 효과 설정")]
[Tooltip("시그니처 패턴 차단에 필요한 누적 피해 비율")]
[Range(0f, 1f)] [SerializeField] protected float signatureRequiredDamageRatio = 0.1f;
[Tooltip("시그니처 준비 상태를 나타내는 이상상태")]
[SerializeField] protected AbnormalityData signatureTelegraphAbnormality;
[Tooltip("시그니처 차단 성공 시 보스가 멈추는 시간")]
[Min(0f)] [SerializeField] protected float signatureSuccessStaggerDuration = 2f;
[Tooltip("시그니처 실패 시 모든 플레이어에게 적용할 디버프")]
[SerializeField] protected AbnormalityData signatureFailureAbnormality;
[Tooltip("시그니처 실패 시 모든 플레이어에게 주는 기본 피해")]
[Min(0f)] [SerializeField] protected float signatureFailureDamage = 40f;
[Tooltip("시그니처 실패 시 넉백이 적용되는 반경")]
[Min(0f)] [SerializeField] protected float signatureFailureKnockbackRadius = 8f;
[Tooltip("시그니처 실패 시 다운이 적용되는 반경")]
[Min(0f)] [SerializeField] protected float signatureFailureDownRadius = 3f;
[Tooltip("시그니처 실패 시 넉백 속도")]
[Min(0f)] [SerializeField] protected float signatureFailureKnockbackSpeed = 12f;
[Tooltip("시그니처 실패 시 넉백 지속 시간")]
[Min(0f)] [SerializeField] protected float signatureFailureKnockbackDuration = 0.35f;
[Tooltip("시그니처 실패 시 다운 지속 시간")]
[Min(0f)] [SerializeField] protected float signatureFailureDownDuration = 2f;
[Header("Phase State")]
[Tooltip("BT가 관리하는 최대 페이즈 수")]
[Min(1)] [SerializeField] protected int maxPatternPhase = 3;
[Tooltip("디버그 로그 출력 여부")]
[SerializeField] protected bool debugMode = false;
protected readonly Dictionary patternCooldownTracker = new Dictionary();
protected readonly Dictionary customPhaseConditions = new Dictionary();
protected GameObject currentTarget;
protected int meleePatternCounter;
protected int basicLoopCountSinceLastBigPattern;
protected int currentPatternPhase = 1;
protected float currentPhaseStartTime;
protected BossPatternExecutionResult lastPatternExecutionResult;
protected BossPatternData lastExecutedPattern;
///
/// 현재 전투 대상
///
public GameObject CurrentTarget => currentTarget;
///
/// BT가 관리하는 현재 페이즈
///
public int CurrentPatternPhase => Mathf.Clamp(currentPatternPhase, 1, Mathf.Max(1, maxPatternPhase));
///
/// BT가 관리하는 최대 페이즈 수
///
public int MaxPatternPhase => Mathf.Max(1, maxPatternPhase);
///
/// 현재 페이즈의 경과 시간
///
public float PhaseElapsedTime => Time.time - currentPhaseStartTime;
///
/// 마지막 대형 패턴 이후 누적된 기본 루프 횟수
///
public int BasicLoopCountSinceLastBigPattern => basicLoopCountSinceLastBigPattern;
///
/// 마지막 패턴 실행 결과
///
public BossPatternExecutionResult LastPatternExecutionResult => lastPatternExecutionResult;
///
/// 마지막으로 실행한 패턴
///
public BossPatternData LastExecutedPattern => lastExecutedPattern;
///
/// EnemyBase 접근자
///
public EnemyBase EnemyBase => enemyBase;
///
/// 디버그 로그 출력 여부
///
public bool DebugModeEnabled => debugMode;
///
/// 시그니처 실패 시 모든 플레이어에게 주는 기본 피해
///
public float SignatureFailureDamage => signatureFailureDamage;
///
/// 시그니처 실패 시 모든 플레이어에게 적용할 디버프
///
public AbnormalityData SignatureFailureAbnormality => signatureFailureAbnormality;
///
/// 시그니처 실패 시 넉백이 적용되는 반경
///
public float SignatureFailureKnockbackRadius => signatureFailureKnockbackRadius;
///
/// 시그니처 실패 시 다운이 적용되는 반경
///
public float SignatureFailureDownRadius => signatureFailureDownRadius;
///
/// 시그니처 실패 시 넉백 속도
///
public float SignatureFailureKnockbackSpeed => signatureFailureKnockbackSpeed;
///
/// 시그니처 실패 시 넉백 지속 시간
///
public float SignatureFailureKnockbackDuration => signatureFailureKnockbackDuration;
///
/// 시그니처 실패 시 다운 지속 시간
///
public float SignatureFailureDownDuration => signatureFailureDownDuration;
///
/// 마지막 충전 차단 시 설정된 경직 시간 (BossPatternActionBase가 설정)
///
public float LastChargeStaggerDuration { get; set; }
///
/// 마지막 패턴 실행에서 충전이 차단되었는지 여부.
/// BT 노드(IsChargeBrokenCondition)에서 판독합니다.
///
public bool WasChargeBroken { get; set; }
///
/// 기절 등으로 인해 보스 전투 로직을 진행할 수 없는 상태인지 여부
///
public bool IsBehaviorSuppressed => abnormalityManager != null && abnormalityManager.IsStunned;
///
protected virtual void Awake()
{
ResolveReferences();
ResetPhaseState();
}
public override void OnNetworkSpawn()
{
ResolveReferences();
ResetPhaseState();
if (!IsServer)
enabled = false;
}
protected virtual void Update()
{
if (!IsServer)
return;
ResolveReferences();
if (bossEnemy == null || enemyBase == null || skillController == null)
return;
if (bossEnemy.IsDead)
return;
if (IsBehaviorSuppressed)
StopMovement();
}
///
/// BT가 선택한 현재 전투 대상을 동기화합니다.
///
public void SetCurrentTarget(GameObject target)
{
currentTarget = target;
}
///
/// BT가 현재 페이즈 값을 갱신합니다.
/// 필요하면 경과 시간 기준도 함께 초기화합니다.
///
public void SetCurrentPatternPhase(int phase, bool resetTimer = true)
{
currentPatternPhase = Mathf.Clamp(phase, 1, MaxPatternPhase);
if (resetTimer)
currentPhaseStartTime = Time.time;
}
///
/// 현재 페이즈 타이머를 다시 시작합니다.
///
public void RestartCurrentPhaseTimer()
{
currentPhaseStartTime = Time.time;
}
///
/// 패턴 실행 시작을 기록합니다.
///
public void BeginPatternExecution(BossPatternData pattern)
{
lastExecutedPattern = pattern;
lastPatternExecutionResult = BossPatternExecutionResult.Running;
}
///
/// 패턴 실행 결과를 기록합니다.
///
public void CompletePatternExecution(BossPatternData pattern, BossPatternExecutionResult result)
{
lastExecutedPattern = pattern;
lastPatternExecutionResult = result;
}
///
/// 페이즈 커스텀 조건을 기록합니다.
///
public void SetPhaseCustomCondition(string conditionId, bool value)
{
if (string.IsNullOrEmpty(conditionId))
return;
customPhaseConditions[conditionId] = value;
}
///
/// 페이즈 커스텀 조건 값을 읽습니다.
///
public bool CheckPhaseCustomCondition(string conditionId)
{
return !string.IsNullOrEmpty(conditionId)
&& customPhaseConditions.TryGetValue(conditionId, out bool value)
&& value;
}
///
/// 근접 패턴 사용 카운터를 갱신합니다.
///
public void RegisterPatternUse(BossPatternData pattern)
{
if (pattern == null)
return;
if (pattern.IsMelee)
{
meleePatternCounter++;
basicLoopCountSinceLastBigPattern++;
}
if (pattern.Category == PatternCategory.Punish || pattern.IsBigPattern)
{
basicLoopCountSinceLastBigPattern = 0;
}
}
///
/// 로그를 출력합니다.
///
public void LogDebug(string source, string message)
{
if (debugMode)
Debug.Log($"[{source}] {message}");
}
///
/// 지정 패턴이 grace period를 통과했는지 반환합니다.
/// Punish/Melee/Utility는 항상 허용됩니다.
///
public bool IsPatternGracePeriodAllowed(BossPatternData pattern)
{
if (pattern == null)
return false;
if (pattern.Category == PatternCategory.Punish)
return true;
if (pattern.IsMelee || pattern.TargetMode == TargetResolveMode.Utility)
return true;
return basicLoopCountSinceLastBigPattern >= basicLoopMinCountAfterBigPattern;
}
public bool IsPatternReady(BossPatternData pattern)
{
if (pattern == null || pattern.Steps == null || pattern.Steps.Count == 0)
return false;
if (!patternCooldownTracker.TryGetValue(pattern, out float readyTime))
return true;
return Time.time >= readyTime;
}
///
/// 패턴 쿨다운을 설정합니다. BT 노드(BossPatternActionBase)와 코드 폴백 모두에서 호출합니다.
///
public void SetPatternCooldown(BossPatternData pattern)
{
if (pattern != null)
patternCooldownTracker[pattern] = Time.time + pattern.Cooldown;
}
protected void StopMovement()
{
if (navMeshAgent == null || !navMeshAgent.enabled)
return;
navMeshAgent.isStopped = true;
navMeshAgent.ResetPath();
}
protected virtual void ResolveReferences()
{
if (bossEnemy == null)
bossEnemy = GetComponent();
if (enemyBase == null)
enemyBase = GetComponent();
if (skillController == null)
skillController = GetComponent();
if (abnormalityManager == null)
abnormalityManager = GetComponent();
if (navMeshAgent == null)
navMeshAgent = GetComponent();
if (behaviorGraphAgent == null)
behaviorGraphAgent = GetComponent();
}
public virtual void ResetPhaseState()
{
currentPatternPhase = 1;
currentPhaseStartTime = Time.time;
lastPatternExecutionResult = BossPatternExecutionResult.None;
lastExecutedPattern = null;
customPhaseConditions.Clear();
}
public override void OnNetworkDespawn()
{
base.OnNetworkDespawn();
}
private static bool HasAnimatorParameter(Animator animator, string parameterName, AnimatorControllerParameterType parameterType)
{
if (animator == null || string.IsNullOrEmpty(parameterName))
return false;
AnimatorControllerParameter[] parameters = animator.parameters;
for (int i = 0; i < parameters.Length; i++)
{
AnimatorControllerParameter parameter = parameters[i];
if (parameter.type == parameterType && parameter.name == parameterName)
return true;
}
return false;
}
}
}