166 lines
8.7 KiB
C#
166 lines
8.7 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// GrowthEvents.cs — WL 성장(레벨업) 통지 허브(정적)
|
||
//
|
||
// PD 지시 #813 · 발주서 WL-813k §1-1 (2026-09-09)
|
||
//
|
||
// ■ 왜 별도 파일인가 (813h BossEvents 선례)
|
||
// 코어 허브 `Assets/WL/Combat/Core/CombatEvents.cs` 는 811d~j 가 동시에 물고 있어 수정 금지다.
|
||
// 811b 가 둔 `CombatEvents.LevelUp` 페이로드는 `pc · newLevel` 2필드뿐이라 813g 팝업이 읽어야 할
|
||
// 「이전/새 스탯 3개」를 담을 수 없다. 그래서 같은 규약으로 이 파일에 성장 축을 따로 둔다.
|
||
// · 정적 클래스(새 Manager/Singleton 0) · 구조체 페이로드를 `in` 으로 전달(복사·박싱 0)
|
||
// · 구독자 = 사전 할당 배열(코어의 CombatEventList<T> 재사용 — Assembly-CSharp 단일 어셈블리)
|
||
// · 구독자 예외는 try/catch 로 격리(코어 CombatEventList.Dispatch 안) → 원본 Add_Exp 흐름을 끊지 않는다
|
||
// · 코어 스위치(WLCombatCoreSettings)와 성장 스위치(WLGrowthSettings) 둘 다 준수 = C8 롤백
|
||
// 코어를 대체하지 않는다 — forwardToCombatEvents 가 켜져 있으면 811b 의 `CombatEvents.RaiseLevelUp`
|
||
// 도 그대로 호출해 코어 구독자(있다면)를 잃지 않는다.
|
||
//
|
||
// ■ 발생 지점 (원본 훅 1줄)
|
||
// ServerClass.cs `SD_PC.Add_Exp` 레벨업 while 루프 안 `++Lv;` 직후 — 오른 레벨 1 당 1회.
|
||
// (한 번의 Add_Exp 로 여러 레벨이 오르면 그 횟수만큼 발생하고 levelsGained 가 1,2,3… 으로 센다)
|
||
//
|
||
// ■ 구독
|
||
// WL.Combat.Growth.GrowthEvents.LevelUp.Add(handler) / Remove(handler)
|
||
// 연출 = LevelUpBurst(G · 이 폴더) · 팝업 = 813g(U). 둘 다 여기를 구독만 한다.
|
||
//
|
||
// 🔴 어셈블리 주의: Assets/WL/Combat/ 에 .asmdef 를 만들지 말 것(SD_PC 가 Assembly-CSharp 에 있다).
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
using UnityEngine;
|
||
using WL.Combat.Core;
|
||
|
||
namespace WL.Combat.Growth
|
||
{
|
||
/// <summary>PC 레벨업 페이로드(발생 시점 스냅샷 · struct · GC 0).</summary>
|
||
public struct PlayerLevelUpEvent
|
||
{
|
||
/// <summary>레벨업한 서버 데이터(ServerData.PC). 항상 non-null.</summary>
|
||
public SD_PC data;
|
||
/// <summary>월드의 메인 PC. 로비·오프라인 보상 등 월드에 PC 가 없으면 null — 구독자는 반드시 null 검사할 것.</summary>
|
||
public Actor pc;
|
||
|
||
/// <summary>레벨업 전/후 레벨.</summary>
|
||
public int prevLevel, newLevel;
|
||
/// <summary>같은 Add_Exp 호출(같은 프레임·같은 SD_PC)에서 이번이 몇 번째 레벨업인가(1부터).</summary>
|
||
public int levelsGained;
|
||
/// <summary>레벨업 뒤 남은 미사용 스탯 포인트(SD_PC.Get_Point()). 원본 = (Lv-1)*5 − 사용분.</summary>
|
||
public int statPointTotal;
|
||
|
||
/// <summary>813g 팝업용 스탯 3종의 종류(WLGrowthSettings.statA/B/C · 기본 MaxHP · MaxMP · ATK).</summary>
|
||
public eStat statAKind, statBKind, statCKind;
|
||
/// <summary>스탯 3종의 이전/새 값. refreshStatOnLevelUp 이 꺼져 있으면(기본) 원본대로 prev == new 다 — 원본은 레벨업으로 스탯이 바로 오르지 않고 포인트만 준다.</summary>
|
||
public double prevStatA, newStatA;
|
||
public double prevStatB, newStatB;
|
||
public double prevStatC, newStatC;
|
||
|
||
/// <summary>전회복 직전의 HP/MP 와 최대치(연출·로그·UI 용 · PC 가 없으면 0).</summary>
|
||
public double hpBefore, maxHp, mpBefore, maxMp;
|
||
|
||
/// <summary>연출 스폰 기준 위치(PC 가 없으면 Vector3.zero).</summary>
|
||
public Vector3 position;
|
||
|
||
public float time;
|
||
public int frame;
|
||
}
|
||
|
||
/// <summary>성장 이벤트 허브. 정적 · GC 0 · 구독은 LevelUp.Add / Remove.</summary>
|
||
public static class GrowthEvents
|
||
{
|
||
/// <summary>레벨업 구독자 목록(코어와 같은 구현 · 예외 격리 · Raised/Dispatched 카운터).</summary>
|
||
public static readonly CombatEventList<PlayerLevelUpEvent> LevelUp = new CombatEventList<PlayerLevelUpEvent>(4);
|
||
|
||
/// <summary>성장 축이 살아 있는가 = 코어 스위치 · 성장 스위치 둘 다 켜짐. 어느 쪽을 꺼도 원본 동작(C8).</summary>
|
||
public static bool Enabled
|
||
{
|
||
get { return WLCombatCoreSettings.Enabled && WLGrowthSettings.Enabled; }
|
||
}
|
||
|
||
// ── 진단(프로브가 읽는다)
|
||
public static int TotalRaised;
|
||
public static string LastEvent = "";
|
||
public static float LastEventTime;
|
||
public static int LastLevel, LastPrevLevel, LastLevelsGained, LastStatPoint;
|
||
|
||
// ── 연속 레벨업 카운트(같은 프레임 · 같은 SD_PC 면 이어서 센다)
|
||
static SD_PC s_lastData;
|
||
static int s_lastFrame = -1;
|
||
static int s_chain;
|
||
|
||
/// <summary>
|
||
/// 원본 훅 1줄이 부른다 — `ServerClass.cs` `SD_PC.Add_Exp` 의 `++Lv;` 직후.
|
||
/// 코어/성장 스위치가 꺼져 있거나 에셋이 없으면 즉시 반환한다(원본 100%).
|
||
/// </summary>
|
||
public static void RaiseLevelUp(SD_PC data, int newLevel)
|
||
{
|
||
if (!Enabled) return;
|
||
|
||
var s = WLGrowthSettings.Instance;
|
||
|
||
// 월드의 메인 PC. 로비·결과창·오프라인 보상 경로에서는 없을 수 있다(파괴된 참조도 Unity != 로 걸러진다).
|
||
var mypc = MyValue.MyPC;
|
||
Actor pc = mypc != null ? mypc : null;
|
||
|
||
int frame = Time.frameCount;
|
||
if (ReferenceEquals(data, s_lastData) && s_lastFrame == frame) s_chain++;
|
||
else s_chain = 1;
|
||
s_lastData = data; s_lastFrame = frame;
|
||
|
||
var e = new PlayerLevelUpEvent
|
||
{
|
||
data = data,
|
||
pc = pc,
|
||
prevLevel = newLevel - 1,
|
||
newLevel = newLevel,
|
||
levelsGained = s_chain,
|
||
statPointTotal = data != null ? data.Get_Point() : 0,
|
||
statAKind = s.statA, statBKind = s.statB, statCKind = s.statC,
|
||
position = pc != null ? pc.Get_position() : Vector3.zero,
|
||
time = Time.unscaledTime,
|
||
frame = frame
|
||
};
|
||
|
||
var info = pc != null ? pc.Get_StatInfo() : null;
|
||
if (info != null)
|
||
{
|
||
e.prevStatA = info.Get_Stat(s.statA);
|
||
e.prevStatB = info.Get_Stat(s.statB);
|
||
e.prevStatC = info.Get_Stat(s.statC);
|
||
e.hpBefore = info.Get_Stat(eStat.HP); e.maxHp = info.Get_Stat(eStat.MaxHP);
|
||
e.mpBefore = info.Get_Stat(eStat.MP); e.maxMp = info.Get_Stat(eStat.MaxMP);
|
||
|
||
// 기본 off — 원본은 레벨업으로 스탯이 즉시 오르지 않는다(포인트만 지급). 켜면 재계산 후 새 값을 담는다.
|
||
if (s.refreshStatOnLevelUp)
|
||
MyValue.Get_ActorStatInfoorNull(eRole.PC, false, "0", 0, true); // showchange=false(StatChangeUI 미호출) · keephpmp=true
|
||
|
||
e.newStatA = info.Get_Stat(s.statA);
|
||
e.newStatB = info.Get_Stat(s.statB);
|
||
e.newStatC = info.Get_Stat(s.statC);
|
||
}
|
||
|
||
TotalRaised++;
|
||
LastEvent = "LevelUp";
|
||
LastEventTime = e.time;
|
||
LastPrevLevel = e.prevLevel; LastLevel = e.newLevel;
|
||
LastLevelsGained = e.levelsGained; LastStatPoint = e.statPointTotal;
|
||
|
||
LevelUp.Dispatch(in e);
|
||
|
||
// 811b 가 API 만 둔 코어 이벤트에도 그대로 흘린다(코어가 꺼져 있으면 내부에서 즉시 반환).
|
||
if (s.forwardToCombatEvents) CombatEvents.RaiseLevelUp(pc, newLevel);
|
||
|
||
if (s.verboseLog)
|
||
Debug.Log("[GrowthEvents] LevelUp " + e.prevLevel + "→" + e.newLevel +
|
||
" chain=" + e.levelsGained + " point=" + e.statPointTotal +
|
||
" pc=" + (pc != null ? pc.name : "none"));
|
||
}
|
||
|
||
/// <summary>프로브용: 카운터 초기화(구독은 유지).</summary>
|
||
public static void ResetDiagnostics()
|
||
{
|
||
TotalRaised = 0; LastEvent = ""; LastEventTime = 0f;
|
||
LastLevel = LastPrevLevel = LastLevelsGained = LastStatPoint = 0;
|
||
LevelUp.Raised = LevelUp.Dispatched = 0;
|
||
s_lastData = null; s_lastFrame = -1; s_chain = 0;
|
||
}
|
||
}
|
||
}
|