141 lines
4.8 KiB
C#
141 lines
4.8 KiB
C#
using UnityEngine;
|
|
using Platformer.Core;
|
|
|
|
namespace EerieVillage.Skills
|
|
{
|
|
/// <summary>
|
|
/// 액티브 스킬 런타임 구현체. ActiveSkillData.Category 기준 단일 클래스.
|
|
/// BT12-Dev v1 §4-2 정합.
|
|
/// - OnTime: Tick()에서 쿨다운 감산 → Fire()
|
|
/// - OnHit·OnKill: PlayerSkillInventory 이벤트 핸들러에서 Fire() 직접 호출
|
|
/// - F(SpecialJudge): Fire() 내 확률 판정 선행
|
|
/// Phase 2-B에서 SkillFireEvent.Execute에 카테고리별 실 발동기 연결 예정.
|
|
/// </summary>
|
|
public class ActiveSkillRuntime : IActiveSkill
|
|
{
|
|
// --- ISkillRuntime ---
|
|
public SkillDataAsset Data => ActiveData;
|
|
public int StackLevel { get; private set; } = 1;
|
|
|
|
// --- IActiveSkill ---
|
|
public float BaseCooldown => ActiveData.BaseCooldown;
|
|
|
|
/// <summary>
|
|
/// 실제 쿨다운 = BaseCooldown * CooldownMultiplier(P06) / StackLevelFactor.
|
|
/// 하드캡 0.5s (balance/01 v0.2 §9).
|
|
/// </summary>
|
|
public float EffectiveCooldown =>
|
|
Mathf.Max(
|
|
BaseCooldown * _inventory.Stats.CooldownMultiplier / StackLevelFactor(StackLevel),
|
|
0.5f
|
|
);
|
|
|
|
public float CooldownRemaining { get; private set; } = 0f;
|
|
public ActiveTrigger Trigger => ActiveData.Trigger;
|
|
|
|
// --- 내부 ---
|
|
public ActiveSkillData ActiveData { get; private set; }
|
|
private PlayerSkillInventory _inventory;
|
|
|
|
public ActiveSkillRuntime(ActiveSkillData data)
|
|
{
|
|
ActiveData = data;
|
|
}
|
|
|
|
public void OnEquip(PlayerSkillInventory inventory)
|
|
{
|
|
_inventory = inventory;
|
|
CooldownRemaining = ActiveData.BaseCooldown; // 첫 발동은 주기 후
|
|
}
|
|
|
|
public void OnUnequip()
|
|
{
|
|
// OnHit·OnKill 이벤트 구독 해제 — Phase 2-D 인벤토리 통합 시 구현
|
|
}
|
|
|
|
public void Upgrade()
|
|
{
|
|
if (StackLevel < ActiveData.maxLevel && StackLevel < 5)
|
|
StackLevel++;
|
|
// Lv.5 도달 시 각성 조건 1 충족 — Phase 2-D AwakeningManager 연결 예정
|
|
}
|
|
|
|
/// <summary>
|
|
/// PlayerSkillInventory.Update가 매 프레임 호출.
|
|
/// OnTime 트리거만 처리 (OnHit·OnKill은 인벤토리 이벤트 핸들러에서 Fire() 직접 호출).
|
|
/// </summary>
|
|
public void Tick(float deltaTime)
|
|
{
|
|
if (Trigger != ActiveTrigger.OnTime) return;
|
|
|
|
CooldownRemaining -= deltaTime;
|
|
if (CooldownRemaining <= 0f)
|
|
{
|
|
Fire();
|
|
// 누적 오버플로 방지 (BT7-Dev PlayerAttackTicker와 동일 패턴)
|
|
CooldownRemaining += EffectiveCooldown;
|
|
CooldownRemaining = Mathf.Max(CooldownRemaining, 0f);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 스킬 효과 Dispatch. SkillFireEvent를 Simulation에 Schedule.
|
|
/// F(SpecialJudge) 카테고리는 확률 판정 선행.
|
|
/// Phase 2-B에서 SkillFireEvent.Execute에 실 발동기 연결.
|
|
/// </summary>
|
|
public void Fire()
|
|
{
|
|
// F 카테고리 확률 판정
|
|
if (ActiveData.Category == ActiveCategory.SpecialJudge)
|
|
{
|
|
if (Random.value > ActiveData.FireProbability) return;
|
|
}
|
|
|
|
var ev = Simulation.Schedule<SkillFireEvent>();
|
|
ev.Runtime = this;
|
|
ev.Inventory = _inventory;
|
|
}
|
|
|
|
/// <summary>
|
|
/// StackLevel에 따른 대미지 팩터 (balance/01 v0.2 §3).
|
|
/// Lv.1=1.0 · Lv.2=1.2 · Lv.3=1.4 · Lv.4=1.6 · Lv.5=2.0
|
|
/// </summary>
|
|
public static float StackLevelFactor(int lv)
|
|
{
|
|
return lv switch
|
|
{
|
|
1 => 1.0f,
|
|
2 => 1.2f,
|
|
3 => 1.4f,
|
|
4 => 1.6f,
|
|
5 => 2.0f,
|
|
_ => 1.0f
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 유효 대미지 산출 (balance/01 v0.2 §3 대미지 공식).
|
|
/// effectiveDamage = BaseDamage * DamageMultiplier * StackLevelFactor * AttributeMultiplier
|
|
/// </summary>
|
|
public int CalculateEffectiveDamage()
|
|
{
|
|
if (_inventory == null) return ActiveData.BaseDamage;
|
|
|
|
var stats = _inventory.Stats;
|
|
float attrMult = 1.0f;
|
|
if (ActiveData.AttributeTags != AttributeTag.None &&
|
|
stats.AttributeMultiplier.TryGetValue(ActiveData.AttributeTags, out float v))
|
|
{
|
|
attrMult = v;
|
|
}
|
|
|
|
return Mathf.RoundToInt(
|
|
ActiveData.BaseDamage *
|
|
stats.DamageMultiplier *
|
|
StackLevelFactor(StackLevel) *
|
|
attrMult
|
|
);
|
|
}
|
|
}
|
|
}
|