using System; using Colosseum; using Colosseum.AI; using Colosseum.Combat; using Colosseum.Enemy; using Colosseum.Player; using Colosseum.Skills; using Unity.Behavior; using Unity.Properties; using UnityEngine; using Action = Unity.Behavior.Action; /// /// 보스 공통 패턴 실행용 Behavior Action 기반 클래스입니다. /// [Serializable, GeneratePropertyBag] public abstract partial class BossPatternActionBase : Action { [SerializeReference] public BlackboardVariable Target; protected BossEnemy bossEnemy; protected EnemyBase enemyBase; protected SkillController skillController; protected BossCombatBehaviorContext combatBehaviorContext; protected UnityEngine.AI.NavMeshAgent navMeshAgent; private BossPatternData activePattern; private GameObject activeTarget; private int currentStepIndex; private bool isWaiting; private float waitEndTime; /// /// 액션 시작 시 실제로 실행할 패턴과 대상을 결정합니다. /// protected abstract bool TryResolvePattern(out BossPatternData pattern, out GameObject target); protected override Status OnStart() { ResolveReferences(); ClearRuntimeState(); if (!IsReady()) return Status.Failure; if (bossEnemy.IsDead || bossEnemy.IsTransitioning) return Status.Failure; if (skillController.IsPlayingAnimation) return Status.Failure; if (!TryResolvePattern(out BossPatternData pattern, out GameObject target)) return Status.Failure; activePattern = pattern; activeTarget = target; if (Target != null) Target.Value = target; StopMovement(); return ExecuteCurrentStep(); } protected override Status OnUpdate() { if (!IsReady() || activePattern == null) return Status.Failure; if (bossEnemy.IsDead || bossEnemy.IsTransitioning) return Status.Failure; if (isWaiting) { if (Time.time < waitEndTime) return Status.Running; isWaiting = false; } else if (skillController.IsPlayingAnimation) { return Status.Running; } currentStepIndex++; if (currentStepIndex >= activePattern.Steps.Count) { UsePatternAction.MarkPatternUsed(GameObject, activePattern); return Status.Success; } return ExecuteCurrentStep(); } protected override void OnEnd() { ClearRuntimeState(); } protected virtual GameObject ResolveStepTarget(GameObject fallbackTarget) { return fallbackTarget; } protected GameObject FindNearestLivingPlayer() { PlayerNetworkController[] players = UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.None); GameObject nearestTarget = null; float nearestDistance = float.MaxValue; float maxDistance = enemyBase != null && enemyBase.Data != null ? enemyBase.Data.AggroRange : Mathf.Infinity; for (int i = 0; i < players.Length; i++) { PlayerNetworkController player = players[i]; if (player == null || player.IsDead || !player.gameObject.activeInHierarchy) continue; GameObject candidate = player.gameObject; if (Team.IsSameTeam(GameObject, candidate)) continue; float distance = Vector3.Distance(GameObject.transform.position, candidate.transform.position); if (distance > maxDistance || distance >= nearestDistance) continue; nearestDistance = distance; nearestTarget = candidate; } return nearestTarget; } protected GameObject ResolvePrimaryTarget() { GameObject highestThreatTarget = enemyBase != null ? enemyBase.GetHighestThreatTarget(Target?.Value, null, enemyBase.Data != null ? enemyBase.Data.AggroRange : Mathf.Infinity) : null; GameObject target = highestThreatTarget != null ? highestThreatTarget : FindNearestLivingPlayer(); if (Target != null) Target.Value = target; return target; } protected bool IsValidHostileTarget(GameObject candidate) { if (candidate == null || !candidate.activeInHierarchy) return false; if (Team.IsSameTeam(GameObject, candidate)) return false; IDamageable damageable = candidate.GetComponent(); return damageable == null || !damageable.IsDead; } protected void StopMovement() { if (navMeshAgent == null || !navMeshAgent.enabled) return; navMeshAgent.isStopped = true; navMeshAgent.ResetPath(); } protected void LogDebug(string message) { combatBehaviorContext?.LogDebug(GetType().Name, message); } private Status ExecuteCurrentStep() { if (activePattern == null || currentStepIndex < 0 || currentStepIndex >= activePattern.Steps.Count) return Status.Failure; PatternStep step = activePattern.Steps[currentStepIndex]; if (step.Type == PatternStepType.Wait) { isWaiting = true; waitEndTime = Time.time + step.Duration; return Status.Running; } if (step.Skill == null) { Debug.LogWarning($"[{GetType().Name}] 스킬이 비어 있는 패턴 스텝입니다: {activePattern.PatternName} / Step={currentStepIndex}"); return Status.Failure; } GameObject skillTarget = activeTarget; if (step.Skill.JumpToTarget) { skillTarget = ResolveStepTarget(activeTarget); if (skillTarget == null) { LogDebug($"점프 타겟을 찾지 못해 실패: {activePattern.PatternName}"); return Status.Failure; } enemyBase?.SetJumpTarget(skillTarget.transform.position); } if (!skillController.ExecuteSkill(step.Skill)) { Debug.LogWarning($"[{GetType().Name}] 스킬 실행 실패: {step.Skill.SkillName}"); return Status.Failure; } LogDebug($"패턴 실행: {activePattern.PatternName} / Step={currentStepIndex} / Skill={step.Skill.SkillName}"); return Status.Running; } private bool IsReady() { return bossEnemy != null && enemyBase != null && skillController != null && combatBehaviorContext != null; } private void ResolveReferences() { if (bossEnemy == null) bossEnemy = GameObject.GetComponent(); if (enemyBase == null) enemyBase = GameObject.GetComponent(); if (skillController == null) skillController = GameObject.GetComponent(); if (combatBehaviorContext == null) combatBehaviorContext = GameObject.GetComponent(); if (navMeshAgent == null) navMeshAgent = GameObject.GetComponent(); } private void ClearRuntimeState() { activePattern = null; activeTarget = null; currentStepIndex = 0; isWaiting = false; waitEndTime = 0f; } }