505 lines
26 KiB
C#
505 lines
26 KiB
C#
|
|
// WL-816m 프로브 — 던전 결과창(던전 이름 + 획득 골드) 실측 (에디트 모드 · Play 0 · 로그인 0).
|
|||
|
|
// unity command run_script --file AgentScripts/WL816m_Probe.cs --entry WL816m_Probe.SaveAsset
|
|||
|
|
// unity command run_script --file AgentScripts/WL816m_Probe.cs --entry WL816m_Probe.RunAll
|
|||
|
|
// unity command run_script --file AgentScripts/WL816m_Probe.cs --entry WL816m_Probe.Capture
|
|||
|
|
// 산출물: <worktree>/AgentScripts/WL816m_PROBE.txt · Screenshots_WL/WL816m/*.png
|
|||
|
|
// 🔴 NewGameUI.prefab 은 LoadPrefabContents 로 열고 저장하지 않는다(UnloadPrefabContents).
|
|||
|
|
// 🔴 PlayerPrefs 는 건드리지 않는다 — 에디트 모드라 WLIslandBridge 는 설치되지 않는다(전후 값 실측 대조).
|
|||
|
|
|
|||
|
|
using System;
|
|||
|
|
using System.IO;
|
|||
|
|
using System.Text;
|
|||
|
|
using UnityEngine;
|
|||
|
|
using UnityEditor;
|
|||
|
|
using WL.UI;
|
|||
|
|
using WL.Island;
|
|||
|
|
|
|||
|
|
public static class WL816m_Probe
|
|||
|
|
{
|
|||
|
|
const string kPrefab = "Assets/Res_Addr/MainUI/NewGameUI.prefab";
|
|||
|
|
|
|||
|
|
static StringBuilder _o;
|
|||
|
|
static int _pass, _fail;
|
|||
|
|
|
|||
|
|
static string Root { get { return Directory.GetParent(Application.dataPath).FullName; } }
|
|||
|
|
static void H(string s) { _o.AppendLine(); _o.AppendLine("── " + s); }
|
|||
|
|
static void N(string s) { _o.AppendLine(" " + s); }
|
|||
|
|
static bool Chk(bool ok, string what)
|
|||
|
|
{
|
|||
|
|
if (ok) { _pass++; _o.AppendLine(" [PASS] " + what); }
|
|||
|
|
else { _fail++; _o.AppendLine(" [FAIL] " + what); }
|
|||
|
|
return ok;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═════════════════════════════════════════════════════════════════════
|
|||
|
|
// ① SO 에 새 필드를 직렬화한다(에셋 YAML 에 값이 보이도록 · C45)
|
|||
|
|
// ═════════════════════════════════════════════════════════════════════
|
|||
|
|
public static string SaveAsset()
|
|||
|
|
{
|
|||
|
|
var g = WLStageUiSettings.Instance;
|
|||
|
|
if (g == null) return "WLStageUiSettings 에셋 없음";
|
|||
|
|
EditorUtility.SetDirty(g);
|
|||
|
|
AssetDatabase.SaveAssets();
|
|||
|
|
AssetDatabase.Refresh();
|
|||
|
|
return "저장 — 던전모드=" + g.dungeonModeEnabled + " 제목=\"" + g.dungeonClearTitleFormat +
|
|||
|
|
"\" 라벨=\"" + g.dungeonLabelFormat + "\" 골드행=" + g.dungeonGoldRowEnabled +
|
|||
|
|
" \"" + g.rowDungeonGoldLabel + "\" \"" + g.rowDungeonGoldFormat + "\"";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═════════════════════════════════════════════════════════════════════
|
|||
|
|
// ② 실측
|
|||
|
|
// ═════════════════════════════════════════════════════════════════════
|
|||
|
|
public static string RunAll()
|
|||
|
|
{
|
|||
|
|
_o = new StringBuilder();
|
|||
|
|
_pass = _fail = 0;
|
|||
|
|
_o.AppendLine("# WL816m Probe " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " (edit mode · Play 0 · 로그인 0)");
|
|||
|
|
_o.AppendLine("playMode=" + Application.isPlaying);
|
|||
|
|
|
|||
|
|
var cfg = WLIslandSettings.Instance;
|
|||
|
|
string prefsKey = cfg != null ? cfg.pendingPrefsKey : "WL_Island_PendingGold";
|
|||
|
|
int prefsBefore = PlayerPrefs.GetInt(prefsKey, -1);
|
|||
|
|
var savedCurrent = WLDungeonSceneSetup.Current;
|
|||
|
|
|
|||
|
|
GameObject prefabRoot = null;
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
Step0_Settings(cfg);
|
|||
|
|
prefabRoot = PrefabUtility.LoadPrefabContents(kPrefab);
|
|||
|
|
BuildRig(prefabRoot);
|
|||
|
|
Step1_Detect(cfg);
|
|||
|
|
Step2_Gold(cfg);
|
|||
|
|
Step3_Result(cfg, prefabRoot);
|
|||
|
|
Step4_Hud(cfg, prefabRoot);
|
|||
|
|
Step5_StageUnchanged(prefabRoot);
|
|||
|
|
Step6_Rollback(cfg, prefabRoot);
|
|||
|
|
}
|
|||
|
|
catch (Exception e)
|
|||
|
|
{
|
|||
|
|
_fail++;
|
|||
|
|
_o.AppendLine(" [FAIL] 예외 " + e.GetType().Name + " : " + e.Message);
|
|||
|
|
_o.AppendLine(e.StackTrace);
|
|||
|
|
}
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
try { StageUiBridge.Teardown(); RunHud.Teardown(); RunResultUI.Teardown(); } catch { }
|
|||
|
|
DestroyRig();
|
|||
|
|
WLDungeonSceneSetup.Current = savedCurrent;
|
|||
|
|
if (prefabRoot != null) PrefabUtility.UnloadPrefabContents(prefabRoot);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int prefsAfter = PlayerPrefs.GetInt(prefsKey, -1);
|
|||
|
|
H("⑦ PlayerPrefs 무변경 (섬 축은 에디트 모드에 설치되지 않는다)");
|
|||
|
|
N("키 \"" + prefsKey + "\" 전=" + prefsBefore + " 후=" + prefsAfter);
|
|||
|
|
Chk(prefsBefore == prefsAfter, "보류 보상 PlayerPrefs 무변경");
|
|||
|
|
|
|||
|
|
_o.AppendLine();
|
|||
|
|
_o.AppendLine("RESULT " + (_fail == 0 ? "PASS" : "FAIL") + " (pass=" + _pass + " fail=" + _fail + ")");
|
|||
|
|
string path = Path.Combine(Path.Combine(Root, "AgentScripts"), "WL816m_PROBE.txt");
|
|||
|
|
File.WriteAllText(path, _o.ToString(), new UTF8Encoding(true));
|
|||
|
|
return _o.ToString();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void Step0_Settings(WLIslandSettings cfg)
|
|||
|
|
{
|
|||
|
|
H("① 설정 실값 (SO · 하드코딩 0)");
|
|||
|
|
var g = WLStageUiSettings.Instance;
|
|||
|
|
Chk(g != null, "WLStageUiSettings 에셋 로드");
|
|||
|
|
if (g == null) return;
|
|||
|
|
N("던전모드=" + g.dungeonModeEnabled + " · 제목 \"" + g.dungeonClearTitleFormat + "\" · HUD \"" +
|
|||
|
|
g.dungeonLabelFormat + "\" · 대체이름 \"" + g.dungeonNameFallbackFormat + "\"");
|
|||
|
|
N("골드행=" + g.dungeonGoldRowEnabled + " · 라벨 \"" + g.rowDungeonGoldLabel + "\" · 값 \"" + g.rowDungeonGoldFormat + "\"");
|
|||
|
|
N("스테이지 제목(불변) \"" + g.stageClearTitleFormat + "\" · 스테이지 HUD(불변) \"" + g.stageLabelFormat + "\"");
|
|||
|
|
Chk(cfg != null && cfg.dungeons != null && cfg.dungeons.Length >= 3, "던전 표 3행 이상");
|
|||
|
|
if (cfg == null || cfg.dungeons == null) return;
|
|||
|
|
for (int i = 0; i < cfg.dungeons.Length; i++)
|
|||
|
|
{
|
|||
|
|
var d = cfg.dungeons[i];
|
|||
|
|
int id = StageIdOf(d.stageIndex);
|
|||
|
|
N("던전 표[" + i + "] id=" + d.id + " \"" + d.displayName + "\" stageIndex=" + d.stageIndex +
|
|||
|
|
" → 스테이지 표 id=" + id + "(지금 화면에 나오던 숫자) · goldMultiplier=" +
|
|||
|
|
WLIslandBridge.MultiplierOf(d.stageIndex).ToString("F2"));
|
|||
|
|
}
|
|||
|
|
N("보상 값 — clearBonus=" + cfg.goldClearBonus + " perKill=" + cfg.goldPerKill + " bossBonus=" +
|
|||
|
|
cfg.goldBossBonus + " 배수적용=" + cfg.applyGoldMultiplier + " rewardEnabled=" + cfg.rewardEnabled);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static int StageIdOf(int index)
|
|||
|
|
{
|
|||
|
|
var t = WL.Combat.Stage.WLStageTable.Instance;
|
|||
|
|
if (t == null) return -1;
|
|||
|
|
var def = t.Get(index);
|
|||
|
|
return def != null ? def.id : -1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void Step1_Detect(WLIslandSettings cfg)
|
|||
|
|
{
|
|||
|
|
H("② 던전 판정 = WLDungeonSceneSetup.Current 하나뿐");
|
|||
|
|
WLDungeonSceneSetup.Current = null;
|
|||
|
|
Chk(StageUiBridge.CurrentDungeon == null, "Current=null → 던전 아님(#815 스테이지 모드)");
|
|||
|
|
Chk(StageUiBridge.DungeonNameOf(null) == null, "이름 null");
|
|||
|
|
|
|||
|
|
WLDungeonSceneSetup.Current = cfg.dungeons[0];
|
|||
|
|
var d1 = StageUiBridge.CurrentDungeon;
|
|||
|
|
Chk(d1 != null, "Current=던전1 행 → 던전으로 판정");
|
|||
|
|
N("이름 = \"" + StageUiBridge.DungeonNameOf(d1) + "\"");
|
|||
|
|
Chk(StageUiBridge.DungeonNameOf(d1) == cfg.dungeons[0].displayName, "이름 = 표의 displayName");
|
|||
|
|
|
|||
|
|
// displayName 이 비었을 때의 대체 이름
|
|||
|
|
string saved = cfg.dungeons[0].displayName;
|
|||
|
|
cfg.dungeons[0].displayName = "";
|
|||
|
|
string fb = StageUiBridge.DungeonNameOf(StageUiBridge.CurrentDungeon);
|
|||
|
|
cfg.dungeons[0].displayName = saved;
|
|||
|
|
N("displayName 빈 경우 대체 이름 = \"" + fb + "\"");
|
|||
|
|
Chk(fb == "던전 " + cfg.dungeons[0].id, "대체 이름 = dungeonNameFallbackFormat + id");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void Step2_Gold(WLIslandSettings cfg)
|
|||
|
|
{
|
|||
|
|
H("③ 획득 골드 = 섬 축 WLIslandBridge.ComputeGold 재사용(식 1곳)");
|
|||
|
|
var r1 = Cleared(cfg.dungeons[0].stageIndex, 3, false);
|
|||
|
|
int g1 = StageUiBridge.DungeonGoldOf(in r1);
|
|||
|
|
N("던전 1 (index " + cfg.dungeons[0].stageIndex + " · 처치 3) → " + g1 +
|
|||
|
|
" / 섬 축 직접 = " + WLIslandBridge.ComputeGold(cfg, in r1));
|
|||
|
|
Chk(g1 == 96, "던전 1 = 96 (816g §5 실측과 같음)");
|
|||
|
|
|
|||
|
|
var r3 = Cleared(cfg.dungeons[2].stageIndex, 3, false);
|
|||
|
|
int g3 = StageUiBridge.DungeonGoldOf(in r3);
|
|||
|
|
N("던전 3 (index " + cfg.dungeons[2].stageIndex + " · 처치 3) → " + g3);
|
|||
|
|
Chk(g3 == 192, "던전 3 = 192 (816i2 §3 실측과 같음)");
|
|||
|
|
Chk(g1 == WLIslandBridge.ComputeGold(cfg, in r1) && g3 == WLIslandBridge.ComputeGold(cfg, in r3),
|
|||
|
|
"UI 표시값 == 섬 축 지급 계산값(두 곳 일치)");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static WL.Combat.Stage.StageClearResult Cleared(int index, int killed, bool isBoss)
|
|||
|
|
{
|
|||
|
|
return new WL.Combat.Stage.StageClearResult
|
|||
|
|
{
|
|||
|
|
index = index, stageId = StageIdOf(index), win = true,
|
|||
|
|
failReason = WL.Combat.Stage.StageFailReason.None,
|
|||
|
|
elapsedSec = 34.2f, timeLimit = 60f, killed = killed, total = killed, remaining = 0,
|
|||
|
|
retries = 0, isLastStage = false, isBoss = isBoss,
|
|||
|
|
time = Time.unscaledTime, frame = Time.frameCount,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── 목업 캔버스(815d 와 같은 방식) — TitleText·PhaseText 를 읽으려면 노드가 있어야 한다 ──
|
|||
|
|
static GameObject _rig;
|
|||
|
|
static Camera _cam;
|
|||
|
|
static RectTransform _crt;
|
|||
|
|
static ReviveDialog _revive;
|
|||
|
|
|
|||
|
|
static void BuildRig(GameObject prefabRoot)
|
|||
|
|
{
|
|||
|
|
_rig = new GameObject("WL816m_Rig");
|
|||
|
|
_rig.hideFlags = HideFlags.HideAndDontSave;
|
|||
|
|
var camGo = new GameObject("Cam");
|
|||
|
|
camGo.transform.SetParent(_rig.transform, false);
|
|||
|
|
_cam = camGo.AddComponent<Camera>();
|
|||
|
|
_cam.clearFlags = CameraClearFlags.SolidColor;
|
|||
|
|
_cam.backgroundColor = new Color(0.09f, 0.10f, 0.12f, 1f);
|
|||
|
|
_cam.orthographic = true; _cam.orthographicSize = 960f;
|
|||
|
|
_cam.nearClipPlane = 0.1f; _cam.farClipPlane = 5000f;
|
|||
|
|
camGo.transform.position = new Vector3(0f, 0f, -1000f);
|
|||
|
|
|
|||
|
|
var canvasGo = new GameObject("Canvas", typeof(RectTransform));
|
|||
|
|
canvasGo.transform.SetParent(_rig.transform, false);
|
|||
|
|
canvasGo.layer = 5;
|
|||
|
|
var canvas = canvasGo.AddComponent<Canvas>();
|
|||
|
|
canvas.renderMode = RenderMode.ScreenSpaceCamera;
|
|||
|
|
canvas.worldCamera = _cam;
|
|||
|
|
canvas.planeDistance = 500f;
|
|||
|
|
var scaler = canvasGo.AddComponent<UnityEngine.UI.CanvasScaler>();
|
|||
|
|
scaler.uiScaleMode = UnityEngine.UI.CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
|||
|
|
scaler.referenceResolution = new Vector2(1080f, 1920f);
|
|||
|
|
scaler.matchWidthOrHeight = 0.5f;
|
|||
|
|
canvasGo.AddComponent<UnityEngine.UI.GraphicRaycaster>();
|
|||
|
|
_crt = (RectTransform)canvasGo.transform;
|
|||
|
|
_crt.sizeDelta = new Vector2(1080f, 1920f);
|
|||
|
|
|
|||
|
|
var ingame = NewRect(_crt, "IngameUIs");
|
|||
|
|
var wlHud = NewRect(ingame, "WL_HUD");
|
|||
|
|
|
|||
|
|
var font = RunHud.BorrowFont(prefabRoot.transform);
|
|||
|
|
RunHud.SetFont(font); RunResultUI.SetFont(font);
|
|||
|
|
StageUiBridge.SetUiRootForProbe(prefabRoot.transform);
|
|||
|
|
RunHud.Subscribe(true); RunResultUI.Subscribe(true); StageUiBridge.Subscribe(true);
|
|||
|
|
RunHud.Bind(_crt); RunResultUI.Bind(_crt);
|
|||
|
|
|
|||
|
|
var dlgGo = new GameObject("WL_ReviveDialog", typeof(RectTransform));
|
|||
|
|
dlgGo.layer = 5;
|
|||
|
|
dlgGo.transform.SetParent(wlHud, false);
|
|||
|
|
_revive = dlgGo.AddComponent<ReviveDialog>();
|
|||
|
|
_revive.SetFont(font);
|
|||
|
|
_revive.Initialize();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void DestroyRig()
|
|||
|
|
{
|
|||
|
|
if (_rig != null) UnityEngine.Object.DestroyImmediate(_rig);
|
|||
|
|
_rig = null; _cam = null; _crt = null; _revive = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void Step3_Result(WLIslandSettings cfg, GameObject prefabRoot)
|
|||
|
|
{
|
|||
|
|
H("④ 결과창 — 던전 1 / 던전 3 (StageEvents 실제 Dispatch 경로)");
|
|||
|
|
// 던전 1
|
|||
|
|
WLDungeonSceneSetup.Current = cfg.dungeons[0];
|
|||
|
|
int idx1 = cfg.dungeons[0].stageIndex;
|
|||
|
|
WLStageUiProbeHooks.RaiseEntered(idx1, StageIdOf(idx1), 3, 60f, Vector3.zero, 13f, 1.5f, 0);
|
|||
|
|
StageUiBridge.ProbeSetInStage(true);
|
|||
|
|
WLStageUiProbeHooks.RaiseCleared(idx1, StageIdOf(idx1), 34.2f, 60f, 3, 3, 0, false, false);
|
|||
|
|
N("제목 \"" + RunResultUI.TitleText + "\" · 행 " + RunResultUI.VisibleRows + "개 · 던전=" +
|
|||
|
|
RunResultUI.IsDungeonResult + " \"" + RunResultUI.DungeonName + "\" 골드=" + RunResultUI.DungeonGold);
|
|||
|
|
N("본문 ⤵\n" + Indent(RunResultUI.BodyText));
|
|||
|
|
Chk(RunResultUI.TitleText == "던전 1 클리어!", "제목 = \"던전 1 클리어!\" (지금까지는 \"스테이지 11 클리어!\")");
|
|||
|
|
Chk(RunResultUI.BodyText.Contains("획득 골드 : +96"), "행 = \"획득 골드 : +96\"");
|
|||
|
|
Chk(RunResultUI.VisibleRows == 7, "행 6 + 획득 골드 1 = 7");
|
|||
|
|
|
|||
|
|
// 던전 3
|
|||
|
|
RunResultUI.Hide();
|
|||
|
|
WLDungeonSceneSetup.Current = cfg.dungeons[2];
|
|||
|
|
int idx3 = cfg.dungeons[2].stageIndex;
|
|||
|
|
WLStageUiProbeHooks.RaiseEntered(idx3, StageIdOf(idx3), 3, 60f, Vector3.zero, 13f, 1.5f, 0);
|
|||
|
|
StageUiBridge.ProbeSetInStage(true);
|
|||
|
|
WLStageUiProbeHooks.RaiseCleared(idx3, StageIdOf(idx3), 41.5f, 60f, 3, 3, 0, false, false);
|
|||
|
|
N("제목 \"" + RunResultUI.TitleText + "\" · 골드=" + RunResultUI.DungeonGold);
|
|||
|
|
Chk(RunResultUI.TitleText == "던전 3 클리어!", "제목 = \"던전 3 클리어!\"");
|
|||
|
|
Chk(RunResultUI.BodyText.Contains("획득 골드 : +192"), "행 = \"획득 골드 : +192\"");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static string Indent(string s)
|
|||
|
|
{
|
|||
|
|
if (string.IsNullOrEmpty(s)) return " (빈 본문)";
|
|||
|
|
var lines = s.Replace("\r", "").Split('\n');
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
for (int i = 0; i < lines.Length; i++)
|
|||
|
|
if (lines[i].Length > 0) sb.AppendLine(" " + lines[i]);
|
|||
|
|
return sb.ToString().TrimEnd();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void Step4_Hud(WLIslandSettings cfg, GameObject prefabRoot)
|
|||
|
|
{
|
|||
|
|
H("⑤ HUD 단계 라벨 — 던전이면 「던전 N」");
|
|||
|
|
WLDungeonSceneSetup.Current = cfg.dungeons[0];
|
|||
|
|
int idx1 = cfg.dungeons[0].stageIndex;
|
|||
|
|
WLStageUiProbeHooks.RaiseEntered(idx1, StageIdOf(idx1), 3, 60f, Vector3.zero, 13f, 1.5f, 0);
|
|||
|
|
N("던전 1 → 단계 \"" + RunHud.PhaseText + "\" · 남은 적 \"" + RunHud.EnemyText + "\"");
|
|||
|
|
Chk(RunHud.PhaseText == "던전 1", "HUD 라벨 = \"던전 1\" (지금까지는 \"스테이지 11\")");
|
|||
|
|
|
|||
|
|
WLDungeonSceneSetup.Current = cfg.dungeons[2];
|
|||
|
|
int idx3 = cfg.dungeons[2].stageIndex;
|
|||
|
|
WLStageUiProbeHooks.RaiseStarted(idx3, StageIdOf(idx3), 3, 60f, 0);
|
|||
|
|
N("던전 3 → 단계 \"" + RunHud.PhaseText + "\"");
|
|||
|
|
Chk(RunHud.PhaseText == "던전 3", "HUD 라벨 = \"던전 3\"");
|
|||
|
|
|
|||
|
|
int w0 = RunHud.StageLabelWrites;
|
|||
|
|
WLStageUiProbeHooks.RaiseStarted(idx3, StageIdOf(idx3), 3, 60f, 0);
|
|||
|
|
Chk(RunHud.StageLabelWrites == w0, "같은 이름 재입력 시 쓰기 0회(GC 0)");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void Step5_StageUnchanged(GameObject prefabRoot)
|
|||
|
|
{
|
|||
|
|
H("⑥ #815 스테이지 모드 무변경 (Current = null)");
|
|||
|
|
WLDungeonSceneSetup.Current = null;
|
|||
|
|
RunResultUI.Hide();
|
|||
|
|
WLStageUiProbeHooks.RaiseEntered(2, 3, 12, 60f, Vector3.zero, 14f, 1.5f, 0);
|
|||
|
|
WLStageUiProbeHooks.RaiseEnemyCount(2, 5, 12);
|
|||
|
|
WLStageUiProbeHooks.RaiseTime(2, 8f, 60f);
|
|||
|
|
N("단계 \"" + RunHud.PhaseText + "\" · 시간 \"" + RunHud.TimerText + "\" · 남은 적 \"" + RunHud.EnemyText + "\"");
|
|||
|
|
Chk(RunHud.PhaseText == "스테이지 3", "HUD = \"스테이지 3\" 그대로");
|
|||
|
|
|
|||
|
|
StageUiBridge.ProbeSetInStage(true);
|
|||
|
|
WLStageUiProbeHooks.RaiseCleared(2, 3, 34.2f, 60f, 12, 12, 0, false, false);
|
|||
|
|
N("제목 \"" + RunResultUI.TitleText + "\" · 행 " + RunResultUI.VisibleRows + "개 · 던전=" + RunResultUI.IsDungeonResult);
|
|||
|
|
N("본문 ⤵\n" + Indent(RunResultUI.BodyText));
|
|||
|
|
Chk(RunResultUI.TitleText == "스테이지 3 클리어!", "제목 = \"스테이지 3 클리어!\" 그대로");
|
|||
|
|
Chk(RunResultUI.VisibleRows == 6, "행 6개 그대로(획득 골드 없음)");
|
|||
|
|
Chk(!RunResultUI.BodyText.Contains("획득 골드"), "본문에 「획득 골드」 0건");
|
|||
|
|
Chk(!RunResultUI.IsDungeonResult && RunResultUI.DungeonGold == -1, "던전 문맥 없음");
|
|||
|
|
|
|||
|
|
// 구 경로(4인자 = 816m 이전 동작)와 같은 본문인가 — 진짜 before/after 대조
|
|||
|
|
string after = RunResultUI.BodyText;
|
|||
|
|
var r = Cleared(2, 12, false);
|
|||
|
|
RunResultUI.Hide();
|
|||
|
|
RunResultUI.SetStageMode(true);
|
|||
|
|
RunResultUI.ShowStage(in r, 0, 0, 0); // 816m 이전과 같은 진입점
|
|||
|
|
RunResultUI.BuildBody(WLRunUiSettings.Instance);
|
|||
|
|
N("구 경로 본문 ⤵\n" + Indent(RunResultUI.BodyText));
|
|||
|
|
Chk(RunResultUI.BodyText == after, "구 4인자 경로(=816m 이전) == 새 경로 본문 문자열 동일");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void Step6_Rollback(WLIslandSettings cfg, GameObject prefabRoot)
|
|||
|
|
{
|
|||
|
|
H("⑦ C8 롤백 — dungeonModeEnabled = 0 이면 던전도 지금까지 그대로");
|
|||
|
|
var g = WLStageUiSettings.Instance;
|
|||
|
|
bool saved = g.dungeonModeEnabled;
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
g.dungeonModeEnabled = false;
|
|||
|
|
WLDungeonSceneSetup.Current = cfg.dungeons[0];
|
|||
|
|
int idx1 = cfg.dungeons[0].stageIndex;
|
|||
|
|
RunResultUI.Hide();
|
|||
|
|
WLStageUiProbeHooks.RaiseEntered(idx1, StageIdOf(idx1), 3, 60f, Vector3.zero, 13f, 1.5f, 0);
|
|||
|
|
StageUiBridge.ProbeSetInStage(true);
|
|||
|
|
WLStageUiProbeHooks.RaiseCleared(idx1, StageIdOf(idx1), 34.2f, 60f, 3, 3, 0, false, false);
|
|||
|
|
N("롤백 제목 \"" + RunResultUI.TitleText + "\" · HUD \"" + RunHud.PhaseText + "\" · 행 " + RunResultUI.VisibleRows);
|
|||
|
|
Chk(RunResultUI.TitleText == "스테이지 11 클리어!", "롤백 = \"스테이지 11 클리어!\"(PD 가 본 화면)");
|
|||
|
|
Chk(RunHud.PhaseText == "스테이지 11", "롤백 HUD = \"스테이지 11\"");
|
|||
|
|
Chk(RunResultUI.VisibleRows == 6, "롤백 행 6개");
|
|||
|
|
}
|
|||
|
|
finally { g.dungeonModeEnabled = saved; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═════════════════════════════════════════════════════════════════════
|
|||
|
|
// ③ 캡처 — 1080×1920 목업(실 노드 · 실 값 · RenderTexture · 815d 와 같은 방식)
|
|||
|
|
// ═════════════════════════════════════════════════════════════════════
|
|||
|
|
public static string Capture()
|
|||
|
|
{
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
string dir = Path.Combine(Path.Combine(Root, "Screenshots_WL"), "WL816m");
|
|||
|
|
Directory.CreateDirectory(dir);
|
|||
|
|
|
|||
|
|
var cfg = WLIslandSettings.Instance;
|
|||
|
|
var savedCurrent = WLDungeonSceneSetup.Current;
|
|||
|
|
GameObject prefabRoot = null;
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
prefabRoot = PrefabUtility.LoadPrefabContents(kPrefab);
|
|||
|
|
BuildRig(prefabRoot);
|
|||
|
|
var cam = _cam;
|
|||
|
|
var revive = _revive;
|
|||
|
|
|
|||
|
|
var rt = new RenderTexture(1080, 1920, 24, RenderTextureFormat.ARGB32);
|
|||
|
|
cam.targetTexture = rt;
|
|||
|
|
|
|||
|
|
int idx1 = cfg.dungeons[0].stageIndex, idx3 = cfg.dungeons[2].stageIndex;
|
|||
|
|
|
|||
|
|
// ⓒ HUD — 던전 1
|
|||
|
|
WLDungeonSceneSetup.Current = cfg.dungeons[0];
|
|||
|
|
WLStageUiProbeHooks.RaiseEntered(idx1, StageIdOf(idx1), 3, 60f, Vector3.zero, 13f, 1.5f, 0);
|
|||
|
|
WLStageUiProbeHooks.RaiseEnemyCount(idx1, 3, 3);
|
|||
|
|
WLStageUiProbeHooks.RaiseTime(idx1, 42f, 60f);
|
|||
|
|
RunHud.ApplyLayout(); RunResultUI.Hide(); revive.HideNow();
|
|||
|
|
sb.AppendLine("ⓒ HUD 던전1 \"" + RunHud.PhaseText + "\" \"" + RunHud.TimerText + "\" \"" + RunHud.EnemyText + "\"");
|
|||
|
|
sb.AppendLine(Shot(cam, rt, Path.Combine(dir, "c_hud_dungeon1.png")));
|
|||
|
|
|
|||
|
|
// ⓐ 던전 1 클리어
|
|||
|
|
StageUiBridge.ProbeSetInStage(true);
|
|||
|
|
WLStageUiProbeHooks.RaiseCleared(idx1, StageIdOf(idx1), 34.2f, 60f, 3, 3, 0, false, false);
|
|||
|
|
RunResultUI.ApplyLayout();
|
|||
|
|
sb.AppendLine("ⓐ 던전1 \"" + RunResultUI.TitleText + "\" 행=" + RunResultUI.VisibleRows +
|
|||
|
|
" 골드=" + RunResultUI.DungeonGold);
|
|||
|
|
sb.AppendLine(Shot(cam, rt, Path.Combine(dir, "a_dungeon1_clear.png")));
|
|||
|
|
|
|||
|
|
// ⓑ 던전 3 클리어
|
|||
|
|
RunResultUI.Hide();
|
|||
|
|
WLDungeonSceneSetup.Current = cfg.dungeons[2];
|
|||
|
|
WLStageUiProbeHooks.RaiseEntered(idx3, StageIdOf(idx3), 3, 60f, Vector3.zero, 13f, 1.5f, 0);
|
|||
|
|
StageUiBridge.ProbeSetInStage(true);
|
|||
|
|
WLStageUiProbeHooks.RaiseCleared(idx3, StageIdOf(idx3), 41.5f, 60f, 3, 3, 0, false, false);
|
|||
|
|
RunResultUI.ApplyLayout();
|
|||
|
|
sb.AppendLine("ⓑ 던전3 \"" + RunResultUI.TitleText + "\" 골드=" + RunResultUI.DungeonGold);
|
|||
|
|
sb.AppendLine(Shot(cam, rt, Path.Combine(dir, "b_dungeon3_clear.png")));
|
|||
|
|
|
|||
|
|
// ⓓ 스테이지 모드 대조 — 새 경로(Current=null)
|
|||
|
|
RunResultUI.Hide();
|
|||
|
|
WLDungeonSceneSetup.Current = null;
|
|||
|
|
WLStageUiProbeHooks.RaiseEntered(2, 3, 12, 60f, Vector3.zero, 14f, 1.5f, 0);
|
|||
|
|
WLStageUiProbeHooks.RaiseEnemyCount(2, 5, 12);
|
|||
|
|
WLStageUiProbeHooks.RaiseTime(2, 8f, 60f);
|
|||
|
|
RunHud.ApplyLayout();
|
|||
|
|
sb.AppendLine("ⓓ HUD 스테이지 \"" + RunHud.PhaseText + "\" \"" + RunHud.TimerText + "\" \"" + RunHud.EnemyText + "\"");
|
|||
|
|
sb.AppendLine(Shot(cam, rt, Path.Combine(dir, "d1_hud_stage_new.png")));
|
|||
|
|
|
|||
|
|
StageUiBridge.ProbeSetInStage(true);
|
|||
|
|
WLStageUiProbeHooks.RaiseCleared(2, 3, 34.2f, 60f, 12, 12, 0, false, false);
|
|||
|
|
RunResultUI.ApplyLayout();
|
|||
|
|
sb.AppendLine("ⓓ 스테이지 클리어(새 경로) \"" + RunResultUI.TitleText + "\" 행=" + RunResultUI.VisibleRows);
|
|||
|
|
sb.AppendLine(Shot(cam, rt, Path.Combine(dir, "d1_stage_clear_new.png")));
|
|||
|
|
|
|||
|
|
// ⓓ 대조군 — 816m 이전과 같은 4인자 진입점
|
|||
|
|
RunResultUI.Hide();
|
|||
|
|
RunHud.WriteStageLabel(3);
|
|||
|
|
RunHud.ApplyLayout();
|
|||
|
|
sb.AppendLine(Shot(cam, rt, Path.Combine(dir, "d2_hud_stage_legacy.png")));
|
|||
|
|
var r = Cleared(2, 12, false);
|
|||
|
|
RunResultUI.SetStageMode(true);
|
|||
|
|
RunResultUI.ShowStage(in r, 0, 0, 0);
|
|||
|
|
RunResultUI.ApplyLayout();
|
|||
|
|
sb.AppendLine("ⓓ 스테이지 클리어(구 4인자) \"" + RunResultUI.TitleText + "\" 행=" + RunResultUI.VisibleRows);
|
|||
|
|
sb.AppendLine(Shot(cam, rt, Path.Combine(dir, "d2_stage_clear_legacy.png")));
|
|||
|
|
|
|||
|
|
// ⓔ 실패 팝업 (ReviveDialog.cs 0줄 변경)
|
|||
|
|
RunResultUI.Hide();
|
|||
|
|
StageUiBridge.ProbeSetInStage(true);
|
|||
|
|
WLStageUiProbeHooks.RaiseFailed(2, 3, WL.Combat.Stage.StageFailReason.TimeLimit, 4, 12, 60f, 0);
|
|||
|
|
revive.ApplyLayout();
|
|||
|
|
sb.AppendLine("ⓔ 실패 \"" + revive.LastMessage.Replace("\n", " / ") + "\" 버튼=[" +
|
|||
|
|
revive.PrimaryButtonText + "] [" + revive.SecondaryButtonText + "]");
|
|||
|
|
sb.AppendLine(Shot(cam, rt, Path.Combine(dir, "e_fail.png")));
|
|||
|
|
|
|||
|
|
cam.targetTexture = null;
|
|||
|
|
UnityEngine.Object.DestroyImmediate(rt);
|
|||
|
|
|
|||
|
|
sb.AppendLine();
|
|||
|
|
sb.AppendLine("픽셀 대조 (MD5) — 새 경로 vs 816m 이전 경로");
|
|||
|
|
sb.AppendLine(" HUD " + SameFile(Path.Combine(dir, "d1_hud_stage_new.png"), Path.Combine(dir, "d2_hud_stage_legacy.png")));
|
|||
|
|
sb.AppendLine(" 결과창 " + SameFile(Path.Combine(dir, "d1_stage_clear_new.png"), Path.Combine(dir, "d2_stage_clear_legacy.png")));
|
|||
|
|
}
|
|||
|
|
catch (Exception e) { sb.AppendLine("예외 " + e.GetType().Name + " : " + e.Message + "\n" + e.StackTrace); }
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
try { StageUiBridge.Teardown(); RunHud.Teardown(); RunResultUI.Teardown(); } catch { }
|
|||
|
|
DestroyRig();
|
|||
|
|
WLDungeonSceneSetup.Current = savedCurrent;
|
|||
|
|
if (prefabRoot != null) PrefabUtility.UnloadPrefabContents(prefabRoot);
|
|||
|
|
}
|
|||
|
|
string path = Path.Combine(Path.Combine(Root, "AgentScripts"), "WL816m_CAPTURE.txt");
|
|||
|
|
File.WriteAllText(path, sb.ToString(), new UTF8Encoding(true));
|
|||
|
|
return sb.ToString();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static string SameFile(string a, string b)
|
|||
|
|
{
|
|||
|
|
if (!File.Exists(a) || !File.Exists(b)) return "파일 없음";
|
|||
|
|
string ha = Md5(a), hb = Md5(b);
|
|||
|
|
return (ha == hb ? "동일 " : "다름 ") + ha.Substring(0, 12) + " / " + hb.Substring(0, 12);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static string Md5(string path)
|
|||
|
|
{
|
|||
|
|
using (var md5 = System.Security.Cryptography.MD5.Create())
|
|||
|
|
using (var fs = File.OpenRead(path))
|
|||
|
|
return BitConverter.ToString(md5.ComputeHash(fs)).Replace("-", "").ToLowerInvariant();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static RectTransform NewRect(RectTransform parent, string name)
|
|||
|
|
{
|
|||
|
|
var go = new GameObject(name, typeof(RectTransform));
|
|||
|
|
go.layer = 5;
|
|||
|
|
var rt = (RectTransform)go.transform;
|
|||
|
|
rt.SetParent(parent, false);
|
|||
|
|
rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one;
|
|||
|
|
rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
|
|||
|
|
rt.localScale = Vector3.one;
|
|||
|
|
return rt;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static string Shot(Camera cam, RenderTexture rt, string path)
|
|||
|
|
{
|
|||
|
|
Canvas.ForceUpdateCanvases();
|
|||
|
|
cam.Render();
|
|||
|
|
var prev = RenderTexture.active;
|
|||
|
|
RenderTexture.active = rt;
|
|||
|
|
var tex = new Texture2D(rt.width, rt.height, TextureFormat.RGBA32, false);
|
|||
|
|
tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
|
|||
|
|
tex.Apply();
|
|||
|
|
RenderTexture.active = prev;
|
|||
|
|
var png = tex.EncodeToPNG();
|
|||
|
|
UnityEngine.Object.DestroyImmediate(tex);
|
|||
|
|
File.WriteAllBytes(path, png);
|
|||
|
|
return " → " + path + " (" + png.Length + " B · " + rt.width + "×" + rt.height + ")";
|
|||
|
|
}
|
|||
|
|
}
|