Project_WL/Assets/WL/Combat/Boss/BossEvents.cs

105 lines
6.0 KiB
C#

// ─────────────────────────────────────────────────────────────────────────────
// BossEvents.cs — WL 전투 액션 · 보스 페이즈 통지 허브(정적)
//
// PD 지시 #813 · 발주서 WL-813h §1-1 (2026-09-09)
//
// ■ 왜 별도 파일인가
// 코어 허브 `Assets/WL/Combat/Core/CombatEvents.cs` 는 811d~j 가 동시에 물고 있어 수정 금지다.
// 보스 페이즈(BossPhase)는 811 12종 이벤트에 없던 새 축이므로 같은 규약으로 이 파일에 따로 둔다.
// · 정적 클래스(새 Manager/Singleton 0) · 구조체 페이로드를 `in` 으로 전달(복사·박싱 0)
// · 구독자 = 사전 할당 배열(코어의 CombatEventList<T> 재사용 — Assembly-CSharp 단일 어셈블리)
// · 구독자 예외는 try/catch 로 격리(코어 CombatEventList.Dispatch 안) → 원본 전투 흐름을 끊지 않는다
// · WLCombatCoreSettings.enabled / RuntimeDisabled 를 그대로 준수(C8 롤백 1순위 스위치 공유)
//
// ■ 발생 지점 (원본 훅은 BossMobActor.cs 1줄뿐 — 실제 판정은 BossPatternTable 안)
// BossMobActor.On_Regen ─ BossPatternTable.Apply → 페이즈 1 진입 통지(스폰)
// BossMobActor.Get_Damage ─ BossPatternTable.UpdatePhase → HP% 가 임계를 내려갈 때만 통지
// 페이즈는 단조 증가(회복해도 되돌아가지 않는다) — 연출·UI 가 페이즈를 왕복하지 않게 한다.
//
// ■ 임계값의 출처 (C45)
// 코드 상수 0. 임계는 `Assets/WL/Combat/Settings/Resources/WL/WL813h_BossPattern.json`
// (보스별 phaseThresholds · 없으면 테이블 기본 [1.0, 0.7, 0.4]).
// JSON 이 없거나 코어가 꺼져 있으면 Raise 자체가 일어나지 않는다 = 원본 100%.
//
// 🔴 어셈블리 주의: Assets/WL/Combat/ 에 .asmdef 를 만들지 말 것(BossMobActor 가 Assembly-CSharp 에 있다).
// ─────────────────────────────────────────────────────────────────────────────
using UnityEngine;
using WL.Combat.Core;
namespace WL.Combat.Boss
{
/// <summary>보스 페이즈 전환 페이로드(발생 시점 스냅샷 · struct · GC 0).</summary>
public struct BossPhaseEvent
{
/// <summary>페이즈가 바뀐 보스(에디트 모드 프로브에서는 프리팹 인스턴스일 수 있다).</summary>
public BossMobActor boss;
/// <summary>MonsterList 의 n_MonsterID (Anubis = 10006).</summary>
public int monsterId;
/// <summary>이전 페이즈(스폰 통지는 0) · 새 페이즈(1부터).</summary>
public int prevPhase, phase;
/// <summary>이 보스의 총 페이즈 수(임계 배열 길이).</summary>
public int phaseCount;
/// <summary>전환 시점의 HP 비율(0~1) · 이 페이즈에 들어오게 한 임계값.</summary>
public float hpPercent, threshold;
/// <summary>스폰(페이즈 1 진입) 통지면 true — 피해로 인한 전환과 구분한다.</summary>
public bool isSpawn;
public float time;
public int frame;
}
/// <summary>보스 이벤트 허브. 정적 · GC 0 · 구독은 BossPhase.Add / Remove.</summary>
public static class BossEvents
{
/// <summary>페이즈 전환 구독자 목록(코어와 같은 구현 · 예외 격리 · Raised/Dispatched 카운터).</summary>
public static readonly CombatEventList<BossPhaseEvent> BossPhase = new CombatEventList<BossPhaseEvent>(4);
/// <summary>코어 활성 여부 = WLCombatCoreSettings.Enabled(에셋 · enabled · RuntimeDisabled).</summary>
public static bool Enabled { get { return WLCombatCoreSettings.Enabled; } }
// ── 진단(프로브가 읽는다)
public static int TotalRaised;
public static string LastEvent = "";
public static float LastEventTime;
public static int LastPhase, LastPrevPhase, LastMonsterId;
public static float LastHpPercent, LastThreshold;
/// <summary>
/// 페이즈 전환 통지. 호출측(BossPatternTable)이 이미 "바뀌었다"를 판정한 뒤에만 부른다.
/// 코어가 꺼져 있으면 즉시 반환(C8) — 구독자도 카운터도 움직이지 않는다.
/// </summary>
public static void RaiseBossPhase(BossMobActor boss, int monsterId, int prevPhase, int phase,
int phaseCount, float hpPercent, float threshold, bool isSpawn)
{
if (!Enabled) return;
var e = new BossPhaseEvent
{
boss = boss, monsterId = monsterId,
prevPhase = prevPhase, phase = phase, phaseCount = phaseCount,
hpPercent = hpPercent, threshold = threshold, isSpawn = isSpawn,
time = Time.unscaledTime, frame = Time.frameCount
};
TotalRaised++;
LastEvent = "BossPhase";
LastEventTime = e.time;
LastPrevPhase = prevPhase; LastPhase = phase; LastMonsterId = monsterId;
LastHpPercent = hpPercent; LastThreshold = threshold;
BossPhase.Dispatch(in e);
var c = WLCombatCoreSettings.Instance;
if (c != null && c.verboseLog)
Debug.Log("[BossEvents] Phase " + prevPhase + "→" + phase + " id=" + monsterId +
" hp=" + hpPercent.ToString("F3") + " th=" + threshold.ToString("F3"));
}
/// <summary>프로브용: 카운터 초기화(구독은 유지).</summary>
public static void ResetDiagnostics()
{
TotalRaised = 0; LastEvent = ""; LastEventTime = 0f;
LastPhase = LastPrevPhase = LastMonsterId = 0;
LastHpPercent = LastThreshold = 0f;
BossPhase.Raised = BossPhase.Dispatched = 0;
}
}
}