// WL907_Probe.cs — #907 Play 중 섬 몹 라인업 확인(몹 ID · 프리팹 · 크기 · 실제 높이 · 애니메이터 상태 · PC 와 거리) — 읽기 전용 using System.Linq; using System.Reflection; using System.Text; using UnityEngine; public static class WL907_Probe { // 실제 정점(BakeMesh) 기준 — 렌더러 bounds 는 여유가 커서 과대 측정 static float Height(GameObject go, out float width) { var mesh = new Mesh(); float top = float.MinValue; var lo = new Vector2(float.MaxValue, float.MaxValue); var hi = new Vector2(float.MinValue, float.MinValue); foreach (var r in go.GetComponentsInChildren(false)) { r.BakeMesh(mesh, true); var tr = r.transform; foreach (var v in mesh.vertices) { var p = tr.position + tr.rotation * v; top = Mathf.Max(top, p.y); lo = Vector2.Min(lo, new Vector2(p.x, p.z)); hi = Vector2.Max(hi, new Vector2(p.x, p.z)); } } Object.Destroy(mesh); width = top > float.MinValue ? Mathf.Max(hi.x - lo.x, hi.y - lo.y) : 0f; return top > float.MinValue ? top - go.transform.position.y : 0f; } /// 꺼져 있는 섬 존 몹 중 id 인 것을 깨운다(On_Regen) · 그 밖의 켜진 몹(시험 몹 제외)은 끈다. public static object Wake(int id, bool offOthers) { var f = typeof(MobActor).GetField("m_MonsterTableData", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); var d0 = WL.IslandMob.WLTrailTest.Dummy; int off = 0; string woke = "없음"; var ids = new StringBuilder(); foreach (var m in Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None)) { var d = f.GetValue(m) as MonsterTableData; int mid = d != null ? d.n_MonsterID : 0; ids.Append(mid).Append(m.gameObject.activeSelf ? "+ " : "- "); if (offOthers && m.gameObject.activeSelf && !ReferenceEquals(m, d0) && mid != id) { m.Off(); off++; } } foreach (var m in Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None)) { var d = f.GetValue(m) as MonsterTableData; if (d == null || d.n_MonsterID != id || m.gameObject.activeSelf) continue; m.On_Regen(); woke = m.name + " 켜짐 " + m.gameObject.activeSelf; break; } return "몹 목록(+켜짐 -꺼짐) " + ids + " · 끈 몹 " + off + " · " + woke; } public static object Mobs() { var sb = new StringBuilder(); sb.AppendLine("lineup applied " + WL.Mobs.WLMobLineup.Applied + " · " + WL.Mobs.WLMobLineup.LastLog); var pc = Object.FindObjectsByType(FindObjectsSortMode.None).FirstOrDefault(p => p.IsMainPC()); if (pc != null) { float pw; sb.AppendLine("PC " + pc.name + " 높이 " + Height(pc.gameObject, out pw).ToString("F2") + " m · 폭 " + pw.ToString("F2")); } var f = typeof(MobActor).GetField("m_MonsterTableData", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); foreach (var m in Object.FindObjectsByType(FindObjectsSortMode.None)) { if (!m.gameObject.activeInHierarchy) continue; var d = f != null ? f.GetValue(m) as MonsterTableData : null; var an = m.GetComponent(); string st = "-"; if (an != null && an.runtimeAnimatorController != null) foreach (var n in new[] { "run", "idle", "attack", "hit", "die", "stun", "casting", "casting_end" }) if (an.GetCurrentAnimatorStateInfo(0).IsName(n)) { st = n; break; } float w; float h = Height(m.gameObject, out w); sb.AppendLine((d != null ? d.n_MonsterID.ToString() : "?") + " " + m.name + " · " + (d != null ? d.s_MonsterPrefab : "-") + " · 크기 " + m.transform.localScale.x.ToString("F2") + " · 높이 " + h.ToString("F2") + " m · 폭 " + w.ToString("F2") + " · 컨트롤러 " + (an != null && an.runtimeAnimatorController ? an.runtimeAnimatorController.name : "-") + " · 상태 " + st + " · 표크기 " + (d != null ? d.f_DefaultScale.ToString("F2") : "-") + " · ObjSize " + ((Vector3)typeof(Actor).GetField("ObjSize", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(m)).ToString("F2") + " · 중심 y " + (m.Get_Center_position().y - m.transform.position.y).ToString("F2") + " · 부품 " + string.Join("/", m.GetComponents().Where(c => c != null).Select(c => c.GetType().Name)) + " · 죽음 " + m.IsDead() + (pc != null ? " · PC 거리 " + Vector3.Distance(pc.Get_position(), m.Get_position()).ToString("F2") : "")); } return sb.ToString(); } static MobActor Pick(int id) { var f = typeof(MobActor).GetField("m_MonsterTableData", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); MobActor pick = null; foreach (var m in Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None)) { var d = f.GetValue(m) as MonsterTableData; if (d == null || d.n_MonsterID != id) continue; if (m.isActiveAndEnabled && !m.IsDead()) return m; if (pick == null) pick = m; } if (pick != null && !pick.isActiveAndEnabled) { int prev = WL.IslandMob.WLTrailTest.Override; WL.IslandMob.WLTrailTest.Override = 0; try { pick.On_Regen(); } finally { WL.IslandMob.WLTrailTest.Override = prev; } } return pick; } /// 101~104 를 PC 앞(카메라 쪽 dist m)에 gap m 간격으로 한 줄로 세우고 멈춘다 — 크기 · 모습 비교 캡처용(실행 중만). public static object Lineup(float dist, float gap) { return LineupIds("101,102,103,104", dist, gap); } public static object LineupIds(string ids, float dist, float gap) { var pc = MyValue.MyPC; var cam = Camera.main; if (pc == null || cam == null) return "PC/카메라 없음"; var fw = cam.transform.forward; fw.y = 0f; fw.Normalize(); var rt = cam.transform.right; rt.y = 0f; rt.Normalize(); var sb = new StringBuilder(); int i = 0; foreach (var id in ids.Split(',').Select(int.Parse)) { var m = Pick(id); if (m == null) { sb.Append(id).Append(" 없음 · "); i++; continue; } var want = pc.Get_position() - fw * dist + rt * ((i - 1.5f) * gap); UnityEngine.AI.NavMeshHit hit; if (UnityEngine.AI.NavMesh.SamplePosition(want, out hit, 2f, UnityEngine.AI.NavMesh.AllAreas)) want = hit.position; m.Stop(true); m.Set_Warp(want); m.transform.rotation = Quaternion.LookRotation(-fw); sb.Append(id).Append(' ').Append(m.name).Append(" 켜짐 ").Append(m.isActiveAndEnabled).Append(" · "); i++; } return sb.ToString(); } /// id 몹만 다시 움직이게 한다(나머지는 멈춘 채) · PC 피격 이벤트 수(기준점)를 돌려준다. public static object Release(int id) { var m = Pick(id); if (m == null) return "없음"; m.Stop(false); return m.name + " 풀림 · PC 피격 이벤트 " + WL.Combat.Core.CombatEvents.Damaged.Raised; } public static object UnfreezeAll() { int n = 0; foreach (var m in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) { m.Stop(false); n++; } return "풀림 " + n + " · PC 피격 이벤트 " + WL.Combat.Core.CombatEvents.Damaged.Raised; } public static object Counters() { var pc = MyValue.MyPC; return "판정 생성 " + WL.Combat.Core.CombatEvents.HitboxSpawned.Raised + " · PC 피격 " + WL.Combat.Core.CombatEvents.Damaged.Raised + " · PC HP " + (pc != null ? pc.Get_HP().ToString("0.#") : "-") + " · t " + Time.time.ToString("0.0"); } // ── 몹 판정 생성 기록(HitboxSpawned · 쏜 몹 · 프리팹 · 시작점 높이 · PC 와 거리) + PC 피격 기록 ── // run_script 는 호출마다 새 어셈블리 — 정적 필드가 공유되지 않아 AppDomain 데이터에 둔다 static StringBuilder s_shots { get { var b = System.AppDomain.CurrentDomain.GetData("wl907_shots") as StringBuilder; if (b == null) { b = new StringBuilder(); System.AppDomain.CurrentDomain.SetData("wl907_shots", b); } return b; } } static float s_until; static void OnShot(in WL.Combat.Core.HitboxSpawnedEvent e) { if (Time.time > s_until) return; if (!(e.shooter is MobActor)) return; var pc = MyValue.MyPC; s_shots.AppendFormat("{0:F2} 쏨 {1} {2} 시작 y{3:F2} · PC 거리 {4:F2}", Time.time, e.shooter.name, e.prefab, e.startPos.y - e.shooter.transform.position.y, pc != null ? Vector3.Distance(pc.Get_position(), e.startPos) : -1f).AppendLine(); } static void OnDmg(in WL.Combat.Core.DamagedEvent e) { if (Time.time > s_until) return; s_shots.AppendFormat("{0:F2} PC 피격 ← {1} 피해 {2:0.#} 무적 {3}", Time.time, e.attacker != null ? e.attacker.name : "-", e.damage, e.invincible).AppendLine(); } public static object WatchShots(float seconds) { s_shots.Length = 0; s_until = Time.time + seconds; WL.Combat.Core.CombatEvents.HitboxSpawned.Remove(OnShot); WL.Combat.Core.CombatEvents.HitboxSpawned.Add(OnShot); WL.Combat.Core.CombatEvents.Damaged.Remove(OnDmg); WL.Combat.Core.CombatEvents.Damaged.Add(OnDmg); return "기록 시작 " + seconds + " s"; } public static object ShotLog() { return s_shots.ToString(); } /// id 몹(시험 몹 아닌 것)을 깨워 PC 에서 dist m(카메라 쪽)에 두고 PC 를 표적으로 잡게 한 뒤(비선공도 반격 상태) 판정 · 피격 기록을 seconds 초 시작. public static object AttackTest(int id, float dist, float seconds) { var pc = MyValue.MyPC; var cam = Camera.main; var dummy = WL.IslandMob.WLTrailTest.Dummy; if (pc == null || cam == null) return "PC/카메라 없음"; var f = typeof(MobActor).GetField("m_MonsterTableData", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); MobActor m = null; foreach (var x in Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None)) { var d = f.GetValue(x) as MonsterTableData; if (d == null || d.n_MonsterID != id || ReferenceEquals(x, dummy)) continue; if (x.isActiveAndEnabled && !x.IsDead()) { m = x; break; } if (m == null && !x.gameObject.activeSelf) m = x; } if (m == null) return id + " 없음"; if (!m.isActiveAndEnabled) { int prev = WL.IslandMob.WLTrailTest.Override; WL.IslandMob.WLTrailTest.Override = 0; try { m.On_Regen(); } finally { WL.IslandMob.WLTrailTest.Override = prev; } } var fw = cam.transform.forward; fw.y = 0f; fw.Normalize(); var want = pc.Get_position() - fw * dist; UnityEngine.AI.NavMeshHit hit; if (UnityEngine.AI.NavMesh.SamplePosition(want, out hit, 2f, UnityEngine.AI.NavMesh.AllAreas)) want = hit.position; m.Stop(false); m.Set_Warp(want); m.Change_Target(true, pc, true); WatchShots(seconds); return m.name + " 켜짐 " + m.isActiveAndEnabled + " · PC 거리 " + Vector3.Distance(pc.Get_position(), m.Get_position()).ToString("F2"); } }