Project_WL/AgentScripts/WL761_Seq.cs

268 lines
15 KiB
C#

// WL761_Seq.cs — PD 지시 #761 + #762 런타임 검증 시퀀스 (Play 중 · Assets 무수정 · 서버 쓰기 없음)
// unity command run_script --file AgentScripts/WL761_Seq.cs --entry WL761_Seq.RunAt --args '[10101, "10101", 1.5, 1]'
// unity command run_script --file AgentScripts/WL761_Seq.cs --entry WL761_Seq.Report --args '["10101"]'
// unity command run_script --file AgentScripts/WL761_Seq.cs --entry WL761_Seq.SetArc --args '[0, 0, 0, 1.0, 0]' (euler xyz, scale, anchor)
// unity command run_script --file AgentScripts/WL761_Seq.cs --entry WL761_Seq.Cfg
// WL760_Seq 확장판: 클립/HP/마스크/트레일 + **PC 위치 시계열(#762)** + **PrefabArc 배치 정보(#761)** 를 기록하고
// 호가 놓이는 순간(LastArcInfo 변화)에 캡처한다. 결과 = Screenshots_WL/combat3/seq_<tag>.txt (Assets 밖).
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.AI;
public static class WL761_Seq
{
const string OutDir = "Screenshots_WL/combat3";
const BindingFlags BF = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
public static object RunAt(int classId, string tag, float dist, int noPet)
{
if (!Application.isPlaying) return "not playing";
var me = UnityEngine.Object.FindObjectsByType<MyActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).FirstOrDefault();
if (me == null) return "MyActor none";
if (GameObject.Find("__wl761seq") != null) return "sequence already running";
var host = new GameObject("__wl761seq").AddComponent<SeqHost>();
host.StartCoroutine(host.Co(me, classId, tag, dist, noPet != 0));
return "started " + tag + " dist=" + dist + " noPet=" + noPet;
}
public static object Report(string tag)
{
var p = System.IO.Path.Combine(OutDir, "seq_" + tag + ".txt");
return System.IO.File.Exists(p) ? System.IO.File.ReadAllText(p) : "(no report yet) " + p;
}
/// <summary>런타임 SlashTrailSettings 의 PrefabArc 값을 바꾼다(메모리만 · 디스크 저장 없음 · 캡처 반복용).</summary>
public static object SetArc(float ex, float ey, float ez, float scale, int anchor)
{
var st = WL.Combat.SlashTrailSettings.Instance;
if (st == null) return "settings null";
st.arcEulerOffset = new Vector3(ex, ey, ez);
st.arcScale = scale;
st.arcAnchor = (WL.Combat.ArcAnchor)anchor;
WL.Combat.WeaponTrailDriver.ClearArcCache();
return Cfg();
}
/// <summary>런타임 burstMode / suppressMode / drawRibbon / fitToBlade 를 바꾼다(메모리만 · 판정 ⓔ 롤백 확인용).</summary>
public static object SetBurst(int burstMode, int suppressMode, int drawRibbon, int fitToBlade)
{
var st = WL.Combat.SlashTrailSettings.Instance;
if (st == null) return "settings null";
st.burstMode = (WL.Combat.BurstMode)burstMode;
st.suppressMode = (WL.Combat.SuppressMode)suppressMode;
st.drawRibbon = drawRibbon != 0;
st.arcFitToBlade = fitToBlade != 0;
WL.Combat.WeaponTrailDriver.ClearArcCache();
return Cfg();
}
/// <summary>런타임 설정 요약.</summary>
public static object Cfg()
{
var st = WL.Combat.SlashTrailSettings.Instance;
var mo = WL.Combat.WLCombatMotionSettings.Instance;
var sb = new StringBuilder();
sb.AppendLine(st == null ? "SlashTrailSettings=null"
: string.Format("Slash: enable={0} burstMode={1} burstTiming={2} suppress={3} keep=[{4}] drawRibbon={5} autoOrient={6} euler={7} fit={8} scale={9} anchor={10} minSamples={11}",
st.enableWeaponTrail, st.burstMode, st.burstTiming, st.suppressMode,
st.keepEmitterNames != null ? string.Join(",", st.keepEmitterNames) : "", st.drawRibbon, st.arcAutoOrient,
st.arcEulerOffset.ToString("F0"), st.arcFitToBlade, st.arcScale, st.arcAnchor, st.arcMinSamples));
sb.AppendLine(mo == null ? "WLCombatMotionSettings=null"
: string.Format("Motion: enable={0} sec={1} scale={2} max={3} easeOut={4} stopDist={5}",
mo.enableAttackStep, mo.attackStepSeconds, mo.attackStepScale, mo.attackStepMaxDistance, mo.attackStepEaseOut, mo.attackStepStopDistance));
sb.AppendLine("lastArc=" + (WL.Combat.WeaponTrailDriver.LastArcInfo ?? "(none)"));
sb.AppendLine("step: count=" + WL.Combat.AttackStepDriver.StepCount + " planned=" + WL.Combat.AttackStepDriver.LastPlannedDistance.ToString("F2")
+ " actual=" + WL.Combat.AttackStepDriver.LastActualDistance.ToString("F2") + " " + (WL.Combat.AttackStepDriver.LastInfo ?? ""));
return sb.ToString();
}
/// <summary>런타임 모션 설정을 바꾼다(메모리만).</summary>
public static object SetMotion(int enable, float seconds, float scale)
{
var mo = WL.Combat.WLCombatMotionSettings.Instance;
if (mo == null) return "motion settings null";
mo.enableAttackStep = enable != 0;
if (seconds > 0f) mo.attackStepSeconds = seconds;
if (scale >= 0f) mo.attackStepScale = scale;
return Cfg();
}
class SeqHost : MonoBehaviour
{
static double HP(Actor a)
{
var mi = typeof(Actor).GetMethod("Get_HP", BF);
if (mi == null || a == null) return double.NaN;
try { return Convert.ToDouble(mi.Invoke(a, null)); } catch { return double.NaN; }
}
static void SuppressPets(bool suppress)
{
foreach (var p in UnityEngine.Object.FindObjectsByType<PetActor>(FindObjectsInactive.Include, FindObjectsSortMode.None))
if (p.gameObject.activeSelf == suppress) p.gameObject.SetActive(!suppress);
}
public IEnumerator Co(MyActor me, int classId, string tag, float dist, bool noPet)
{
var sb = new StringBuilder();
System.IO.Directory.CreateDirectory(OutDir);
if (noPet) SuppressPets(true);
var pc = me as PCActor;
if (classId > 0 && pc != null)
{
var data = table_classconfig.Ins.Get_Data_orNull(classId);
if (data == null) { sb.AppendLine("class data null " + classId); Done(tag, sb); yield break; }
pc.Change_Class(data, true);
yield return new WaitForSeconds(1.5f);
}
var anim = me.GetComponentInChildren<Animator>();
sb.AppendLine("class=" + classId + " ctrl=" + (anim != null && anim.runtimeAnimatorController != null ? anim.runtimeAnimatorController.name : "(null)")
+ " scene=" + UnityEngine.SceneManagement.SceneManager.GetActiveScene().name);
sb.AppendLine(Cfg().ToString());
var mobs = UnityEngine.Object.FindObjectsByType<MobActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None)
.Where(m => HP(m) > 0).OrderBy(m => Vector3.Distance(m.transform.position, me.transform.position)).ToArray();
if (mobs.Length == 0) { sb.AppendLine("no live MobActor"); Done(tag, sb); yield break; }
var t = mobs[0];
var dir = t.transform.position - me.transform.position; dir.y = 0f;
if (Mathf.Abs(dir.magnitude - dist) > 0.1f)
{
var pos = t.transform.position - dir.normalized * dist;
var na = me.GetComponent<NavMeshAgent>();
if (na != null && na.enabled) na.Warp(pos); else me.transform.position = pos;
}
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 { } }
yield return null;
var watch = UnityEngine.Object.FindObjectsByType<MobActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None)
.Where(m => HP(m) > 0 && Vector3.Distance(m.transform.position, me.transform.position) < 8f).ToList();
var lastHpAll = watch.ToDictionary(m => m, m => HP(m));
double hp0 = HP(t);
Vector3 pos0 = me.transform.position;
sb.AppendLine("target=" + t.name + " hp0=" + hp0.ToString("F0") + " d=" + Vector3.Distance(pos0, t.transform.position).ToString("F2")
+ " pcPos0=" + pos0.ToString("F2") + " watched=" + watch.Count);
double aspd = 1.0;
try
{
var fi = typeof(Actor).GetField("m_Stat", BF);
var stat = fi != null ? fi.GetValue(me) : null;
var gm = stat != null ? stat.GetType().GetMethod("Get_Stat", new[] { typeof(eStat) }) : null;
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") + ")");
float t0 = Time.time; string lastClip = ""; int caps = 0;
var seenMasks = new HashSet<int>();
var clipLog = new List<string>(); var hpLog = new List<string>(); var maskLog = new List<string>();
var arcLog = new List<string>(); var posLog = new List<string>(); var stepLog = new List<string>();
string lastArc = WL.Combat.WeaponTrailDriver.LastArcInfo;
int lastStep = WL.Combat.AttackStepDriver.StepCount;
float nextCapAt = -1f; string capClip = ""; float lastPosLog = -1f; float maxOffMesh = 0f;
Vector3 prevPos = pos0;
while (Time.time - t0 < 4.2f)
{
if (noPet) SuppressPets(true);
float el = Time.time - t0;
string clip = "(none)"; float nt = 0f;
if (anim != null && anim.layerCount > 0)
{
var ci = anim.GetCurrentAnimatorClipInfo(0);
if (ci.Length > 0) { clip = ci[0].clip.name; nt = anim.GetCurrentAnimatorStateInfo(0).normalizedTime; }
}
if (clip != lastClip) { clipLog.Add(el.ToString("F2") + "s " + clip); lastClip = clip; capClip = clip; }
// ── #762 PC 위치 시계열 (0.05 s 간격 + NavMesh 이탈 검사)
if (el - lastPosLog >= 0.05f)
{
lastPosLog = el;
Vector3 p = me.transform.position;
NavMeshHit nh; float off = 0f;
if (NavMesh.SamplePosition(p, out nh, 2f, NavMesh.AllAreas)) off = Vector3.Distance(p, nh.position);
else off = 999f;
if (off > maxOffMesh) maxOffMesh = off;
posLog.Add(string.Format("{0:F2}s pos={1} Δ0={2:F3} Δf={3:F3} off={4:F3} clip={5} n={6:F2}",
el, p.ToString("F2"), Vector3.Distance(pos0, p), Vector3.Distance(prevPos, p), off, clip, nt));
prevPos = p;
}
if (WL.Combat.AttackStepDriver.StepCount != lastStep)
{
lastStep = WL.Combat.AttackStepDriver.StepCount;
stepLog.Add(string.Format("{0:F2}s STEP#{1} planned={2:F2} clip={3}", el, lastStep, WL.Combat.AttackStepDriver.LastPlannedDistance, clip));
}
foreach (var m in watch)
{
if (m == null) continue;
double hp = HP(m); double prev = lastHpAll[m];
if (Math.Abs(hp - prev) > 0.01)
{
hpLog.Add(el.ToString("F2") + "s " + m.name + " " + prev.ToString("F0") + " -> " + hp.ToString("F0") + " (" + clip + ")");
lastHpAll[m] = hp;
}
}
foreach (var m in UnityEngine.Object.FindObjectsByType<WL.Combat.SlashCrescentMask>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
{
int id = m.GetInstanceID();
if (seenMasks.Add(id)) maskLog.Add(el.ToString("F2") + "s " + m.gameObject.name + " :: " + m.DumpState());
}
// ── #761 호가 놓이는 순간에 캡처
string arcNow = WL.Combat.WeaponTrailDriver.LastArcInfo;
if (arcNow != lastArc)
{
lastArc = arcNow;
arcLog.Add(el.ToString("F2") + "s " + arcNow);
nextCapAt = el + 0.03f;
}
if (nextCapAt > 0f && el >= nextCapAt)
{
nextCapAt = -1f;
yield return new WaitForEndOfFrame();
Texture2D tex = null;
try { tex = ScreenCapture.CaptureScreenshotAsTexture(); } catch { }
if (tex != null)
{
string safe = capClip.Replace("&", "n").Replace("/", "_");
var path = System.IO.Path.Combine(OutDir, "seq_" + tag + "_" + (++caps) + "_" + safe + ".png");
System.IO.File.WriteAllBytes(path, tex.EncodeToPNG());
UnityEngine.Object.Destroy(tex);
arcLog.Add(el.ToString("F2") + "s CAP " + path);
}
continue;
}
yield return null;
}
sb.AppendLine("== clips =="); foreach (var s in clipLog) sb.AppendLine(" " + s);
sb.AppendLine("== arc placements (" + arcLog.Count + ") =="); foreach (var s in arcLog) sb.AppendLine(" " + s);
sb.AppendLine("== slash masks (" + maskLog.Count + ") =="); foreach (var s in maskLog) sb.AppendLine(" " + s);
sb.AppendLine("== attack steps (" + stepLog.Count + ") =="); foreach (var s in stepLog) sb.AppendLine(" " + s);
sb.AppendLine("== pc position (" + posLog.Count + ") =="); foreach (var s in posLog) sb.AppendLine(" " + s);
sb.AppendLine("== hp changes (" + hpLog.Count + ") =="); foreach (var s in hpLog) sb.AppendLine(" " + s);
Vector3 pend = me.transform.position;
sb.AppendLine("final pcPos=" + pend.ToString("F2") + " totalΔ=" + Vector3.Distance(pos0, pend).ToString("F3") + " m"
+ " maxNavMeshOff=" + maxOffMesh.ToString("F3") + " m"
+ " dmgTotal=" + (hp0 - HP(t)).ToString("F0") + " hits=" + hpLog.Count);
if (noPet) SuppressPets(false);
Done(tag, sb);
}
void Done(string tag, StringBuilder sb)
{
System.IO.File.WriteAllText(System.IO.Path.Combine(OutDir, "seq_" + tag + ".txt"), sb.ToString());
Destroy(gameObject);
}
}
}