477 lines
28 KiB
C#
477 lines
28 KiB
C#
// WL-813tj (#813) — 실측 프로브(Play 0 · 에디트 모드).
|
||
// unity command run_script --file AgentScripts/WL813tj_Probe.cs --entry WL813tj_Probe.DumpSafeArea
|
||
// unity command run_script --file AgentScripts/WL813tj_Probe.cs --entry WL813tj_Probe.DumpBoss
|
||
// unity command run_script --file AgentScripts/WL813tj_Probe.cs --entry WL813tj_Probe.DumpSurvival
|
||
// unity command run_script --file AgentScripts/WL813tj_Probe.cs --entry WL813tj_Probe.DumpPrefab
|
||
// 🔴 프리팹은 LoadPrefabContents 로 열고 **저장하지 않는다**(검증만). 구독은 전부 되돌린다.
|
||
|
||
using System.Text;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
|
||
public static class WL813tj_Probe
|
||
{
|
||
public const string NewGameUIPath = "Assets/Res_Addr/MainUI/NewGameUI.prefab";
|
||
public const string HudPath = "IngameUIs/WL_HUD";
|
||
public const string PadPath = "IngameUIs/BattleUI";
|
||
|
||
static Transform FindByPath(Transform root, string path)
|
||
{
|
||
var cur = root;
|
||
foreach (var part in path.Split('/'))
|
||
{
|
||
Transform next = null;
|
||
for (int i = 0; i < cur.childCount; i++)
|
||
if (cur.GetChild(i).name == part) { next = cur.GetChild(i); break; }
|
||
if (next == null) return null;
|
||
cur = next;
|
||
}
|
||
return cur;
|
||
}
|
||
|
||
static void Save(string file, string body)
|
||
{
|
||
try { System.IO.File.WriteAllText(System.IO.Path.Combine("Logs", file), body); } catch { }
|
||
}
|
||
|
||
// ═══ ⓒ Safe Area ════════════════════════════════════════════════════════
|
||
struct Res { public string name; public int w, h; public Rect safe; public string src; }
|
||
|
||
static Res[] QaResolutions()
|
||
{
|
||
// QA 5종(802b·813c 와 같은 목록). safeArea 는 「추정」 — 실기 인셋은 빌드 전까지 확인 불가(C23).
|
||
return new[]
|
||
{
|
||
new Res { name="1080x1920", w=1080, h=1920, safe=new Rect(0, 0, 1080, 1920), src="노치 없음(전체)" },
|
||
new Res { name="720x1600", w= 720, h=1600, safe=new Rect(0, 0, 720, 1600), src="노치 없음(전체)" },
|
||
new Res { name="1440x3200", w=1440, h=3200, safe=new Rect(0, 0, 1440, 3200), src="노치 없음(전체)" },
|
||
new Res { name="1179x2556", w=1179, h=2556, safe=new Rect(0, 102, 1179, 2277), src="다이내믹 아일랜드 「추정」 top 177 / bottom 102 px" },
|
||
new Res { name="1080x2400", w=1080, h=2400, safe=new Rect(0, 0, 1080, 2310), src="펀치홀 「추정」 top 90 px" },
|
||
};
|
||
}
|
||
|
||
/// <summary>SafeAreaFitter.Apply 와 **같은 4줄**을 재현한다(Screen.safeArea 를 주입할 수 없어서).</summary>
|
||
static void FitterMath(Rect safe, int w, int h, bool applyH, bool applyV, out Vector2 min, out Vector2 max)
|
||
{
|
||
min = new Vector2(safe.xMin / w, safe.yMin / h);
|
||
max = new Vector2(safe.xMax / w, safe.yMax / h);
|
||
if (!applyH) { min.x = 0f; max.x = 1f; }
|
||
if (!applyV) { min.y = 0f; max.y = 1f; }
|
||
}
|
||
|
||
public static object DumpSafeArea()
|
||
{
|
||
var sb = new StringBuilder();
|
||
var root = PrefabUtility.LoadPrefabContents(NewGameUIPath);
|
||
try
|
||
{
|
||
var rt = root.transform;
|
||
sb.AppendLine("[1] 캔버스 루트 순서 (children=" + rt.childCount + ")");
|
||
for (int i = 0; i < rt.childCount; i++)
|
||
{
|
||
var c = rt.GetChild(i);
|
||
var crt = c as RectTransform;
|
||
bool panel = c.name.StartsWith("WL_SafeArea");
|
||
sb.AppendLine(" [" + i + "] " + c.name + (panel ? " ← Safe Area 패널" : "") +
|
||
" active=" + c.gameObject.activeSelf +
|
||
(crt != null ? " aMin" + crt.anchorMin + " aMax" + crt.anchorMax +
|
||
" off" + crt.offsetMin + crt.offsetMax + " scale" + crt.localScale : ""));
|
||
if (!panel) continue;
|
||
var f = c.GetComponent<WL.UI.SafeAreaFitter>();
|
||
sb.AppendLine(" SafeAreaFitter=" + (f != null) + " Graphic=" + (c.GetComponent<UnityEngine.UI.Graphic>() != null) +
|
||
" comps=" + c.GetComponents<Component>().Length);
|
||
for (int j = 0; j < c.childCount; j++)
|
||
{
|
||
var g = c.GetChild(j) as RectTransform;
|
||
sb.AppendLine(" └ " + c.GetChild(j).name + " aMin" + g.anchorMin + " aMax" + g.anchorMax +
|
||
" off" + g.offsetMin + g.offsetMax + " scale" + g.localScale + " z=" + g.localPosition.z +
|
||
" active=" + g.gameObject.activeSelf);
|
||
}
|
||
}
|
||
|
||
sb.AppendLine();
|
||
sb.AppendLine("[2] 평탄화 순서(그리는 순서) = 원본과 같아야 회귀 0");
|
||
var flat = new StringBuilder();
|
||
for (int i = 0; i < rt.childCount; i++)
|
||
{
|
||
var c = rt.GetChild(i);
|
||
if (c.name.StartsWith("WL_SafeArea")) for (int j = 0; j < c.childCount; j++) flat.Append(c.GetChild(j).name).Append(" · ");
|
||
else flat.Append(c.name).Append(" · ");
|
||
}
|
||
sb.AppendLine(" 지금 : " + flat.ToString().TrimEnd(' ', '·'));
|
||
sb.AppendLine(" 원본 : New Image · Camera · Floating Joystick · CamRot · Common · LobbyUIs · IngameUIs · FarmUIs · MessageInfo · Center · PopupMgr · TestMapUIMgr");
|
||
|
||
sb.AppendLine();
|
||
sb.AppendLine("[3] SafeAreaFitter 산식 표 (QA 5해상도 · applyH=applyV=true)");
|
||
sb.AppendLine(" ※ Screen.safeArea 를 주입할 수 없어 SafeAreaFitter.Apply 의 4줄을 그대로 재현했다(802b 와 같은 방법).");
|
||
sb.AppendLine(" 해상도 | safeArea(x,y,w,h) | anchorMin | anchorMax | 화면 인셋(px) | 근거");
|
||
foreach (var r in QaResolutions())
|
||
{
|
||
Vector2 mn, mx;
|
||
FitterMath(r.safe, r.w, r.h, true, true, out mn, out mx);
|
||
float insetTop = r.h - r.safe.yMax, insetBottom = r.safe.yMin;
|
||
sb.AppendLine(" " + r.name.PadRight(12) + "| " +
|
||
("(" + r.safe.x + "," + r.safe.y + "," + r.safe.width + "," + r.safe.height + ")").PadRight(25) + "| " +
|
||
("(" + mn.x.ToString("F4") + ", " + mn.y.ToString("F4") + ")").PadRight(17) + "| " +
|
||
("(" + mx.x.ToString("F4") + ", " + mx.y.ToString("F4") + ")").PadRight(17) + "| " +
|
||
("top " + insetTop + " / bottom " + insetBottom).PadRight(22) + "| " + r.src);
|
||
}
|
||
sb.AppendLine(" offsetMin/offsetMax 는 언제나 (0,0) — 패널은 앵커만 움직인다(802b 와 동일).");
|
||
|
||
// 실측 1건: 에디터가 보고하는 실제 Screen 으로 진짜 컴포넌트를 돌려 재현식과 대조한다.
|
||
sb.AppendLine();
|
||
sb.AppendLine("[4] 실측 대조 1건 — 에디터 Screen(" + Screen.width + "x" + Screen.height + ") safeArea=" + Screen.safeArea);
|
||
foreach (var pn in new[] { "WL_SafeArea_Back", "WL_SafeArea_Front" })
|
||
{
|
||
var p = rt.Find(pn) as RectTransform;
|
||
if (p == null) { sb.AppendLine(" " + pn + " 없음"); continue; }
|
||
var f = p.GetComponent<WL.UI.SafeAreaFitter>();
|
||
if (f == null) { sb.AppendLine(" " + pn + " SafeAreaFitter 없음"); continue; }
|
||
f.Apply();
|
||
Vector2 mn, mx;
|
||
FitterMath(Screen.safeArea, Screen.width, Screen.height, true, true, out mn, out mx);
|
||
sb.AppendLine(" " + pn + " 실제 Apply → aMin" + p.anchorMin + " aMax" + p.anchorMax +
|
||
" off" + p.offsetMin + p.offsetMax + " | 재현식 aMin(" + mn.x.ToString("F4") + ", " + mn.y.ToString("F4") +
|
||
") aMax(" + mx.x.ToString("F4") + ", " + mx.y.ToString("F4") + ") | 일치=" +
|
||
(Vector2.Distance(p.anchorMin, mn) < 1e-5f && Vector2.Distance(p.anchorMax, mx) < 1e-5f));
|
||
// 검증만 — 프리팹은 저장하지 않는다
|
||
}
|
||
|
||
sb.AppendLine();
|
||
sb.AppendLine("[5] 회귀 — WLIngameUiOverride 경로(설정 에셋의 옛 문자열이 그대로 먹는가)");
|
||
var gs = WL.Settings.WLGameplaySettings.Instance;
|
||
if (gs == null) sb.AppendLine(" WLGameplaySettings 에셋 없음");
|
||
else
|
||
{
|
||
foreach (var p in gs.ingameHideCanvasGroupPaths ?? new string[0])
|
||
{
|
||
var t = WL.UI.WLVignetteUtil.FindUiPath(rt, p);
|
||
sb.AppendLine(" [canvasGroupHide] \"" + p + "\" → " + (t == null ? "🔴 없음" : "OK " + FullPath(rt, t)));
|
||
}
|
||
foreach (var p in gs.battlePadPaths ?? new string[0])
|
||
{
|
||
var t = WL.UI.WLVignetteUtil.FindUiPath(rt, p);
|
||
sb.AppendLine(" [battlePad] \"" + p + "\" → " + (t == null ? "🔴 없음" : "OK " + FullPath(rt, t) + " children=" + t.childCount));
|
||
}
|
||
var ui = root.GetComponent<NewGameUI>();
|
||
var common = (ui != null && ui.gos != null && ui.gos.Length > 0) ? ui.gos[0] : null;
|
||
sb.AppendLine(" [gos[0]] NewGameUI.gos[0]=" + (common == null ? "🔴 null" : common.name + " (참조 · 재부모화 무관) 부모=" +
|
||
(common.transform.parent != null ? common.transform.parent.name : "-")));
|
||
if (common != null)
|
||
foreach (var n in gs.ingameHideCommonObjectNames ?? new string[0])
|
||
sb.AppendLine(" [commonHide] \"" + n + "\" → " + (common.transform.Find(n) == null ? "🔴 없음" : "OK"));
|
||
sb.AppendLine(" hideBattlePad=" + gs.hideBattlePad + " (읽기만 · S 소유 · 값 전환은 Lead)");
|
||
}
|
||
|
||
// 813j 피격 테두리 대상도 같은 탐색기로 확인
|
||
var sur = WL.UI.WLSurvivalUiSettings.Instance;
|
||
if (sur != null)
|
||
{
|
||
var t = WL.UI.WLVignetteUtil.FindUiPath(rt, sur.hitBorderTargetPath);
|
||
sb.AppendLine(" [hitBorderTarget] \"" + sur.hitBorderTargetPath + "\" → " +
|
||
(t == null ? "🔴 없음" : "OK " + FullPath(rt, t) +
|
||
" size" + ((RectTransform)t).sizeDelta));
|
||
}
|
||
}
|
||
finally { PrefabUtility.UnloadPrefabContents(root); }
|
||
var body = sb.ToString();
|
||
Save("WL813tj_probe_safearea.txt", body);
|
||
return body;
|
||
}
|
||
|
||
static string FullPath(Transform root, Transform t)
|
||
{
|
||
var sb = new StringBuilder(t.name);
|
||
var cur = t.parent;
|
||
while (cur != null && cur != root) { sb.Insert(0, cur.name + "/"); cur = cur.parent; }
|
||
return sb.ToString();
|
||
}
|
||
|
||
// ═══ ⓑ 813t 보스 배너 · 경고 비네트 ═════════════════════════════════════
|
||
public static object DumpBoss()
|
||
{
|
||
var sb = new StringBuilder();
|
||
var root = PrefabUtility.LoadPrefabContents(NewGameUIPath);
|
||
WL.UI.BossBanner bc = null;
|
||
try
|
||
{
|
||
var hud = FindByPath(root.transform, HudPath);
|
||
var node = hud != null ? hud.Find(WL813tj_ApplyNames.Banner) : null;
|
||
if (node == null) return "🔴 " + HudPath + "/" + WL813tj_ApplyNames.Banner + " 없음 — Apply 먼저";
|
||
bc = node.GetComponent<WL.UI.BossBanner>();
|
||
if (bc == null) return "🔴 BossBanner 컴포넌트 없음";
|
||
|
||
sb.AppendLine("[0] 초기화 — 런타임과 같은 경로(OnEnable→Initialize)");
|
||
sb.AppendLine(" 구독 전 Spawned=" + WL.Combat.Core.CombatEvents.Spawned.Count +
|
||
" BossPhase=" + WL.Combat.Boss.BossEvents.BossPhase.Count);
|
||
sb.AppendLine(" " + bc.Initialize());
|
||
sb.AppendLine(" 구독 후 Spawned=" + WL.Combat.Core.CombatEvents.Spawned.Count +
|
||
" BossPhase=" + WL.Combat.Boss.BossEvents.BossPhase.Count);
|
||
float t0 = Time.unscaledTime;
|
||
|
||
sb.AppendLine();
|
||
sb.AppendLine("[1] 비보스 스폰 → 반응 없음(미표시 규칙)");
|
||
sb.AppendLine(" " + WL.UI.BossBanner.RaiseFakeNonBossSpawned());
|
||
bc.Tick(t0);
|
||
sb.AppendLine(" visible=" + bc.Visible + " shows=" + bc.ShowCount + " (기대 False / 0)");
|
||
|
||
sb.AppendLine();
|
||
sb.AppendLine("[2] 보스 스폰(CombatEvents.Spawned isBoss=true) → 배너 + 비네트");
|
||
sb.AppendLine(" " + WL.UI.BossBanner.RaiseFakeBossSpawned());
|
||
var s = WL.UI.WLBossUiSettings.Instance;
|
||
float[] marks = { 0f, 0.05f, 0.15f, 0.20f, 0.40f, 0.79f, 0.81f, 1.0f, 1.19f, 1.21f };
|
||
sb.AppendLine(" t(s) | 배너α | 배너scale | 경고α | 상태");
|
||
foreach (var m in marks)
|
||
{
|
||
bc.Tick(t0 + m);
|
||
string state = (m < s.bannerSeconds ? "배너 " : "배너끝 ") + (m < s.warnVignetteSeconds ? "· 비네트" : "· 비네트끝");
|
||
sb.AppendLine(" " + m.ToString("F2").PadLeft(5) + " | " + bc.BannerAlpha.ToString("F3") +
|
||
" | " + bc.BannerScale.ToString("F3").PadLeft(8) +
|
||
" | " + bc.WarnAlpha.ToString("F3") + " | " + state);
|
||
}
|
||
sb.AppendLine(" shows=" + bc.ShowCount + " seen(Spawned/보스/Phase)=" + bc.SpawnedSeen + "/" + bc.BossSpawnSeen + "/" + bc.PhaseSeen);
|
||
|
||
sb.AppendLine();
|
||
sb.AppendLine("[3] 이름 승격 — BossEvents.BossPhase(isSpawn, monsterId) 가 MonsterList 이름을 준다");
|
||
sb.AppendLine(" 표 로드 여부: table_monsterlist.Ins=" + (table_monsterlist.Ins != null ? "있음" : "없음(에디트 모드)") +
|
||
" · table_localtext.Ins=" + (table_localtext.Ins != null ? "있음" : "없음"));
|
||
sb.AppendLine(" ResolveMonsterName(10006 Anubis)=" + (WL.UI.BossBanner.ResolveMonsterName(10006) ?? "(null · 표 미로드)"));
|
||
sb.AppendLine(" " + WL.UI.BossBanner.RaiseFakeBossPhaseSpawn(10006));
|
||
sb.AppendLine(" → " + bc.Dump().Split('\n')[0].Trim());
|
||
|
||
sb.AppendLine();
|
||
sb.AppendLine("[4] 직접 Trigger(이름 주입) — 표 없이도 이름 줄이 붙는지");
|
||
sb.AppendLine(" " + bc.ResetDiagnostics() + " (에디트 모드는 Time 이 멈춰 가드가 안 풀린다)");
|
||
sb.AppendLine(" " + bc.Trigger("아누비스", true));
|
||
bc.Tick(Time.unscaledTime + 0.2f);
|
||
sb.AppendLine();
|
||
sb.AppendLine("[5] 최종 덤프");
|
||
sb.Append(bc.Dump());
|
||
|
||
sb.AppendLine();
|
||
sb.AppendLine("[6] 813c 보스 HP 바와 같은 프레임 — 같은 Spawned Dispatch 를 함께 탄다");
|
||
var barNode = hud.Find("WL_BossHpBar");
|
||
var bar = barNode != null ? barNode.GetComponent<WL.UI.BossHpBar>() : null;
|
||
if (bar == null) sb.AppendLine(" 🔴 WL_BossHpBar 없음");
|
||
else
|
||
{
|
||
bar.Initialize();
|
||
bc.ResetDiagnostics();
|
||
sb.AppendLine(" 구독자 Spawned=" + WL.Combat.Core.CombatEvents.Spawned.Count + " (배너 + 보스바)");
|
||
int f0 = Time.frameCount;
|
||
sb.AppendLine(" " + WL.UI.BossHpBar.RaiseFakeBossSpawned(1000f));
|
||
bc.Tick(Time.unscaledTime + 0.2f); // 페이드인 0.15s 뒤(t=0 은 α 0 이 정상)
|
||
sb.AppendLine(" 같은 프레임(frame=" + f0 + "): 배너 shows=" + bc.ShowCount + " visible=" + bc.Visible +
|
||
" · 보스바 visible=" + bar.Visible + " ratio=" + bar.LastRatio.ToString("F3"));
|
||
bar.Subscribe(false);
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
if (bc != null) bc.Subscribe(false);
|
||
PrefabUtility.UnloadPrefabContents(root);
|
||
}
|
||
sb.AppendLine();
|
||
sb.AppendLine("해제 후 구독자 Spawned=" + WL.Combat.Core.CombatEvents.Spawned.Count +
|
||
" BossPhase=" + WL.Combat.Boss.BossEvents.BossPhase.Count + " (0 이어야 한다)");
|
||
var body = sb.ToString();
|
||
Save("WL813tj_probe_boss.txt", body);
|
||
return body;
|
||
}
|
||
|
||
// ═══ ⓑ 813j 물약 · 부활 · 피격 ══════════════════════════════════════════
|
||
public static object DumpSurvival()
|
||
{
|
||
var sb = new StringBuilder();
|
||
var root = PrefabUtility.LoadPrefabContents(NewGameUIPath);
|
||
WL.UI.HitVignette hv = null;
|
||
try
|
||
{
|
||
var hud = FindByPath(root.transform, HudPath);
|
||
var pad = FindByPath(root.transform, PadPath);
|
||
var s = WL.UI.WLSurvivalUiSettings.Instance;
|
||
if (s == null) return "🔴 WLSurvivalUiSettings 에셋 없음";
|
||
|
||
// ── A. 피격 테두리 + 가장자리 비네트
|
||
var hvNode = hud != null ? hud.Find(WL813tj_ApplyNames.Hit) : null;
|
||
hv = hvNode != null ? hvNode.GetComponent<WL.UI.HitVignette>() : null;
|
||
sb.AppendLine("[A] 피격 연출 (CombatEvents.Damaged · 실측 발생지 MyActor.cs:309 = PC 전용)");
|
||
if (hv == null) sb.AppendLine(" 🔴 WL_HitVignette 없음");
|
||
else
|
||
{
|
||
sb.AppendLine(" 구독 전 Damaged=" + WL.Combat.Core.CombatEvents.Damaged.Count);
|
||
sb.AppendLine(" " + hv.Initialize());
|
||
sb.AppendLine(" 구독 후 Damaged=" + WL.Combat.Core.CombatEvents.Damaged.Count);
|
||
float t0 = Time.unscaledTime;
|
||
|
||
sb.AppendLine(" ① 무적 피격(invincible=true · dmg 0) ×3 → 무시되어야 한다");
|
||
for (int i = 0; i < 3; i++) WL.UI.HitVignette.RaiseFakeDamaged(0, true);
|
||
sb.AppendLine(" seen=" + hv.DamagedSeen + " 무적스킵=" + hv.InvincibleSkipped + " flashes=" + hv.FlashCount +
|
||
" borderα=" + hv.BorderAlpha.ToString("F3") + " (기대 flashes=0)");
|
||
|
||
sb.AppendLine(" ② 실피격(invincible=false · dmg 120) → 번쩍");
|
||
sb.AppendLine(" " + WL.UI.HitVignette.RaiseFakeDamaged(120, false));
|
||
sb.AppendLine(" t(s) | 테두리α | 가장자리α");
|
||
foreach (var m in new[] { 0f, 0.10f, 0.20f, 0.29f, 0.31f })
|
||
{
|
||
hv.Tick(t0 + m);
|
||
sb.AppendLine(" " + m.ToString("F2").PadLeft(5) + " | " + hv.BorderAlpha.ToString("F3").PadLeft(7) +
|
||
" | " + hv.EdgeAlpha.ToString("F3"));
|
||
}
|
||
sb.AppendLine(" ③ 같은 프레임 연타 → 스로틀(" + s.hitMinIntervalSeconds + "s)");
|
||
int before = hv.FlashCount;
|
||
for (int i = 0; i < 4; i++) WL.UI.HitVignette.RaiseFakeDamaged(50, false);
|
||
sb.AppendLine(" 추가 요청 4 → flashes " + before + "→" + hv.FlashCount + " 스로틀스킵=" + hv.ThrottleSkipped);
|
||
sb.AppendLine();
|
||
sb.Append(hv.Dump());
|
||
hv.RemoveBorder(); // 프리팹에 굽지 않는다 — 검증 후 즉시 제거
|
||
}
|
||
|
||
// ── B. 부활 팝업
|
||
sb.AppendLine();
|
||
sb.AppendLine("[B] 부활 팝업 (공용 Popup/SortOrder_5 재사용 · 813i 미연결)");
|
||
var rvNode = hud != null ? hud.Find(WL813tj_ApplyNames.Revive) : null;
|
||
var rv = rvNode != null ? rvNode.GetComponent<WL.UI.ReviveDialog>() : null;
|
||
if (rv == null) sb.AppendLine(" 🔴 WL_ReviveDialog 없음");
|
||
else
|
||
{
|
||
sb.AppendLine(" " + rv.Initialize());
|
||
float t0 = Time.unscaledTime;
|
||
sb.AppendLine(" 813i 진입점 NotifyDeath() → " + WL.UI.ReviveDialog.NotifyDeath());
|
||
rv.Tick(t0 + s.reviveDelaySeconds - 0.01f);
|
||
sb.AppendLine(" t=" + (s.reviveDelaySeconds - 0.01f).ToString("F2") + "s : 표시=" + rv.FallbackVisible + " 대기중=" + rv.Pending + " (기대 False/True)");
|
||
rv.Tick(t0 + s.reviveDelaySeconds);
|
||
sb.AppendLine(" t=" + s.reviveDelaySeconds.ToString("F2") + "s : 표시=" + rv.FallbackVisible +
|
||
" 공용팝업사용=" + rv.UsedCommonPopup + " shows=" + rv.ShownCount);
|
||
sb.AppendLine(" 본문=\"" + rv.LastMessage.Replace("\n", " / ") + "\"");
|
||
bool got = false;
|
||
WL.UI.ReviveDialog.ReviveRequested = () => got = true;
|
||
sb.AppendLine(" 확인 → " + rv.OnConfirm() + " · 813i 콜백 수신=" + got + " · 표시=" + rv.FallbackVisible);
|
||
WL.UI.ReviveDialog.ReviveRequested = null;
|
||
sb.AppendLine();
|
||
sb.Append(rv.Dump());
|
||
}
|
||
|
||
// ── C. 물약 버튼
|
||
sb.AppendLine();
|
||
sb.AppendLine("[C] 물약 버튼 (813c 예약 슬롯 · 813i Provider 미연결 → 대역으로 왕복 검증)");
|
||
var ptNode = pad != null ? pad.Find(WL813tj_ApplyNames.Potion) : null;
|
||
var pt = ptNode != null ? ptNode.GetComponent<WL.UI.PotionButton>() : null;
|
||
if (pt == null) sb.AppendLine(" 🔴 WL_PotionButton 없음");
|
||
else
|
||
{
|
||
WL.UI.PotionButton.ClearProvider();
|
||
sb.AppendLine(" " + pt.Initialize());
|
||
sb.AppendLine(" ① 813i 미연결: HasProvider=" + WL.UI.PotionButton.HasProvider + " · " + pt.Refresh());
|
||
sb.AppendLine(" 클릭 → " + pt.OnClickPotion() + " (used=" + pt.UsedCount + " blocked=" + pt.BlockedCount + ")");
|
||
sb.AppendLine(" ② 813i 대역 설치: " + WL.UI.PotionButton.InstallFakeProvider(3, 5f));
|
||
sb.AppendLine(" " + pt.Refresh());
|
||
sb.AppendLine(" 클릭1 → " + pt.OnClickPotion() + " · " + pt.Refresh());
|
||
sb.AppendLine(" 쿨 중 클릭2 → " + pt.OnClickPotion() + " · " + pt.Refresh());
|
||
if (WL.UI.PotionButton.FakeCooldownSetter != null) WL.UI.PotionButton.FakeCooldownSetter(2.5f);
|
||
sb.AppendLine(" 쿨 절반(2.5/5) → " + pt.Refresh());
|
||
if (WL.UI.PotionButton.FakeCooldownSetter != null) WL.UI.PotionButton.FakeCooldownSetter(0f);
|
||
sb.AppendLine(" 쿨 종료 → " + pt.Refresh());
|
||
sb.AppendLine(" 클릭3 → " + pt.OnClickPotion());
|
||
if (WL.UI.PotionButton.FakeCooldownSetter != null) WL.UI.PotionButton.FakeCooldownSetter(0f);
|
||
sb.AppendLine(" 클릭4 → " + pt.OnClickPotion() + " · " + pt.Refresh());
|
||
if (WL.UI.PotionButton.FakeCooldownSetter != null) WL.UI.PotionButton.FakeCooldownSetter(0f);
|
||
sb.AppendLine(" 잔량 0 클릭5 → " + pt.OnClickPotion() + " · " + pt.Refresh());
|
||
sb.AppendLine();
|
||
sb.Append(pt.Dump());
|
||
sb.AppendLine(" " + WL.UI.PotionButton.ClearProvider());
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
if (hv != null) hv.Subscribe(false);
|
||
PrefabUtility.UnloadPrefabContents(root);
|
||
}
|
||
sb.AppendLine();
|
||
sb.AppendLine("해제 후 구독자 Damaged=" + WL.Combat.Core.CombatEvents.Damaged.Count + " (0 이어야 한다)");
|
||
var body = sb.ToString();
|
||
Save("WL813tj_probe_survival.txt", body);
|
||
return body;
|
||
}
|
||
|
||
// ═══ 노드 배치 · 겹침 ═══════════════════════════════════════════════════
|
||
public static object DumpPrefab()
|
||
{
|
||
var sb = new StringBuilder();
|
||
var root = PrefabUtility.LoadPrefabContents(NewGameUIPath);
|
||
try
|
||
{
|
||
var hud = FindByPath(root.transform, HudPath);
|
||
var pad = FindByPath(root.transform, PadPath);
|
||
sb.AppendLine("[WL_HUD] children=" + (hud != null ? hud.childCount : -1) +
|
||
" SafeAreaFitter=" + (hud != null && hud.GetComponent<WL.UI.SafeAreaFitter>() != null));
|
||
if (hud != null)
|
||
for (int i = 0; i < hud.childCount; i++)
|
||
{
|
||
var c = hud.GetChild(i) as RectTransform;
|
||
sb.AppendLine(" [" + i + "] " + c.name + " pos" + c.anchoredPosition + " size" + c.sizeDelta +
|
||
" aMin" + c.anchorMin + " children=" + c.childCount + " comps=" + c.GetComponents<Component>().Length);
|
||
}
|
||
sb.AppendLine("[BattleUI] children=" + (pad != null ? pad.childCount : -1));
|
||
if (pad != null)
|
||
for (int i = 0; i < pad.childCount; i++)
|
||
{
|
||
var c = pad.GetChild(i) as RectTransform;
|
||
sb.AppendLine(" [" + i + "] " + c.name + " pos" + c.anchoredPosition + " size" + c.sizeDelta +
|
||
" aMin" + c.anchorMin + " active=" + c.gameObject.activeSelf);
|
||
}
|
||
|
||
// 배너 · 보스바 세로 겹침 검사(px)
|
||
var hudS = WL.UI.WLHudLayoutSettings.Instance;
|
||
var bs = WL.UI.WLBossUiSettings.Instance;
|
||
if (hudS != null && bs != null)
|
||
{
|
||
float barTop = hudS.bossBarTopMarginPx;
|
||
float barBottom = barTop + hudS.bossBarSizePx.y + hudS.bossBarNameFontPx + hudS.bossBarNameGapPx;
|
||
float banTop = bs.bannerTopMarginPx, banBottom = banTop + bs.bannerSizePx.y;
|
||
sb.AppendLine();
|
||
sb.AppendLine("[겹침 · 상단에서 px] 보스바 " + barTop.ToString("F0") + "~" + barBottom.ToString("F0") +
|
||
" · 배너 " + banTop.ToString("F0") + "~" + banBottom.ToString("F0") +
|
||
" → 겹침=" + (banTop < barBottom));
|
||
sb.AppendLine(" 813g 연쇄문구 = 화면 중앙 위 " + (WL.UI.WLCombatTextSettings.Instance != null ?
|
||
WL.UI.WLCombatTextSettings.Instance.killChainTopOffsetPx.ToString("F0") : "?") + " px (중앙 기준 · 상단 밴드와 별개)");
|
||
}
|
||
|
||
// 물약 버튼과 813c 슬롯 간 최소 여백
|
||
if (hudS != null)
|
||
{
|
||
var sur = WL.UI.WLSurvivalUiSettings.Instance;
|
||
int idx = sur != null ? sur.potionReserveSlotIndex : 0;
|
||
Vector2 pp = hudS.ReserveSlotPx(idx);
|
||
float pr = hudS.reserveDiameterPx * 0.5f;
|
||
float min = float.MaxValue; string who = "";
|
||
for (int i = 0; i < hudS.activeSkillSlots; i++)
|
||
{
|
||
var q = hudS.FanSlotPx(i);
|
||
float g = Vector2.Distance(pp, q) - pr - hudS.slotDiameterPx * 0.5f;
|
||
if (g < min) { min = g; who = "slot" + i; }
|
||
}
|
||
float ga = Vector2.Distance(pp, hudS.attackCenterPx) - pr - hudS.attackDiameterPx * 0.5f;
|
||
if (ga < min) { min = ga; who = "Attack"; }
|
||
sb.AppendLine();
|
||
sb.AppendLine("[물약 버튼] 예약 슬롯" + idx + " dx/dy px=" + pp + " 지름=" + hudS.reserveDiameterPx +
|
||
"px · 최소 여백=" + min.ToString("F1") + "px (" + who + ") · 기준 ≥16 → " + (min >= 16f ? "OK" : "🔴"));
|
||
sb.AppendLine(" 모서리 도달 반경 = " + (pp.magnitude + pr).ToString("F1") + "px (기준서 목표 " + hudS.thumbReachRadiusPx + "px)");
|
||
}
|
||
}
|
||
finally { PrefabUtility.UnloadPrefabContents(root); }
|
||
var body = sb.ToString();
|
||
Save("WL813tj_probe_prefab.txt", body);
|
||
return body;
|
||
}
|
||
}
|
||
|
||
/// <summary>Apply 스크립트와 이름을 맞추기 위한 상수(run_script 는 파일마다 독립 어셈블리라 참조가 안 된다 · 813c 교훈).</summary>
|
||
public static class WL813tj_ApplyNames
|
||
{
|
||
public const string Banner = "WL_BossBanner";
|
||
public const string Hit = "WL_HitVignette";
|
||
public const string Revive = "WL_ReviveDialog";
|
||
public const string Potion = "WL_PotionButton";
|
||
}
|