[WL-816m] 던전 결과창 = 던전 번호 + 획득 골드 (#816)

PD 원문(2026-09-15) 「클리어 던전은 무조건 11로 나오고 획득한 재화 정보를 알 수 없어」.

- 결과창 제목 = 「던전 N 클리어!」 (WLDungeonDef.displayName) · 행에 「획득 골드 +96」 추가
- HUD 단계 라벨 = 「던전 N」
- 판정 = WLDungeonSceneSetup.Current 하나 · 골드 = WLIslandBridge.ComputeGold 재사용(식 1곳)
- 문구·형식 전부 SO(WLStageUiSettings 던전 절 7필드 · C45) · C8 롤백 = dungeonModeEnabled 0
- #815 스테이지 모드 무변경(구 4인자 경로와 캡처 MD5 동일) · Assets/WL/Island/** 0줄
- 게이트 PASS(resident · console errors 0) · 프로브 28/28 PASS

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
깃 관리자 2026-09-15 00:49:56 +09:00
parent ed4c305b20
commit 87fdb49648
8 changed files with 839 additions and 31 deletions

View File

@ -0,0 +1,19 @@
ⓒ HUD 던전1 "던전 1" "00:42" "남은 적 3 / 3"
→ E:\NerdNavis\WL_wt\ui\Screenshots_WL\WL816m\c_hud_dungeon1.png (62853 B · 1080×1920)
ⓐ 던전1 "던전 1 클리어!" 행=7 골드=96
→ E:\NerdNavis\WL_wt\ui\Screenshots_WL\WL816m\a_dungeon1_clear.png (145037 B · 1080×1920)
ⓑ 던전3 "던전 3 클리어!" 골드=192
→ E:\NerdNavis\WL_wt\ui\Screenshots_WL\WL816m\b_dungeon3_clear.png (145609 B · 1080×1920)
ⓓ HUD 스테이지 "스테이지 3" "00:08" "남은 적 5 / 12"
→ E:\NerdNavis\WL_wt\ui\Screenshots_WL\WL816m\d1_hud_stage_new.png (66700 B · 1080×1920)
ⓓ 스테이지 클리어(새 경로) "스테이지 3 클리어!" 행=6
→ E:\NerdNavis\WL_wt\ui\Screenshots_WL\WL816m\d1_stage_clear_new.png (146352 B · 1080×1920)
→ E:\NerdNavis\WL_wt\ui\Screenshots_WL\WL816m\d2_hud_stage_legacy.png (66700 B · 1080×1920)
ⓓ 스테이지 클리어(구 4인자) "스테이지 3 클리어!" 행=6
→ E:\NerdNavis\WL_wt\ui\Screenshots_WL\WL816m\d2_stage_clear_legacy.png (146352 B · 1080×1920)
ⓔ 실패 "시간 초과! / 남은 적 4마리" 버튼=[다시 도전] [섬으로]
→ E:\NerdNavis\WL_wt\ui\Screenshots_WL\WL816m\e_fail.png (84370 B · 1080×1920)
픽셀 대조 (MD5) — 새 경로 vs 816m 이전 경로
HUD 동일 ae76d1c543ba / ae76d1c543ba
결과창 동일 f77b6ffa7382 / f77b6ffa7382

View File

@ -0,0 +1,96 @@
# WL816m Probe 2026-09-15 00:47:59 (edit mode · Play 0 · 로그인 0)
playMode=False
── ① 설정 실값 (SO · 하드코딩 0)
[PASS] WLStageUiSettings 에셋 로드
던전모드=True · 제목 "{0} 클리어!" · HUD "{0}" · 대체이름 "던전 {0}"
골드행=True · 라벨 "획득 골드" · 값 "+{0}"
스테이지 제목(불변) "스테이지 {0} 클리어!" · 스테이지 HUD(불변) "스테이지 {0}"
[PASS] 던전 표 3행 이상
던전 표[0] id=1 "던전 1" stageIndex=10 → 스테이지 표 id=11(지금 화면에 나오던 숫자) · goldMultiplier=1.00
던전 표[1] id=2 "던전 2" stageIndex=11 → 스테이지 표 id=12(지금 화면에 나오던 숫자) · goldMultiplier=1.50
던전 표[2] id=3 "던전 3" stageIndex=12 → 스테이지 표 id=13(지금 화면에 나오던 숫자) · goldMultiplier=2.00
던전 표[3] id=4 "던전 4" stageIndex=13 → 스테이지 표 id=14(지금 화면에 나오던 숫자) · goldMultiplier=3.00
보상 값 — clearBonus=60 perKill=12 bossBonus=120 배수적용=1 rewardEnabled=1
── ② 던전 판정 = WLDungeonSceneSetup.Current 하나뿐
[PASS] Current=null → 던전 아님(#815 스테이지 모드)
[PASS] 이름 null
[PASS] Current=던전1 행 → 던전으로 판정
이름 = "던전 1"
[PASS] 이름 = 표의 displayName
displayName 빈 경우 대체 이름 = "던전 1"
[PASS] 대체 이름 = dungeonNameFallbackFormat + id
── ③ 획득 골드 = 섬 축 WLIslandBridge.ComputeGold 재사용(식 1곳)
던전 1 (index 10 · 처치 3) → 96 / 섬 축 직접 = 96
[PASS] 던전 1 = 96 (816g §5 실측과 같음)
던전 3 (index 12 · 처치 3) → 192
[PASS] 던전 3 = 192 (816i2 §3 실측과 같음)
[PASS] UI 표시값 == 섬 축 지급 계산값(두 곳 일치)
── ④ 결과창 — 던전 1 / 던전 3 (StageEvents 실제 Dispatch 경로)
제목 "던전 1 클리어!" · 행 7개 · 던전=True "던전 1" 골드=96
본문 ⤵
던전 1 클리어!
클리어 시간 : 00:35
처치 : 3 / 3
엘리트 처치 : 0
최대 연쇄 : 0
피격 / 물약 : 0회 / 0개
재도전 : 0회
획득 골드 : +96
무피격 클리어!
[PASS] 제목 = "던전 1 클리어!" (지금까지는 "스테이지 11 클리어!")
[PASS] 행 = "획득 골드 : +96"
[PASS] 행 6 + 획득 골드 1 = 7
제목 "던전 3 클리어!" · 골드=192
[PASS] 제목 = "던전 3 클리어!"
[PASS] 행 = "획득 골드 : +192"
── ⑤ HUD 단계 라벨 — 던전이면 「던전 N」
던전 1 → 단계 "던전 1" · 남은 적 "남은 적 3 / 3"
[PASS] HUD 라벨 = "던전 1" (지금까지는 "스테이지 11")
던전 3 → 단계 "던전 3"
[PASS] HUD 라벨 = "던전 3"
[PASS] 같은 이름 재입력 시 쓰기 0회(GC 0)
── ⑥ #815 스테이지 모드 무변경 (Current = null)
단계 "스테이지 3" · 시간 "00:08" · 남은 적 "남은 적 5 / 12"
[PASS] HUD = "스테이지 3" 그대로
제목 "스테이지 3 클리어!" · 행 6개 · 던전=False
본문 ⤵
스테이지 3 클리어!
클리어 시간 : 00:35
처치 : 12 / 12
엘리트 처치 : 0
최대 연쇄 : 0
피격 / 물약 : 0회 / 0개
재도전 : 0회
무피격 클리어!
[PASS] 제목 = "스테이지 3 클리어!" 그대로
[PASS] 행 6개 그대로(획득 골드 없음)
[PASS] 본문에 「획득 골드」 0건
[PASS] 던전 문맥 없음
구 경로 본문 ⤵
스테이지 3 클리어!
클리어 시간 : 00:35
처치 : 12 / 12
엘리트 처치 : 0
최대 연쇄 : 0
피격 / 물약 : 0회 / 0개
재도전 : 0회
무피격 클리어!
[PASS] 구 4인자 경로(=816m 이전) == 새 경로 본문 문자열 동일
── ⑦ C8 롤백 — dungeonModeEnabled = 0 이면 던전도 지금까지 그대로
롤백 제목 "스테이지 11 클리어!" · HUD "스테이지 11" · 행 6
[PASS] 롤백 = "스테이지 11 클리어!"(PD 가 본 화면)
[PASS] 롤백 HUD = "스테이지 11"
[PASS] 롤백 행 6개
── ⑦ PlayerPrefs 무변경 (섬 축은 에디트 모드에 설치되지 않는다)
키 "WL_Island_PendingGold" 전=0 후=0
[PASS] 보류 보상 PlayerPrefs 무변경
RESULT PASS (pass=28 fail=0)

View File

@ -0,0 +1,504 @@
// 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 + ")";
}
}

