using System.Collections.Generic; using System.Linq; using UnityEngine; using Platformer.Mechanics; namespace EerieVillage.Skills { /// /// Player GameObject에 부착. 장착 스킬 슬롯·Lv·각성 상태 관리 중앙 컴포넌트. /// BT12-Dev v1 §2-3·§4-3·§4-4 정합. /// Phase 2-D에서 SkillCardPlaceholder·LevelUpManager와 정식 통합 예정. /// [RequireComponent(typeof(Health))] public class PlayerSkillInventory : MonoBehaviour { [Header("슬롯 상한 (balance-designer 재확인 — VS 원작 6/6 참고)")] public int ActiveSlotMax = 6; public int PassiveSlotMax = 6; // PD 지시 2026-05-13 — 게임 시작 시 기본 습득 스킬 CardId 배열 (Inspector 조절·다중 지원) [Header("기본 습득 스킬 (게임 시작 시 자동 장착)")] [Tooltip("게임 시작 시 자동 습득할 스킬 CardId 배열 (예: A02 파이어볼·중복·잘못된 ID 자동 skip)")] public string[] StartingCardIds = new string[] { "A02" }; // 장착 슬롯 (인덱스 = 슬롯 번호) private readonly List _activeSkills = new List(); private readonly List _passiveSkills = new List(); private readonly List _awakenedSkills = new List(); // 각 카드 ID → 런타임 인스턴스 매핑 (재픽 시 Lv 업용) private readonly Dictionary _cardIdToRuntime = new Dictionary(); // 적 처치 누적 (OnKill 트리거용) private int _totalKillCount = 0; // Health 참조 — OnEnable에서 이벤트 구독 private Health _health; /// 통합 PlayerStats — 패시브 보정이 여기에 누적 적용 public PlayerStats Stats { get; private set; } /// 적 처치·피격 이벤트 구독자 (OnKill·OnHit 트리거용) public event System.Action OnEnemyKilled; public event System.Action OnPlayerDamaged; void Awake() { Stats = new PlayerStats(); PlayerStats.Current = Stats; _health = GetComponent(); } // PD 지시 2026-05-13 — Start 시점에 기본 습득 스킬 자동 장착 (Resources 로드 완료 후·Awake 영역 아님) void Start() { if (StartingCardIds == null) return; foreach (var cardId in StartingCardIds) { if (string.IsNullOrEmpty(cardId)) continue; AddSkillByCardId(cardId); } } void OnEnable() { if (_health != null) { _health.OnDamagedEvent += OnPlayerDamagedHandler; } } void OnDisable() { if (_health != null) { _health.OnDamagedEvent -= OnPlayerDamagedHandler; } } void Update() { // 액티브 Cooldown 감산·OnTime 발동 foreach (var active in _activeSkills) { active.Tick(Time.deltaTime); } // 조건부 패시브 타이머 감산 (P17 감로수 등 OnTimer 계열) foreach (var passive in _passiveSkills) { if (!passive.IsAlwaysOn) { passive.OnTrigger(new PassiveTriggerContext { Kind = PassiveTriggerKind.OnTimer, TimeElapsed = Time.deltaTime }); } } } /// /// 픽 UI 선택 결과 반영 — cardId로 SkillDataAsset 조회 후 런타임 생성·장착. /// Phase 2-D에서 SkillCardPlaceholder·LevelUpManager 통합 hook 연결 예정. /// public bool AddSkillByCardId(string cardId) { if (string.IsNullOrEmpty(cardId)) return false; // 재픽 = Lv 업 if (_cardIdToRuntime.TryGetValue(cardId, out var existing)) { existing.Upgrade(); return true; } var data = SkillRuntimeFactory.Resolve(cardId); if (data == null) { Debug.LogWarning($"[PlayerSkillInventory] CardId '{cardId}' 에 해당하는 SkillDataAsset을 찾을 수 없습니다."); return false; } var runtime = SkillRuntimeFactory.Create(data); if (runtime == null) return false; if (runtime is IActiveSkill active) { if (_activeSkills.Count >= ActiveSlotMax) { Debug.LogWarning($"[PlayerSkillInventory] 액티브 슬롯 초과 (상한 {ActiveSlotMax})."); return false; } runtime.OnEquip(this); _activeSkills.Add(active); _cardIdToRuntime[cardId] = runtime; return true; } if (runtime is IPassiveSkill passive) { if (_passiveSkills.Count >= PassiveSlotMax) { Debug.LogWarning($"[PlayerSkillInventory] 패시브 슬롯 초과 (상한 {PassiveSlotMax})."); return false; } runtime.OnEquip(this); passive.ApplyModifier(Stats); _passiveSkills.Add(passive); _cardIdToRuntime[cardId] = runtime; return true; } return false; } /// /// 적 처치 이벤트 외부 호출 (EnemyController 사망 시 연결 예정 — Phase 2-D). /// OnKill 트리거 액티브·패시브 발동. /// public void NotifyEnemyKilled(EnemyKillContext ctx) { _totalKillCount++; OnEnemyKilled?.Invoke(ctx); foreach (var active in _activeSkills.Where(a => a.Trigger == ActiveTrigger.OnKill)) active.Fire(); foreach (var passive in _passiveSkills.Where(p => !p.IsAlwaysOn)) passive.OnTrigger(new PassiveTriggerContext { Kind = PassiveTriggerKind.OnEnemyKilled, KillCount = _totalKillCount }); } // Health.OnDamagedEvent 구독 핸들러 private void OnPlayerDamagedHandler(int damage) { OnPlayerDamaged?.Invoke(damage); foreach (var active in _activeSkills.Where(a => a.Trigger == ActiveTrigger.OnHit)) active.Fire(); foreach (var passive in _passiveSkills.Where(p => !p.IsAlwaysOn)) passive.OnTrigger(new PassiveTriggerContext { Kind = PassiveTriggerKind.OnPlayerDamaged, DamageTaken = damage }); } // --- 읽기 전용 접근자 (디버그·UI 용) --- public IReadOnlyList ActiveSkills => _activeSkills; public IReadOnlyList PassiveSkills => _passiveSkills; } /// 적 처치 컨텍스트 (Phase 2-D에서 EnemyController 참조 추가 예정) public struct EnemyKillContext { public string EnemyId; public int XPReward; } }