Project_WL/AgentScripts/WL813g_Probe.cs

408 lines
21 KiB
C#
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// WL-813g (#813) — 에디트 모드 실측 프로브(Play 0 · 합성 이벤트).
// 실행: unity command run_script --file AgentScripts/WL813g_Probe.cs --entry WL813g_Probe.DumpSlots
// unity command run_script --file AgentScripts/WL813g_Probe.cs --entry WL813g_Probe.DumpText
// unity command run_script --file AgentScripts/WL813g_Probe.cs --entry WL813g_Probe.DumpDamage
// unity command run_script --file AgentScripts/WL813g_Probe.cs --entry WL813g_Probe.DumpPrefab
// 🔴 이 파일은 독립 어셈블리로 컴파일된다 — Assembly-CSharp 의 internal(Dispatch 등)은 못 쓴다.
// 합성 이벤트는 Assembly-CSharp 안의 헬퍼(KillChainText.RaiseFakeTier 등)를 통해 낸다(813c 교훈).
// 🔴 씬은 저장하지 않는다. 만든 임시 오브젝트는 전부 DestroyImmediate 로 회수한다.
using System.Collections.Generic;
using System.Text;
using UnityEditor;
using UnityEngine;
public static class WL813g_Probe
{
const string LogDir = "Logs";
public const string NewGameUIPath = "Assets/Res_Addr/MainUI/NewGameUI.prefab";
public const string DmgPrefabPath = "Assets/Res_Addr/Ingame/HUDDMGUI.prefab";
public const string SkillJson = "Assets/ResWork/Table/Export/_WL813n_SkillList.json";
public const string GlobalJson = "Assets/ResWork/Table/Export/GlobalValue.json";
const int UILayer = 5;
static void Write(string name, string body)
{
System.IO.Directory.CreateDirectory(LogDir);
System.IO.File.WriteAllText(System.IO.Path.Combine(LogDir, name), body, new System.Text.UTF8Encoding(true));
}
static string Desc(RectTransform rt)
{
if (rt == null) return "(rt 없음)";
return "aMin=" + rt.anchorMin + " aMax=" + rt.anchorMax + " pos=" + rt.anchoredPosition +
" size=" + rt.sizeDelta + " pivot=" + rt.pivot + " scale=" + rt.localScale;
}
// ─────────────────────────────────────────── 표 읽기(에디트 모드에서 table_* 싱글턴이 없어서 JSON 직독)
// 표는 단일 라인 JSON 배열이고 값은 전부 문자열이다(813n 실측) → 행 단위로 잘라 정규식으로 읽는다.
// (run_script 는 파일마다 독립 어셈블리라 Newtonsoft 참조를 가정하지 않는다.)
static List<string> SplitRows(string path)
{
var rows = new List<string>();
try
{
var txt = System.IO.File.ReadAllText(path, System.Text.Encoding.UTF8).TrimStart('');
foreach (System.Text.RegularExpressions.Match m in
System.Text.RegularExpressions.Regex.Matches(txt, "\\{[^{}]*\\}"))
rows.Add(m.Value);
}
catch (System.Exception e) { Debug.LogWarning("[813g] 표 읽기 실패 " + path + " : " + e.Message); }
return rows;
}
static string Field(string row, string key)
{
var m = System.Text.RegularExpressions.Regex.Match(row, "\"" + key + "\"\\s*:\\s*\"([^\"]*)\"");
return m.Success ? m.Groups[1].Value : null;
}
static Dictionary<int, int> ReadSkillMp()
{
var map = new Dictionary<int, int>();
foreach (var row in SplitRows(SkillJson))
{
var id = Field(row, "n_SkillID"); var mp = Field(row, "n_MP");
int i, v;
if (id != null && mp != null && int.TryParse(id, out i) && int.TryParse(mp, out v)) map[i] = v;
}
return map;
}
static Dictionary<int, float> ReadSkillCool()
{
var map = new Dictionary<int, float>();
foreach (var row in SplitRows(SkillJson))
{
var id = Field(row, "n_SkillID"); var cd = Field(row, "f_CoolTime");
int i; float v;
if (id != null && cd != null && int.TryParse(id, out i) && float.TryParse(cd, out v)) map[i] = v;
}
return map;
}
static float ReadGlobal(string id, float fallback)
{
foreach (var row in SplitRows(GlobalJson))
{
if (Field(row, "s_ID") != id) continue;
float v;
if (float.TryParse(Field(row, "n_Value"), out v)) return v;
}
return fallback;
}
// ─────────────────────────────────────────── ① 슬롯 오버라이드 (발주서 §3 검증 = 4×4 덤프)
public static object DumpSlots()
{
var sb = new StringBuilder();
var preset = WL.UI.WLSkillSlotPreset.Instance;
sb.AppendLine("=== WL-813g ① 슬롯 장착 로컬 오버라이드 (에디트 모드 · Play 0) ===");
if (preset == null) { var s0 = "🔴 WLSkillSlotPreset.asset 로드 실패"; Write("WL813g_probe_slots.txt", s0); return s0; }
sb.AppendLine("[해석 표 4×4]");
sb.Append(WL.UI.WLSkillSlotOverride.ResolveDump(preset));
var mp = ReadSkillMp();
var cool = ReadSkillCool();
float mpPool = ReadGlobal("MP", -1f);
float mpRegen = ReadGlobal("MP_REGEN", -1f);
sb.AppendLine();
sb.AppendLine("[MP 게이트 · Actor.cs:733-741 과 같은 식 · SKILL_COST=0 가정 · MP 풀=" + mpPool + " (GlobalValue 실측) · 회복=" + mpRegen + "/s]");
for (int r = 0; r < preset.rows.Length; r++)
{
var row = preset.rows[r];
int n = preset.SlotCountFor(row.classId);
sb.Append("class ").Append(row.classId).Append(" :");
for (int s = 0; s < n; s++)
{
int id = preset.SkillIdFor(row.classId, s);
int need = mp.ContainsKey(id) ? mp[id] : -1;
float cd = cool.ContainsKey(id) ? cool[id] : -1f;
bool dim = mpPool > 0f && need > mpPool;
sb.Append(" [").Append(s).Append("] id=").Append(id).Append(" mp=").Append(need)
.Append(" cool=").Append(cd).Append("s dim@만탱=").Append(dim);
}
sb.AppendLine();
}
// ── 합성 SD_Equip 에 실제 ApplyTo 를 태워 before→after 를 잰다(서버 데이터 무접촉)
sb.AppendLine();
sb.AppendLine("[합성 SD_Equip 적용 · 실제 WLSkillSlotOverride.ApplyTo 경로]");
foreach (var row in preset.rows)
{
var equip = new SD_Equip();
equip.Preset = 0;
equip.Equip = new Dictionary<eItem, CodeStage.AntiCheat.ObscuredTypes.ObscuredInt>();
equip.Equip[eItem.PC] = row.classId;
equip.Skill = new Dictionary<int, List<CodeStage.AntiCheat.ObscuredTypes.ObscuredInt>>();
var lst = new List<CodeStage.AntiCheat.ObscuredTypes.ObscuredInt>();
for (int i = 0; i < 6; i++) lst.Add(0);
lst[0] = 700001; // 서버 프리셋 흉내(패시브·엉뚱한 값)
lst[1] = 0; lst[2] = 0; lst[3] = 0;
equip.Skill[0] = lst;
var restore = new List<int>();
string log = WL.UI.WLSkillSlotOverride.ApplyTo(equip, preset, row.classId, restore);
sb.AppendLine(" " + log);
var after = new StringBuilder(" Equip.Skill[0] = ");
for (int i = 0; i < lst.Count; i++) after.Append(lst[i]).Append(i < lst.Count - 1 ? ", " : "");
after.Append(" (슬롯 4·5 = 물약/회피 예약 · 건드리지 않음)");
sb.AppendLine(after.ToString());
}
sb.AppendLine();
sb.AppendLine("[C8 롤백] overrideEnabled=false 또는 에셋 삭제 → ApplyTo 미호출 = 서버 프리셋 원본");
var s1 = sb.ToString();
Write("WL813g_probe_slots.txt", s1);
return s1;
}
// ─────────────────────────────────────────── ② 텍스트 연출 3종 (합성 이벤트)
static Canvas MakeCanvas(out GameObject go)
{
go = new GameObject("WL813g_TempCanvas", typeof(RectTransform), typeof(Canvas), typeof(UnityEngine.UI.CanvasScaler));
go.layer = UILayer;
var canvas = go.GetComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
var scaler = go.GetComponent<UnityEngine.UI.CanvasScaler>();
// 값을 가정하지 않는다 — 실제 NewGameUI.prefab 루트의 CanvasScaler 설정을 그대로 복제한다(802b/813c 규약).
scaler.uiScaleMode = UnityEngine.UI.CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1920f, 1080f);
scaler.screenMatchMode = UnityEngine.UI.CanvasScaler.ScreenMatchMode.Expand;
scaler.matchWidthOrHeight = 0f;
var srcPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(NewGameUIPath);
var src = srcPrefab != null ? srcPrefab.GetComponent<UnityEngine.UI.CanvasScaler>() : null;
if (src != null)
{
scaler.uiScaleMode = src.uiScaleMode;
scaler.referenceResolution = src.referenceResolution;
scaler.screenMatchMode = src.screenMatchMode;
scaler.matchWidthOrHeight = src.matchWidthOrHeight;
}
LastScalerInfo = "CanvasScaler(원본 " + (src != null ? "복제" : "「미확인」 기본값") + ") mode=" + scaler.uiScaleMode +
" ref=" + scaler.referenceResolution + " match=" + scaler.screenMatchMode + "/" + scaler.matchWidthOrHeight;
var rt = (RectTransform)go.transform;
rt.sizeDelta = new Vector2(1080f, 1920f); // 에디트 모드에서 캔버스가 스스로 크기를 못 잡으므로 기준 화면 주입
return canvas;
}
static string LastScalerInfo = "";
static RectTransform Child(RectTransform parent, string name)
{
var go = new GameObject(name, typeof(RectTransform));
go.layer = UILayer;
var rt = (RectTransform)go.transform;
rt.SetParent(parent, false);
return rt;
}
public static object DumpText()
{
var sb = new StringBuilder();
sb.AppendLine("=== WL-813g ② 텍스트 연출 3종 · 합성 이벤트 (에디트 모드 · Play 0) ===");
var st = WL.UI.WLCombatTextSettings.Instance;
if (st == null) { var s0 = "🔴 WLCombatTextSettings.asset 로드 실패"; Write("WL813g_probe_text.txt", s0); return s0; }
sb.AppendLine("설정: enabled=" + WL.UI.WLCombatTextSettings.Enabled +
" killChain=" + st.killChainSeconds + "s/punch " + st.killChainPunchScale +
" loot=" + st.lootToastSeconds + "s/" + st.lootToastLines + "줄" +
" stat=" + st.statPopupSeconds + "s/" + st.statPopupRows + "줄");
GameObject canvasGo;
var canvas = MakeCanvas(out canvasGo);
try
{
var croot = (RectTransform)canvas.transform;
sb.AppendLine(LastScalerInfo + " → unitsPerPx=" + st.UnitsPerPx(canvas).ToString("F4") +
" (802b 실측 1.7778 과 대조)");
// ── (A) 연쇄 처치 문구
var kcRt = Child(croot, "WL_KillChainText");
var kc = kcRt.gameObject.AddComponent<WL.UI.KillChainText>();
sb.AppendLine();
sb.AppendLine("[A] KillChainText");
sb.AppendLine(" init: " + kc.Initialize());
sb.AppendLine(" 구독자 수(KillChain.TierReached) = " + WL.Combat.Reaction.KillChain.TierReached.Count);
var tiers = WL.Combat.Reaction.WLReactionSettings.Instance != null
? WL.Combat.Reaction.WLReactionSettings.Instance.tiers : null;
if (tiers == null) sb.AppendLine(" 🔴 WLReactionSettings.tiers 없음(811e 에셋 미로드)");
else
for (int i = 0; i < tiers.Length; i++)
{
sb.AppendLine(" ── 합성 " + WL.UI.KillChainText.RaiseFakeTier(i, tiers[i].name, tiers[i].kills));
sb.Append(" ").Append(kc.Dump());
}
kc.Subscribe(false);
sb.AppendLine(" 구독 해제 후 구독자 = " + WL.Combat.Reaction.KillChain.TierReached.Count);
// ── (B) 전리품 토스트
var ltRt = Child(croot, "WL_LootToast");
var lt = ltRt.gameObject.AddComponent<WL.UI.LootToast>();
sb.AppendLine();
sb.AppendLine("[B] LootToast");
sb.AppendLine(" init: " + lt.Initialize());
sb.AppendLine(" 구독자 수(CombatEvents.Killed) = " + WL.Combat.Core.CombatEvents.Killed.Count);
sb.AppendLine(" ── " + WL.UI.LootToast.RaiseFakeKilled() + " → killedSeen=" + lt.KilledSeen);
// 등급 1~6 을 순서대로 밀어 넣어 동시 줄 상한을 잰다(표 이름이 없으면 ID 를 이름으로 쓴다)
int[] fakeItems = { 1001, 1002, 1003, 1004, 1005 };
for (int i = 0; i < fakeItems.Length; i++)
sb.AppendLine(" push[" + i + "] " + lt.Push(fakeItems[i], 1) + " → visible=" + lt.VisibleLines);
sb.AppendLine(" 중복 병합 " + lt.Push(fakeItems[fakeItems.Length - 1], 2) + " → visible=" + lt.VisibleLines);
sb.Append(lt.Dump());
lt.Subscribe(false);
sb.AppendLine(" 구독 해제 후 구독자 = " + WL.Combat.Core.CombatEvents.Killed.Count);
// ── (C) 레벨업 스탯 팝업
var spRt = Child(croot, "WL_StatPopup");
var sp = spRt.gameObject.AddComponent<WL.UI.StatPopup>();
sb.AppendLine();
sb.AppendLine("[C] StatPopup");
sb.AppendLine(" init: " + sp.Initialize());
sb.AppendLine(" 구독자 수(CombatEvents.LevelUp) = " + WL.Combat.Core.CombatEvents.LevelUp.Count);
sb.AppendLine(" ── " + WL.UI.StatPopup.RaiseFakeLevelUp(7) + " (PC 없음 → 스냅샷 대체 경로도 값 0 · 표시는 제목만)");
sb.Append(sp.Dump());
sb.AppendLine(" ── 813k 페이로드 모양으로 직접 표시:");
sb.AppendLine(" " + sp.ShowFake(8, new[] { "STR", "DEX", "INT" }, new double[] { 120, 80, 45 }, new double[] { 128, 84, 48 }));
sb.Append(sp.Dump());
sp.Subscribe(false);
sb.AppendLine(" 구독 해제 후 구독자 = " + WL.Combat.Core.CombatEvents.LevelUp.Count);
}
finally
{
if (canvasGo != null) Object.DestroyImmediate(canvasGo);
}
var s1 = sb.ToString();
Write("WL813g_probe_text.txt", s1);
return s1;
}
// ─────────────────────────────────────────── ③ 데미지 숫자 리듬 (실제 HUDDMGUI 프리팹)
public static object DumpDamage()
{
var sb = new StringBuilder();
sb.AppendLine("=== WL-813g ③ 데미지 숫자 리듬 · 실제 HUDDMGUI 프리팹 (에디트 모드 · Play 0) ===");
sb.AppendLine("설정: " + HUDDMGUI.SettingsDump());
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(DmgPrefabPath);
if (prefab == null) { var s0 = "🔴 " + DmgPrefabPath + " 없음"; Write("WL813g_probe_damage.txt", s0); return s0; }
GameObject canvasGo;
var canvas = MakeCanvas(out canvasGo);
var made = new List<GameObject>();
try
{
HUDDMGUI.ClearActiveList();
var st = WL.UI.WLCombatTextSettings.Instance;
int cap = st != null ? st.damageMaxConcurrent : 0;
var croot = (RectTransform)canvas.transform;
System.Func<HUDDMGUI> spawn = () =>
{
var go = (GameObject)Object.Instantiate(prefab, croot);
go.name = "HUDDMGUI_" + made.Count;
made.Add(go);
return go.GetComponent<HUDDMGUI>();
};
// (1) 일반 vs 크리 — 표시 시간 · 배율(기준서 §B 요소 2 = 0.6 / 1.0 / 1.6배)
sb.AppendLine();
sb.AppendLine("[1] 일반 / 크리 규격");
var a = spawn(); a.SetCore(null, "<color=#FFFFFF>", 1234, eStat.None);
sb.AppendLine(" 일반 : " + a.DumpState());
var b = spawn(); b.SetCore(null, "<color=#FFFFFF>", 1234, eStat.CRI);
sb.AppendLine(" 크리 : " + b.DumpState());
float sa = a.dmgs[0].transform.localScale.x, sbb = b.dmgs[1].transform.localScale.x;
sb.AppendLine(" 크기 비 = " + (sa > 0f ? (sbb / sa).ToString("F3") : "?") + " (목표 " + (st != null ? st.damageCritScale : 1f) + ")");
// (2) 합산 윈도우 — 같은 대상 · 같은 색이면 새 숫자 대신 합산(811e 0.25 s)
sb.AppendLine();
sb.AppendLine("[2] 합산 윈도우 (같은 대상 · 같은 색)");
var targetGo = new GameObject("WL813g_FakeVictim"); made.Add(targetGo);
var t = targetGo.transform;
HUDDMGUI.ClearActiveList();
var m0 = spawn(); m0.SetCore(t, "<color=#FFFFFF>", 100, eStat.None);
sb.AppendLine(" 1타 : " + m0.DumpState() + " | active=" + HUDDMGUI.ActiveCount);
var m1 = spawn(); m1.SetCore(t, "<color=#FFFFFF>", 250, eStat.None);
sb.AppendLine(" 2타 : " + m0.DumpState() + " | active=" + HUDDMGUI.ActiveCount + " merged=" + HUDDMGUI.MergedCount);
var m2 = spawn(); m2.SetCore(t, "<color=#FFFFFF>", 900, eStat.CRI);
sb.AppendLine(" 3타(크리) : " + m0.DumpState() + " | active=" + HUDDMGUI.ActiveCount + " merged=" + HUDDMGUI.MergedCount);
sb.AppendLine(" → 합산 창 안에서는 인스턴스가 늘지 않고 합계·타수만 오른다(크리 섞이면 크리 규격으로 승격)");
// (3) 동시 상한 — 상한 초과 시 오래된 일반부터 회수
sb.AppendLine();
sb.AppendLine("[3] 동시 표시 상한 (설정 " + cap + ")");
HUDDMGUI.ClearActiveList();
int n = Mathf.Max(1, cap) + 5;
for (int i = 0; i < n; i++)
{
var g = new GameObject("victim" + i); made.Add(g);
var h = spawn();
h.SetCore(g.transform, "<color=#FFFFFF>", 10 + i, (i % 4 == 0) ? eStat.CRI : eStat.None);
}
sb.AppendLine(" " + n + "개 요청 → active=" + HUDDMGUI.ActiveCount + " (상한 " + cap + ") evicted=" + HUDDMGUI.EvictedCount +
" · 상한 준수=" + (cap <= 0 || HUDDMGUI.ActiveCount <= cap));
// (4) C8 롤백 — 설정 off 면 원본 상수(0.9 s · 배율 1)
sb.AppendLine();
sb.AppendLine("[4] C8 롤백 (RuntimeDisabled=true = 에셋 없음과 같은 경로)");
HUDDMGUI.ClearActiveList();
WL.UI.WLCombatTextSettings.RuntimeDisabled = true;
var c = spawn(); c.SetCore(null, "<color=#FFFFFF>", 777, eStat.CRI);
sb.AppendLine(" 크리(롤백) : " + c.DumpState());
sb.AppendLine(" 설정 덤프 : " + HUDDMGUI.SettingsDump());
WL.UI.WLCombatTextSettings.RuntimeDisabled = false;
}
finally
{
for (int i = made.Count - 1; i >= 0; i--) if (made[i] != null) Object.DestroyImmediate(made[i]);
if (canvasGo != null) Object.DestroyImmediate(canvasGo);
HUDDMGUI.ClearActiveList();
}
var s1 = sb.ToString();
Write("WL813g_probe_damage.txt", s1);
return s1;
}
// ─────────────────────────────────────────── ④ 프리팹 노드 (통째 Read 금지 · 노드만)
public static object DumpPrefab()
{
var sb = new StringBuilder();
sb.AppendLine("=== WL-813g ④ NewGameUI.prefab 추가 노드 실측 ===");
var root = PrefabUtility.LoadPrefabContents(NewGameUIPath);
try
{
var ingame = root.transform.Find("IngameUIs");
if (ingame == null) { sb.AppendLine("🔴 IngameUIs 없음"); }
else
{
var hud = ingame.Find("WL_HUD");
sb.AppendLine("IngameUIs/WL_HUD : " + (hud != null ? "있음 children=" + hud.childCount : "🔴 없음"));
if (hud != null)
{
sb.AppendLine(" SafeAreaFitter=" + (hud.GetComponent<WL.UI.SafeAreaFitter>() != null));
foreach (Transform c in hud)
sb.AppendLine(" child " + c.name + " " + Desc(c as RectTransform) +
" comps=" + string.Join(",", System.Array.ConvertAll(c.GetComponents<Component>(), x => x.GetType().Name)));
}
var pad = ingame.Find("BattleUI");
sb.AppendLine("IngameUIs/BattleUI : " + (pad != null ? "있음" : "🔴 없음"));
if (pad != null)
sb.AppendLine(" comps=" + string.Join(",", System.Array.ConvertAll(pad.GetComponents<Component>(), x => x.GetType().Name)));
}
}
finally { PrefabUtility.UnloadPrefabContents(root); }
var s1 = sb.ToString();
Write("WL813g_probe_prefab.txt", s1);
return s1;
}
}