View File

@ -76,6 +76,7 @@ namespace WL.UI
private static TextMeshProUGUI s_enemy; // "남은 적 M / T" private static TextMeshProUGUI s_enemy; // "남은 적 M / T"
private static TextMeshProUGUI s_notice; // 카운트다운("준비") 등 한 줄 알림 private static TextMeshProUGUI s_notice; // 카운트다운("준비") 등 한 줄 알림
private static int s_shownStageNo = int.MinValue; private static int s_shownStageNo = int.MinValue;
private static string s_shownDungeonLabel; // WL-816m — 던전 라벨 캐시(같은 문구면 쓰기 0 = GC 0)
private static int s_shownEnemyRemain = int.MinValue, s_shownEnemyTotal = int.MinValue; private static int s_shownEnemyRemain = int.MinValue, s_shownEnemyTotal = int.MinValue;
private static bool s_shownEnemyHi; private static bool s_shownEnemyHi;
private static string s_shownNotice = null; private static string s_shownNotice = null;
@ -360,6 +361,7 @@ namespace WL.UI
s_shownSec = int.MinValue; s_timerColorDirty = true; s_shownSec = int.MinValue; s_timerColorDirty = true;
s_shownPhaseKind = -1; s_shownPhaseZone = -1; s_shownPhaseKind = -1; s_shownPhaseZone = -1;
s_shownStageNo = int.MinValue; s_shownStageNo = int.MinValue;
s_shownDungeonLabel = null; // WL-816m
s_shownEnemyRemain = s_shownEnemyTotal = int.MinValue; s_shownEnemyRemain = s_shownEnemyTotal = int.MinValue;
s_shownNotice = null; s_shownNotice = null;
if (!on) WriteNotice(null); if (!on) WriteNotice(null);
@ -375,6 +377,7 @@ namespace WL.UI
if (s_phase == null || g == null) return; if (s_phase == null || g == null) return;
if (stageNo == s_shownStageNo) return; if (stageNo == s_shownStageNo) return;
s_shownStageNo = stageNo; s_shownStageNo = stageNo;
s_shownDungeonLabel = null; // WL-816m — 던전 캐시 무효화(던전 → 스테이지 복귀)
s_shownPhaseKind = 2; s_shownPhaseZone = stageNo; // 813q WritePhase 의 캐시와 충돌하지 않게 s_shownPhaseKind = 2; s_shownPhaseZone = stageNo; // 813q WritePhase 의 캐시와 충돌하지 않게
s_phase.SetText(g.stageLabelFormat, stageNo); // TMP 무할당 오버로드({0} = float) s_phase.SetText(g.stageLabelFormat, stageNo); // TMP 무할당 오버로드({0} = float)
var s = St; var s = St;
@ -382,6 +385,35 @@ namespace WL.UI
StageLabelWrites++; StageLabelWrites++;
} }
/// <summary>
/// WL-816m — 던전 중 단계 라벨(「던전 1」). 이름은 <see cref="StageUiBridge"/> 가
/// 던전 표(`WLDungeonDef.displayName`)에서 읽어 넘긴다 — 이 파일은 그리기만 한다.
/// 같은 문구면 쓰지 않는다(GC 0).
/// </summary>
public static void WriteDungeonLabel(string dungeonName)
{
var g = WLStageUiSettings.Instance;
if (s_phase == null || g == null || string.IsNullOrEmpty(dungeonName)) return;
string text = FormatName(g.dungeonLabelFormat, dungeonName);
if (string.Equals(text, s_shownDungeonLabel)) return;
s_shownDungeonLabel = text;
s_shownStageNo = int.MinValue; // 숫자 캐시 무효화(스테이지 복귀 시 다시 쓰게)
s_shownPhaseKind = 3; s_shownPhaseZone = -1; // 813q WritePhase 의 캐시와 충돌하지 않게
s_phase.SetText(text);
var s = St;
if (s != null) s_phase.color = s.phaseColor;
StageLabelWrites++;
}
/// <summary>"{0}" 한 자리를 문자열로 치환(입장 1회 경로 · 틱 부담 0).</summary>
private static string FormatName(string format, string a0)
{
if (string.IsNullOrEmpty(format)) return a0;
int i = format.IndexOf("{0}", System.StringComparison.Ordinal);
if (i < 0) return format;
return format.Substring(0, i) + a0 + format.Substring(i + 3);
}
/// <summary>「남은 적 M / T」(값이 그대로면 쓰기 0 = GC 0).</summary> /// <summary>「남은 적 M / T」(값이 그대로면 쓰기 0 = GC 0).</summary>
public static void WriteEnemies(int remaining, int total) public static void WriteEnemies(int remaining, int total)
{ {
@ -677,6 +709,7 @@ namespace WL.UI
s_shownSec = int.MinValue; s_shownWarn = false; s_timerColorDirty = false; s_shownSec = int.MinValue; s_shownWarn = false; s_timerColorDirty = false;
s_shownPhaseKind = -1; s_shownPhaseZone = -1; s_shownPhaseKind = -1; s_shownPhaseZone = -1;
s_shownStageNo = int.MinValue; s_shownStageNo = int.MinValue;
s_shownDungeonLabel = null; // WL-816m
s_shownEnemyRemain = s_shownEnemyTotal = int.MinValue; s_shownEnemyHi = false; s_shownEnemyRemain = s_shownEnemyTotal = int.MinValue; s_shownEnemyHi = false;
s_shownNotice = null; s_shownNotice = null;
if (s_notice != null && s_notice.gameObject.activeSelf) s_notice.gameObject.SetActive(false); if (s_notice != null && s_notice.gameObject.activeSelf) s_notice.gameObject.SetActive(false);

View File

@ -113,6 +113,13 @@ namespace WL.UI
private static TextMeshProUGUI s_badge; // 무피격 클리어 배지 private static TextMeshProUGUI s_badge; // 무피격 클리어 배지
private static bool s_suppressNextRunEnded; // 실패 경로 — 병행 RunEnded 로 결과창이 겹쳐 뜨는 것을 막는다 private static bool s_suppressNextRunEnded; // 실패 경로 — 병행 RunEnded 로 결과창이 겹쳐 뜨는 것을 막는다
// ── WL-816m 던전 모드 (#816) ─────────────────────────────────────────
// 🔴 이 파일은 던전인지 **스스로 판정하지 않는다** — 유일한 이음매 `StageUiBridge` 가
// `WLDungeonSceneSetup.Current` 를 보고 이름·골드를 넘겨준다(UI ↔ 게임 로직 분리).
// s_dungeonName == null 이면 #815 스테이지 모드 그대로다(한 글자도 안 바뀐다).
private static string s_dungeonName;
private static int s_dungeonGold = -1; // -1 = 모름(보상 축이 꺼져 있거나 던전이 아님)
/// <summary>815d 가 대입한다 — [다음 스테이지] / [처음부터].</summary> /// <summary>815d 가 대입한다 — [다음 스테이지] / [처음부터].</summary>
public static System.Action StagePrimary; public static System.Action StagePrimary;
/// <summary>815d 가 대입한다 — [로비로].</summary> /// <summary>815d 가 대입한다 — [로비로].</summary>
@ -528,10 +535,23 @@ namespace WL.UI
public static string SetStageMode(bool on) public static string SetStageMode(bool on)
{ {
s_stageMode = on; s_stageMode = on;
if (!on) { s_hasStage = false; if (s_badge != null && s_badge.gameObject.activeSelf) s_badge.gameObject.SetActive(false); } if (!on)
{
s_hasStage = false;
s_dungeonName = null; s_dungeonGold = -1; // WL-816m — 던전 문맥도 함께 비운다
if (s_badge != null && s_badge.gameObject.activeSelf) s_badge.gameObject.SetActive(false);
}
return "결과창 스테이지 모드=" + on; return "결과창 스테이지 모드=" + on;
} }
// ── WL-816m 던전 문맥 (실측 덤프용) ──────────────────────────────────
/// <summary>이번 결과가 던전인가(= StageUiBridge 가 이름을 넘겼는가).</summary>
public static bool IsDungeonResult { get { return !string.IsNullOrEmpty(s_dungeonName); } }
/// <summary>이번 결과의 던전 이름(던전이 아니면 빈 문자열).</summary>
public static string DungeonName { get { return s_dungeonName ?? ""; } }
/// <summary>이번 결과의 획득 골드(모르면 -1).</summary>
public static int DungeonGold { get { return s_dungeonGold; } }
/// <summary>다음 `RunEnded` 1건을 무시한다(스테이지 실패 경로 · 결과창 중복 표시 0).</summary> /// <summary>다음 `RunEnded` 1건을 무시한다(스테이지 실패 경로 · 결과창 중복 표시 0).</summary>
public static string SuppressNextRunEnded() public static string SuppressNextRunEnded()
{ {
@ -545,6 +565,16 @@ namespace WL.UI
/// hits/potions/maxChain 은 <see cref="StageUiBridge"/> 가 스테이지 구간 동안 실측한 값이다. /// hits/potions/maxChain 은 <see cref="StageUiBridge"/> 가 스테이지 구간 동안 실측한 값이다.
/// </summary> /// </summary>
public static string ShowStage(in WL.Combat.Stage.StageClearResult r, int hits, int potions, int maxChain) public static string ShowStage(in WL.Combat.Stage.StageClearResult r, int hits, int potions, int maxChain)
{
return ShowStage(in r, hits, potions, maxChain, null, -1);
}
/// <summary>
/// WL-816m — 던전 문맥을 함께 받는 경로. <paramref name="dungeonName"/> 이 비어 있으면
/// 위 4인자 경로와 **완전히 같다**(#815 스테이지 모드 무변경).
/// </summary>
public static string ShowStage(in WL.Combat.Stage.StageClearResult r, int hits, int potions, int maxChain,
string dungeonName, int dungeonGold)
{ {
var s = St; var s = St;
var g = WLStageUiSettings.Instance; var g = WLStageUiSettings.Instance;
@ -552,6 +582,9 @@ namespace WL.UI
if (g == null || !WLStageUiSettings.Enabled || !g.stageResultEnabled) return "815d off — 스테이지 결과 생략"; if (g == null || !WLStageUiSettings.Enabled || !g.stageResultEnabled) return "815d off — 스테이지 결과 생략";
if (!WLRunUiSettings.Enabled || !s.resultEnabled) return "813q off — 결과 화면 생략"; if (!WLRunUiSettings.Enabled || !s.resultEnabled) return "813q off — 결과 화면 생략";
s_dungeonName = g.dungeonModeEnabled && !string.IsNullOrEmpty(dungeonName) ? dungeonName : null;
s_dungeonGold = s_dungeonName != null ? dungeonGold : -1;
s_stage = r; s_hasStage = true; s_stage = r; s_hasStage = true;
s_stageHits = hits; s_stagePotions = potions; s_stageMaxChain = maxChain; s_stageHits = hits; s_stagePotions = potions; s_stageMaxChain = maxChain;
s_stageMode = true; s_stageMode = true;
@ -564,13 +597,29 @@ namespace WL.UI
return TryShow(Time.unscaledTime); return TryShow(Time.unscaledTime);
} }
/// <summary>스테이지 결과 제목(마지막 스테이지면 다른 문구).</summary> /// <summary>스테이지 결과 제목(마지막 스테이지면 다른 문구). WL-816m — 던전이면 「던전 N 클리어!」.</summary>
private static string StageTitle(WLStageUiSettings g) private static string StageTitle(WLStageUiSettings g)
{ {
// 🔴 WL-816m — 던전은 스테이지 표 id(던전 1 = 11)가 아니라 던전 이름을 쓴다.
// isLastStage(전 스테이지 클리어) 문구도 던전에는 해당하지 않는다(던전은 표의 한 줄이다).
if (s_dungeonName != null && g.dungeonModeEnabled)
return FormatName(g.dungeonClearTitleFormat, s_dungeonName);
string f = s_stage.isLastStage ? g.stageAllClearTitleFormat : g.stageClearTitleFormat; string f = s_stage.isLastStage ? g.stageAllClearTitleFormat : g.stageClearTitleFormat;
return FormatOne(f, s_stage.stageId > 0 ? s_stage.stageId : s_stage.index + 1); return FormatOne(f, s_stage.stageId > 0 ? s_stage.stageId : s_stage.index + 1);
} }
/// <summary>"{0}" 한 자리를 **문자열**로 치환(string.Format 없이 · 클리어 1회 경로라 틱 부담 0).</summary>
private static string FormatName(string format, string a0)
{
if (string.IsNullOrEmpty(format)) return a0 ?? "";
int i = format.IndexOf("{0}", System.StringComparison.Ordinal);
if (i < 0) return format;
s_sb.Length = 0;
s_sb.Append(format, 0, i).Append(a0).Append(format, i + 3, format.Length - i - 3);
return s_sb.ToString();
}
/// <summary>"{0}" 한 자리 치환(정수 전용 · string.Format 없이 · GC 최소).</summary> /// <summary>"{0}" 한 자리 치환(정수 전용 · string.Format 없이 · GC 최소).</summary>
private static string FormatOne(string format, long a0) private static string FormatOne(string format, long a0)
{ {
@ -583,7 +632,11 @@ namespace WL.UI
/// <summary>스테이지 본문(기준서 §F-1). 813q 의 행 노드를 그대로 재사용한다.</summary> /// <summary>스테이지 본문(기준서 §F-1). 813q 의 행 노드를 그대로 재사용한다.</summary>
private static string BuildStageBody(WLRunUiSettings s, WLStageUiSettings g) private static string BuildStageBody(WLRunUiSettings s, WLStageUiSettings g)
{ {
EnsureStageRows(s, g); // 🔴 WL-816m — 던전이면 「획득 골드」 행이 한 줄 늘어난다.
// stageResultRows 에 Gold 를 이미 넣어 뒀다면 그 자리에 쓰고 줄을 더하지 않는다(중복 0).
bool goldRow = s_dungeonName != null && g.dungeonModeEnabled && g.dungeonGoldRowEnabled &&
!g.HasRow(WLStageResultRow.Gold);
EnsureStageRows(s, g, goldRow ? 1 : 0);
var body = new StringBuilder(256); var body = new StringBuilder(256);
string title = StageTitle(g); string title = StageTitle(g);
@ -599,15 +652,14 @@ namespace WL.UI
string label = StageLabelOf(g, kind); string label = StageLabelOf(g, kind);
s_sb.Length = 0; s_sb.Length = 0;
StageValueOf(g, kind, s_sb); StageValueOf(g, kind, s_sb);
string value = s_sb.ToString(); WriteStageRow(slot, label, s_sb.ToString(), body);
slot++;
if (slot < s_row.Length) }
{ if (goldRow)
if (s_rowLabel[slot] != null) s_rowLabel[slot].SetText(label); {
if (s_rowValue[slot] != null) s_rowValue[slot].SetText(value); s_sb.Length = 0;
if (s_row[slot] != null && !s_row[slot].gameObject.activeSelf) s_row[slot].gameObject.SetActive(true); StageValueOf(g, WLStageResultRow.Gold, s_sb);
} WriteStageRow(slot, StageLabelOf(g, WLStageResultRow.Gold), s_sb.ToString(), body);
body.Append(label).Append(" : ").Append(value).Append('\n');
slot++; slot++;
} }
for (int i = slot; i < s_row.Length; i++) for (int i = slot; i < s_row.Length; i++)
@ -632,9 +684,21 @@ namespace WL.UI
return "스테이지 행 " + slot + "개 · 배지=" + badge; return "스테이지 행 " + slot + "개 · 배지=" + badge;
} }
private static void EnsureStageRows(WLRunUiSettings s, WLStageUiSettings g) /// <summary>행 하나를 노드에 쓰고 본문 문자열에도 남긴다(노드가 없어도 본문은 끝까지 만든다).</summary>
private static void WriteStageRow(int slot, string label, string value, StringBuilder body)
{ {
int want = g.stageResultRows != null ? g.stageResultRows.Length : 0; if (slot < s_row.Length)
{
if (s_rowLabel[slot] != null) s_rowLabel[slot].SetText(label);
if (s_rowValue[slot] != null) s_rowValue[slot].SetText(value);
if (s_row[slot] != null && !s_row[slot].gameObject.activeSelf) s_row[slot].gameObject.SetActive(true);
}
body.Append(label).Append(" : ").Append(value).Append('\n');
}
private static void EnsureStageRows(WLRunUiSettings s, WLStageUiSettings g, int extra)
{
int want = (g.stageResultRows != null ? g.stageResultRows.Length : 0) + extra;
if (s_rowRoot == null || s_row.Length >= want) return; if (s_rowRoot == null || s_row.Length >= want) return;
int old = s_row.Length; int old = s_row.Length;
System.Array.Resize(ref s_row, want); System.Array.Resize(ref s_row, want);
@ -661,7 +725,8 @@ namespace WL.UI
case WLStageResultRow.Retries: return g.rowRetriesLabel; case WLStageResultRow.Retries: return g.rowRetriesLabel;
case WLStageResultRow.BossClear: return g.rowBossLabel; case WLStageResultRow.BossClear: return g.rowBossLabel;
case WLStageResultRow.Level: return g.rowLevelLabel; case WLStageResultRow.Level: return g.rowLevelLabel;
case WLStageResultRow.Gold: return g.rowGoldLabel; // 🔴 WL-816m — 던전이면 「획득 골드」, 스테이지 모드면 지금까지와 같은 「골드」.
case WLStageResultRow.Gold: return s_dungeonName != null && g.dungeonModeEnabled ? g.rowDungeonGoldLabel : g.rowGoldLabel;
default: return g.rowLootLabel; default: return g.rowLootLabel;
} }
} }
@ -692,8 +757,15 @@ namespace WL.UI
case WLStageResultRow.BossClear: case WLStageResultRow.BossClear:
sb.Append(s_stage.win ? g.rowBossClearedText : g.rowUnknownText); sb.Append(s_stage.win ? g.rowBossClearedText : g.rowUnknownText);
break; break;
case WLStageResultRow.Gold:
// 🔴 WL-816m — 던전이면 섬 축이 실제로 지급할 값(StageUiBridge 가 WLIslandBridge.ComputeGold 로 받아 넘긴다).
// 던전이 아니거나 보상 축이 꺼져 있으면 지금까지처럼 「미확인」.
if (s_dungeonName != null && g.dungeonModeEnabled && s_dungeonGold >= 0)
AppendFormat2(sb, g.rowDungeonGoldFormat, s_dungeonGold, 0);
else sb.Append(g.rowUnknownText);
break;
default: default:
// 🔴 레벨 · 골드 · 등급별 획득 = 815b 페이로드에 없다(실측) → 「미확인」 표기 // 🔴 레벨 · 등급별 획득 = 815b 페이로드에 없다(실측) → 「미확인」 표기
sb.Append(g.rowUnknownText); sb.Append(g.rowUnknownText);
break; break;
} }
@ -1196,6 +1268,7 @@ namespace WL.UI
s_title = null; s_rowRoot = null; s_nextButton = null; s_closeButton = null; s_title = null; s_rowRoot = null; s_nextButton = null; s_closeButton = null;
s_nextLabel = null; s_closeLabel = null; s_nextBg = null; s_closeBg = null; s_nextLabel = null; s_closeLabel = null; s_nextBg = null; s_closeBg = null;
s_badge = null; s_stageMode = false; // WL-815d s_badge = null; s_stageMode = false; // WL-815d
s_dungeonName = null; s_dungeonGold = -1; // WL-816m
s_row = new RectTransform[0]; s_rowLabel = new TextMeshProUGUI[0]; s_rowValue = new TextMeshProUGUI[0]; s_row = new RectTransform[0]; s_rowLabel = new TextMeshProUGUI[0]; s_rowValue = new TextMeshProUGUI[0];
s_uiRoot = null; s_uiRoot = null;
ResetState(); ResetState();
@ -1221,6 +1294,7 @@ namespace WL.UI
" " + s_stage.elapsedSec.ToString("F1") + "s " + s_stage.killed + "/" + s_stage.total + " " + s_stage.elapsedSec.ToString("F1") + "s " + s_stage.killed + "/" + s_stage.total +
" 재도전=" + s_stage.retries + " 마지막=" + s_stage.isLastStage + " 보스=" + s_stage.isBoss + " 재도전=" + s_stage.retries + " 마지막=" + s_stage.isLastStage + " 보스=" + s_stage.isBoss +
" · 피격=" + s_stageHits + " 물약=" + s_stagePotions + " 최대연쇄=" + s_stageMaxChain); " · 피격=" + s_stageHits + " 물약=" + s_stagePotions + " 최대연쇄=" + s_stageMaxChain);
sb.AppendLine(" [816m] 던전=" + IsDungeonResult + " \"" + DungeonName + "\" 골드=" + s_dungeonGold);
sb.AppendLine(" [813q2] 부활우선=" + ReviveBusy + "(" + ReviveBusyReason + ") · DeathFlow=" + DeathFlow.State + sb.AppendLine(" [813q2] 부활우선=" + ReviveBusy + "(" + ReviveBusyReason + ") · DeathFlow=" + DeathFlow.State +
" 런=" + RunDirector.Phase + "(running=" + RunDirector.IsRunning + ") · died=" + DiedSeen + " request=" + ReviveRequestSeen + " 런=" + RunDirector.Phase + "(running=" + RunDirector.IsRunning + ") · died=" + DiedSeen + " request=" + ReviveRequestSeen +
" revived=" + RevivedSeen + " 팝업생략부활=" + SkipPopupReviveCount + " revived=" + RevivedSeen + " 팝업생략부활=" + SkipPopupReviveCount +

View File

@ -71,6 +71,51 @@ namespace WL.UI
private static WLStageUiSettings St { get { return WLStageUiSettings.Instance; } } private static WLStageUiSettings St { get { return WLStageUiSettings.Instance; } }
// ═════════════════════════════════════════════════════════════════════
// WL-816m — 던전 문맥 (#816)
// PD 원문(2026-09-15) 「클리어 던전은 무조건 11로 나오고 획득한 재화 정보를 알 수 없어」.
// 🔴 판정 기준은 **`WLDungeonSceneSetup.Current` 하나**다 — null 이면 #815 스테이지 모드
// 그대로이고 아래 값은 한 번도 읽히지 않는다.
// 🔴 골드는 여기서 계산하지 않는다 — 섬 축의 `WLIslandBridge.ComputeGold` 를 그대로 다시 부른다
// (계산식 1곳 · 지급은 섬 축이 한다 · `Assets/WL/Island/**` 는 읽기만 · 0줄 변경).
// ═════════════════════════════════════════════════════════════════════
/// <summary>지금 도는 것이 던전이면 그 표 행, 아니면 null(던전 모드가 꺼져 있어도 null).</summary>
public static WL.Island.WLDungeonDef CurrentDungeon
{
get
{
var g = St;
if (g == null || !WLStageUiSettings.Enabled || !g.dungeonModeEnabled) return null;
return WL.Island.WLDungeonSceneSetup.Current;
}
}
/// <summary>던전 이름(표의 displayName · 비었으면 id 로 만든다). 던전이 아니면 null.</summary>
public static string DungeonNameOf(WL.Island.WLDungeonDef d)
{
var g = St;
if (d == null || g == null) return null;
if (!string.IsNullOrEmpty(d.displayName)) return d.displayName;
return FormatOne(g.dungeonNameFallbackFormat, d.id);
}
/// <summary>이번 클리어로 섬에서 받을 코인(보상 축이 꺼져 있으면 -1 = 「모름」).</summary>
public static int DungeonGoldOf(in WL.Combat.Stage.StageClearResult r)
{
var cfg = WL.Island.WLIslandSettings.Instance;
if (cfg == null || cfg.enabled_ == 0 || cfg.rewardEnabled == 0) return -1;
return WL.Island.WLIslandBridge.ComputeGold(cfg, in r);
}
/// <summary>상단 단계 라벨을 쓴다 — 던전이면 「던전 N」, 아니면 지금까지대로 「스테이지 N」.</summary>
private static void WritePhaseLabel(int stageNo)
{
string name = DungeonNameOf(CurrentDungeon);
if (name != null) RunHud.WriteDungeonLabel(name);
else RunHud.WriteStageLabel(stageNo);
}
// ── 부팅 ────────────────────────────────────────────────────────────── // ── 부팅 ──────────────────────────────────────────────────────────────
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
private static void Boot() private static void Boot()
@ -161,7 +206,7 @@ namespace WL.UI
{ {
RunHud.SetStageMode(true); RunHud.SetStageMode(true);
RunHud.SetVisible(true); RunHud.SetVisible(true);
RunHud.WriteStageLabel(s_stageNo); WritePhaseLabel(s_stageNo); // WL-816m — 던전이면 「던전 N」
RunHud.WriteEnemies(e.totalEnemies, e.totalEnemies); RunHud.WriteEnemies(e.totalEnemies, e.totalEnemies);
RunHud.WriteStageTime(e.timeLimit); RunHud.WriteStageTime(e.timeLimit);
} }
@ -191,7 +236,7 @@ namespace WL.UI
s_hits = 0; s_maxChain = 0; s_hits = 0; s_maxChain = 0;
if (!g.stageHudEnabled) return; if (!g.stageHudEnabled) return;
RunHud.WriteNotice(null); // 카운트다운 줄을 접는다 RunHud.WriteNotice(null); // 카운트다운 줄을 접는다
RunHud.WriteStageLabel(e.stageId > 0 ? e.stageId : e.index + 1); WritePhaseLabel(e.stageId > 0 ? e.stageId : e.index + 1); // WL-816m — 던전이면 「던전 N」
RunHud.WriteStageTime(e.timeLimit); RunHud.WriteStageTime(e.timeLimit);
} }
@ -218,8 +263,13 @@ namespace WL.UI
if (g == null || !WLStageUiSettings.Enabled) return; if (g == null || !WLStageUiSettings.Enabled) return;
if (g.stageHudEnabled) RunHud.WriteNotice(null); if (g.stageHudEnabled) RunHud.WriteNotice(null);
if (!g.stageResultEnabled) return; if (!g.stageResultEnabled) return;
LastLog = RunResultUI.ShowStage(in r, s_hits, PotionsUsed, s_maxChain); // WL-816m — 던전이면 제목을 「던전 N 클리어!」로 바꾸고 획득 골드 행을 한 줄 더한다.
Log(g, "클리어 스테이지 " + (r.stageId > 0 ? r.stageId : r.index + 1) + " — " + LastLog); var d = CurrentDungeon;
string dName = DungeonNameOf(d);
int dGold = dName != null ? DungeonGoldOf(in r) : -1;
LastLog = RunResultUI.ShowStage(in r, s_hits, PotionsUsed, s_maxChain, dName, dGold);
Log(g, "클리어 " + (dName ?? ("스테이지 " + (r.stageId > 0 ? r.stageId : r.index + 1))) +
(dName != null ? " · 획득 골드 " + dGold : "") + " — " + LastLog);
} }
private static void OnFailed(in WL.Combat.Stage.StageFailedEvent e) private static void OnFailed(in WL.Combat.Stage.StageFailedEvent e)
@ -533,6 +583,10 @@ namespace WL.UI
" · 입장 " + EnteredSeen + " 카운트 " + CountdownSeen + " 시작 " + StartedSeen + " · 입장 " + EnteredSeen + " 카운트 " + CountdownSeen + " 시작 " + StartedSeen +
" 적수 " + EnemySeen + " 시간 " + TimeSeen + " 클리어 " + ClearedSeen + " 적수 " + EnemySeen + " 시간 " + TimeSeen + " 클리어 " + ClearedSeen +
" 실패 " + FailedSeen + " 이탈 " + ExitedSeen); " 실패 " + FailedSeen + " 이탈 " + ExitedSeen);
var dd = CurrentDungeon;
sb.AppendLine(" [816m] 던전=" + (dd != null) + " \"" + (DungeonNameOf(dd) ?? "-") + "\" (Current=" +
(WL.Island.WLDungeonSceneSetup.Current != null ? WL.Island.WLDungeonSceneSetup.Current.displayName : "null") +
" · 던전모드=" + (g != null && g.dungeonModeEnabled) + ")");
sb.AppendLine(" 실측 — 피격 " + s_hits + "회 · 물약 " + PotionsUsed + "개(시작 스냅 " + s_potionsAtStart + sb.AppendLine(" 실측 — 피격 " + s_hits + "회 · 물약 " + PotionsUsed + "개(시작 스냅 " + s_potionsAtStart +
" / 현재 " + PotionUse.UsedCount + ") · 최대 연쇄 " + s_maxChain + " / 현재 " + PotionUse.UsedCount + ") · 최대 연쇄 " + s_maxChain +
"(진행중 " + KillChain.ChainKills + ")"); "(진행중 " + KillChain.ChainKills + ")");

