// WL760_Play.cs — PD 지시 #760 2-A Play 검증 probe (런타임 전용 · 서버 쓰기 없음 · Assets 무수정) // unity command run_script --file AgentScripts/WL760_Play.cs --entry WL760_Play.State // unity command run_script --file AgentScripts/WL760_Play.cs --entry WL760_Play.SetClass --args '[10101]' // unity command run_script --file AgentScripts/WL760_Play.cs --entry WL760_Play.Attack // unity command run_script --file AgentScripts/WL760_Play.cs --entry WL760_Play.Cap --args '["10101_1"]' // unity command run_script --file AgentScripts/WL760_Play.cs --entry WL760_Play.Mask using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using UnityEngine; public static class WL760_Play { const string OutDir = "Screenshots_WL/combat2"; const BindingFlags BF = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; static MyActor Me() { return UnityEngine.Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None) .FirstOrDefault(); } static double HP(Actor a) { var mi = typeof(Actor).GetMethod("Get_HP", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (mi == null) return double.NaN; return Convert.ToDouble(mi.Invoke(a, null)); } // ── 현재 상태 ──────────────────────────────────────────────────────────── public static object State() { var sb = new StringBuilder(); sb.AppendLine("playing=" + Application.isPlaying + " scene=" + UnityEngine.SceneManagement.SceneManager.GetActiveScene().name + " loaded=" + string.Join("+", Enumerable.Range(0, UnityEngine.SceneManagement.SceneManager.sceneCount) .Select(i => UnityEngine.SceneManagement.SceneManager.GetSceneAt(i).name))); var me = Me(); if (me == null) { sb.AppendLine("MyActor: none"); return sb.ToString(); } var anim = me.GetComponentInChildren(); sb.AppendLine("MyActor pos=" + me.transform.position.ToString("F1") + " ctrl=" + (anim != null && anim.runtimeAnimatorController != null ? anim.runtimeAnimatorController.name : "(null)") + " hp=" + HP(me).ToString("F0")); if (anim != null && anim.layerCount > 0) { var ci = anim.GetCurrentAnimatorClipInfo(0); sb.AppendLine(" playing clip=" + (ci.Length > 0 ? ci[0].clip.name : "(none)")); } var mobs = UnityEngine.Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); sb.AppendLine("MobActor alive=" + mobs.Length); foreach (var m in mobs.OrderBy(m => Vector3.Distance(m.transform.position, me.transform.position)).Take(4)) sb.AppendLine(" " + m.name + " d=" + Vector3.Distance(m.transform.position, me.transform.position).ToString("F1") + " hp=" + HP(m).ToString("F0")); return sb.ToString(); } // ── 클래스 전환 (서버 쓰기 없음 · #747 선례) ───────────────────────────── public static object SetClass(int classId) { var me = Me(); if (me == null) return "MyActor none"; var pc = me as PCActor; if (pc == null) return "MyActor is not PCActor: " + me.GetType().Name; var data = table_classconfig.Ins.Get_Data_orNull(classId); if (data == null) return "class data null: " + classId; pc.Change_Class(data, true); var anim = me.GetComponentInChildren(); return "Change_Class(" + classId + ") ok ctrl=" + (anim != null && anim.runtimeAnimatorController != null ? anim.runtimeAnimatorController.name : "(null)"); } // ── 공격 1회 (자동 타겟) ───────────────────────────────────────────────── public static object Attack() { var me = Me(); if (me == null) return "MyActor none"; var sb = new StringBuilder(); var mobs = UnityEngine.Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None) .OrderBy(m => Vector3.Distance(m.transform.position, me.transform.position)).ToArray(); if (mobs.Length == 0) return "no MobActor"; var t = mobs[0]; // 사거리 안으로 이동 + 바라보기 (입력 주입 없이 트랜스폼만 — 서버 쓰기 없음) var dir = (t.transform.position - me.transform.position); dir.y = 0f; if (dir.magnitude > 1.6f) me.transform.position = t.transform.position - dir.normalized * 1.5f; me.transform.LookAt(new Vector3(t.transform.position.x, me.transform.position.y, t.transform.position.z)); var setT = typeof(Actor).GetMethod("Change_Target", BF); if (setT != null) { try { setT.Invoke(me, new object[] { false, (Actor)t, false }); } catch { } } sb.AppendLine("target=" + t.name + " hpBefore=" + HP(t).ToString("F0") + " d=" + Vector3.Distance(me.transform.position, t.transform.position).ToString("F2")); double aspd = 1.0; try { var fi = typeof(Actor).GetField("m_Stat", BF); var stat = fi != null ? fi.GetValue(me) : null; if (stat != null) { var gm = stat.GetType().GetMethod("Get_Stat", new[] { typeof(eStat) }); if (gm != null) aspd = Convert.ToDouble(gm.Invoke(stat, new object[] { eStat.FinalAttackSpeed })); } } catch { } me.Play_Attack(0, (float)aspd); sb.AppendLine("Play_Attack(0," + aspd.ToString("F2") + ") issued"); return sb.ToString(); } /// 공격 후 대상 HP 를 다시 읽는다 (판정 ⓒ 데미지 0 아님). public static object Hp() { var me = Me(); if (me == null) return "MyActor none"; var mobs = UnityEngine.Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None) .OrderBy(m => Vector3.Distance(m.transform.position, me.transform.position)).Take(3).ToArray(); var anim = me.GetComponentInChildren(); string clip = "(none)"; if (anim != null && anim.layerCount > 0) { var ci = anim.GetCurrentAnimatorClipInfo(0); if (ci.Length > 0) clip = ci[0].clip.name + " n=" + anim.GetCurrentAnimatorStateInfo(0).normalizedTime.ToString("F2"); } return "clip=" + clip + " " + string.Join(" | ", mobs.Select(m => m.name + " hp=" + HP(m).ToString("F0"))); } // ── 캡처 (Assets 밖 · LightProbe.Render 패턴) ──────────────────────────── public static object Cap(string tag) { var cam = Camera.main; if (cam == null) return "no Camera.main"; System.IO.Directory.CreateDirectory(OutDir); int w = 1280, h = 720; var rt = new RenderTexture(w, h, 24, RenderTextureFormat.ARGB32); var go = new GameObject("__wl760cam"); var c = go.AddComponent(); c.CopyFrom(cam); c.targetTexture = rt; c.enabled = false; c.Render(); var prev = RenderTexture.active; RenderTexture.active = rt; var tex = new Texture2D(w, h, TextureFormat.RGB24, false); tex.ReadPixels(new Rect(0, 0, w, h), 0, 0); tex.Apply(); RenderTexture.active = prev; // 마젠타(셰이더 미컴파일) 픽셀 카운트 — 판정 ⓓ var px = tex.GetPixels32(); int magenta = 0; foreach (var p in px) if (p.r > 200 && p.b > 200 && p.g < 60) magenta++; var path = System.IO.Path.Combine(OutDir, tag + ".png"); System.IO.File.WriteAllBytes(path, tex.EncodeToPNG()); UnityEngine.Object.DestroyImmediate(tex); c.targetTexture = null; UnityEngine.Object.DestroyImmediate(go); rt.Release(); UnityEngine.Object.DestroyImmediate(rt); return path + " magentaPx=" + magenta + "/" + px.Length; } /// 씬 안의 살아 있는 슬래시 이펙트 인스턴스별 이미터 on/off 전수 (판정 ⓓ). public static object Mask() { var sb = new StringBuilder(); var masks = UnityEngine.Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); sb.AppendLine("SlashCrescentMask instances=" + masks.Length + " last=" + (WL.Combat.WeaponTrailDriver.LastMaskPrefab ?? "(none)")); foreach (var m in masks) sb.AppendLine(" " + m.gameObject.name + " active=" + m.gameObject.activeInHierarchy + " :: " + m.DumpState()); var trails = UnityEngine.Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); sb.AppendLine("BladeTrail instances=" + trails.Length); foreach (var t in trails) { var mr = t.GetComponent(); sb.AppendLine(" " + t.gameObject.name + " enabled=" + t.enabled + " rendererEnabled=" + (mr != null ? mr.enabled.ToString() : "(no MR)") + " mat=" + (mr != null && mr.sharedMaterial != null ? mr.sharedMaterial.name + "/" + (mr.sharedMaterial.shader != null ? mr.sharedMaterial.shader.name : "?") : "(none)")); } return sb.ToString(); } }