205 lines
7.4 KiB
C#
205 lines
7.4 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
using Platformer.Mechanics;
|
|
|
|
namespace EerieVillage.Skills
|
|
{
|
|
/// <summary>
|
|
/// Player GameObject에 부착. 장착 스킬 슬롯·Lv·각성 상태 관리 중앙 컴포넌트.
|
|
/// BT12-Dev v1 §2-3·§4-3·§4-4 정합.
|
|
/// Phase 2-D에서 SkillCardPlaceholder·LevelUpManager와 정식 통합 예정.
|
|
/// </summary>
|
|
[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<IActiveSkill> _activeSkills = new List<IActiveSkill>();
|
|
private readonly List<IPassiveSkill> _passiveSkills = new List<IPassiveSkill>();
|
|
private readonly List<IAwakeningSkill> _awakenedSkills = new List<IAwakeningSkill>();
|
|
|
|
// 각 카드 ID → 런타임 인스턴스 매핑 (재픽 시 Lv 업용)
|
|
private readonly Dictionary<string, ISkillRuntime> _cardIdToRuntime = new Dictionary<string, ISkillRuntime>();
|
|
|
|
// 적 처치 누적 (OnKill 트리거용)
|
|
private int _totalKillCount = 0;
|
|
|
|
// Health 참조 — OnEnable에서 이벤트 구독
|
|
private Health _health;
|
|
|
|
/// <summary>통합 PlayerStats — 패시브 보정이 여기에 누적 적용</summary>
|
|
public PlayerStats Stats { get; private set; }
|
|
|
|
/// <summary>적 처치·피격 이벤트 구독자 (OnKill·OnHit 트리거용)</summary>
|
|
public event System.Action<EnemyKillContext> OnEnemyKilled;
|
|
public event System.Action<float> OnPlayerDamaged;
|
|
|
|
void Awake()
|
|
{
|
|
Stats = new PlayerStats();
|
|
PlayerStats.Current = Stats;
|
|
_health = GetComponent<Health>();
|
|
}
|
|
|
|
// 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
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 픽 UI 선택 결과 반영 — cardId로 SkillDataAsset 조회 후 런타임 생성·장착.
|
|
/// Phase 2-D에서 SkillCardPlaceholder·LevelUpManager 통합 hook 연결 예정.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 적 처치 이벤트 외부 호출 (EnemyController 사망 시 연결 예정 — Phase 2-D).
|
|
/// OnKill 트리거 액티브·패시브 발동.
|
|
/// </summary>
|
|
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<IActiveSkill> ActiveSkills => _activeSkills;
|
|
public IReadOnlyList<IPassiveSkill> PassiveSkills => _passiveSkills;
|
|
}
|
|
|
|
/// <summary>적 처치 컨텍스트 (Phase 2-D에서 EnemyController 참조 추가 예정)</summary>
|
|
public struct EnemyKillContext
|
|
{
|
|
public string EnemyId;
|
|
public int XPReward;
|
|
}
|
|
}
|