156 lines
7.5 KiB
C#
156 lines
7.5 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
|
// DamageAggregator.cs — 데미지 텍스트 합산 윈도우(0.25 s · 대상별) 데이터 API — 표시는 813g(U)
|
|
//
|
|
// PD 지시 #813 · 발주서 WL-811def §1-2 · 기준서 v1 §B 요소 2 「합산 윈도우 0.25 s · 숫자죽 방지」 (2026-09-09)
|
|
//
|
|
// ■ 무엇을 하나 (HitConfirmed 구독 · 원본 훅 0 · HUDDMGUI 무수정)
|
|
// 같은 대상에 첫 타격이 들어온 순간부터 damageMergeWindowSeconds(unscaled) 안의 타격을 한 슬롯에 더한다.
|
|
// 윈도우가 끝나면(또는 처치 타격이면 즉시) Flushed(대상 · 합계 · 타수 · 최대 단타 · 크리 여부 · 처치 여부 · 위치)를 1회 낸다.
|
|
// 813g 는 Flushed 만 구독하면 "초당 3~5개 겹치는 숫자" 대신 합산 1개를 띄울 수 있다. 원본 HUDDMGUI 는 그대로 돈다(병행).
|
|
// ■ GC 0 — 슬롯 배열(고정 · 첫 사용 때 1회 할당) · 선형 탐색(≤ 32) · 구조체 이벤트.
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
using UnityEngine;
|
|
using WL.Combat.Core;
|
|
|
|
namespace WL.Combat.Reaction
|
|
{
|
|
/// <summary>합산 결과(윈도우 만료 또는 처치 즉시).</summary>
|
|
public struct DamageMergeEvent
|
|
{
|
|
public Actor victim; public double total; public int hits; public double maxSingle; public bool anyCritical; public bool isKill; public bool byMainPC;
|
|
public Vector3 position; public float firstTime; public float time; public int frame;
|
|
}
|
|
|
|
public static class DamageAggregator
|
|
{
|
|
public static readonly CombatEventList<DamageMergeEvent> Flushed = new CombatEventList<DamageMergeEvent>(4);
|
|
|
|
struct Slot
|
|
{
|
|
public bool used; public Actor victim; public double total, maxSingle; public int hits;
|
|
public bool anyCritical, isKill, byMainPC; public float start; public Vector3 pos;
|
|
}
|
|
|
|
static Slot[] s_slots;
|
|
static int s_used;
|
|
|
|
// ── 진단
|
|
public static bool Subscribed;
|
|
public static int HitCount, FlushCount, MaxHitsMerged, EvictCount;
|
|
public static string LastInfo = "";
|
|
public static int ActiveSlots { get { return s_used; } }
|
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
|
|
static void Register()
|
|
{
|
|
CombatEvents.HitConfirmed.Add(OnHitConfirmed);
|
|
Subscribed = true;
|
|
}
|
|
|
|
static WLReactionSettings St { get { return WLReactionSettings.Instance; } }
|
|
|
|
static bool Verbose { get { var st = St; return st != null && st.verboseLog; } }
|
|
|
|
static void Log(string msg)
|
|
{
|
|
var st = St;
|
|
if (st != null && st.verboseLog) Debug.Log("[DamageAggregator] " + msg);
|
|
}
|
|
|
|
static void EnsureSlots(WLReactionSettings st)
|
|
{
|
|
int n = Mathf.Max(4, st.damageMergeSlots);
|
|
if (s_slots == null || s_slots.Length != n) { s_slots = new Slot[n]; s_used = 0; }
|
|
}
|
|
|
|
static void OnHitConfirmed(in HitConfirmedEvent e)
|
|
{
|
|
if (!WLReactionSettings.Enabled) return;
|
|
var st = St;
|
|
if (!st.damageMergeEnabled || e.victim == null) return;
|
|
if (st.damageMergeMainPCOnly && !e.byMainPC) return;
|
|
|
|
EnsureSlots(st);
|
|
WLReactionRunner.Ensure();
|
|
float now = Time.unscaledTime;
|
|
HitCount++;
|
|
|
|
int idx = -1, free = -1, oldest = -1; float oldestStart = float.MaxValue;
|
|
for (int i = 0; i < s_slots.Length; i++)
|
|
{
|
|
if (!s_slots[i].used) { if (free < 0) free = i; continue; }
|
|
if (ReferenceEquals(s_slots[i].victim, e.victim)) { idx = i; break; }
|
|
if (s_slots[i].start < oldestStart) { oldestStart = s_slots[i].start; oldest = i; }
|
|
}
|
|
if (idx < 0)
|
|
{
|
|
if (free < 0) { Flush(oldest, now); EvictCount++; free = oldest; }
|
|
idx = free;
|
|
s_slots[idx].used = true; s_slots[idx].victim = e.victim; s_slots[idx].total = 0d; s_slots[idx].maxSingle = 0d;
|
|
s_slots[idx].hits = 0; s_slots[idx].anyCritical = false; s_slots[idx].isKill = false; s_slots[idx].byMainPC = e.byMainPC; s_slots[idx].start = now;
|
|
s_used++;
|
|
}
|
|
s_slots[idx].total += e.damage;
|
|
if (e.damage > s_slots[idx].maxSingle) s_slots[idx].maxSingle = e.damage;
|
|
s_slots[idx].hits++;
|
|
s_slots[idx].anyCritical |= e.critical;
|
|
s_slots[idx].isKill |= e.isKill;
|
|
s_slots[idx].byMainPC |= e.byMainPC;
|
|
s_slots[idx].pos = e.victim.Get_position();
|
|
|
|
if (e.isKill && st.damageMergeFlushOnKill) Flush(idx, now);
|
|
}
|
|
|
|
static void Flush(int i, float now)
|
|
{
|
|
if (i < 0 || !s_slots[i].used) return;
|
|
var ev = new DamageMergeEvent
|
|
{
|
|
victim = s_slots[i].victim, total = s_slots[i].total, hits = s_slots[i].hits, maxSingle = s_slots[i].maxSingle,
|
|
anyCritical = s_slots[i].anyCritical, isKill = s_slots[i].isKill, byMainPC = s_slots[i].byMainPC,
|
|
position = s_slots[i].pos, firstTime = s_slots[i].start, time = now, frame = Time.frameCount
|
|
};
|
|
if (ev.hits > MaxHitsMerged) MaxHitsMerged = ev.hits;
|
|
s_slots[i].used = false; s_slots[i].victim = null; s_used--;
|
|
FlushCount++;
|
|
Flushed.Dispatch(in ev);
|
|
if (Verbose) Log("플러시 victim=" + (ev.victim != null ? ev.victim.name : "?") + " total=" + ev.total.ToString("F0") + " hits=" + ev.hits + " cri=" + ev.anyCritical + " kill=" + ev.isKill);
|
|
}
|
|
|
|
internal static void Tick(float now)
|
|
{
|
|
if (s_used <= 0 || s_slots == null) return;
|
|
var st = St;
|
|
float win = st != null ? st.damageMergeWindowSeconds : 0.25f;
|
|
for (int i = 0; i < s_slots.Length; i++)
|
|
if (s_slots[i].used && now - s_slots[i].start >= win) Flush(i, now);
|
|
}
|
|
|
|
/// <summary>데이터 API — 대상의 합산 중 값(813g 가 미리 읽고 싶을 때). 슬롯이 없으면 false.</summary>
|
|
public static bool TryGetPending(Actor victim, out double total, out int hits)
|
|
{
|
|
total = 0d; hits = 0;
|
|
if (s_slots == null || victim == null) return false;
|
|
for (int i = 0; i < s_slots.Length; i++)
|
|
if (s_slots[i].used && ReferenceEquals(s_slots[i].victim, victim)) { total = s_slots[i].total; hits = s_slots[i].hits; return true; }
|
|
return false;
|
|
}
|
|
|
|
/// <summary>씬 전환 · 프로브 정리: 슬롯을 비운다(이벤트 없음).</summary>
|
|
public static void ClearAll()
|
|
{
|
|
if (s_slots == null) return;
|
|
for (int i = 0; i < s_slots.Length; i++) { s_slots[i].used = false; s_slots[i].victim = null; }
|
|
s_used = 0;
|
|
}
|
|
|
|
/// <summary>프로브용 카운터 초기화.</summary>
|
|
public static void ResetDiagnostics()
|
|
{
|
|
HitCount = FlushCount = MaxHitsMerged = EvictCount = 0; LastInfo = "";
|
|
Flushed.Raised = Flushed.Dispatched = 0;
|
|
}
|
|
}
|
|
}
|