View File

@ -27,7 +27,7 @@ MonoBehaviour:
timerWarnColor: {r: 1, g: 0.29, b: 0.278, a: 1} timerWarnColor: {r: 1, g: 0.29, b: 0.278, a: 1}
countdownEnabled: 1 countdownEnabled: 1
countdownText: "\uC900\uBE44" countdownText: "\uC900\uBE44"
countdownNumberFormat: "{0}" countdownNumberFormat: '{0}'
countdownFontPx: 72 countdownFontPx: 72
countdownColor: {r: 1, g: 0.86, b: 0.42, a: 1} countdownColor: {r: 1, g: 0.86, b: 0.42, a: 1}
countdownTopGapPx: 24 countdownTopGapPx: 24
@ -58,13 +58,7 @@ MonoBehaviour:
stageResultEnabled: 1 stageResultEnabled: 1
stageClearTitleFormat: "\uC2A4\uD14C\uC774\uC9C0 {0} \uD074\uB9AC\uC5B4!" stageClearTitleFormat: "\uC2A4\uD14C\uC774\uC9C0 {0} \uD074\uB9AC\uC5B4!"
stageAllClearTitleFormat: "\uC804 \uC2A4\uD14C\uC774\uC9C0 \uD074\uB9AC\uC5B4!" stageAllClearTitleFormat: "\uC804 \uC2A4\uD14C\uC774\uC9C0 \uD074\uB9AC\uC5B4!"
stageResultRows: stageResultRows: 000000000100000002000000030000000400000005000000
- 0
- 1
- 2
- 3
- 4
- 5
rowClearTimeLabel: "\uD074\uB9AC\uC5B4 \uC2DC\uAC04" rowClearTimeLabel: "\uD074\uB9AC\uC5B4 \uC2DC\uAC04"
rowKillsLabel: "\uCC98\uCE58" rowKillsLabel: "\uCC98\uCE58"
rowEliteLabel: "\uC5D8\uB9AC\uD2B8 \uCC98\uCE58" rowEliteLabel: "\uC5D8\uB9AC\uD2B8 \uCC98\uCE58"
@ -75,7 +69,7 @@ MonoBehaviour:
rowLevelLabel: "\uB808\uBCA8" rowLevelLabel: "\uB808\uBCA8"
rowGoldLabel: "\uACE8\uB4DC" rowGoldLabel: "\uACE8\uB4DC"
rowLootLabel: "\uD68D\uB4DD" rowLootLabel: "\uD68D\uB4DD"
rowKillsFormat: "{0} / {1}" rowKillsFormat: '{0} / {1}'
rowHitsPotionsFormat: "{0}\uD68C / {1}\uAC1C" rowHitsPotionsFormat: "{0}\uD68C / {1}\uAC1C"
rowRetriesFormat: "{0}\uD68C" rowRetriesFormat: "{0}\uD68C"
rowBossClearedText: "\uCC98\uCE58" rowBossClearedText: "\uCC98\uCE58"
@ -87,13 +81,20 @@ MonoBehaviour:
nextStageText: "\uB2E4\uC74C \uC2A4\uD14C\uC774\uC9C0" nextStageText: "\uB2E4\uC74C \uC2A4\uD14C\uC774\uC9C0"
restartFromFirstText: "\uCC98\uC74C\uBD80\uD130" restartFromFirstText: "\uCC98\uC74C\uBD80\uD130"
showRestartOnLastStage: 1 showRestartOnLastStage: 1
toLobbyText: "섬으로" toLobbyText: "\uC12C\uC73C\uB85C"
dungeonModeEnabled: 1
dungeonClearTitleFormat: "{0} \uD074\uB9AC\uC5B4!"
dungeonLabelFormat: '{0}'
dungeonNameFallbackFormat: "\uB358\uC804 {0}"
dungeonGoldRowEnabled: 1
rowDungeonGoldLabel: "\uD68D\uB4DD \uACE8\uB4DC"
rowDungeonGoldFormat: +{0}
stageFailEnabled: 1 stageFailEnabled: 1
failDeathMessage: "\uC4F0\uB7EC\uC84C\uC2B5\uB2C8\uB2E4.\n\uB2E4\uC2DC \uB3C4\uC804\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?" failDeathMessage: "\uC4F0\uB7EC\uC84C\uC2B5\uB2C8\uB2E4.\n\uB2E4\uC2DC \uB3C4\uC804\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?"
failTimeoutMessage: "\uC2DC\uAC04 \uCD08\uACFC!\n\uB0A8\uC740 \uC801 {0}\uB9C8\uB9AC" failTimeoutMessage: "\uC2DC\uAC04 \uCD08\uACFC!\n\uB0A8\uC740 \uC801 {0}\uB9C8\uB9AC"
failAbortedMessage: "\uC2A4\uD14C\uC774\uC9C0\uAC00 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4." failAbortedMessage: "\uC2A4\uD14C\uC774\uC9C0\uAC00 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."
failRetryText: "\uB2E4\uC2DC \uB3C4\uC804" failRetryText: "\uB2E4\uC2DC \uB3C4\uC804"
failLobbyText: "섬으로" failLobbyText: "\uC12C\uC73C\uB85C"
suppressResultOnFail: 1 suppressResultOnFail: 1
lobbyMode: 0 lobbyMode: 0
titleSceneName: Title titleSceneName: Title

