182 lines
11 KiB
C#
182 lines
11 KiB
C#
|
|
// 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<SkinnedMeshRenderer>(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;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>꺼져 있는 섬 존 몹 중 id 인 것을 깨운다(On_Regen) · 그 밖의 켜진 몹(시험 몹 제외)은 끈다.</summary>
|
||
|
|
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<MobActor>(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<MobActor>(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<PCActor>(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<MobActor>(FindObjectsSortMode.None))
|
||
|
|
{
|
||
|
|
if (!m.gameObject.activeInHierarchy) continue;
|
||
|
|
var d = f != null ? f.GetValue(m) as MonsterTableData : null;
|
||
|
|
var an = m.GetComponent<Animator>();
|
||
|
|
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<MonoBehaviour>().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<MobActor>(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;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>101~104 를 PC 앞(카메라 쪽 dist m)에 gap m 간격으로 한 줄로 세우고 멈춘다 — 크기 · 모습 비교 캡처용(실행 중만).</summary>
|
||
|
|
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();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>id 몹만 다시 움직이게 한다(나머지는 멈춘 채) · PC 피격 이벤트 수(기준점)를 돌려준다.</summary>
|
||
|
|
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<MobActor>(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(); }
|
||
|
|
|
||
|
|
/// <summary>id 몹(시험 몹 아닌 것)을 깨워 PC 에서 dist m(카메라 쪽)에 두고 PC 를 표적으로 잡게 한 뒤(비선공도 반격 상태) 판정 · 피격 기록을 seconds 초 시작.</summary>
|
||
|
|
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<MobActor>(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");
|
||
|
|
}
|
||
|
|
}
|