From 60738c0fa50e3b1b1053b9e08618c2925a6643fc Mon Sep 17 00:00:00 2001 From: swrring Date: Wed, 9 Sep 2026 00:49:35 +0900 Subject: [PATCH] =?UTF-8?q?[WL-813g]=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=97=B0=EC=B6=9C=203=EC=A2=85=20+=20=EC=8A=AC=EB=A1=AF=20?= =?UTF-8?q?=EC=98=A4=EB=B2=84=EB=9D=BC=EC=9D=B4=EB=93=9C=20(#813)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 2b1d10662295fd02ae38e71da0b12222be02c0c0) --- AgentScripts/WL813g_Apply.cs | 209 ++ AgentScripts/WL813g_Probe.cs | 407 ++++ Assets/Res_Addr/MainUI/NewGameUI.prefab | 1707 +++++++++++++++++ Assets/Script/HUD/HUDDMGUI.cs | 240 ++- Assets/WL/UI/Scripts/KillChainText.cs | 230 +++ Assets/WL/UI/Scripts/KillChainText.cs.meta | 2 + Assets/WL/UI/Scripts/LootToast.cs | 351 ++++ Assets/WL/UI/Scripts/LootToast.cs.meta | 2 + Assets/WL/UI/Scripts/StatPopup.cs | 297 +++ Assets/WL/UI/Scripts/StatPopup.cs.meta | 2 + Assets/WL/UI/Scripts/WLSkillSlotOverride.cs | 291 +++ .../WL/UI/Scripts/WLSkillSlotOverride.cs.meta | 2 + Assets/WL/UI/Scripts/WLTextFxUtil.cs | 86 + Assets/WL/UI/Scripts/WLTextFxUtil.cs.meta | 2 + .../Resources/WL/WLCombatTextSettings.asset | 87 + .../WL/WLCombatTextSettings.asset.meta | 8 + .../Resources/WL/WLSkillSlotPreset.asset | 37 + .../Resources/WL/WLSkillSlotPreset.asset.meta | 8 + Assets/WL/UI/Settings/WLCombatTextSettings.cs | 336 ++++ .../UI/Settings/WLCombatTextSettings.cs.meta | 2 + Assets/WL/UI/Settings/WLSkillSlotPreset.cs | 147 ++ .../WL/UI/Settings/WLSkillSlotPreset.cs.meta | 2 + 22 files changed, 4440 insertions(+), 15 deletions(-) create mode 100644 AgentScripts/WL813g_Apply.cs create mode 100644 AgentScripts/WL813g_Probe.cs create mode 100644 Assets/WL/UI/Scripts/KillChainText.cs create mode 100644 Assets/WL/UI/Scripts/KillChainText.cs.meta create mode 100644 Assets/WL/UI/Scripts/LootToast.cs create mode 100644 Assets/WL/UI/Scripts/LootToast.cs.meta create mode 100644 Assets/WL/UI/Scripts/StatPopup.cs create mode 100644 Assets/WL/UI/Scripts/StatPopup.cs.meta create mode 100644 Assets/WL/UI/Scripts/WLSkillSlotOverride.cs create mode 100644 Assets/WL/UI/Scripts/WLSkillSlotOverride.cs.meta create mode 100644 Assets/WL/UI/Scripts/WLTextFxUtil.cs create mode 100644 Assets/WL/UI/Scripts/WLTextFxUtil.cs.meta create mode 100644 Assets/WL/UI/Settings/Resources/WL/WLCombatTextSettings.asset create mode 100644 Assets/WL/UI/Settings/Resources/WL/WLCombatTextSettings.asset.meta create mode 100644 Assets/WL/UI/Settings/Resources/WL/WLSkillSlotPreset.asset create mode 100644 Assets/WL/UI/Settings/Resources/WL/WLSkillSlotPreset.asset.meta create mode 100644 Assets/WL/UI/Settings/WLCombatTextSettings.cs create mode 100644 Assets/WL/UI/Settings/WLCombatTextSettings.cs.meta create mode 100644 Assets/WL/UI/Settings/WLSkillSlotPreset.cs create mode 100644 Assets/WL/UI/Settings/WLSkillSlotPreset.cs.meta diff --git a/AgentScripts/WL813g_Apply.cs b/AgentScripts/WL813g_Apply.cs new file mode 100644 index 000000000..3c3691c8c --- /dev/null +++ b/AgentScripts/WL813g_Apply.cs @@ -0,0 +1,209 @@ +// WL-813g (#813) — 설정 에셋 2종 생성 + NewGameUI.prefab 에 텍스트 연출 노드 3개 · 슬롯 오버라이드 컴포넌트 추가. +// 실행: unity command run_script --file AgentScripts/WL813g_Apply.cs --entry WL813g_Apply.CreateSettings +// unity command run_script --file AgentScripts/WL813g_Apply.cs --entry WL813g_Apply.Resave +// unity command run_script --file AgentScripts/WL813g_Apply.cs --entry WL813g_Apply.Apply +// unity command run_script --file AgentScripts/WL813g_Apply.cs --entry WL813g_Apply.Revert +// 🔴 run_script 파일은 파일마다 독립 어셈블리 — 다른 AgentScript 상수 참조 불가(813c 교훈). + +using System.Text; +using UnityEditor; +using UnityEngine; + +public static class WL813g_Apply +{ + public const string NewGameUIPath = "Assets/Res_Addr/MainUI/NewGameUI.prefab"; + public const string SettingsDir = "Assets/WL/UI/Settings/Resources/WL"; + public const string TextSettingsPath = SettingsDir + "/WLCombatTextSettings.asset"; + public const string SlotPresetPath = SettingsDir + "/WLSkillSlotPreset.asset"; + public const string HudPath = "IngameUIs/WL_HUD"; + public const string PadPath = "IngameUIs/BattleUI"; + public const string KillChainName = "WL_KillChainText"; + public const string LootToastName = "WL_LootToast"; + public const string StatPopupName = "WL_StatPopup"; + const string TmpFontGuid = "a387e325271126742ad15569dba1ac57"; // 813c 가 실측한 SkillCard/t_cooltime 폰트 + const int UILayer = 5; + + 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 EnsureFolder(string dir) + { + var parts = dir.Split('/'); + var cur = parts[0]; + for (int i = 1; i < parts.Length; i++) + { + var next = cur + "/" + parts[i]; + if (!AssetDatabase.IsValidFolder(next)) AssetDatabase.CreateFolder(cur, parts[i]); + cur = next; + } + } + + /// 설정 에셋 2종 생성(이미 있으면 값 유지 · 덮어쓰지 않는다). + public static object CreateSettings() + { + var sb = new StringBuilder(); + EnsureFolder(SettingsDir); + + var text = AssetDatabase.LoadAssetAtPath(TextSettingsPath); + if (text == null) + { + text = ScriptableObject.CreateInstance(); + AssetDatabase.CreateAsset(text, TextSettingsPath); + sb.AppendLine("생성: " + TextSettingsPath); + } + else sb.AppendLine("이미 있음(값 유지): " + TextSettingsPath); + + var slot = AssetDatabase.LoadAssetAtPath(SlotPresetPath); + if (slot == null) + { + slot = ScriptableObject.CreateInstance(); + AssetDatabase.CreateAsset(slot, SlotPresetPath); + sb.AppendLine("생성: " + SlotPresetPath); + } + else sb.AppendLine("이미 있음(값 유지): " + SlotPresetPath); + + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + WL.UI.WLCombatTextSettings.ClearCache(); + WL.UI.WLSkillSlotPreset.ClearCache(); + + sb.AppendLine("── 데미지 리듬(기준서 §B 요소 2)"); + sb.AppendLine(" normal=" + text.damageNormalSeconds + "s crit=" + text.damageCritSeconds + + "s critScale=" + text.damageCritScale + " maxConcurrent=" + text.damageMaxConcurrent); + sb.AppendLine("── 연쇄 문구 / 토스트 / 스탯 팝업"); + sb.AppendLine(" killChain=" + text.killChainSeconds + "s punch=" + text.killChainPunchScale + + " topPx=" + text.killChainTopOffsetPx + + " · loot=" + text.lootToastSeconds + "s lines=" + text.lootToastLines + + " · stat=" + text.statPopupSeconds + "s rows=" + text.statPopupRows); + sb.AppendLine("── 슬롯 오버라이드(813n)"); + sb.Append(WL.UI.WLSkillSlotOverride.ResolveDump(slot)); + return sb.ToString(); + } + + /// 무변경 재저장 — 프리팹 편집 전에 재직렬화 diff 가 생기는지 먼저 잰다(802b·813c 교훈). + public static object Resave() + { + var root = PrefabUtility.LoadPrefabContents(NewGameUIPath); + try { PrefabUtility.SaveAsPrefabAsset(root, NewGameUIPath); } + finally { PrefabUtility.UnloadPrefabContents(root); } + AssetDatabase.Refresh(); + return NewGameUIPath + " : 무변경 재저장 (git diff 로 재직렬화 여부 확인)"; + } + + static Transform EnsureNode(Transform parent, string name, StringBuilder sb) + { + var t = parent.Find(name); + if (t == null) + { + var go = new GameObject(name, typeof(RectTransform)); + go.layer = UILayer; + t = go.transform; + ((RectTransform)t).SetParent((RectTransform)parent, false); + sb.AppendLine(parent.name + "/" + name + " : 노드 추가"); + } + else sb.AppendLine(parent.name + "/" + name + " : 이미 있음"); + return t; + } + + public static object Apply() + { + var sb = new StringBuilder(); + var text = AssetDatabase.LoadAssetAtPath(TextSettingsPath); + if (text == null) return "🔴 " + TextSettingsPath + " 없음 — CreateSettings 먼저"; + + TMPro.TMP_FontAsset font = null; + var fontPath = AssetDatabase.GUIDToAssetPath(TmpFontGuid); + if (!string.IsNullOrEmpty(fontPath)) font = AssetDatabase.LoadAssetAtPath(fontPath); + sb.AppendLine("폰트: " + (font != null ? fontPath : "「미확인」 — TMP 기본 폰트로 떨어진다")); + + var root = PrefabUtility.LoadPrefabContents(NewGameUIPath); + try + { + // ── ① 텍스트 연출 3종 = IngameUIs/WL_HUD 아래(802b SafeAreaFitter 레이어 · 813c 규칙) + var hud = FindByPath(root.transform, HudPath); + if (hud == null) sb.AppendLine("🔴 " + HudPath + " 없음"); + else + { + var kc = EnsureNode(hud, KillChainName, sb); + var kct = kc.GetComponent(); + if (kct == null) { kct = kc.gameObject.AddComponent(); sb.AppendLine(" KillChainText 추가"); } + if (font != null) kct.SetFont(font); + sb.AppendLine(" build made=" + kct.BuildIfNeeded() + " · " + kct.ApplyLayout()); + kct.Hide(); + + var lt = EnsureNode(hud, LootToastName, sb); + var ltc = lt.GetComponent(); + if (ltc == null) { ltc = lt.gameObject.AddComponent(); sb.AppendLine(" LootToast 추가"); } + if (font != null) ltc.SetFont(font); + sb.AppendLine(" build made=" + ltc.BuildIfNeeded() + " · " + ltc.ApplyLayout()); + ltc.HideAll(); + + var sp = EnsureNode(hud, StatPopupName, sb); + var spc = sp.GetComponent(); + if (spc == null) { spc = sp.gameObject.AddComponent(); sb.AppendLine(" StatPopup 추가"); } + if (font != null) spc.SetFont(font); + sb.AppendLine(" build made=" + spc.BuildIfNeeded() + " · " + spc.ApplyLayout()); + spc.Hide(); + } + + // ── ② 슬롯 장착 로컬 오버라이드 = IngameUIs/BattleUI 인스턴스 전용 추가 컴포넌트(813c 방식) + var pad = FindByPath(root.transform, PadPath); + if (pad == null) sb.AppendLine("🔴 " + PadPath + " 없음"); + else + { + var ov = pad.GetComponent(); + if (ov == null) { ov = pad.gameObject.AddComponent(); sb.AppendLine(PadPath + " : WLSkillSlotOverride 추가"); } + else sb.AppendLine(PadPath + " : WLSkillSlotOverride 이미 있음"); + var so = new SerializedObject(ov); + so.FindProperty("target").objectReferenceValue = pad.GetComponent(); + so.ApplyModifiedPropertiesWithoutUndo(); + sb.AppendLine(" target=BattleUI 주입 · BattleUI.prefab 원본 무수정(인스턴스 전용 추가 컴포넌트)"); + } + + PrefabUtility.SaveAsPrefabAsset(root, NewGameUIPath); + } + finally { PrefabUtility.UnloadPrefabContents(root); } + + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + return sb.ToString(); + } + + public static object Revert() + { + var sb = new StringBuilder(); + var root = PrefabUtility.LoadPrefabContents(NewGameUIPath); + try + { + var hud = FindByPath(root.transform, HudPath); + if (hud != null) + foreach (var n in new[] { KillChainName, LootToastName, StatPopupName }) + { + var t = hud.Find(n); + if (t != null) { Object.DestroyImmediate(t.gameObject); sb.AppendLine(HudPath + "/" + n + " : 제거"); } + } + var pad = FindByPath(root.transform, PadPath); + if (pad != null) + { + var ov = pad.GetComponent(); + if (ov != null) { Object.DestroyImmediate(ov, true); sb.AppendLine(PadPath + " : WLSkillSlotOverride 제거"); } + } + PrefabUtility.SaveAsPrefabAsset(root, NewGameUIPath); + } + finally { PrefabUtility.UnloadPrefabContents(root); } + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + return sb.ToString(); + } +} diff --git a/AgentScripts/WL813g_Probe.cs b/AgentScripts/WL813g_Probe.cs new file mode 100644 index 000000000..950df9c90 --- /dev/null +++ b/AgentScripts/WL813g_Probe.cs @@ -0,0 +1,407 @@ +// 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 SplitRows(string path) + { + var rows = new List(); + 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 ReadSkillMp() + { + var map = new Dictionary(); + 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 ReadSkillCool() + { + var map = new Dictionary(); + 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(); + equip.Equip[eItem.PC] = row.classId; + equip.Skill = new Dictionary>(); + var lst = new List(); + 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(); + 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.renderMode = RenderMode.ScreenSpaceOverlay; + var scaler = go.GetComponent(); + // 값을 가정하지 않는다 — 실제 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(NewGameUIPath); + var src = srcPrefab != null ? srcPrefab.GetComponent() : 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(); + 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(); + 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(); + 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(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(); + try + { + HUDDMGUI.ClearActiveList(); + var st = WL.UI.WLCombatTextSettings.Instance; + int cap = st != null ? st.damageMaxConcurrent : 0; + var croot = (RectTransform)canvas.transform; + + System.Func spawn = () => + { + var go = (GameObject)Object.Instantiate(prefab, croot); + go.name = "HUDDMGUI_" + made.Count; + made.Add(go); + return go.GetComponent(); + }; + + // (1) 일반 vs 크리 — 표시 시간 · 배율(기준서 §B 요소 2 = 0.6 / 1.0 / 1.6배) + sb.AppendLine(); + sb.AppendLine("[1] 일반 / 크리 규격"); + var a = spawn(); a.SetCore(null, "", 1234, eStat.None); + sb.AppendLine(" 일반 : " + a.DumpState()); + var b = spawn(); b.SetCore(null, "", 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, "", 100, eStat.None); + sb.AppendLine(" 1타 : " + m0.DumpState() + " | active=" + HUDDMGUI.ActiveCount); + var m1 = spawn(); m1.SetCore(t, "", 250, eStat.None); + sb.AppendLine(" 2타 : " + m0.DumpState() + " | active=" + HUDDMGUI.ActiveCount + " merged=" + HUDDMGUI.MergedCount); + var m2 = spawn(); m2.SetCore(t, "", 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, "", 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, "", 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() != null)); + foreach (Transform c in hud) + sb.AppendLine(" child " + c.name + " " + Desc(c as RectTransform) + + " comps=" + string.Join(",", System.Array.ConvertAll(c.GetComponents(), 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(), x => x.GetType().Name))); + } + } + finally { PrefabUtility.UnloadPrefabContents(root); } + + var s1 = sb.ToString(); + Write("WL813g_probe_prefab.txt", s1); + return s1; + } +} diff --git a/Assets/Res_Addr/MainUI/NewGameUI.prefab b/Assets/Res_Addr/MainUI/NewGameUI.prefab index 94e4d8697..2189f76d1 100644 --- a/Assets/Res_Addr/MainUI/NewGameUI.prefab +++ b/Assets/Res_Addr/MainUI/NewGameUI.prefab @@ -2376,6 +2376,144 @@ MonoBehaviour: m_ShadowLayerMask: 1 m_RenderingLayers: 1 m_ShadowRenderingLayers: 1 +--- !u!1 &167533606467711004 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4706323252742176131} + - component: {fileID: 3892278377034187267} + - component: {fileID: 1035462236461146247} + m_Layer: 5 + m_Name: Label + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &4706323252742176131 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 167533606467711004} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1044245567630828775} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &3892278377034187267 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 167533606467711004} + m_CullTransparentMesh: 1 +--- !u!114 &1035462236461146247 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 167533606467711004} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: a387e325271126742ad15569dba1ac57, type: 2} + m_sharedMaterial: {fileID: -6459762427321423278, guid: a387e325271126742ad15569dba1ac57, + type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 1, g: 1, b: 1, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: 0 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 71.111115 + m_fontSizeBase: 71.111115 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 0 + m_fontSizeMax: 0 + m_fontStyle: 0 + m_HorizontalAlignment: 4 + m_VerticalAlignment: 4096 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_characterHorizontalScale: 1 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 0 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 00000000 + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 0 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} --- !u!1 &172581590431189161 GameObject: m_ObjectHideFlags: 0 @@ -9621,6 +9759,44 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_localtextKey: 375 +--- !u!1 &685818931676943199 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2775712678265894820} + m_Layer: 5 + m_Name: Lines + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2775712678265894820 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 685818931676943199} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 3303144280452176177} + - {fileID: 1636465243566216056} + - {fileID: 1044245567630828775} + m_Father: {fileID: 2659634104557254342} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 1, y: 0.5} + m_AnchorMax: {x: 1, y: 0.5} + m_AnchoredPosition: {x: -71.111115, y: 213.33333} + m_SizeDelta: {x: 924.44446, y: 288} + m_Pivot: {x: 1, y: 0.5} --- !u!1 &691804955668209839 GameObject: m_ObjectHideFlags: 0 @@ -14917,6 +15093,73 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: ab4e7dba3733e584abb76cd4fbc0e116, type: 3} m_Name: m_EditorClassIdentifier: +--- !u!1 &1012743025828453768 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3852854134505927621} + - component: {fileID: 7751170293315542711} + - component: {fileID: 260273615803606548} + m_Layer: 5 + m_Name: WL_KillChainText + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3852854134505927621 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1012743025828453768} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1780088930407854519} + m_Father: {fileID: 6746702601929578265} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &7751170293315542711 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1012743025828453768} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 649e686492f34f444a5a77f815f22b9a, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::WL.UI.KillChainText + group: {fileID: 260273615803606548} + box: {fileID: 1780088930407854519} + mainLabel: {fileID: 5866340581009751585} + subLabel: {fileID: 6125142720107581882} + font: {fileID: 11400000, guid: a387e325271126742ad15569dba1ac57, type: 2} +--- !u!225 &260273615803606548 +CanvasGroup: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1012743025828453768} + m_Enabled: 1 + m_Alpha: 0 + m_Interactable: 0 + m_BlocksRaycasts: 0 + m_IgnoreParentGroups: 0 --- !u!1 &1020533420941626403 GameObject: m_ObjectHideFlags: 0 @@ -18810,6 +19053,144 @@ MonoBehaviour: TextCol_off: {r: 1, g: 1, b: 1, a: 1} texts: - {fileID: 2979903005605208643} +--- !u!1 &1212542734698127035 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2193776743168947530} + - component: {fileID: 3120205646591241493} + - component: {fileID: 1675051974952828461} + m_Layer: 5 + m_Name: Row2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2193776743168947530 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1212542734698127035} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4562647675167680002} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 1} + m_AnchorMax: {x: 0.5, y: 1} + m_AnchoredPosition: {x: 0, y: -352.71112} + m_SizeDelta: {x: 1920, y: 99.55556} + m_Pivot: {x: 0.5, y: 1} +--- !u!222 &3120205646591241493 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1212542734698127035} + m_CullTransparentMesh: 1 +--- !u!114 &1675051974952828461 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1212542734698127035} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: a387e325271126742ad15569dba1ac57, type: 2} + m_sharedMaterial: {fileID: -6459762427321423278, guid: a387e325271126742ad15569dba1ac57, + type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 0.55, g: 0.95, b: 0.62, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: 0 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 81.77778 + m_fontSizeBase: 81.77778 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 0 + m_fontSizeMax: 0 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_characterHorizontalScale: 1 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 0 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 00000000 + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 0 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} --- !u!1 &1218174464368113822 GameObject: m_ObjectHideFlags: 0 @@ -27051,6 +27432,57 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_localtextKey: 327 +--- !u!1 &1707981235730303750 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2659634104557254342} + - component: {fileID: 8800829269112967982} + m_Layer: 5 + m_Name: WL_LootToast + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2659634104557254342 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1707981235730303750} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2775712678265894820} + m_Father: {fileID: 6746702601929578265} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &8800829269112967982 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1707981235730303750} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 169f8694dff47c6409dc8af946ea2dcf, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::WL.UI.LootToast + lineRoot: {fileID: 2775712678265894820} + font: {fileID: 11400000, guid: a387e325271126742ad15569dba1ac57, type: 2} --- !u!1 &1708297075324637377 GameObject: m_ObjectHideFlags: 0 @@ -27617,6 +28049,43 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: ab4e7dba3733e584abb76cd4fbc0e116, type: 3} m_Name: m_EditorClassIdentifier: +--- !u!1 &1735093912695322795 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1780088930407854519} + m_Layer: 5 + m_Name: Box + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1780088930407854519 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1735093912695322795} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4467113179390627013} + - {fileID: 1867298862764848390} + m_Father: {fileID: 3852854134505927621} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 924.44446} + m_SizeDelta: {x: 1920, y: 409.6} + m_Pivot: {x: 0.5, y: 0.5} --- !u!1 &1738194743749524113 GameObject: m_ObjectHideFlags: 0 @@ -53560,6 +54029,144 @@ MonoBehaviour: m_hasFontAssetChanged: 0 m_baseMaterial: {fileID: 0} m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!1 &3274635655979199402 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1867298862764848390} + - component: {fileID: 4574204474547781698} + - component: {fileID: 6125142720107581882} + m_Layer: 5 + m_Name: Sub + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1867298862764848390 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3274635655979199402} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1780088930407854519} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &4574204474547781698 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3274635655979199402} + m_CullTransparentMesh: 1 +--- !u!114 &6125142720107581882 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3274635655979199402} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: a387e325271126742ad15569dba1ac57, type: 2} + m_sharedMaterial: {fileID: -6459762427321423278, guid: a387e325271126742ad15569dba1ac57, + type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 1, g: 1, b: 1, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: 0 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 170.66667 + m_fontSizeBase: 170.66667 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 0 + m_fontSizeMax: 0 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + m_VerticalAlignment: 256 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_characterHorizontalScale: 1 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 0 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 00000000 + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 0 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} --- !u!1 &3286703680632006674 GameObject: m_ObjectHideFlags: 0 @@ -57685,6 +58292,144 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &3547048522238544855 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3238056312370672540} + - component: {fileID: 1872298896964454902} + - component: {fileID: 1857853866161479881} + m_Layer: 5 + m_Name: Row0 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3238056312370672540 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3547048522238544855} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4562647675167680002} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 1} + m_AnchorMax: {x: 0.5, y: 1} + m_AnchoredPosition: {x: 0, y: -153.6} + m_SizeDelta: {x: 1920, y: 99.55556} + m_Pivot: {x: 0.5, y: 1} +--- !u!222 &1872298896964454902 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3547048522238544855} + m_CullTransparentMesh: 1 +--- !u!114 &1857853866161479881 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3547048522238544855} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: a387e325271126742ad15569dba1ac57, type: 2} + m_sharedMaterial: {fileID: -6459762427321423278, guid: a387e325271126742ad15569dba1ac57, + type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 0.55, g: 0.95, b: 0.62, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: 0 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 81.77778 + m_fontSizeBase: 81.77778 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 0 + m_fontSizeMax: 0 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_characterHorizontalScale: 1 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 0 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 00000000 + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 0 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} --- !u!1 &3550593034916014132 GameObject: m_ObjectHideFlags: 0 @@ -70098,6 +70843,144 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: ab4e7dba3733e584abb76cd4fbc0e116, type: 3} m_Name: m_EditorClassIdentifier: +--- !u!1 &4464455731768058660 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5552600103643287392} + - component: {fileID: 1147372670865698631} + - component: {fileID: 5523172456201242990} + m_Layer: 5 + m_Name: Row1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &5552600103643287392 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4464455731768058660} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4562647675167680002} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 1} + m_AnchorMax: {x: 0.5, y: 1} + m_AnchoredPosition: {x: 0, y: -253.15556} + m_SizeDelta: {x: 1920, y: 99.55556} + m_Pivot: {x: 0.5, y: 1} +--- !u!222 &1147372670865698631 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4464455731768058660} + m_CullTransparentMesh: 1 +--- !u!114 &5523172456201242990 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4464455731768058660} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: a387e325271126742ad15569dba1ac57, type: 2} + m_sharedMaterial: {fileID: -6459762427321423278, guid: a387e325271126742ad15569dba1ac57, + type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 0.55, g: 0.95, b: 0.62, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: 0 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 81.77778 + m_fontSizeBase: 81.77778 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 0 + m_fontSizeMax: 0 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_characterHorizontalScale: 1 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 0 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 00000000 + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 0 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} --- !u!1 &4465787347686829182 GameObject: m_ObjectHideFlags: 0 @@ -71817,6 +72700,55 @@ MonoBehaviour: m_hasFontAssetChanged: 0 m_baseMaterial: {fileID: 0} m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!1 &4544478545932670092 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1044245567630828775} + - component: {fileID: 1303725201824019155} + m_Layer: 5 + m_Name: Line2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1044245567630828775 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4544478545932670092} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4706323252742176131} + m_Father: {fileID: 2775712678265894820} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 1, y: 1} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -192} + m_SizeDelta: {x: 924.44446, y: 96} + m_Pivot: {x: 1, y: 1} +--- !u!225 &1303725201824019155 +CanvasGroup: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4544478545932670092} + m_Enabled: 1 + m_Alpha: 0 + m_Interactable: 0 + m_BlocksRaycasts: 0 + m_IgnoreParentGroups: 0 --- !u!1 &4569225388110773966 GameObject: m_ObjectHideFlags: 0 @@ -72869,6 +73801,144 @@ MonoBehaviour: m_FillOrigin: 0 m_UseSpriteMesh: 0 m_PixelsPerUnitMultiplier: 1 +--- !u!1 &4664109411339408985 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7038745108317708003} + - component: {fileID: 294579816828339149} + - component: {fileID: 5196231048109300733} + m_Layer: 5 + m_Name: Title + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7038745108317708003 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4664109411339408985} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4562647675167680002} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 1} + m_AnchorMax: {x: 0.5, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 1920, y: 153.6} + m_Pivot: {x: 0.5, y: 1} +--- !u!222 &294579816828339149 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4664109411339408985} + m_CullTransparentMesh: 1 +--- !u!114 &5196231048109300733 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4664109411339408985} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: a387e325271126742ad15569dba1ac57, type: 2} + m_sharedMaterial: {fileID: -6459762427321423278, guid: a387e325271126742ad15569dba1ac57, + type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 1, g: 0.9, b: 0.45, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: 0 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 128 + m_fontSizeBase: 128 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 0 + m_fontSizeMax: 0 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_characterHorizontalScale: 1 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 0 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 00000000 + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 0 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} --- !u!1 &4669640697998942678 GameObject: m_ObjectHideFlags: 0 @@ -75317,6 +76387,55 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: ab4e7dba3733e584abb76cd4fbc0e116, type: 3} m_Name: m_EditorClassIdentifier: +--- !u!1 &4827701665123822899 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1636465243566216056} + - component: {fileID: 4847908321778661952} + m_Layer: 5 + m_Name: Line1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1636465243566216056 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4827701665123822899} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1884511015705201974} + m_Father: {fileID: 2775712678265894820} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 1, y: 1} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -96} + m_SizeDelta: {x: 924.44446, y: 96} + m_Pivot: {x: 1, y: 1} +--- !u!225 &4847908321778661952 +CanvasGroup: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4827701665123822899} + m_Enabled: 1 + m_Alpha: 0 + m_Interactable: 0 + m_BlocksRaycasts: 0 + m_IgnoreParentGroups: 0 --- !u!1 &4832543650469986300 GameObject: m_ObjectHideFlags: 0 @@ -94124,6 +95243,144 @@ MonoBehaviour: m_hasFontAssetChanged: 0 m_baseMaterial: {fileID: 0} m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!1 &6226558004243588709 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1884511015705201974} + - component: {fileID: 6733274850422476488} + - component: {fileID: 8179246318373860555} + m_Layer: 5 + m_Name: Label + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1884511015705201974 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6226558004243588709} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1636465243566216056} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &6733274850422476488 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6226558004243588709} + m_CullTransparentMesh: 1 +--- !u!114 &8179246318373860555 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6226558004243588709} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: a387e325271126742ad15569dba1ac57, type: 2} + m_sharedMaterial: {fileID: -6459762427321423278, guid: a387e325271126742ad15569dba1ac57, + type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 1, g: 1, b: 1, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: 0 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 71.111115 + m_fontSizeBase: 71.111115 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 0 + m_fontSizeMax: 0 + m_fontStyle: 0 + m_HorizontalAlignment: 4 + m_VerticalAlignment: 4096 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_characterHorizontalScale: 1 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 0 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 00000000 + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 0 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} --- !u!1 &6228499332890546286 GameObject: m_ObjectHideFlags: 0 @@ -94815,6 +96072,55 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_localtextKey: 341 +--- !u!1 &6276440792749918578 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3303144280452176177} + - component: {fileID: 7098938687148605668} + m_Layer: 5 + m_Name: Line0 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3303144280452176177 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6276440792749918578} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4763384437825821231} + m_Father: {fileID: 2775712678265894820} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 1, y: 1} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 924.44446, y: 96} + m_Pivot: {x: 1, y: 1} +--- !u!225 &7098938687148605668 +CanvasGroup: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6276440792749918578} + m_Enabled: 1 + m_Alpha: 0 + m_Interactable: 0 + m_BlocksRaycasts: 0 + m_IgnoreParentGroups: 0 --- !u!1 &6277099292957955946 GameObject: m_ObjectHideFlags: 0 @@ -95050,6 +96356,144 @@ MonoBehaviour: m_FlexibleWidth: -1 m_FlexibleHeight: -1 m_LayoutPriority: 1 +--- !u!1 &6299259584143453585 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4467113179390627013} + - component: {fileID: 6437134376297173442} + - component: {fileID: 5866340581009751585} + m_Layer: 5 + m_Name: Main + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &4467113179390627013 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6299259584143453585} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1780088930407854519} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &6437134376297173442 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6299259584143453585} + m_CullTransparentMesh: 1 +--- !u!114 &5866340581009751585 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6299259584143453585} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: a387e325271126742ad15569dba1ac57, type: 2} + m_sharedMaterial: {fileID: -6459762427321423278, guid: a387e325271126742ad15569dba1ac57, + type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 1, g: 1, b: 1, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: 0 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 170.66667 + m_fontSizeBase: 170.66667 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 0 + m_fontSizeMax: 0 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + m_VerticalAlignment: 1024 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_characterHorizontalScale: 1 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 0 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 00000000 + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 0 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} --- !u!1 &6301371811069244108 GameObject: m_ObjectHideFlags: 0 @@ -101033,6 +102477,144 @@ MonoBehaviour: m_hasFontAssetChanged: 0 m_baseMaterial: {fileID: 0} m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!1 &6739477430406679316 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4763384437825821231} + - component: {fileID: 5161967690879218184} + - component: {fileID: 7633039294923261436} + m_Layer: 5 + m_Name: Label + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &4763384437825821231 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6739477430406679316} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 3303144280452176177} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &5161967690879218184 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6739477430406679316} + m_CullTransparentMesh: 1 +--- !u!114 &7633039294923261436 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6739477430406679316} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: a387e325271126742ad15569dba1ac57, type: 2} + m_sharedMaterial: {fileID: -6459762427321423278, guid: a387e325271126742ad15569dba1ac57, + type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 1, g: 1, b: 1, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: 0 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 71.111115 + m_fontSizeBase: 71.111115 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 0 + m_fontSizeMax: 0 + m_fontStyle: 0 + m_HorizontalAlignment: 4 + m_VerticalAlignment: 4096 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_characterHorizontalScale: 1 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 0 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 00000000 + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 0 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} --- !u!1 &6740615159599010644 GameObject: m_ObjectHideFlags: 0 @@ -103596,6 +105178,45 @@ MonoBehaviour: m_FillOrigin: 0 m_UseSpriteMesh: 0 m_PixelsPerUnitMultiplier: 1 +--- !u!1 &6867107481970029433 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4562647675167680002} + m_Layer: 5 + m_Name: Box + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &4562647675167680002 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6867107481970029433} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 7038745108317708003} + - {fileID: 3238056312370672540} + - {fileID: 5552600103643287392} + - {fileID: 2193776743168947530} + m_Father: {fileID: 2892688380935435354} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 320} + m_SizeDelta: {x: 1920, y: 426.66666} + m_Pivot: {x: 0.5, y: 0.5} --- !u!1 &6869674503594283187 GameObject: m_ObjectHideFlags: 0 @@ -108246,6 +109867,9 @@ RectTransform: - {fileID: 2856595641425040628} - {fileID: 2614694673119463930} - {fileID: 4008587971485836497} + - {fileID: 3852854134505927621} + - {fileID: 2659634104557254342} + - {fileID: 2892688380935435354} m_Father: {fileID: 2647977513349308266} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} @@ -114418,6 +116042,72 @@ MonoBehaviour: m_hasFontAssetChanged: 0 m_baseMaterial: {fileID: 0} m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!1 &7619617252624222251 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2892688380935435354} + - component: {fileID: 335285449460938616} + - component: {fileID: 1077514224272703578} + m_Layer: 5 + m_Name: WL_StatPopup + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2892688380935435354 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7619617252624222251} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4562647675167680002} + m_Father: {fileID: 6746702601929578265} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &335285449460938616 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7619617252624222251} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c455fdfebd21acd479d9fda351961628, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::WL.UI.StatPopup + group: {fileID: 1077514224272703578} + box: {fileID: 4562647675167680002} + titleLabel: {fileID: 5196231048109300733} + font: {fileID: 11400000, guid: a387e325271126742ad15569dba1ac57, type: 2} +--- !u!225 &1077514224272703578 +CanvasGroup: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7619617252624222251} + m_Enabled: 1 + m_Alpha: 0 + m_Interactable: 0 + m_BlocksRaycasts: 0 + m_IgnoreParentGroups: 0 --- !u!1 &7633094443372677680 GameObject: m_ObjectHideFlags: 0 @@ -168852,6 +170542,10 @@ PrefabInstance: type: 3} insertIndex: -1 addedObject: {fileID: 3187828139587058435} + - targetCorrespondingSourceObject: {fileID: 4055677999475420392, guid: c327e063c90438e419668019b3490455, + type: 3} + insertIndex: -1 + addedObject: {fileID: 708871460743600700} m_SourcePrefab: {fileID: 100100000, guid: c327e063c90438e419668019b3490455, type: 3} --- !u!114 &2351113276399051383 stripped MonoBehaviour: @@ -168906,3 +170600,16 @@ MonoBehaviour: target: {fileID: 2351113276399051383} autoButtonName: btn_auto attackButtonName: Attack +--- !u!114 &708871460743600700 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5141691710815562090} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 376e0db7fbb566748ae0ca78bc4c4f35, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::WL.UI.WLSkillSlotOverride + target: {fileID: 2351113276399051383} diff --git a/Assets/Script/HUD/HUDDMGUI.cs b/Assets/Script/HUD/HUDDMGUI.cs index c452cacbd..5cecd007b 100644 --- a/Assets/Script/HUD/HUDDMGUI.cs +++ b/Assets/Script/HUD/HUDDMGUI.cs @@ -1,3 +1,10 @@ +// WL-813g (#813) — 데미지 숫자 리듬화. 원본(힘민지) HUDDMGUI 에 표시 규칙만 얹는다. +// 기준서 v1 §B 요소 2: 일반 0.6 s · 크리 1.0 s · 크리 1.6배 · 합산 윈도우 0.25 s · 동시 상한(숫자죽 방지). +// 값은 전부 Assets/WL/UI/Settings/Resources/WL/WLCombatTextSettings.asset (C45 · 코드 상수 0). +// 🔴 C8 롤백: 에셋이 없거나 textEnabled/damageRhythmEnabled = false 면 **아래 원본 상수**(0.9 s · 배율 1 · 상한 없음) +// 로 그대로 돈다 — 호출부(Actor.cs:616/789/811/841)와 프리팹은 무수정이다. + +using System.Collections.Generic; using TMPro; using UnityEngine; @@ -5,12 +12,49 @@ public class HUDDMGUI : HUDBase { public TextMeshProUGUI[] dmgs; + // ── 원본 상수(롤백 기준값) — 설정 에셋이 없을 때 이 값으로 돈다 private float moveSpeed = 1.0f; private float alphaSpeed = 0.5f; private float destroyTime = .9f; bool usedmg2; int m_Index; + // ── WL-813g 상태(설정이 꺼져 있으면 전부 미사용) + static readonly List s_active = new List(); + /// 프로브가 읽는다 — 지금 화면에 떠 있는 숫자 수. + public static int ActiveCount { get { return s_active.Count; } } + /// 프로브가 읽는다 — 상한 초과로 회수한 횟수 · 합산으로 흡수한 횟수. + public static int EvictedCount, MergedCount; + public static void ResetDiagnostics() { EvictedCount = MergedCount = 0; } + + bool m_wl; // 이번 표시에 813g 규칙이 적용됐는가 + bool m_crit; // 크리티컬로 표시 중인가 + float m_life, m_lifeMax; // 남은/전체 표시 시간(unscaled 아님 — 원본과 같은 Time.deltaTime 계열) + float m_rise, m_fadeStart, m_scale; + string m_color; // 합산 판정 키(같은 색끼리만 합친다 = 피해/회복/마나 섞임 방지) + double m_total; int m_hits; + + /// 이 인스턴스가 합산 중인 대상(합산 창이 살아 있는 동안만 유효). + public Transform MergeTarget { get { return m_MergeTarget; } } + Transform m_MergeTarget; + float m_mergeUntil; + + static WL.UI.WLCombatTextSettings St + { + get { return WL.UI.WLCombatTextSettings.Enabled ? WL.UI.WLCombatTextSettings.Instance : null; } + } + + /// 합산 윈도우 길이 — 데이터 SOT 는 811e(WLReactionSettings). 없으면 합산하지 않는다. + static float MergeWindow + { + get + { + var r = WL.Combat.Reaction.WLReactionSettings.Instance; + if (r == null || !r.enabled || !r.damageMergeEnabled) return 0f; + return r.damageMergeWindowSeconds; + } + } + public void Set(Transform _target, string TextColor, double Damage, eStat Critical) { if (NewGameUI.Ins.m_BangchiUI.isActiveAndEnabled || !OptionInfo.Ins.go_check[0].activeSelf) @@ -18,32 +62,198 @@ public class HUDDMGUI : HUDBase Off(true); return; } + SetCore(_target, TextColor, Damage, Critical); + } + + /// + /// 표시 본체 — 원본 `Set` 의 UI 게이트(방치UI · 옵션) 뒤 부분을 그대로 뺀 것. + /// 게이트가 `NewGameUI`·`OptionInfo` 싱글턴을 요구해 Play 없이는 못 부르므로, + /// 에디트 모드 프로브(WL-813g ⓑ)가 **런타임과 같은 코드**를 재도록 분리했다(813c 교훈). + /// + public void SetCore(Transform _target, string TextColor, double Damage, eStat Critical) + { + var st = St; + bool wl = st != null && st.damageRhythmEnabled; + + // ── 합산 윈도우: 같은 대상 · 같은 색이면 새 숫자를 띄우지 않고 살아 있는 숫자에 더한다(기준서 §B 0.25 s) + if (wl && st.damageUseMergeWindow && _target != null) + { + float win = MergeWindow; + if (win > 0f) + { + for (int i = 0; i < s_active.Count; i++) + { + var h = s_active[i]; + if (h == null || h == this) continue; + if (h.m_MergeTarget != _target || h.m_color != TextColor) continue; + if (Time.time > h.m_mergeUntil) continue; + h.Accumulate(Damage, Critical, st); + MergedCount++; + Off(true); // 이 인스턴스는 쓰지 않고 풀로 돌려보낸다 + return; + } + } + } gameObject.SetActive(true); Set_Target(_target); - Set_Pos(); - - Vector3 randomPosition = new Vector3( - Random.Range(-25f, 25f), // x 축 랜덤 범위 - Random.Range(-25f, 25f) // y 축 랜덤 범위 - ); - dmgs[0].transform.localPosition = dmgs[1].transform.localPosition = randomPosition; - //dmgs[0].transform.localPosition = dmgs[1].transform.localPosition = Vector3.zero; - dmgs[0].alpha = dmgs[1].alpha = 1f; - dmgs[0].text = dmgs[1].text = DSUtil.Format("{0}{1}", TextColor, NumberFormatter.FormatNumber(Damage)); + if (m_Target != null && m_Camera != null && m_CanvasRect != null) Set_Pos(); // 대상·카메라·캔버스가 다 있을 때만(원본은 항상 있다) usedmg2 = Critical == eStat.CRI || Critical == eStat.M_CRI; + m_Index = usedmg2 ? 1 : 0; dmgs[0].enabled = !usedmg2; dmgs[1].enabled = usedmg2; - m_Index = usedmg2 ? 1 : 0; + dmgs[0].alpha = dmgs[1].alpha = 1f; - OffTime = destroyTime; + m_wl = wl; + m_crit = usedmg2; + m_color = TextColor; + m_total = Damage; + m_hits = 1; + m_MergeTarget = _target; + + float scatter = wl ? st.damageScatterPx * (usedmg2 ? st.damageCritScatterMultiplier : 1f) : 25f; + Vector3 randomPosition = new Vector3( + Random.Range(-scatter, scatter), // x 축 랜덤 범위 + Random.Range(-scatter, scatter)); // y 축 랜덤 범위 + dmgs[0].transform.localPosition = dmgs[1].transform.localPosition = randomPosition; + + if (wl) + { + m_lifeMax = m_life = usedmg2 ? st.damageCritSeconds : st.damageNormalSeconds; + m_scale = usedmg2 ? st.damageCritScale : st.damageNormalScale; + m_rise = st.damageRiseSpeed * (usedmg2 ? st.damageCritRiseMultiplier : 1f); + m_fadeStart = Mathf.Clamp01(st.damageFadeStartRatio); + m_mergeUntil = Time.time + Mathf.Max(0f, MergeWindow); + dmgs[m_Index].transform.localScale = Vector3.one * m_scale; + OffTime = m_lifeMax; + Register(st); + } + else + { + m_lifeMax = m_life = destroyTime; + m_scale = 1f; m_rise = moveSpeed; m_fadeStart = 0f; + m_mergeUntil = 0f; + dmgs[0].transform.localScale = dmgs[1].transform.localScale = Vector3.one; + OffTime = destroyTime; + Register(null); + } + + Redraw(TextColor, st); isUpdate = false; } + /// 합산 윈도우 안의 추가 타격 — 숫자를 더하고, 크리가 섞이면 크리 규격으로 승격한다. + void Accumulate(double Damage, eStat Critical, WL.UI.WLCombatTextSettings st) + { + m_total += Damage; + m_hits++; + bool crit = Critical == eStat.CRI || Critical == eStat.M_CRI; + if (crit && !m_crit) + { + m_crit = usedmg2 = true; + var pos = dmgs[m_Index].transform.localPosition; + m_Index = 1; + dmgs[0].enabled = false; dmgs[1].enabled = true; + dmgs[1].transform.localPosition = pos; + m_scale = st.damageCritScale; + m_lifeMax = st.damageCritSeconds; + m_rise = st.damageRiseSpeed * st.damageCritRiseMultiplier; + } + m_life = m_lifeMax; // 창이 이어지는 동안 표시 시간을 되감는다 + OffTime = m_lifeMax; + dmgs[m_Index].alpha = 1f; + dmgs[m_Index].transform.localScale = Vector3.one * m_scale; + m_mergeUntil = Time.time + Mathf.Max(0f, MergeWindow); + Redraw(m_color, st); + } + + void Redraw(string TextColor, WL.UI.WLCombatTextSettings st) + { + string num = NumberFormatter.FormatNumber(m_total); + if (m_wl && st != null && m_hits >= Mathf.Max(2, st.damageMergeHitsMin) && !string.IsNullOrEmpty(st.damageMergeHitsSuffix)) + num = num + DSUtil.Format(st.damageMergeHitsSuffix, m_hits); + dmgs[0].text = dmgs[1].text = DSUtil.Format("{0}{1}", TextColor, num); + } + + // ── 동시 표시 상한 (기준서 §B 요소 2 「초당 3~5개 겹침」 방지) + void Register(WL.UI.WLCombatTextSettings st) + { + if (!s_active.Contains(this)) s_active.Add(this); + if (st == null || st.damageMaxConcurrent <= 0) return; + while (s_active.Count > st.damageMaxConcurrent) + { + int victim = -1; + if (st.damageEvictNormalFirst) + for (int i = 0; i < s_active.Count; i++) + if (s_active[i] != null && s_active[i] != this && !s_active[i].m_crit) { victim = i; break; } + if (victim < 0) + for (int i = 0; i < s_active.Count; i++) + if (s_active[i] != null && s_active[i] != this) { victim = i; break; } + if (victim < 0) break; + var h = s_active[victim]; + EvictedCount++; + h.Off(true); // OnDisable 이 목록에서 뺀다 + if (s_active.Count > victim && s_active[victim] == h) s_active.RemoveAt(victim); + } + } + + void OnDisable() + { + s_active.Remove(this); + m_MergeTarget = null; + m_mergeUntil = 0f; + if (dmgs != null) + { + if (dmgs.Length > 0 && dmgs[0] != null) dmgs[0].transform.localScale = Vector3.one; + if (dmgs.Length > 1 && dmgs[1] != null) dmgs[1].transform.localScale = Vector3.one; + } + } + + /// 프로브(에디트 모드)용 상태 덤프 — 실측 값만. + public string DumpState() + { + var t = dmgs[m_Index].transform; + return "wl=" + m_wl + " crit=" + m_crit + " hits=" + m_hits + " total=" + m_total.ToString("N0") + + " life=" + m_life.ToString("F2") + "/" + m_lifeMax.ToString("F2") + + " scale=" + t.localScale.x.ToString("F3") + " rise=" + m_rise.ToString("F2") + + " fadeStart=" + m_fadeStart.ToString("F2") + " alpha=" + dmgs[m_Index].alpha.ToString("F2") + + " idx=" + m_Index + " enabled0/1=" + dmgs[0].enabled + "/" + dmgs[1].enabled + + " localPos=" + t.localPosition + " text=\"" + dmgs[m_Index].text + "\""; + } + + /// 프로브용 — 지금 설정이 만들어 내는 리듬 값(코드 상수 0 확인용). + public static string SettingsDump() + { + var st = St; + if (st == null) return "WLCombatTextSettings 없음/off — 원본 상수(0.9 s · 배율 1 · 상한 없음)로 동작"; + return "rhythm=" + st.damageRhythmEnabled + " normal=" + st.damageNormalSeconds + "s crit=" + st.damageCritSeconds + + "s critScale=" + st.damageCritScale + " normalScale=" + st.damageNormalScale + + " maxConcurrent=" + st.damageMaxConcurrent + " evictNormalFirst=" + st.damageEvictNormalFirst + + " mergeWindow=" + MergeWindow.ToString("F2") + "s(811e) useMerge=" + st.damageUseMergeWindow + + " scatterPx=" + st.damageScatterPx + " critScatterMul=" + st.damageCritScatterMultiplier; + } + + /// 프로브용 — 활성 목록을 비운다(에디트 모드 반복 실행 대비). + public static void ClearActiveList() { s_active.Clear(); EvictedCount = MergedCount = 0; } + private void LateUpdate() { - dmgs[m_Index].transform.Translate(new Vector3(0, moveSpeed * Time.deltaTime, 0)); // 텍스트 위치 - dmgs[m_Index].alpha = Mathf.Lerp(dmgs[m_Index].alpha, 0, Time.deltaTime * alphaSpeed); // 텍스트 알파값 + var t = dmgs[m_Index].transform; + if (!m_wl) + { // ── 원본 경로 그대로 + t.Translate(new Vector3(0, moveSpeed * Time.deltaTime, 0)); // 텍스트 위치 + dmgs[m_Index].alpha = Mathf.Lerp(dmgs[m_Index].alpha, 0, Time.deltaTime * alphaSpeed); // 텍스트 알파값 + return; + } + + m_life -= Time.deltaTime; + t.Translate(new Vector3(0, m_rise * Time.deltaTime, 0)); + float passed = m_lifeMax > 0f ? 1f - Mathf.Clamp01(m_life / m_lifeMax) : 1f; + if (passed >= m_fadeStart) + { + float k = m_fadeStart >= 1f ? 1f : (passed - m_fadeStart) / (1f - m_fadeStart); + dmgs[m_Index].alpha = 1f - Mathf.Clamp01(k); + } } -} \ No newline at end of file +} diff --git a/Assets/WL/UI/Scripts/KillChainText.cs b/Assets/WL/UI/Scripts/KillChainText.cs new file mode 100644 index 000000000..bdf07df2d --- /dev/null +++ b/Assets/WL/UI/Scripts/KillChainText.cs @@ -0,0 +1,230 @@ +// ───────────────────────────────────────────────────────────────────────────── +// KillChainText.cs — 연쇄 처치 문구 표시 (WL-813g §1-1 · #813) +// +// 기준서 v1 §B 요소 2 「처치 연출 · 연쇄 처치 3 s 윈도우 2/3/5/8킬」 · 발주서 §1-1 +// 「811e KillChain 티어 이벤트(DOUBLE/TRIPLE/RAMPAGE) 구독 → 화면 중앙 상단 0.8 s 팝(스케일 펀치)」. +// +// ■ 데이터/표시 분리 +// 카운터·윈도우·티어 표는 전부 811e(`WL.Combat.Reaction.KillChain` + `WLReactionSettings.tiers`)가 갖는다. +// 이 파일은 `KillChain.TierReached` 를 **구독만** 하고 문구를 띄운다(원본 훅 0 · Gameplay 코드 0). +// +// ■ 문구 톤 — PD Q5 전까지 임시 영문(발주서 §1-1). 형식·색·크기는 전부 WLCombatTextSettings 에셋. +// +// ■ Safe Area (813c 규칙) +// 이 컴포넌트는 이미 SafeAreaFitter 가 붙은 `IngameUIs/WL_HUD` 아래에 놓인다 → +// 부모가 안전 영역이므로 여기서는 **부모 기준 중앙 상단 오프셋**만 계산한다(Screen.safeArea 재계산 0). +// +// ■ 롤백(C8) +// WLCombatTextSettings 가 없거나 textEnabled/killChainTextEnabled = false 면 구독조차 하지 않는다(표시 0). +// +// 🔴 어셈블리 주의: Assets/WL/UI/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일). +// ───────────────────────────────────────────────────────────────────────────── + +using System.Text; +using TMPro; +using UnityEngine; +using WL.Combat.Reaction; + +namespace WL.UI +{ + [DisallowMultipleComponent] + [RequireComponent(typeof(RectTransform))] + public class KillChainText : MonoBehaviour + { + [Header("구성 요소 (BuildIfNeeded 가 만든다)")] + [SerializeField] private CanvasGroup group; + [SerializeField] private RectTransform box; + [SerializeField] private TextMeshProUGUI mainLabel; + [SerializeField] private TextMeshProUGUI subLabel; + [SerializeField] private TMP_FontAsset font; + + private RectTransform _rt; + private float _life, _lifeMax, _elapsed; + private bool _subscribed; + + // ── 진단(프로브가 읽는다 · 실측만) + public int TierSeen { get; private set; } + public int LastTier { get; private set; } + public int LastKills { get; private set; } + public string LastText { get; private set; } + public bool Visible { get { return group != null && group.alpha > 0f; } } + public bool Subscribed { get { return _subscribed; } } + public float LastScale { get; private set; } + public Vector2 LastAnchoredPos { get; private set; } + + private void OnEnable() { Initialize(); } + private void OnDisable() { Subscribe(false); } + + /// 에디트 모드에서도 런타임과 같은 경로를 타게 OnEnable 본체를 분리한다(813c 교훈). + public string Initialize() + { + _rt = GetComponent(); + BuildIfNeeded(); + string layout = ApplyLayout(); + Hide(); + var s = WLCombatTextSettings.Instance; + bool on = WLCombatTextSettings.Enabled && s != null && s.killChainTextEnabled; + Subscribe(on); + return (on ? "구독 ON · " : "설정 off — 구독 0 · ") + layout; + } + + public void Subscribe(bool on) + { + if (on == _subscribed) return; + if (on) KillChain.TierReached.Add(OnTier); + else KillChain.TierReached.Remove(OnTier); + _subscribed = on; + } + + /// 필요한 자식(박스 · 본문 · 부제)을 만든다. 이미 있으면 그대로 쓴다. + public bool BuildIfNeeded() + { + if (_rt == null) _rt = GetComponent(); + bool made = false; + if (group == null) + { + group = GetComponent(); + if (group == null) { group = gameObject.AddComponent(); made = true; } + group.interactable = false; group.blocksRaycasts = false; + } + if (box == null) { box = WLTextFxUtil.NewChild(_rt, "Box"); made = true; } + if (mainLabel == null) { mainLabel = WLTextFxUtil.NewText(box, "Main", font, TextAlignmentOptions.Center); made = true; } + if (subLabel == null) { subLabel = WLTextFxUtil.NewText(box, "Sub", font, TextAlignmentOptions.Center); made = true; } + return made; + } + + /// 설정 값대로 위치·크기를 잡는다(px → 유닛 환산은 캔버스에서 구한다 · 상수 0). + public string ApplyLayout() + { + var s = WLCombatTextSettings.Instance; + if (s == null) return "WLCombatTextSettings 없음 — 배치 건너뜀"; + if (_rt == null) _rt = GetComponent(); + if (box == null) return "구성 요소 없음 — BuildIfNeeded 먼저"; + + float u = WLTextFxUtil.UnitsPerPx(this, s); + + // 루트는 부모(Safe Area 레이어)를 가득 채운다 — 좌표 계산의 기준이 안전 영역이 되게. + WLTextFxUtil.Stretch(_rt); + + // 박스 = 화면 중앙 기준으로 위로 killChainTopOffsetPx. + box.anchorMin = box.anchorMax = new Vector2(0.5f, 0.5f); + box.pivot = new Vector2(0.5f, 0.5f); + box.sizeDelta = new Vector2(s.designWidthPx * u, (s.killChainFontPx * 2.4f) * u); + box.anchoredPosition = new Vector2(0f, s.killChainTopOffsetPx * u); + LastAnchoredPos = box.anchoredPosition; + + mainLabel.rectTransform.anchorMin = new Vector2(0f, 0.5f); + mainLabel.rectTransform.anchorMax = new Vector2(1f, 1f); + mainLabel.rectTransform.offsetMin = Vector2.zero; + mainLabel.rectTransform.offsetMax = Vector2.zero; + mainLabel.fontSize = s.killChainFontPx * u; + mainLabel.alignment = TextAlignmentOptions.Bottom; + + subLabel.rectTransform.anchorMin = new Vector2(0f, 0f); + subLabel.rectTransform.anchorMax = new Vector2(1f, 0.5f); + subLabel.rectTransform.offsetMin = Vector2.zero; + subLabel.rectTransform.offsetMax = Vector2.zero; + subLabel.fontSize = s.killChainFontPx * u; + subLabel.alignment = TextAlignmentOptions.Top; + + return "box pos=" + box.anchoredPosition + " size=" + box.sizeDelta + " unitsPerPx=" + u.ToString("F4") + + " fontPx=" + s.killChainFontPx + "→" + mainLabel.fontSize.ToString("F1"); + } + + // ───────────────────────────────────────────── 이벤트 + + private void OnTier(in KillChainTierEvent e) + { + var s = WLCombatTextSettings.Instance; + if (s == null || !WLCombatTextSettings.Enabled || !s.killChainTextEnabled) return; + Show(e.tier, e.name, e.kills, s); + } + + /// 티어 문구를 띄운다(합성 이벤트·프로브도 이 경로를 쓴다). + public string Show(int tier, string tierName, int kills, WLCombatTextSettings s) + { + if (s == null) s = WLCombatTextSettings.Instance; + if (s == null) return "설정 없음"; + BuildIfNeeded(); + ApplyLayout(); + + TierSeen++; LastTier = tier; LastKills = kills; + string main = string.IsNullOrEmpty(s.killChainFormat) ? tierName : DSUtil.Format(s.killChainFormat, tierName, kills); + string sub = string.IsNullOrEmpty(s.killChainSubFormat) ? "" : DSUtil.Format(s.killChainSubFormat, kills); + mainLabel.text = main; + subLabel.text = sub; + LastText = main + (string.IsNullOrEmpty(sub) ? "" : " / " + sub); + + var c = s.TierColor(tier); + mainLabel.color = c; + subLabel.color = new Color(c.r, c.g, c.b, c.a * 0.8f); + + LastScale = s.TierScale(tier); + _lifeMax = _life = Mathf.Max(0.01f, s.killChainSeconds); + _elapsed = 0f; + group.alpha = 1f; + box.localScale = Vector3.one * (LastScale * WLTextFxUtil.PunchScale(0f, s.killChainPunchScale, s.killChainPunchSeconds)); + enabled = true; + return "tier=" + tier + " name=" + tierName + " kills=" + kills + " text=\"" + LastText + + "\" scale=" + box.localScale.x.ToString("F3") + " pos=" + box.anchoredPosition + " alpha=" + group.alpha; + } + + public void Hide() + { + if (group != null) group.alpha = 0f; + if (box != null) box.localScale = Vector3.one; + _life = 0f; + } + + private void Update() + { + if (_life <= 0f) return; + var s = WLCombatTextSettings.Instance; + if (s == null) { Hide(); return; } + + float dt = Time.unscaledDeltaTime; // 히트스톱(TimeScaleArbiter) 중에도 문구는 흐른다 + _life -= dt; _elapsed += dt; + + float passed = WLTextFxUtil.Passed(_life, _lifeMax); + group.alpha = WLTextFxUtil.FadeAlpha(passed, s.killChainFadeStartRatio); + box.localScale = Vector3.one * (LastScale * WLTextFxUtil.PunchScale(_elapsed, s.killChainPunchScale, s.killChainPunchSeconds)); + if (_life <= 0f) Hide(); + } + + // ───────────────────────────────────────────── 프로브(에디트 모드 · Play 0) + + /// 합성 티어 이벤트 — 811e 의 실제 Dispatch 경로로 보낸다(구독 배선까지 함께 검증). + public static string RaiseFakeTier(int tier, string name, int kills) + { + var e = new KillChainTierEvent + { + tier = tier, name = name, kills = kills, centroid = Vector3.zero, + lastVictim = null, burstSpawned = false, time = Time.unscaledTime, frame = Time.frameCount + }; + KillChain.TierReached.Dispatch(in e); + return "dispatch tier=" + tier + " name=" + name + " kills=" + kills + + " 구독자=" + KillChain.TierReached.Count; + } + + public string Dump() + { + var sb = new StringBuilder(); + sb.AppendLine("KillChainText subscribed=" + _subscribed + " tierSeen=" + TierSeen + + " lastTier=" + LastTier + " lastKills=" + LastKills); + sb.AppendLine(" text=\"" + LastText + "\" alpha=" + (group != null ? group.alpha.ToString("F2") : "-") + + " scale=" + (box != null ? box.localScale.x.ToString("F3") : "-") + + " pos=" + (box != null ? box.anchoredPosition.ToString() : "-") + + " size=" + (box != null ? box.sizeDelta.ToString() : "-")); + sb.AppendLine(" fontSize=" + (mainLabel != null ? mainLabel.fontSize.ToString("F1") : "-") + + " color=" + (mainLabel != null ? ColorUtility.ToHtmlStringRGB(mainLabel.color) : "-")); + return sb.ToString(); + } + + public void SetFont(TMP_FontAsset f) + { + font = f; + if (mainLabel != null && f != null) mainLabel.font = f; + if (subLabel != null && f != null) subLabel.font = f; + } + } +} diff --git a/Assets/WL/UI/Scripts/KillChainText.cs.meta b/Assets/WL/UI/Scripts/KillChainText.cs.meta new file mode 100644 index 000000000..674553f31 --- /dev/null +++ b/Assets/WL/UI/Scripts/KillChainText.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 649e686492f34f444a5a77f815f22b9a \ No newline at end of file diff --git a/Assets/WL/UI/Scripts/LootToast.cs b/Assets/WL/UI/Scripts/LootToast.cs new file mode 100644 index 000000000..637a2ce85 --- /dev/null +++ b/Assets/WL/UI/Scripts/LootToast.cs @@ -0,0 +1,351 @@ +// ───────────────────────────────────────────────────────────────────────────── +// LootToast.cs — 전리품 획득 토스트 (WL-813g §1-3 · #813) +// +// 기준서 v1 §B 요소 4 「토스트 1.2 s · 동시 3줄 · 등급 색(보유 팔레트 그대로)」 · §C 요소 4 「토스트 없음 → 813g(U)」. +// +// ■ 입력 경로 (지금 / 나중) +// · 지금 = `CombatEvents.Killed`(811b 코어) 로 "뭔가 죽었다"를 알고, 그 뒤 `DropItemInfo.m_StageDropData` +// 획득 집계의 **델타**를 폴링해 실제로 먹은 아이템만 줄로 띄운다(원본 훅 0 · Gameplay 코드 0). +// 집계는 `DropItem.cs:125 Add_Item` 이 채우는 값이라 "떨어진 것"이 아니라 "**주운 것**"이다. +// · 나중 = 813f 가 드랍/획득 이벤트를 내면 `Push(itemId, count)` 로 바로 밀어 넣고 폴링을 걷어낸다(후속). +// +// ■ 등급 색 = `MyValue.Get_GradeColor`(원본 팔레트) 를 그대로 쓴다 — 기준서 §B 요소 4 「새 색 만들 이유 없음」. +// 설정 에셋의 lootToastGradeColors 를 채우면 그 값이 우선한다(SO 이관 대비). +// +// ■ Safe Area (813c 규칙) — 부모(`IngameUIs/WL_HUD`)가 이미 SafeAreaFitter 아래다. 여기서는 부모 기준 우측 정렬만 한다. +// +// ■ 롤백(C8) — 설정이 없거나 textEnabled/lootToastEnabled = false 면 구독·폴링 0 · 표시 0. +// +// 🔴 어셈블리 주의: Assets/WL/UI/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일). +// ───────────────────────────────────────────────────────────────────────────── + +using System.Collections.Generic; +using System.Text; +using TMPro; +using UnityEngine; +using WL.Combat.Core; + +namespace WL.UI +{ + [DisallowMultipleComponent] + [RequireComponent(typeof(RectTransform))] + public class LootToast : MonoBehaviour + { + [Header("구성 요소 (BuildIfNeeded 가 만든다)")] + [SerializeField] private RectTransform lineRoot; + [SerializeField] private TMP_FontAsset font; + + private class Line + { + public RectTransform rt; + public TextMeshProUGUI label; + public CanvasGroup group; + public int itemId, count; + public float life, lifeMax, elapsed; + public bool used; + } + + private readonly List _lines = new List(); + private readonly Dictionary _seen = new Dictionary(); // itemId → 마지막으로 본 누적 수량 + private RectTransform _rt; + private bool _subscribed; + private float _nextPoll; + private bool _armed; // Killed 를 한 번이라도 본 뒤부터 폴링한다(로비에서 헛돌지 않게) + + // ── 진단(프로브가 읽는다 · 실측만) + public int KilledSeen { get; private set; } + public int PushCount { get; private set; } + public int MergeCount { get; private set; } + public int DropCount { get; private set; } // 줄 상한 초과로 밀려난 수 + public bool Subscribed { get { return _subscribed; } } + public int VisibleLines + { + get { int n = 0; for (int i = 0; i < _lines.Count; i++) if (_lines[i].used) n++; return n; } + } + + private void OnEnable() { Initialize(); } + private void OnDisable() { Subscribe(false); } + + /// OnEnable 본체 분리(813c 교훈) — 에디트 모드도 같은 경로. + public string Initialize() + { + _rt = GetComponent(); + BuildIfNeeded(); + string layout = ApplyLayout(); + HideAll(); + var s = WLCombatTextSettings.Instance; + bool on = WLCombatTextSettings.Enabled && s != null && s.lootToastEnabled; + Subscribe(on); + return (on ? "구독 ON · " : "설정 off — 구독 0 · ") + layout; + } + + public void Subscribe(bool on) + { + if (on == _subscribed) return; + if (on) CombatEvents.Killed.Add(OnKilled); + else CombatEvents.Killed.Remove(OnKilled); + _subscribed = on; + } + + /// 설정의 줄 수만큼 라벨을 만든다(줄 수를 바꾸면 다시 만든다). + public bool BuildIfNeeded() + { + if (_rt == null) _rt = GetComponent(); + var s = WLCombatTextSettings.Instance; + int want = s != null ? Mathf.Max(1, s.lootToastLines) : 3; + bool made = false; + if (lineRoot == null) { lineRoot = WLTextFxUtil.NewChild(_rt, "Lines"); made = true; } + + while (_lines.Count < want) + { + int idx = _lines.Count; + var lrt = WLTextFxUtil.NewChild(lineRoot, "Line" + idx); + var label = WLTextFxUtil.NewText(lrt, "Label", font, TextAlignmentOptions.Right); + var cg = lrt.GetComponent(); + if (cg == null) cg = lrt.gameObject.AddComponent(); + cg.interactable = false; cg.blocksRaycasts = false; cg.alpha = 0f; + _lines.Add(new Line { rt = lrt, label = label, group = cg }); + made = true; + } + for (int i = want; i < _lines.Count; i++) + if (_lines[i].rt != null) _lines[i].group.alpha = 0f; // 남는 줄은 숨기기만(파괴하지 않는다) + return made; + } + + /// 우측 정렬 · 줄 간격 · 글자 크기 (px → 유닛 환산은 캔버스에서 · 상수 0). + public string ApplyLayout() + { + var s = WLCombatTextSettings.Instance; + if (s == null) return "WLCombatTextSettings 없음 — 배치 건너뜀"; + if (_rt == null) _rt = GetComponent(); + if (lineRoot == null) return "구성 요소 없음 — BuildIfNeeded 먼저"; + + float u = WLTextFxUtil.UnitsPerPx(this, s); + WLTextFxUtil.Stretch(_rt); + + lineRoot.anchorMin = lineRoot.anchorMax = new Vector2(1f, 0.5f); // 우측 · 세로 중앙 기준 + lineRoot.pivot = new Vector2(1f, 0.5f); + lineRoot.sizeDelta = new Vector2(s.lootToastWidthPx * u, s.lootToastLineHeightPx * s.lootToastLines * u); + lineRoot.anchoredPosition = new Vector2(-s.lootToastRightMarginPx * u, s.lootToastCenterOffsetPx * u); + + var sb = new StringBuilder(); + sb.Append("lines=").Append(_lines.Count).Append(" root pos=").Append(lineRoot.anchoredPosition) + .Append(" size=").Append(lineRoot.sizeDelta).Append(" unitsPerPx=").Append(u.ToString("F4")); + for (int i = 0; i < _lines.Count; i++) + { + var l = _lines[i]; + l.rt.anchorMin = l.rt.anchorMax = new Vector2(1f, 1f); + l.rt.pivot = new Vector2(1f, 1f); + l.rt.sizeDelta = new Vector2(s.lootToastWidthPx * u, s.lootToastLineHeightPx * u); + l.rt.anchoredPosition = new Vector2(0f, -s.lootToastLineHeightPx * i * u); + WLTextFxUtil.Stretch(l.label.rectTransform); + l.label.fontSize = s.lootToastFontPx * u; + l.label.alignment = TextAlignmentOptions.MidlineRight; + sb.Append(" · [").Append(i).Append("] pos=").Append(l.rt.anchoredPosition); + } + return sb.ToString(); + } + + // ───────────────────────────────────────────── 입력 + + private void OnKilled(in KilledEvent e) + { + KilledSeen++; + _armed = true; // 처치가 있어야 드랍이 있다 — 그 뒤부터만 획득 집계를 본다 + } + + /// 813f(후속) · 프로브 공용 입력 — 아이템 1건 획득을 줄로 띄운다. + public string Push(int itemId, int count) + { + var s = WLCombatTextSettings.Instance; + if (s == null || !WLCombatTextSettings.Enabled || !s.lootToastEnabled) return "설정 off — 표시 0"; + if (!s.lootToastIncludeGold && itemId == GoldItemId) return "골드 제외(설정) — 표시 0"; + + int grade = 1; + string name = itemId.ToString(); + // 표가 아직 안 올라온 시점(에디트 모드·로딩 중)에도 죽지 않게 감싼다 — 이름/등급이 없으면 ID·등급 1 로 뜬다. + try + { + var data = table_itemlist.Ins != null ? table_itemlist.Ins.Get_Data(itemId) : null; + if (data != null) + { + grade = Mathf.Max(1, data.n_ItemGrade); + var n = data.Get_Name(); + if (!string.IsNullOrEmpty(n)) name = n; + } + } + catch { /* 표 미로드 · ID 없음 — 기본값으로 표시 */ } + if (grade < s.lootToastMinGrade) return "등급 " + grade + " < 최소 " + s.lootToastMinGrade + " — 표시 0"; + + BuildIfNeeded(); + + // 같은 아이템이 병합 창 안이면 기존 줄의 수량만 올린다(줄 폭주 방지) + for (int i = 0; i < _lines.Count; i++) + { + var l = _lines[i]; + if (!l.used || l.itemId != itemId) continue; + if (l.elapsed > s.lootToastMergeSeconds) continue; + l.count += count; + l.life = l.lifeMax; l.elapsed = 0f; + Render(l, name, grade, s); + MergeCount++; + return "merge item=" + itemId + " count=" + l.count + " line=" + i; + } + + int slot = TakeSlot(s); + var line = _lines[slot]; + line.used = true; line.itemId = itemId; line.count = count; + line.lifeMax = line.life = Mathf.Max(0.01f, s.lootToastSeconds); + line.elapsed = 0f; + line.group.alpha = 1f; + Render(line, name, grade, s); + PushCount++; + return "push item=" + itemId + " \"" + name + "\" grade=" + grade + " count=" + count + " line=" + slot; + } + + /// 빈 줄을 찾는다. 없으면 가장 오래된 줄을 밀어내고(위로 한 칸씩 당김) 마지막 자리를 준다. + private int TakeSlot(WLCombatTextSettings s) + { + int n = Mathf.Min(_lines.Count, Mathf.Max(1, s.lootToastLines)); + for (int i = 0; i < n; i++) if (!_lines[i].used) return i; + + // 전부 차 있음 — 0번(가장 오래된 줄)을 버리고 한 칸씩 올린다(내용만 옮기고 RectTransform 은 그대로). + DropCount++; + for (int i = 0; i < n - 1; i++) + { + var a = _lines[i]; var b = _lines[i + 1]; + a.itemId = b.itemId; a.count = b.count; a.life = b.life; a.lifeMax = b.lifeMax; a.elapsed = b.elapsed; + a.used = b.used; a.label.text = b.label.text; a.label.color = b.label.color; a.group.alpha = b.group.alpha; + } + _lines[n - 1].used = false; + return n - 1; + } + + private void Render(Line l, string name, int grade, WLCombatTextSettings s) + { + string colored = s.GradeColorTag(grade) + name; + l.label.text = l.count > 1 + ? DSUtil.Format(s.lootToastFormat, colored, l.count) + : DSUtil.Format(s.lootToastFormatSingle, colored); + l.label.color = Color.white; // 등급색은 리치텍스트 태그가 담당한다(원본 팔레트 그대로) + } + + public void HideAll() + { + for (int i = 0; i < _lines.Count; i++) + { + _lines[i].used = false; + _lines[i].life = 0f; + if (_lines[i].group != null) _lines[i].group.alpha = 0f; + if (_lines[i].label != null) _lines[i].label.text = ""; + } + } + + // ───────────────────────────────────────────── 폴링 · 수명 + + /// 골드 아이템 ID — 원본 DropItemInfo 의 코인 분기 키(`DropItemInfo.cs:12` dic_addrPath[2]). + public const int GoldItemId = 2; + + private void Update() + { + var s = WLCombatTextSettings.Instance; + if (s == null) return; + + // ── 획득 집계 폴링(813f 이벤트가 오면 걷어낸다) + if (_armed && Application.isPlaying && s.lootToastPollHz > 0f && Time.unscaledTime >= _nextPoll) + { + _nextPoll = Time.unscaledTime + 1f / Mathf.Max(0.1f, s.lootToastPollHz); + PollStageDrop(); + } + + // ── 줄 수명 + float dt = Time.unscaledDeltaTime; + float u = WLTextFxUtil.UnitsPerPx(this, s); + for (int i = 0; i < _lines.Count; i++) + { + var l = _lines[i]; + if (!l.used) continue; + l.life -= dt; l.elapsed += dt; + float passed = WLTextFxUtil.Passed(l.life, l.lifeMax); + l.group.alpha = WLTextFxUtil.FadeAlpha(passed, s.lootToastFadeStartRatio); + if (s.lootToastSlideInPx > 0f && s.lootToastSlideSeconds > 0f) + { + float k = Mathf.Clamp01(l.elapsed / s.lootToastSlideSeconds); + float x = Mathf.Lerp(s.lootToastSlideInPx, 0f, k) * u; + l.rt.anchoredPosition = new Vector2(x, -s.lootToastLineHeightPx * i * u); + } + if (l.life <= 0f) { l.used = false; l.group.alpha = 0f; l.label.text = ""; } + } + } + + /// StageDropData 의 아이템 누적 수량 델타 = 이번에 주운 것. + public string PollStageDrop() + { + if (!DropItemInfo.isIns || DropItemInfo.Ins == null) return "DropItemInfo 없음"; + var data = DropItemInfo.Ins.m_StageDropData; + if (data == null) return "StageDropData 없음"; + var dic = data.Get_Item(); + if (dic == null) return "집계 없음"; + + var sb = new StringBuilder(); + foreach (var kv in dic) + { + int id = kv.Key; + int now = kv.Value; + int last; + if (!_seen.TryGetValue(id, out last)) last = 0; + if (now > last) + { + DropCountedItems++; + sb.Append(Push(id, now - last)).Append(" | "); + } + _seen[id] = now; + } + return sb.Length > 0 ? sb.ToString() : "델타 0"; + } + + /// 진단 — 폴링으로 잡아낸 획득 건수. + public int DropCountedItems { get; private set; } + + /// 스테이지가 바뀌면 집계 기준을 초기화한다(StageDropData.Init 과 짝). + public void ResetPollBaseline() { _seen.Clear(); _armed = false; DropCountedItems = 0; } + + // ───────────────────────────────────────────── 프로브(에디트 모드 · Play 0) + + /// 합성 처치 이벤트 — 811b 코어의 실제 Dispatch 경로(구독 배선까지 검증). + public static string RaiseFakeKilled() + { + var e = new KilledEvent + { + victim = null, killer = null, subRole = eSubRol.None, id = 0, + position = Vector3.zero, byDirectHit = false, time = Time.unscaledTime, frame = Time.frameCount + }; + CombatEvents.Killed.Dispatch(in e); + return "dispatch Killed · 구독자=" + CombatEvents.Killed.Count; + } + + public string Dump() + { + var sb = new StringBuilder(); + sb.AppendLine("LootToast subscribed=" + _subscribed + " killedSeen=" + KilledSeen + + " push=" + PushCount + " merge=" + MergeCount + " drop=" + DropCount + + " visible=" + VisibleLines); + for (int i = 0; i < _lines.Count; i++) + { + var l = _lines[i]; + sb.AppendLine(" [" + i + "] used=" + l.used + " item=" + l.itemId + " x" + l.count + + " alpha=" + l.group.alpha.ToString("F2") + " pos=" + l.rt.anchoredPosition + + " size=" + l.rt.sizeDelta + " font=" + l.label.fontSize.ToString("F1") + + " text=\"" + l.label.text + "\""); + } + return sb.ToString(); + } + + public void SetFont(TMP_FontAsset f) + { + font = f; + for (int i = 0; i < _lines.Count; i++) + if (_lines[i].label != null && f != null) _lines[i].label.font = f; + } + } +} diff --git a/Assets/WL/UI/Scripts/LootToast.cs.meta b/Assets/WL/UI/Scripts/LootToast.cs.meta new file mode 100644 index 000000000..6af07564f --- /dev/null +++ b/Assets/WL/UI/Scripts/LootToast.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 169f8694dff47c6409dc8af946ea2dcf \ No newline at end of file diff --git a/Assets/WL/UI/Scripts/StatPopup.cs b/Assets/WL/UI/Scripts/StatPopup.cs new file mode 100644 index 000000000..f04d46b8f --- /dev/null +++ b/Assets/WL/UI/Scripts/StatPopup.cs @@ -0,0 +1,297 @@ +// ───────────────────────────────────────────────────────────────────────────── +// StatPopup.cs — 레벨업 스탯 팝업 (WL-813g §4 · #813) +// +// 기준서 v1 §B 요소 5 「레벨업 = FX + HP/MP 전회복 + **스탯 팝업 3개**」 · §C 요소 5 「이벤트 0 → 813k(S)」. +// +// 🔴 **현재 상태 = 인터페이스 + 표시만 (발생원은 후속)** +// · `WL.Combat.Core.CombatEvents.LevelUp` 는 811b 가 **API 만** 만들어 두었고 발생 지점 +// (`ServerClass.Add_Exp :2729-2734` · Systems 소유)은 **813k** 몫이다 — 이 세션 시점에 +// `wl/gameplay/WL-813k-levelup` 브랜치에는 실작업 커밋이 없다(`git diff main...` 비어 있음 · 실측). +// · 그래서 페이로드는 지금 `{pc, newLevel}` 뿐이다. 이전/새 스탯 3개를 813k 가 실어 보내면 +// **`StatPopup.Report(...)` 로 그대로 밀어 넣으면 된다**(아래 공개 API · 이 파일만 수정하면 끝). +// · 그 전까지는 이 컴포넌트가 스스로 **직전 스냅샷과의 차이**로 스탯 증가분을 만든다(대체 경로). +// +// ■ 롤백(C8) — 설정이 없거나 textEnabled/statPopupEnabled = false 면 구독 0 · 표시 0. +// +// 🔴 어셈블리 주의: Assets/WL/UI/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일). +// ───────────────────────────────────────────────────────────────────────────── + +using System.Collections.Generic; +using System.Text; +using TMPro; +using UnityEngine; +using WL.Combat.Core; + +namespace WL.UI +{ + /// 스탯 한 줄 — 813k 가 이전/새 값을 실어 보낼 때 쓰는 페이로드. + public struct WLStatDelta + { + public string name; + public double prev; + public double next; + public double Delta { get { return next - prev; } } + } + + [DisallowMultipleComponent] + [RequireComponent(typeof(RectTransform))] + public class StatPopup : MonoBehaviour + { + [Header("구성 요소 (BuildIfNeeded 가 만든다)")] + [SerializeField] private CanvasGroup group; + [SerializeField] private RectTransform box; + [SerializeField] private TextMeshProUGUI titleLabel; + [SerializeField] private TMP_FontAsset font; + + private readonly List _rows = new List(); + private readonly Dictionary _snapshot = new Dictionary(); + private RectTransform _rt; + private bool _subscribed; + private float _life, _lifeMax, _elapsed; + + /// 813k(또는 다른 발생원)가 찾아 쓸 수 있는 현재 활성 팝업. + public static StatPopup Active { get; private set; } + + // ── 진단(프로브가 읽는다 · 실측만) + public int LevelUpSeen { get; private set; } + public int LastLevel { get; private set; } + public int LastRowCount { get; private set; } + public string LastText { get; private set; } + public bool Subscribed { get { return _subscribed; } } + public bool Visible { get { return group != null && group.alpha > 0f; } } + + private void OnEnable() { Initialize(); } + private void OnDisable() { Subscribe(false); if (Active == this) Active = null; } + + /// OnEnable 본체 분리(813c 교훈) — 에디트 모드도 같은 경로. + public string Initialize() + { + _rt = GetComponent(); + Active = this; + BuildIfNeeded(); + string layout = ApplyLayout(); + Hide(); + var s = WLCombatTextSettings.Instance; + bool on = WLCombatTextSettings.Enabled && s != null && s.statPopupEnabled; + Subscribe(on); + TakeSnapshot(); + return (on ? "구독 ON · " : "설정 off — 구독 0 · ") + layout; + } + + public void Subscribe(bool on) + { + if (on == _subscribed) return; + if (on) CombatEvents.LevelUp.Add(OnLevelUp); + else CombatEvents.LevelUp.Remove(OnLevelUp); + _subscribed = on; + } + + public bool BuildIfNeeded() + { + if (_rt == null) _rt = GetComponent(); + var s = WLCombatTextSettings.Instance; + int want = s != null ? Mathf.Max(1, s.statPopupRows) : 3; + bool made = false; + + if (group == null) + { + group = GetComponent(); + if (group == null) { group = gameObject.AddComponent(); made = true; } + group.interactable = false; group.blocksRaycasts = false; + } + if (box == null) { box = WLTextFxUtil.NewChild(_rt, "Box"); made = true; } + if (titleLabel == null) { titleLabel = WLTextFxUtil.NewText(box, "Title", font, TextAlignmentOptions.Center); made = true; } + while (_rows.Count < want) + { + _rows.Add(WLTextFxUtil.NewText(box, "Row" + _rows.Count, font, TextAlignmentOptions.Center)); + made = true; + } + return made; + } + + public string ApplyLayout() + { + var s = WLCombatTextSettings.Instance; + if (s == null) return "WLCombatTextSettings 없음 — 배치 건너뜀"; + if (_rt == null) _rt = GetComponent(); + if (box == null) return "구성 요소 없음 — BuildIfNeeded 먼저"; + + float u = WLTextFxUtil.UnitsPerPx(this, s); + WLTextFxUtil.Stretch(_rt); + + float h = (s.statPopupTitleFontPx + s.statPopupRowHeightPx * s.statPopupRows) * u; + box.anchorMin = box.anchorMax = new Vector2(0.5f, 0.5f); + box.pivot = new Vector2(0.5f, 0.5f); + box.sizeDelta = new Vector2(s.designWidthPx * u, h); + box.anchoredPosition = new Vector2(0f, s.statPopupCenterOffsetPx * u); + + titleLabel.rectTransform.anchorMin = titleLabel.rectTransform.anchorMax = new Vector2(0.5f, 1f); + titleLabel.rectTransform.pivot = new Vector2(0.5f, 1f); + titleLabel.rectTransform.sizeDelta = new Vector2(s.designWidthPx * u, s.statPopupTitleFontPx * 1.2f * u); + titleLabel.rectTransform.anchoredPosition = Vector2.zero; + titleLabel.fontSize = s.statPopupTitleFontPx * u; + titleLabel.color = s.statPopupTitleColor; + + for (int i = 0; i < _rows.Count; i++) + { + var rt = _rows[i].rectTransform; + rt.anchorMin = rt.anchorMax = new Vector2(0.5f, 1f); + rt.pivot = new Vector2(0.5f, 1f); + rt.sizeDelta = new Vector2(s.designWidthPx * u, s.statPopupRowHeightPx * u); + rt.anchoredPosition = new Vector2(0f, -(s.statPopupTitleFontPx * 1.2f + s.statPopupRowHeightPx * i) * u); + _rows[i].fontSize = s.statPopupRowFontPx * u; + _rows[i].color = s.statPopupRowColor; + } + return "box pos=" + box.anchoredPosition + " size=" + box.sizeDelta + " rows=" + _rows.Count + + " unitsPerPx=" + u.ToString("F4") + " titleFont=" + titleLabel.fontSize.ToString("F1"); + } + + // ───────────────────────────────────────────── 이벤트 · 공개 API + + private void OnLevelUp(in LevelUpEvent e) + { + var s = WLCombatTextSettings.Instance; + if (s == null || !WLCombatTextSettings.Enabled || !s.statPopupEnabled) return; + Report(e.newLevel, BuildDeltasFromSnapshot(e.pc, s)); + } + + /// + /// 🔵 813k 연결점 — 이전/새 스탯을 그대로 실어 보내면 이 메서드가 팝업을 띄운다. + /// deltas 가 null 이면 컴포넌트가 스스로 스냅샷 차이를 만든다. + /// + public static string Report(int newLevel, WLStatDelta[] deltas) + { + if (Active == null) return "StatPopup 없음(프리팹 노드 미배치)"; + return Active.Show(newLevel, deltas); + } + + /// 팝업을 띄운다(프로브도 이 경로를 쓴다). + public string Show(int newLevel, WLStatDelta[] deltas) + { + var s = WLCombatTextSettings.Instance; + if (s == null) return "설정 없음"; + BuildIfNeeded(); + ApplyLayout(); + + LevelUpSeen++; LastLevel = newLevel; + titleLabel.text = DSUtil.Format(s.statPopupTitleFormat, newLevel); + + int shown = 0; + var sb = new StringBuilder(titleLabel.text); + for (int i = 0; i < _rows.Count; i++) + { + bool has = deltas != null && i < deltas.Length; + double d = has ? deltas[i].Delta : 0d; + if (!has || (!s.statPopupShowZero && d <= 0d)) { _rows[i].text = ""; continue; } + _rows[i].text = DSUtil.Format(s.statPopupRowFormat, deltas[i].name, d.ToString("N0"), deltas[i].next.ToString("N0")); + sb.Append(" / ").Append(_rows[i].text); + shown++; + } + LastRowCount = shown; + LastText = sb.ToString(); + + _lifeMax = _life = Mathf.Max(0.01f, s.statPopupSeconds); + _elapsed = 0f; + group.alpha = 1f; + box.localScale = Vector3.one * WLTextFxUtil.PunchScale(0f, s.statPopupPunchScale, s.statPopupPunchSeconds); + TakeSnapshot(); + return "level=" + newLevel + " rows=" + shown + " text=\"" + LastText + "\" pos=" + box.anchoredPosition; + } + + public void Hide() + { + if (group != null) group.alpha = 0f; + if (box != null) box.localScale = Vector3.one; + _life = 0f; + } + + private void Update() + { + if (_life <= 0f) return; + var s = WLCombatTextSettings.Instance; + if (s == null) { Hide(); return; } + float dt = Time.unscaledDeltaTime; + _life -= dt; _elapsed += dt; + float passed = WLTextFxUtil.Passed(_life, _lifeMax); + group.alpha = WLTextFxUtil.FadeAlpha(passed, s.statPopupFadeStartRatio); + box.localScale = Vector3.one * WLTextFxUtil.PunchScale(_elapsed, s.statPopupPunchScale, s.statPopupPunchSeconds); + if (_life <= 0f) Hide(); + } + + // ───────────────────────────────────────────── 스냅샷 대체 경로(813k 전까지) + + /// 설정에 적힌 스탯들의 현재 값을 기억한다. + public void TakeSnapshot() + { + var s = WLCombatTextSettings.Instance; + if (s == null || s.statPopupStats == null) return; + var stat = (Application.isPlaying && MyValue.bMyPC && MyValue.MyPC != null) ? MyValue.MyPC.Get_StatInfo() : null; + if (stat == null) return; + for (int i = 0; i < s.statPopupStats.Length; i++) + _snapshot[(int)s.statPopupStats[i]] = stat.Get_Stat(s.statPopupStats[i]); + } + + private WLStatDelta[] BuildDeltasFromSnapshot(Actor pc, WLCombatTextSettings s) + { + if (s.statPopupStats == null || s.statPopupStats.Length == 0) return null; + var actor = pc != null ? pc : (MyValue.bMyPC ? (Actor)MyValue.MyPC : null); + if (actor == null) return null; + var stat = actor.Get_StatInfo(); + if (stat == null) return null; + + int n = Mathf.Min(s.statPopupStats.Length, Mathf.Max(1, s.statPopupRows)); + var arr = new WLStatDelta[n]; + for (int i = 0; i < n; i++) + { + var key = s.statPopupStats[i]; + double now = stat.Get_Stat(key); + double prev; + if (!_snapshot.TryGetValue((int)key, out prev)) prev = now; + arr[i] = new WLStatDelta { name = key.ToString(), prev = prev, next = now }; + } + return arr; + } + + // ───────────────────────────────────────────── 프로브(에디트 모드 · Play 0) + + /// 합성 레벨업 이벤트 — 811b 코어의 실제 Dispatch 경로(구독 배선까지 검증). + public static string RaiseFakeLevelUp(int newLevel) + { + var e = new LevelUpEvent { pc = null, newLevel = newLevel, time = Time.unscaledTime, frame = Time.frameCount }; + CombatEvents.LevelUp.Dispatch(in e); + return "dispatch LevelUp level=" + newLevel + " · 구독자=" + CombatEvents.LevelUp.Count; + } + + /// 합성 스탯 델타로 직접 표시(813k 페이로드가 오면 이 모양이 된다). + public string ShowFake(int newLevel, string[] names, double[] prev, double[] next) + { + int n = Mathf.Min(names.Length, Mathf.Min(prev.Length, next.Length)); + var arr = new WLStatDelta[n]; + for (int i = 0; i < n; i++) arr[i] = new WLStatDelta { name = names[i], prev = prev[i], next = next[i] }; + return Show(newLevel, arr); + } + + public string Dump() + { + var sb = new StringBuilder(); + sb.AppendLine("StatPopup subscribed=" + _subscribed + " levelUpSeen=" + LevelUpSeen + + " lastLevel=" + LastLevel + " rows=" + LastRowCount + + " alpha=" + (group != null ? group.alpha.ToString("F2") : "-")); + sb.AppendLine(" title=\"" + (titleLabel != null ? titleLabel.text : "") + "\" pos=" + + (box != null ? box.anchoredPosition.ToString() : "-") + + " size=" + (box != null ? box.sizeDelta.ToString() : "-") + + " scale=" + (box != null ? box.localScale.x.ToString("F3") : "-")); + for (int i = 0; i < _rows.Count; i++) + sb.AppendLine(" row[" + i + "] \"" + _rows[i].text + "\" pos=" + _rows[i].rectTransform.anchoredPosition + + " font=" + _rows[i].fontSize.ToString("F1")); + return sb.ToString(); + } + + public void SetFont(TMP_FontAsset f) + { + font = f; + if (titleLabel != null && f != null) titleLabel.font = f; + for (int i = 0; i < _rows.Count; i++) if (_rows[i] != null && f != null) _rows[i].font = f; + } + } +} diff --git a/Assets/WL/UI/Scripts/StatPopup.cs.meta b/Assets/WL/UI/Scripts/StatPopup.cs.meta new file mode 100644 index 000000000..e001fd944 --- /dev/null +++ b/Assets/WL/UI/Scripts/StatPopup.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c455fdfebd21acd479d9fda351961628 \ No newline at end of file diff --git a/Assets/WL/UI/Scripts/WLSkillSlotOverride.cs b/Assets/WL/UI/Scripts/WLSkillSlotOverride.cs new file mode 100644 index 000000000..a6443dc8a --- /dev/null +++ b/Assets/WL/UI/Scripts/WLSkillSlotOverride.cs @@ -0,0 +1,291 @@ +// ───────────────────────────────────────────────────────────────────────────── +// WLSkillSlotOverride.cs — 전투 패드 4슬롯 장착 **로컬 오버라이드** + MP 부족 흐림 (WL-813g §3 · #813) +// +// 813c 완료보고 §5 이월(「슬롯 장착은 서버 프리셋이라 813c 와 묶어야 함」 · 813n §1) · 기준서 v1 §B 요소 3. +// +// ■ 무엇을 하나 (UI 코드 안 · 원본 훅 0 · 핫스팟(PCActor/Actor) 변경 0) +// ① WLSkillSlotPreset.asset 에서 **내 클래스 행**(813n 4클래스 × 4슬롯)을 찾아, +// `ServerData.Equip.Set_Equip(eItem.Skill, id, slot)` — 클라 메모리 전용 공개 API(서버 호출 0) — 로 써 넣는다. +// 그 뒤 `PCActor.Set/ReSet` 이 `Get_EquipSkillID(i)` 로 읽는 **기존 `Use_Skill` 경로 그대로** 발동한다. +// ② 이미 카드가 서버 값으로 채워진 뒤라면 `SkillCard.Set(데이터)` 로 아이콘만 즉시 맞춘다(표시 동기화). +// ③ MP 가 모자란 슬롯의 버튼을 흐리게 한다(`Actor.cs:733-741` 과 **같은 식**: n_MP − n_MP·SKILL_COST > MP). +// +// 🔴 실행 순서 주의(실측 · Play 없이 정적 확인) +// `PCActor.Set()`(:151-163)은 `dic_MagicData` 를 **한 번만** 만든다(`ReSet()` 은 `arr_magicData` 만 새로 읽는다). +// `Actor.Use_Skill(:707)` 은 `m_Magic = dic_MagicData[index]` 를 쓰므로, 오버라이드가 `PCActor.Set()` **뒤**에 +// 적용되면 아이콘·게이트·쿨·애니는 새 스킬이지만 발사 실체(`m_SkillData.m_SkillListTableData`)는 옛 스킬로 남는다. +// → 이 컴포넌트는 `ServerData` 가 준비되는 즉시(패드 활성 이전부터) 적용을 시도하고, +// 늦게 적용됐으면 `LateApplied = true` 를 남긴다(완료보고 「미확인」 항목 · Play 검증 필요). +// +// ■ 롤백(C8) +// WLSkillSlotPreset.asset 이 없거나 overrideEnabled=false 면 **아무 것도 쓰지 않는다** = 서버 프리셋 원본. +// restoreOnDisable 이면 꺼질 때 원래 SkillID 를 그대로 복원한다. +// +// 🔴 어셈블리 주의: Assets/WL/UI/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일). +// ───────────────────────────────────────────────────────────────────────────── + +using System.Collections.Generic; +using System.Text; +using UnityEngine; +using UnityEngine.UI; + +namespace WL.UI +{ + [DisallowMultipleComponent] + public class WLSkillSlotOverride : MonoBehaviour + { + [Tooltip("대상 전투 패드 — 비우면 같은 오브젝트의 BattleUI 를 찾는다")] + [SerializeField] private BattleUI target; + + // ── 진단(프로브가 읽는다 · 실측만 채운다) + public static bool Applied; + public static bool LateApplied; + public static int AppliedClassId; + public static int AppliedSlots; + public static string LastInfo = ""; + public static int[] LastBefore = new int[0]; + public static int[] LastAfter = new int[0]; + + private readonly List _restore = new List(); + private bool _didApply; + private float _nextMpCheck; + private float[] _alpha; + private SkillListTableData[] _slotData; + + /// Play 중에만 서버 데이터를 만진다 — ServerInfo.Ins 는 없으면 GameObject 를 만든다(에디트 모드 오염 금지). + private static ServerData LiveServerData + { + get { return Application.isPlaying && ServerInfo.Ins != null ? ServerInfo.Ins.m_ServerData : null; } + } + + private void OnEnable() { Initialize(); } + + private void OnDisable() { RestoreIfNeeded(); } + + /// 에디트 모드에서도 런타임과 같은 경로를 타게 하기 위해 OnEnable 본체를 분리한다(813c 교훈). + public string Initialize() + { + if (target == null) target = GetComponent(); + _nextMpCheck = 0f; + return TryApply(); + } + + private void Update() + { + if (!_didApply) TryApply(); // ServerData 가 늦게 준비되는 경우 재시도(가벼움 · 실패는 즉시 반환) + UpdateMpGate(); + } + + // ───────────────────────────────────────────── ① 슬롯 오버라이드 + + /// 설정대로 내 클래스의 4슬롯을 써 넣는다. 설정·서버데이터가 없으면 아무 것도 하지 않는다. + public string TryApply() + { + if (!WLSkillSlotPreset.Enabled) return "WLSkillSlotPreset 없음/off — 서버 프리셋 유지"; + + var sdata = LiveServerData; + if (sdata == null || sdata.Equip == null || sdata.Equip.Skill == null) return "ServerData 미준비 — 재시도"; + + int classId = ResolveClassId(sdata); + if (classId == 0) return "클래스 ID 미확인 — 재시도"; + + var preset = WLSkillSlotPreset.Instance; + int n = preset.SlotCountFor(classId); + if (n <= 0) { _didApply = true; LastInfo = "클래스 " + classId + " 행 없음 — 오버라이드 0"; return LastInfo; } + + string log = ApplyTo(sdata.Equip, preset, classId, _restore); + _didApply = true; + Applied = true; + AppliedClassId = classId; + AppliedSlots = n; + LateApplied = MyValue.bMyPC; // PC 가 이미 살아 있으면 dic_MagicData 는 옛 값일 수 있다(위 🔴) + LastInfo = log; + if (preset.verboseLog) Debug.Log("[WLSkillSlotOverride] " + log); + + RefreshCards(sdata, preset, classId, n); + return log; + } + + /// + /// SD_Equip 에 프리셋을 써 넣는다(테스트에서 합성 SD_Equip 으로도 부를 수 있게 정적 · 부작용은 인자만). + /// restore 에 원래 값을 남긴다(복원용). + /// + public static string ApplyTo(SD_Equip equip, WLSkillSlotPreset preset, int classId, List restore) + { + var sb = new StringBuilder(); + int p = equip.Preset; + if (!equip.Skill.ContainsKey(p)) return "Equip.Skill[" + p + "] 없음 — 오버라이드 0"; + var lst = equip.Skill[p]; + int n = Mathf.Min(preset.SlotCountFor(classId), lst.Count); + + var before = new int[n]; + var after = new int[n]; + if (restore != null && restore.Count == 0) + for (int i = 0; i < lst.Count; i++) restore.Add(lst[i]); + + for (int i = 0; i < n; i++) before[i] = lst[i]; + for (int i = 0; i < n; i++) + { + int id = preset.SkillIdFor(classId, i); + if (id <= 0) continue; // 0 = 그 슬롯은 서버 값 유지 + equip.Set_Equip(eItem.Skill, id, i); + } + for (int i = 0; i < n; i++) after[i] = lst[i]; + + LastBefore = before; LastAfter = after; + sb.Append("class=").Append(classId).Append(" preset=").Append(p).Append(" slots=").Append(n).Append(" · "); + for (int i = 0; i < n; i++) + { + if (i > 0) sb.Append(' '); + sb.Append('[').Append(i).Append("] ").Append(before[i]).Append("→").Append(after[i]); + } + return sb.ToString(); + } + + private void RestoreIfNeeded() + { + var preset = WLSkillSlotPreset.Instance; + if (preset == null || !preset.restoreOnDisable || _restore.Count == 0) { _restore.Clear(); _didApply = false; return; } + var sdata = LiveServerData; + if (sdata != null && sdata.Equip != null && sdata.Equip.Skill != null) + { + int p = sdata.Equip.Preset; + if (sdata.Equip.Skill.ContainsKey(p)) + { + var lst = sdata.Equip.Skill[p]; + for (int i = 0; i < lst.Count && i < _restore.Count; i++) + { + var v = lst[i]; + v = _restore[i]; + v.RandomizeCryptoKey(); + lst[i] = v; + } + } + } + _restore.Clear(); + _didApply = false; + Applied = false; + } + + /// 내 클래스 ID — 살아 있는 PC 가 있으면 그것, 없으면 장착 PC(Equip[eItem.PC]). + public static int ResolveClassId(ServerData sdata) + { + if (MyValue.bMyPC && MyValue.MyPC != null) + { + int id = MyValue.MyPC.Get_ID(); + if (id > 0) return id; + } + if (sdata != null && sdata.Equip != null && sdata.Equip.Equip != null && sdata.Equip.Equip.ContainsKey(eItem.PC)) + return sdata.Equip.Equip[eItem.PC]; + return 0; + } + + /// 카드가 이미 서버 값으로 채워졌다면 아이콘을 새 스킬로 즉시 맞춘다(표시 동기화 · Use 타입 시각 복원). + private void RefreshCards(ServerData sdata, WLSkillSlotPreset preset, int classId, int n) + { + if (target == null || target.skillCards == null) return; + if (_slotData == null || _slotData.Length != target.skillCards.Length) + _slotData = new SkillListTableData[target.skillCards.Length]; + for (int i = 0; i < n && i < target.skillCards.Length; i++) + { + int id = preset.SkillIdFor(classId, i); + if (id <= 0) continue; + var card = target.skillCards[i]; + if (card == null) continue; + var data = table_skilllist.Ins.Get_Data_orNull(id); + if (data == null) continue; + _slotData[i] = data; + card.Set(data); + // SkillCard.Set 는 UI 카드 기준으로 상태 라벨을 켠다 — 전투(Use) 카드에서는 다시 끈다(Set_CardType(Use) 와 같은 상태). + if (card.gos != null) + { + if (card.gos.Length > 0 && card.gos[0] != null) card.gos[0].SetActive(false); + if (card.gos.Length > 2 && card.gos[2] != null) card.gos[2].SetActive(false); + } + } + } + + // ───────────────────────────────────────────── ③ MP 부족 흐림 + + private void UpdateMpGate() + { + var preset = WLSkillSlotPreset.Instance; + if (preset == null || !preset.mpDimEnabled || !WLSkillSlotPreset.Enabled) return; + if (target == null || target.skillCards == null) return; + if (Time.unscaledTime < _nextMpCheck) return; + _nextMpCheck = Time.unscaledTime + Mathf.Max(0.02f, preset.mpCheckInterval); + ApplyMpGate(preset); + } + + /// 현재 MP 로 각 슬롯 버튼 알파를 갱신한다(에디트 모드 프로브도 이 경로를 쓴다). + public string ApplyMpGate(WLSkillSlotPreset preset) + { + if (target == null) target = GetComponent(); + if (target == null || target.skillCards == null) return "BattleUI 없음"; + if (!MyValue.bMyPC || MyValue.MyPC == null) return "PC 없음 — MP 게이트 건너뜀"; + + var stat = MyValue.MyPC.Get_StatInfo(); + double mp = stat.Get_Stat(eStat.MP); + double costRate = stat.Get_Stat(eStat.SKILL_COST); + var sb = new StringBuilder(); + int cards = target.skillCards.Length; + if (_alpha == null || _alpha.Length != cards) { _alpha = new float[cards]; for (int i = 0; i < cards; i++) _alpha[i] = preset.mpFullAlpha; } + + int n = Mathf.Min(preset.slotCount, cards); + for (int i = 0; i < n; i++) + { + var card = target.skillCards[i]; + if (card == null || !card.gameObject.activeSelf) continue; + // 오버라이드로 확정된 슬롯 데이터를 먼저 쓰고, 없으면 카드가 들고 있는 데이터(서버 프리셋)로 떨어진다. + var data = (_slotData != null && i < _slotData.Length && _slotData[i] != null) + ? _slotData[i] : card.Get_Data() as SkillListTableData; + bool enough = true; + double need = 0d; + if (data != null) + { + need = data.n_MP - (data.n_MP * costRate); // Actor.cs:733 과 같은 식 + enough = need <= mp; + } + float want = enough ? preset.mpFullAlpha : preset.mpDimAlpha; + float cur = _alpha[i]; + float t = preset.mpDimLerpSeconds <= 0f ? 1f : Mathf.Clamp01(Time.unscaledDeltaTime / preset.mpDimLerpSeconds); + _alpha[i] = Mathf.Lerp(cur, want, t); + SetCardAlpha(card, _alpha[i]); + sb.Append('[').Append(i).Append("] need=").Append(need.ToString("F0")) + .Append(" mp=").Append(mp.ToString("F0")).Append(" enough=").Append(enough) + .Append(" alpha=").Append(_alpha[i].ToString("F2")).Append(' '); + } + return sb.ToString(); + } + + private static void SetCardAlpha(SkillCard card, float a) + { + var cg = card.GetComponent(); + if (cg == null) cg = card.gameObject.AddComponent(); + cg.alpha = a; + // 흐릴 때도 누를 수는 있게 둔다 — 실제 차단은 Actor.Use_Skill 의 MP 게이트(ToastUI 106)가 한다. + } + + // ───────────────────────────────────────────── 프로브(에디트 모드 · 런타임 의존 0) + + /// 클래스 × 슬롯 → SkillID 표를 문자열로 만든다(설정 에셋만 읽는다). + public static string ResolveDump(WLSkillSlotPreset preset) + { + var sb = new StringBuilder(); + if (preset == null) return "WLSkillSlotPreset 없음"; + sb.AppendLine("overrideEnabled=" + preset.overrideEnabled + " slotCount=" + preset.slotCount + + " restoreOnDisable=" + preset.restoreOnDisable + " mpDim=" + preset.mpDimEnabled + + " dimAlpha=" + preset.mpDimAlpha.ToString("F2")); + if (preset.rows == null) return sb.Append("rows 없음").ToString(); + for (int r = 0; r < preset.rows.Length; r++) + { + var row = preset.rows[r]; + sb.Append("class ").Append(row.classId).Append(" :"); + int n = preset.SlotCountFor(row.classId); + for (int s = 0; s < n; s++) sb.Append(" [").Append(s).Append("]=").Append(preset.SkillIdFor(row.classId, s)); + sb.Append(" // ").AppendLine(row.note); + } + return sb.ToString(); + } + } +} diff --git a/Assets/WL/UI/Scripts/WLSkillSlotOverride.cs.meta b/Assets/WL/UI/Scripts/WLSkillSlotOverride.cs.meta new file mode 100644 index 000000000..b5f0b2359 --- /dev/null +++ b/Assets/WL/UI/Scripts/WLSkillSlotOverride.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 376e0db7fbb566748ae0ca78bc4c4f35 \ No newline at end of file diff --git a/Assets/WL/UI/Scripts/WLTextFxUtil.cs b/Assets/WL/UI/Scripts/WLTextFxUtil.cs new file mode 100644 index 000000000..8843a5ed5 --- /dev/null +++ b/Assets/WL/UI/Scripts/WLTextFxUtil.cs @@ -0,0 +1,86 @@ +// ───────────────────────────────────────────────────────────────────────────── +// WLTextFxUtil.cs — 전투 텍스트 연출 3종이 공유하는 소소한 UI 조립 헬퍼 (WL-813g · #813) +// +// KillChainText · LootToast · StatPopup 이 자기 노드를 스스로 만든다(BuildIfNeeded 패턴 · 813c BossHpBar 선례). +// 프리팹에는 **빈 노드 1개 + 컴포넌트**만 들어가고 자식·좌표는 런타임에 만든다 → +// NewGameUI.prefab diff 를 "노드 추가"로만 유지한다(발주서 §2 ⓒ). +// +// 값은 하나도 들고 있지 않다(전부 WLCombatTextSettings). 여기 있는 숫자는 Unity 기본값(앵커 0/1)뿐이다. +// ───────────────────────────────────────────────────────────────────────────── + +using TMPro; +using UnityEngine; + +namespace WL.UI +{ + internal static class WLTextFxUtil + { + internal const int UILayer = 5; // 813c 와 같은 UI 레이어 인덱스 + + /// 이름으로 자식을 찾고 없으면 RectTransform 자식을 만든다. + internal static RectTransform NewChild(RectTransform parent, string name) + { + var t = parent.Find(name) as RectTransform; + if (t != null) return t; + var go = new GameObject(name, typeof(RectTransform)); + go.layer = UILayer; + t = (RectTransform)go.transform; + t.SetParent(parent, false); + t.localScale = Vector3.one; + t.localRotation = Quaternion.identity; + return t; + } + + /// 부모를 가득 채우도록 늘린다. + internal static void Stretch(RectTransform rt) + { + rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one; + rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero; + rt.pivot = new Vector2(0.5f, 0.5f); + } + + /// 자식 TextMeshProUGUI 를 만들거나 찾는다(레이캐스트 off · 폰트는 있으면 주입). + internal static TextMeshProUGUI NewText(RectTransform parent, string name, TMP_FontAsset font, TextAlignmentOptions align) + { + var t = NewChild(parent, name); + var label = t.GetComponent(); + if (label == null) label = t.gameObject.AddComponent(); + label.raycastTarget = false; + label.alignment = align; + // 줄바꿈·오버플로 API 는 TMP 버전마다 이름이 갈려(enableWordWrapping ↔ textWrappingMode) 건드리지 않는다 — 기본값 유지. + if (font != null) label.font = font; + return label; + } + + /// px → 캔버스 유닛 계수(802b 규약 · 캔버스 폭 / 기준 폭). 캔버스가 없으면 1. + internal static float UnitsPerPx(Component c, WLCombatTextSettings s) + { + if (s == null) return 1f; + var canvas = c != null ? c.GetComponentInParent() : null; + return s.UnitsPerPx(canvas != null ? canvas.rootCanvas : null); + } + + /// 0 → 1 로 지난 비율(life 는 남은 시간). + internal static float Passed(float life, float lifeMax) + { + return lifeMax > 0f ? 1f - Mathf.Clamp01(life / lifeMax) : 1f; + } + + /// fadeStart 비율 이후에만 1 → 0 으로 떨어지는 알파. + internal static float FadeAlpha(float passed, float fadeStart) + { + fadeStart = Mathf.Clamp01(fadeStart); + if (passed < fadeStart) return 1f; + if (fadeStart >= 1f) return 0f; + return 1f - Mathf.Clamp01((passed - fadeStart) / (1f - fadeStart)); + } + + /// 등장 펀치 스케일(punchSeconds 안에 punchScale → 1 로 수렴). + internal static float PunchScale(float elapsed, float punchScale, float punchSeconds) + { + if (punchSeconds <= 0f || punchScale <= 1f) return 1f; + float k = Mathf.Clamp01(elapsed / punchSeconds); + return Mathf.Lerp(punchScale, 1f, k * k); + } + } +} diff --git a/Assets/WL/UI/Scripts/WLTextFxUtil.cs.meta b/Assets/WL/UI/Scripts/WLTextFxUtil.cs.meta new file mode 100644 index 000000000..58347d469 --- /dev/null +++ b/Assets/WL/UI/Scripts/WLTextFxUtil.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 823b74862a7140d4996dc84ac1032690 \ No newline at end of file diff --git a/Assets/WL/UI/Settings/Resources/WL/WLCombatTextSettings.asset b/Assets/WL/UI/Settings/Resources/WL/WLCombatTextSettings.asset new file mode 100644 index 000000000..7296a4891 --- /dev/null +++ b/Assets/WL/UI/Settings/Resources/WL/WLCombatTextSettings.asset @@ -0,0 +1,87 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 58612e2aec3ae5149af9f8fc191e6177, type: 3} + m_Name: WLCombatTextSettings + m_EditorClassIdentifier: Assembly-CSharp::WL.UI.WLCombatTextSettings + textEnabled: 1 + verboseLog: 0 + designWidthPx: 1080 + designHeightPx: 1920 + damageRhythmEnabled: 1 + damageNormalSeconds: 0.6 + damageCritSeconds: 1 + damageCritScale: 1.6 + damageNormalScale: 1 + damageMaxConcurrent: 12 + damageEvictNormalFirst: 1 + damageRiseSpeed: 1 + damageCritRiseMultiplier: 1.35 + damageFadeStartRatio: 0.55 + damageScatterPx: 25 + damageCritScatterMultiplier: 0.4 + damageUseMergeWindow: 1 + damageMergeFormat: '{0}' + damageMergeHitsMin: 2 + damageMergeHitsSuffix: ' x{0}' + killChainTextEnabled: 1 + killChainSeconds: 0.8 + killChainPunchScale: 1.45 + killChainPunchSeconds: 0.18 + killChainFadeStartRatio: 0.6 + killChainTopOffsetPx: 520 + killChainFontPx: 96 + killChainTierColors: + - {r: 1, g: 0.94, b: 0.72, a: 1} + - {r: 1, g: 0.8, b: 0.35, a: 1} + - {r: 1, g: 0.52, b: 0.22, a: 1} + - {r: 1, g: 0.27, b: 0.28, a: 1} + killChainTierScales: + - 1 + - 1.12 + - 1.28 + - 1.45 + killChainFormat: '{0}!' + killChainSubFormat: {0} KILLS + lootToastEnabled: 1 + lootToastSeconds: 1.2 + lootToastLines: 3 + lootToastMergeSeconds: 1 + lootToastRightMarginPx: 40 + lootToastCenterOffsetPx: 120 + lootToastLineHeightPx: 54 + lootToastFontPx: 40 + lootToastWidthPx: 520 + lootToastFadeStartRatio: 0.65 + lootToastSlideInPx: 60 + lootToastSlideSeconds: 0.14 + lootToastFormat: '{0}[-] x{1}' + lootToastFormatSingle: '{0}[-]' + lootToastMinGrade: 1 + lootToastIncludeGold: 0 + lootToastPollHz: 10 + lootToastGradeColors: [] + statPopupEnabled: 1 + statPopupSeconds: 2 + statPopupPunchScale: 1.3 + statPopupPunchSeconds: 0.22 + statPopupFadeStartRatio: 0.7 + statPopupCenterOffsetPx: 180 + statPopupTitleFontPx: 72 + statPopupRowFontPx: 46 + statPopupRowHeightPx: 56 + statPopupRows: 3 + statPopupStats: 000000000200000004000000 + statPopupShowZero: 0 + statPopupTitleFormat: LEVEL {0} + statPopupRowFormat: '{0} +{1}' + statPopupTitleColor: {r: 1, g: 0.9, b: 0.45, a: 1} + statPopupRowColor: {r: 0.55, g: 0.95, b: 0.62, a: 1} diff --git a/Assets/WL/UI/Settings/Resources/WL/WLCombatTextSettings.asset.meta b/Assets/WL/UI/Settings/Resources/WL/WLCombatTextSettings.asset.meta new file mode 100644 index 000000000..3b5e54ab3 --- /dev/null +++ b/Assets/WL/UI/Settings/Resources/WL/WLCombatTextSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6266b783a1ac8614dab1117845f500ac +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/WL/UI/Settings/Resources/WL/WLSkillSlotPreset.asset b/Assets/WL/UI/Settings/Resources/WL/WLSkillSlotPreset.asset new file mode 100644 index 000000000..eea6b65d4 --- /dev/null +++ b/Assets/WL/UI/Settings/Resources/WL/WLSkillSlotPreset.asset @@ -0,0 +1,37 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7a213dd34e76ecd43963c424aef9e4e6, type: 3} + m_Name: WLSkillSlotPreset + m_EditorClassIdentifier: Assembly-CSharp::WL.UI.WLSkillSlotPreset + overrideEnabled: 1 + verboseLog: 0 + restoreOnDisable: 1 + slotCount: 4 + rows: + - classId: 10101 + note: "onehand \xB7 703101/703102/703103 + \uAD11\uC5ED 702006 \uB3CC\uAC1C\uBC14\uB78C" + skillIds: 7dba0a007eba0a007fba0a0036b60a00 + - classId: 10102 + note: "spellblade \xB7 703106/703105/703104 + \uAD11\uC5ED 702009 \uB9C8\uB098\uD3ED\uBC1C" + skillIds: 82ba0a0081ba0a0080ba0a0039b60a00 + - classId: 10501 + note: "bluntshield \xB7 703502/703503/703501 + \uAD11\uC5ED 702005 \uD30C\uB3C4" + skillIds: 0ebc0a000fbc0a000dbc0a0035b60a00 + - classId: 10502 + note: "paladin \xB7 703505/703506/703504 + \uAD11\uC5ED 702012 \uC2EC\uD310\uC758\uBE5B" + skillIds: 11bc0a0012bc0a0010bc0a003cb60a00 + mpDimEnabled: 1 + mpDimAlpha: 0.4 + mpFullAlpha: 1 + mpDimLerpSeconds: 0.12 + mpCheckInterval: 0.1 + mpDimDuringCooldown: 1 diff --git a/Assets/WL/UI/Settings/Resources/WL/WLSkillSlotPreset.asset.meta b/Assets/WL/UI/Settings/Resources/WL/WLSkillSlotPreset.asset.meta new file mode 100644 index 000000000..e29dff90a --- /dev/null +++ b/Assets/WL/UI/Settings/Resources/WL/WLSkillSlotPreset.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 76225631a0d5ec84baafb9f176ae0f51 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/WL/UI/Settings/WLCombatTextSettings.cs b/Assets/WL/UI/Settings/WLCombatTextSettings.cs new file mode 100644 index 000000000..1db079b99 --- /dev/null +++ b/Assets/WL/UI/Settings/WLCombatTextSettings.cs @@ -0,0 +1,336 @@ +// ───────────────────────────────────────────────────────────────────────────── +// WLCombatTextSettings.cs — 전투 텍스트 연출 값의 단일 출처 (WL-813g · #813) +// +// 기준서 v1 §B 요소 2 「데미지 숫자 = 일반 0.6 s · 크리 1.0 s · 1.6배 · 합산 윈도우 0.25 s」 +// 요소 2 「연쇄 처치 3 s 윈도우 2/3/5/8킬」(윈도우·티어 자체는 811e = WLReactionSettings) +// 요소 4 「토스트 1.2 s · 동시 3줄 · 등급 색」 +// 요소 5 「레벨업 = 스탯 팝업 3개」 +// +// ■ 왜 이 에셋인가 (C45 · 발주서 §2 ⓓ) +// 지속 시간·배율·상한·오프셋을 코드 상수로 두지 않는다. 이 파일에는 "필드와 기본값"만 있고, +// 게임이 실제로 읽는 값은 Resources/WL/WLCombatTextSettings.asset 이다. +// +// ■ 롤백(C8) +// 에셋이 없거나 enabled = false 면 네 연출(데미지 리듬 · 연쇄 문구 · 전리품 토스트 · 스탯 팝업)이 +// 전부 무동작으로 떨어지고 HUDDMGUI 는 **원본 값(0.9 s · 스케일 1배 · 상한 없음)** 으로 돈다. +// +// ■ 좌표계 (px) +// px 값은 세로 1080×1920 기준 화면 픽셀이다(813c WLHudLayoutSettings 와 같은 규약). +// px → 캔버스 유닛 환산은 런타임에 캔버스 폭에서 구한다(802b 노트 · 세로에서 1 px = 1.7778 유닛). +// +// 🔴 어셈블리 주의: Assets/WL/UI/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일). +// ───────────────────────────────────────────────────────────────────────────── + +using UnityEngine; +using UnityEngine.UI; + +namespace WL.UI +{ + [CreateAssetMenu(fileName = "WLCombatTextSettings", menuName = "WL/Combat Text Settings", order = 31)] + public sealed class WLCombatTextSettings : ScriptableObject + { + /// Resources 경로 — 런타임 로드 키. + public const string ResourcesPath = "WL/WLCombatTextSettings"; + + private static WLCombatTextSettings s_instance; + private static bool s_tried; + + /// 에셋이 없으면 null(호출부는 null 이면 원본 동작 유지 = C8 롤백). + public static WLCombatTextSettings Instance + { + get + { + if (s_instance == null && !s_tried) + { + s_tried = true; + s_instance = Resources.Load(ResourcesPath); + } + return s_instance; + } + } + + /// 에디터에서 에셋을 새로 만들거나 값을 바꾼 뒤 캐시를 버린다. + public static void ClearCache() { s_instance = null; s_tried = false; } + + /// 진단·A/B 전용 런타임 스위치(에셋을 건드리지 않고 끈다). + public static bool RuntimeDisabled; + + /// 이 웨이브의 텍스트 연출이 살아 있는가 = 에셋 존재 + enabled + 런타임 스위치. + public static bool Enabled { get { var i = Instance; return i != null && i.textEnabled && !RuntimeDisabled; } } + + // ── 전체 스위치 ──────────────────────────────────────────────────────── + [Header("전체 스위치")] + [Tooltip("끄면 데미지 리듬 · 연쇄 문구 · 전리품 토스트 · 스탯 팝업이 전부 무동작(원본 HUDDMGUI 동작으로 복귀). 롤백 1순위.")] + public bool textEnabled = true; + + [Tooltip("표시 판정을 Console 에 남긴다(검증용 · GC 발생).")] + public bool verboseLog = false; + + [Header("기준 해상도 (px · 813c 와 동일 규약)")] + [Tooltip("모든 px 값의 기준이 되는 화면 폭")] + public float designWidthPx = 1080f; + [Tooltip("모든 px 값의 기준이 되는 화면 높이")] + public float designHeightPx = 1920f; + + // ── ① 데미지 숫자 리듬 (기준서 §B 요소 2) ────────────────────────────── + [Header("① 데미지 숫자 — 리듬 (HUDDMGUI · 원본 0.9 s · ±25 px 랜덤)")] + [Tooltip("리듬화 사용 여부. off 면 HUDDMGUI 가 원본 상수(0.9 s · 배율 1)로 돈다.")] + public bool damageRhythmEnabled = true; + + [Tooltip("일반 타격 숫자 표시 시간(초). 기준서 §B = 0.6 s.")] + public float damageNormalSeconds = 0.6f; + + [Tooltip("크리티컬 숫자 표시 시간(초). 기준서 §B = 1.0 s.")] + public float damageCritSeconds = 1.0f; + + [Tooltip("크리티컬 글자 크기 배율. 기준서 §B = 1.6배(일반 = 1.0).")] + public float damageCritScale = 1.6f; + + [Tooltip("일반 타격 글자 크기 배율(기준 1.0 · 숫자죽 방지로 낮출 수 있다).")] + public float damageNormalScale = 1.0f; + + [Tooltip("화면에 동시에 떠 있을 수 있는 데미지 숫자 상한. 초과하면 가장 오래된 것부터 즉시 회수한다. 0 이하 = 상한 없음.")] + public int damageMaxConcurrent = 12; + + [Tooltip("상한 초과로 회수할 때 크리티컬은 남기고 일반부터 회수한다.")] + public bool damageEvictNormalFirst = true; + + [Tooltip("위로 떠오르는 속도(원본 1.0 · 캔버스 유닛/초).")] + public float damageRiseSpeed = 1.0f; + + [Tooltip("크리티컬은 이 배율로 더 빨리 떠오른다(1 = 일반과 같음).")] + public float damageCritRiseMultiplier = 1.35f; + + [Tooltip("표시 시간 대비 이 비율이 지난 뒤부터 알파가 빠지기 시작한다(0~1 · 원본은 처음부터 서서히).")] + public float damageFadeStartRatio = 0.55f; + + [Tooltip("랜덤 산포 반경(px · 원본 ±25). 0 이면 정확히 대상 위. 겹침을 줄이려면 키운다.")] + public float damageScatterPx = 25f; + + [Tooltip("크리티컬은 산포를 이 배율로 줄여 중앙에 가깝게 띄운다(눈에 잘 띄게).")] + public float damageCritScatterMultiplier = 0.4f; + + [Tooltip("811e DamageAggregator.Flushed(합산 0.25 s)를 구독해 '합계 · N타' 를 함께 보여 준다. 데이터는 811e 소유 · 여기는 표시만.")] + public bool damageUseMergeWindow = true; + + [Tooltip("합산 표시 형식 — {0} = 합계 · {1} = 타수. 빈 문자열이면 합계만 표시.")] + public string damageMergeFormat = "{0}"; + + [Tooltip("합산 타수가 이 값 이상일 때만 타수 꼬리표를 붙인다(damageMergeHitsSuffix).")] + public int damageMergeHitsMin = 2; + + [Tooltip("타수 꼬리표 형식 — {0} = 타수. 예: \" x{0}\". 빈 문자열이면 붙이지 않는다.")] + public string damageMergeHitsSuffix = " x{0}"; + + // ── ② 연쇄 처치 문구 (기준서 §B 요소 2 · 데이터는 811e KillChain) ────── + [Header("② 연쇄 처치 문구 (KillChain.TierReached 구독 · 화면 중앙 상단)")] + [Tooltip("연쇄 처치 문구 표시 여부.")] + public bool killChainTextEnabled = true; + + [Tooltip("문구 표시 시간(초 · unscaled). 발주서 §1-1 = 0.8 s.")] + public float killChainSeconds = 0.8f; + + [Tooltip("스케일 펀치 — 등장 순간의 배율(1.0 이면 펀치 없음).")] + public float killChainPunchScale = 1.45f; + + [Tooltip("펀치가 1.0 으로 돌아오는 시간(초). 표시 시간보다 짧아야 한다.")] + public float killChainPunchSeconds = 0.18f; + + [Tooltip("표시 시간 대비 이 비율이 지난 뒤부터 알파가 빠진다(0~1).")] + public float killChainFadeStartRatio = 0.6f; + + [Tooltip("화면 중앙 기준 위쪽 오프셋(px · 세로 1080×1920 기준). Safe Area 안에서 잰다.")] + public float killChainTopOffsetPx = 520f; + + [Tooltip("글자 크기(px · 세로 1080×1920 기준).")] + public float killChainFontPx = 96f; + + [Tooltip("티어 인덱스별 글자 색(tiers 순서 = DOUBLE/TRIPLE/RAMPAGE/MASSACRE). 모자라면 마지막 색을 쓴다.")] + public Color[] killChainTierColors = new Color[] + { + new Color(1f, 0.94f, 0.72f, 1f), + new Color(1f, 0.80f, 0.35f, 1f), + new Color(1f, 0.52f, 0.22f, 1f), + new Color(1f, 0.27f, 0.28f, 1f), + }; + + [Tooltip("티어 인덱스별 글자 크기 배율(위 색과 같은 순서). 모자라면 마지막 값.")] + public float[] killChainTierScales = new float[] { 1f, 1.12f, 1.28f, 1.45f }; + + [Tooltip("문구 형식 — {0} = 티어 이름(811e 에셋의 tiers[i].name) · {1} = 연쇄 킬 수. PD Q5 전까지 임시 영문(발주서 §1-1).")] + public string killChainFormat = "{0}!"; + + [Tooltip("연쇄 킬 수를 아래 줄에 덧붙인다(빈 문자열이면 표시 안 함) — {0} = 킬 수.")] + public string killChainSubFormat = "{0} KILLS"; + + // ── ③ 전리품 토스트 (기준서 §B 요소 4) ──────────────────────────────── + [Header("③ 전리품 토스트 (획득 아이템 · 우측 · 등급 색)")] + [Tooltip("토스트 표시 여부.")] + public bool lootToastEnabled = true; + + [Tooltip("한 줄이 남아 있는 시간(초). 기준서 §B 요소 4 = 1.2 s.")] + public float lootToastSeconds = 1.2f; + + [Tooltip("동시에 보이는 줄 수. 기준서 §B 요소 4 = 3줄. 넘치면 가장 오래된 줄부터 밀려난다.")] + public int lootToastLines = 3; + + [Tooltip("같은 아이템이 이 시간(초) 안에 또 들어오면 새 줄 대신 기존 줄의 수량을 올린다(줄 폭주 방지).")] + public float lootToastMergeSeconds = 1.0f; + + [Tooltip("우측 여백(px · Safe Area 우측 기준).")] + public float lootToastRightMarginPx = 40f; + + [Tooltip("첫 줄의 화면 세로 중앙 기준 오프셋(px · 양수 = 위).")] + public float lootToastCenterOffsetPx = 120f; + + [Tooltip("줄 간격(px).")] + public float lootToastLineHeightPx = 54f; + + [Tooltip("줄 글자 크기(px).")] + public float lootToastFontPx = 40f; + + [Tooltip("줄 폭(px) — 오른쪽 정렬 기준의 상자 폭.")] + public float lootToastWidthPx = 520f; + + [Tooltip("표시 시간 대비 이 비율이 지난 뒤부터 알파가 빠진다(0~1).")] + public float lootToastFadeStartRatio = 0.65f; + + [Tooltip("등장할 때 오른쪽에서 밀려 들어오는 거리(px · 0 이면 슬라이드 없음).")] + public float lootToastSlideInPx = 60f; + + [Tooltip("슬라이드가 끝나는 시간(초).")] + public float lootToastSlideSeconds = 0.14f; + + [Tooltip("줄 형식 — {0} = 등급 색 태그 + 이름 · {1} = 개수. 개수 1 이면 lootToastFormatSingle 을 쓴다.")] + public string lootToastFormat = "{0}[-] x{1}"; + + [Tooltip("개수 1 일 때의 줄 형식 — {0} = 등급 색 태그 + 이름.")] + public string lootToastFormatSingle = "{0}[-]"; + + [Tooltip("이 등급 이상만 토스트로 띄운다(기준서 §B 요소 4 = 연출은 4 이상 · 토스트는 전부 보고 싶으면 1).")] + public int lootToastMinGrade = 1; + + [Tooltip("골드(아이템 ID 2)도 토스트로 띄운다.")] + public bool lootToastIncludeGold = false; + + [Tooltip("획득 집계를 초당 몇 번 확인할지(폴링 · DropItemInfo.m_StageDropData 델타). 813f 이벤트가 오면 이 폴링은 걷어낸다.")] + public float lootToastPollHz = 10f; + + [Tooltip("등급 색 팔레트(index 0 = 등급 1). 비우면 MyValue.Get_GradeColor(코드 팔레트)를 그대로 쓴다.")] + public Color[] lootToastGradeColors = new Color[0]; + + // ── ④ 레벨업 스탯 팝업 (기준서 §B 요소 5) ───────────────────────────── + [Header("④ 레벨업 스탯 팝업 (CombatEvents.LevelUp 구독 · 스탯 3줄)")] + [Tooltip("스탯 팝업 표시 여부.")] + public bool statPopupEnabled = true; + + [Tooltip("팝업이 남아 있는 시간(초 · unscaled).")] + public float statPopupSeconds = 2.0f; + + [Tooltip("등장 스케일 펀치(1.0 이면 없음).")] + public float statPopupPunchScale = 1.3f; + + [Tooltip("펀치가 1.0 으로 돌아오는 시간(초).")] + public float statPopupPunchSeconds = 0.22f; + + [Tooltip("표시 시간 대비 이 비율이 지난 뒤부터 알파가 빠진다(0~1).")] + public float statPopupFadeStartRatio = 0.7f; + + [Tooltip("화면 중앙 기준 위쪽 오프셋(px).")] + public float statPopupCenterOffsetPx = 180f; + + [Tooltip("제목 글자 크기(px).")] + public float statPopupTitleFontPx = 72f; + + [Tooltip("스탯 줄 글자 크기(px).")] + public float statPopupRowFontPx = 46f; + + [Tooltip("스탯 줄 간격(px).")] + public float statPopupRowHeightPx = 56f; + + [Tooltip("보여 줄 스탯 줄 수 상한. 기준서 §B 요소 5 = 3개.")] + public int statPopupRows = 3; + + [Tooltip("보여 줄 스탯 종류(순서대로 · statPopupRows 개까지). 813k 가 이전/새 값을 주면 그 값을, 아니면 이 스탯의 스냅샷 차이를 쓴다.")] + public eStat[] statPopupStats = new eStat[] { eStat.STR, eStat.DEX, eStat.INT }; + + [Tooltip("증가분이 0 인 스탯 줄도 보여 준다.")] + public bool statPopupShowZero = false; + + [Tooltip("제목 형식 — {0} = 새 레벨.")] + public string statPopupTitleFormat = "LEVEL {0}"; + + [Tooltip("스탯 줄 형식 — {0} = 스탯 이름 · {1} = 증가분 · {2} = 새 값.")] + public string statPopupRowFormat = "{0} +{1}"; + + [Tooltip("제목 색.")] + public Color statPopupTitleColor = new Color(1f, 0.90f, 0.45f, 1f); + + [Tooltip("스탯 줄 색.")] + public Color statPopupRowColor = new Color(0.55f, 0.95f, 0.62f, 1f); + + // ── 파생값 헬퍼 (코드 상수 0) ────────────────────────────────────────── + + /// + /// px → 캔버스 유닛 계수. 813c(WLHudLayoutSettings.UnitsPerPx)와 **같은 산식**을 쓴다 — + /// CanvasScaler 설정을 재현해 구하고(에디트 모드처럼 캔버스가 레이아웃 전이어도 값이 나온다), + /// 스케일러가 없으면 캔버스 rect 폭으로 떨어진다. 세로 1080×1920 · 참조 1920×1080 Expand = 1.7778(802b 실측). + /// + public float UnitsPerPx(Canvas canvas) + { + if (designWidthPx <= 0f || designHeightPx <= 0f) return 1f; + + var scaler = canvas != null ? canvas.GetComponent() : null; + if (scaler != null && scaler.uiScaleMode == CanvasScaler.ScaleMode.ScaleWithScreenSize) + { + Vector2 r = scaler.referenceResolution; + if (r.x > 0f && r.y > 0f) + { + float w = designWidthPx, h = designHeightPx, scale; + switch (scaler.screenMatchMode) + { + case CanvasScaler.ScreenMatchMode.Expand: scale = Mathf.Min(w / r.x, h / r.y); break; + case CanvasScaler.ScreenMatchMode.Shrink: scale = Mathf.Max(w / r.x, h / r.y); break; + default: scale = Mathf.Pow(w / r.x, 1f - scaler.matchWidthOrHeight) * + Mathf.Pow(h / r.y, scaler.matchWidthOrHeight); break; + } + if (scale > 0f) return (w / scale) / designWidthPx; + } + } + + var canvasRect = canvas != null ? canvas.transform as RectTransform : null; + if (canvasRect != null && canvasRect.rect.width > 0f) return canvasRect.rect.width / designWidthPx; + return 1f; + } + + /// 티어 인덱스의 색(배열이 짧으면 마지막 색 · 비어 있으면 흰색). + public Color TierColor(int tier) + { + if (killChainTierColors == null || killChainTierColors.Length == 0) return Color.white; + return killChainTierColors[Mathf.Clamp(tier, 0, killChainTierColors.Length - 1)]; + } + + /// 티어 인덱스의 크기 배율(배열이 짧으면 마지막 값 · 비어 있으면 1). + public float TierScale(int tier) + { + if (killChainTierScales == null || killChainTierScales.Length == 0) return 1f; + return killChainTierScales[Mathf.Clamp(tier, 0, killChainTierScales.Length - 1)]; + } + + /// 등급(1~9) 색. 팔레트가 비어 있으면 MyValue 코드 팔레트를 파싱해 쓴다. + public Color GradeColor(int grade) + { + if (lootToastGradeColors != null && lootToastGradeColors.Length > 0) + return lootToastGradeColors[Mathf.Clamp(grade - 1, 0, lootToastGradeColors.Length - 1)]; + Color c; + var tag = MyValue.Get_GradeColor(grade); // "" + int s = tag.IndexOf('#'); + int e = tag.IndexOf('>'); + if (s >= 0 && e > s && ColorUtility.TryParseHtmlString(tag.Substring(s, e - s), out c)) return c; + return Color.white; + } + + /// 등급의 리치텍스트 색 태그(원본 팔레트 그대로 · 기준서 §B 요소 4 「보유 팔레트 그대로」). + public string GradeColorTag(int grade) { return MyValue.Get_GradeColor(grade); } + } +} diff --git a/Assets/WL/UI/Settings/WLCombatTextSettings.cs.meta b/Assets/WL/UI/Settings/WLCombatTextSettings.cs.meta new file mode 100644 index 000000000..a7477c5b2 --- /dev/null +++ b/Assets/WL/UI/Settings/WLCombatTextSettings.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 58612e2aec3ae5149af9f8fc191e6177 \ No newline at end of file diff --git a/Assets/WL/UI/Settings/WLSkillSlotPreset.cs b/Assets/WL/UI/Settings/WLSkillSlotPreset.cs new file mode 100644 index 000000000..44f6d246d --- /dev/null +++ b/Assets/WL/UI/Settings/WLSkillSlotPreset.cs @@ -0,0 +1,147 @@ +// ───────────────────────────────────────────────────────────────────────────── +// WLSkillSlotPreset.cs — 전투 패드 4슬롯 장착 **로컬 오버라이드** 값의 단일 출처 (WL-813g §3 · #813) +// +// 813c 완료보고 §5 이월분 + 813n 완료보고(슬롯 SkillID 16행 · 쿨 6/8/12/20 s · MP 10/15/25/40). +// 기준서 v1 §B 요소 3 「4슬롯 · 쿨·자원 차등」. +// +// ■ 왜 필요한가 (813n §1 「슬롯 장착은 서버 프리셋이라 813c 와 묶어야 함」) +// 장착 슬롯의 SOT 는 서버(`ServerData.Equip.Skill[Preset]`)다. 서버 프리셋을 바꾸지 않고도 +// 전투 패드가 813n 프리셋(클래스별 Active 3 + 광역 1)으로 도는지 보려면 **클라 로컬 오버라이드**가 필요하다. +// +// ■ 어디에 쓰는가 +// WLSkillSlotOverride(UI) 가 이 표에서 내 클래스 행을 찾아 4슬롯 SkillID 를 +// `ServerData.Set_Equip(eItem.Skill, id, slot)`(클라 전용 공개 API · 서버 호출 0)로 써 넣는다. +// 그 뒤 `PCActor` 가 `Get_EquipSkillID(i)` 로 읽는 경로 = 기존 `Use_Skill` 경로 그대로다(핫스팟 0). +// +// ■ 롤백(C8) +// 이 에셋이 없거나 enabled = false 면 아무 것도 쓰지 않는다 = **서버 프리셋 원본** 그대로. +// restoreOnDisable 이 켜져 있으면 패드가 꺼질 때 원래 SkillID 를 되돌린다. +// +// 🔴 이 오버라이드는 **클라이언트 메모리만** 바꾼다. 서버 저장 호출(`Save_*`)은 하지 않는다. +// ───────────────────────────────────────────────────────────────────────────── + +using System; +using UnityEngine; + +namespace WL.UI +{ + /// 한 클래스(ClassConfig n_ClassID)의 4슬롯 장착 SkillID. + [Serializable] + public struct WLSkillSlotRow + { + [Tooltip("ClassConfig 의 n_ClassID (예: 10101 / 10102 / 10501 / 10502).")] + public int classId; + + [Tooltip("메모용 이름(런타임 미사용).")] + public string note; + + [Tooltip("슬롯 0~3 에 장착할 SkillID. 0 이면 그 슬롯은 건드리지 않는다(서버 값 유지).")] + public int[] skillIds; + } + + [CreateAssetMenu(fileName = "WLSkillSlotPreset", menuName = "WL/Skill Slot Preset", order = 32)] + public sealed class WLSkillSlotPreset : ScriptableObject + { + /// Resources 경로 — 런타임 로드 키. + public const string ResourcesPath = "WL/WLSkillSlotPreset"; + + private static WLSkillSlotPreset s_instance; + private static bool s_tried; + + /// 에셋이 없으면 null(호출부는 null 이면 서버 프리셋 원본 유지 = C8 롤백). + public static WLSkillSlotPreset Instance + { + get + { + if (s_instance == null && !s_tried) + { + s_tried = true; + s_instance = Resources.Load(ResourcesPath); + } + return s_instance; + } + } + + public static void ClearCache() { s_instance = null; s_tried = false; } + + /// 진단·A/B 전용 런타임 스위치. + public static bool RuntimeDisabled; + + /// 오버라이드가 살아 있는가 = 에셋 존재 + enabled + 런타임 스위치. + public static bool Enabled { get { var i = Instance; return i != null && i.overrideEnabled && !RuntimeDisabled; } } + + // ── 스위치 ──────────────────────────────────────────────────────────── + [Header("스위치")] + [Tooltip("끄면 서버 프리셋을 그대로 쓴다(오버라이드 0). 롤백 1순위.")] + public bool overrideEnabled = true; + + [Tooltip("적용/차단 결정을 Console 에 남긴다(검증용).")] + public bool verboseLog = false; + + [Tooltip("패드가 꺼질 때(씬 이동·컴포넌트 비활성) 원래 SkillID 를 되돌린다.")] + public bool restoreOnDisable = true; + + [Tooltip("오버라이드할 슬롯 수(0~4). 원본 배열은 6칸이고 813c 가 쓰는 활성 슬롯은 4개다.")] + public int slotCount = 4; + + // ── 클래스별 4슬롯 (813n 완료보고 16행) ─────────────────────────────── + [Header("클래스별 4슬롯 SkillID (813n 프리셋 · 슬롯 1~3 = 클래스 Active · 슬롯 4 = 광역)")] + [Tooltip("클래스 행. 내 클래스(Actor.Get_ID())와 classId 가 같은 행을 쓴다. 없으면 오버라이드 0.")] + public WLSkillSlotRow[] rows = new WLSkillSlotRow[] + { + new WLSkillSlotRow { classId = 10101, note = "onehand · 703101/703102/703103 + 광역 702006 돌개바람", skillIds = new[] { 703101, 703102, 703103, 702006 } }, + new WLSkillSlotRow { classId = 10102, note = "spellblade · 703106/703105/703104 + 광역 702009 마나폭발", skillIds = new[] { 703106, 703105, 703104, 702009 } }, + new WLSkillSlotRow { classId = 10501, note = "bluntshield · 703502/703503/703501 + 광역 702005 파도", skillIds = new[] { 703502, 703503, 703501, 702005 } }, + new WLSkillSlotRow { classId = 10502, note = "paladin · 703505/703506/703504 + 광역 702012 심판의빛", skillIds = new[] { 703505, 703506, 703504, 702012 } }, + }; + + // ── MP 부족 시 버튼 흐림 (발주서 §3) ────────────────────────────────── + [Header("MP 게이트 — 부족하면 버튼 흐림 (Actor.cs:733-741 과 같은 식)")] + [Tooltip("MP 부족 표시 사용 여부.")] + public bool mpDimEnabled = true; + + [Tooltip("MP 가 모자랄 때 버튼 알파(0~1 · 1 = 흐리지 않음).")] + public float mpDimAlpha = 0.4f; + + [Tooltip("MP 가 충분할 때 버튼 알파(보통 1).")] + public float mpFullAlpha = 1f; + + [Tooltip("알파가 바뀌는 데 걸리는 시간(초 · 0 이면 즉시).")] + public float mpDimLerpSeconds = 0.12f; + + [Tooltip("MP 확인 주기(초). 매 프레임 확인하지 않는다.")] + public float mpCheckInterval = 0.1f; + + [Tooltip("MP 부족 표시를 쿨타임 중에도 유지한다(off 면 쿨 중에는 쿨 링만 보여 준다).")] + public bool mpDimDuringCooldown = true; + + // ── 헬퍼 ────────────────────────────────────────────────────────────── + + /// 클래스 ID 의 행을 찾는다. 없으면 -1. + public int FindRow(int classId) + { + if (rows == null) return -1; + for (int i = 0; i < rows.Length; i++) if (rows[i].classId == classId) return i; + return -1; + } + + /// 클래스 ID · 슬롯의 SkillID. 행이 없거나 슬롯이 비면 0(= 서버 값 유지). + public int SkillIdFor(int classId, int slot) + { + int r = FindRow(classId); + if (r < 0) return 0; + var ids = rows[r].skillIds; + if (ids == null || slot < 0 || slot >= ids.Length) return 0; + return ids[slot]; + } + + /// 실제로 오버라이드할 슬롯 수(설정 값과 행 길이 중 작은 쪽). + public int SlotCountFor(int classId) + { + int r = FindRow(classId); + if (r < 0) return 0; + var ids = rows[r].skillIds; + return ids == null ? 0 : Mathf.Clamp(slotCount, 0, ids.Length); + } + } +} diff --git a/Assets/WL/UI/Settings/WLSkillSlotPreset.cs.meta b/Assets/WL/UI/Settings/WLSkillSlotPreset.cs.meta new file mode 100644 index 000000000..4db9b14ae --- /dev/null +++ b/Assets/WL/UI/Settings/WLSkillSlotPreset.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7a213dd34e76ecd43963c424aef9e4e6 \ No newline at end of file