View File

@ -284,6 +284,33 @@ namespace WL.UI
[Tooltip("오른쪽 버튼 문구 — 누르면 「로비로」(lobbyMode).")] [Tooltip("오른쪽 버튼 문구 — 누르면 「로비로」(lobbyMode).")]
public string toLobbyText = "로비로"; public string toLobbyText = "로비로";
// ── 던전 모드 (WL-816m · #816) ───────────────────────────────────────
// PD 원문(2026-09-15) 「클리어 던전은 무조건 11로 나오고 획득한 재화 정보를 알 수 없어」.
// 던전에서는 스테이지 표 id(던전 1 = 11)가 아니라 **던전 이름**을 쓰고 획득 골드를 한 줄 더한다.
// 🔴 판정 기준은 `WL.Island.WLDungeonSceneSetup.Current` 하나뿐이다 —
// 그것이 null 인 #815 스테이지 모드는 이 절의 어떤 값도 읽지 않는다(한 글자도 안 바뀐다).
[Header("던전 모드 (WL-816m · 던전에서만 · 스테이지 모드는 불변)")]
[Tooltip("끄면 던전에서도 지금처럼 「스테이지 N 클리어!」 로 나온다(C8 롤백 1줄).")]
public bool dungeonModeEnabled = true;
[Tooltip("던전 클리어 제목 — {0} = 던전 이름(WLIslandSettings 던전 표의 displayName · 예 「던전 1」).")]
public string dungeonClearTitleFormat = "{0} 클리어!";
[Tooltip("던전 중 상단 단계 라벨 — {0} = 던전 이름(스테이지 모드의 stageLabelFormat 을 대신한다).")]
public string dungeonLabelFormat = "{0}";
[Tooltip("던전 표의 displayName 이 비었을 때 쓰는 이름 — {0} = 그 행의 id.")]
public string dungeonNameFallbackFormat = "던전 {0}";
[Tooltip("던전 클리어 화면에 획득 골드 행을 더한다(stageResultRows 에 Gold 가 이미 있으면 그 자리에 쓴다).")]
public bool dungeonGoldRowEnabled = true;
[Tooltip("획득 골드 행 라벨.")]
public string rowDungeonGoldLabel = "획득 골드";
[Tooltip("획득 골드 행 값 문구 — {0} = 코인 수.")]
public string rowDungeonGoldFormat = "+{0}";
// ── 실패 팝업 (기준서 §E) ──────────────────────────────────────────── // ── 실패 팝업 (기준서 §E) ────────────────────────────────────────────
[Header("실패 팝업 (기준서 §E · 813z ReviveDialog 2버튼)")] [Header("실패 팝업 (기준서 §E · 813z ReviveDialog 2버튼)")]
[Tooltip("끄면 스테이지 실패에 813z 부활 팝업이 **원래대로**(1버튼 「부활」) 뜬다.")] [Tooltip("끄면 스테이지 실패에 813z 부활 팝업이 **원래대로**(1버튼 「부활」) 뜬다.")]