546 lines
30 KiB
C#
546 lines
30 KiB
C#
// WL785_Probe.cs — PD 지시 #785/#786/#787 런타임 검증 (Play 전용 · 서버 쓰기 없음 · Assets 무수정)
|
|
// unity command run_script --file AgentScripts/WL785_Probe.cs --entry WL785_Probe.Combo --args '[10501, "combo10501", 1.2]'
|
|
// unity command run_script --file AgentScripts/WL785_Probe.cs --entry WL785_Probe.Dir --args '[10501, "dir10501"]'
|
|
// unity command run_script --file AgentScripts/WL785_Probe.cs --entry WL785_Probe.MoveAtk--args '["move10501"]'
|
|
// unity command run_script --file AgentScripts/WL785_Probe.cs --entry WL785_Probe.Dash --args '["dash30", 3.0]'
|
|
// unity command run_script --file AgentScripts/WL785_Probe.cs --entry WL785_Probe.Report --args '["combo10501"]'
|
|
// unity command run_script --file AgentScripts/WL785_Probe.cs --entry WL785_Probe.Regress
|
|
//
|
|
// 측정 항목
|
|
// ⓐ 콤보 : 클립 전이 시각 · 몹 HP 감소(=타격) 횟수/시각 · 콤보 총 소요
|
|
// ⓑ 리본 : 매 프레임 (드라이버가 읽은 실제 칼끝/손잡이) vs (리본이 마지막에 기록한 점) 오차 —
|
|
// 리본이 칼날 위에 생기는지의 직접 증거. 메시 마지막 리브의 정점 2개도 함께 잰다.
|
|
// ⓒ 예산 : UnityStats(setPass/batches/drawCalls/triangles) 를 전투 중 최대값으로 기록
|
|
// ⓓ 대쉬 : DashDriver 의 정적 카운터(시작 시각·이동 거리·공격 전환 여부)
|
|
//
|
|
// BladeTrail 은 HideFlags.DontSave 라 FindObjectsByType 에 잡히지 않는다 → 드라이버의 GetTrail(i) 로 읽는다.
|
|
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 WL785_Probe
|
|
{
|
|
const string OutDir = "Screenshots_WL/combat5";
|
|
const BindingFlags BF = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
|
|
|
// ── 진입점 ──────────────────────────────────────────────────────────────
|
|
public static object Combo(int classId, string tag, float dist)
|
|
{
|
|
return Start(h => h.CoCombo(classId, tag, dist), tag);
|
|
}
|
|
public static object Dir(int classId, string tag)
|
|
{
|
|
return Start(h => h.CoDir(classId, tag), tag);
|
|
}
|
|
public static object MoveAtk(string tag)
|
|
{
|
|
return Start(h => h.CoMoveAtk(tag), tag);
|
|
}
|
|
public static object Dash(string tag, float dist)
|
|
{
|
|
return Start(h => h.CoDash(tag, dist), tag);
|
|
}
|
|
/// <summary>스윙 한 번을 from~to 초 구간에서 step 간격으로 연속 캡처(리본 룩 육안 확인용).</summary>
|
|
public static object Burst(string tag, float from, float to, float step)
|
|
{
|
|
return Start(h => h.CoBurst(tag, from, to, step), tag);
|
|
}
|
|
|
|
static object Start(Func<Host, IEnumerator> make, string tag)
|
|
{
|
|
if (!Application.isPlaying) return "not playing";
|
|
if (GameObject.Find("__wl785probe") != null) return "probe already running";
|
|
var me = UnityEngine.Object.FindObjectsByType<MyActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).FirstOrDefault();
|
|
if (me == null) return "MyActor none";
|
|
var host = new GameObject("__wl785probe").AddComponent<Host>();
|
|
host.StartCoroutine(make(host));
|
|
return "started " + tag;
|
|
}
|
|
|
|
public static object Report(string tag)
|
|
{
|
|
var p = System.IO.Path.Combine(OutDir, "p_" + tag + ".txt");
|
|
return System.IO.File.Exists(p) ? System.IO.File.ReadAllText(p) : "(no report yet) " + p;
|
|
}
|
|
|
|
/// <summary>회귀 점검 — 펫·무적·어그로·자동교전 설정과 현재 씬 상태를 한 번에 읽는다.</summary>
|
|
public static object Regress()
|
|
{
|
|
if (!Application.isPlaying) return "not playing";
|
|
var sb = new StringBuilder();
|
|
var me = UnityEngine.Object.FindObjectsByType<MyActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).FirstOrDefault();
|
|
var pets = UnityEngine.Object.FindObjectsByType<PetActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
|
sb.AppendLine("scene=" + string.Join("+", Enumerable.Range(0, UnityEngine.SceneManagement.SceneManager.sceneCount)
|
|
.Select(i => UnityEngine.SceneManagement.SceneManager.GetSceneAt(i).name)));
|
|
sb.AppendLine("PC=" + (me != null ? me.name + " id=" + me.Get_ID() : "(none)") + " 활성 펫=" + pets.Length);
|
|
sb.AppendLine("spawnPlayerPet=" + WL.Settings.WLGameplaySettings.SpawnPlayerPet +
|
|
" playerInvincible=" + WL.Settings.WLGameplaySettings.PlayerInvincible);
|
|
sb.AppendLine("pcAggroOnHit=" + WL.Settings.WLTargetingSettings.PcAggroOnHit +
|
|
" autoEngage=" + WL.Settings.WLTargetingSettings.AutoEngage +
|
|
" retargetOnKill=" + WL.Settings.WLTargetingSettings.RetargetOnKill +
|
|
" attackStartRetarget=" + WL.Settings.WLTargetingSettings.AttackStartRetarget);
|
|
var st = WL.Combat.SlashTrailSettings.Instance;
|
|
sb.AppendLine("trail: enable=" + (st != null && st.enableWeaponTrail) + " drawRibbon=" + (st != null && st.drawRibbon) +
|
|
" burstMode=" + (st != null ? st.burstMode.ToString() : "?") +
|
|
" mat=" + (st != null && st.materialAdditive != null ? st.materialAdditive.name + "/" + st.materialAdditive.shader.name : "(null)"));
|
|
var mc = WL.Combat.WLCombatMotionSettings.Instance;
|
|
sb.AppendLine("motion: attackStep=" + (mc != null && mc.enableAttackStep) +
|
|
" dash=" + (mc != null && mc.dashEnabled) +
|
|
" window=[" + (mc != null ? mc.dashMinDistance + "," + mc.dashMaxDistance : "?") + "]" +
|
|
" dashStop=" + (mc != null ? mc.dashStopDistance.ToString() : "?"));
|
|
sb.AppendLine("DashDriver: count=" + WL.Combat.DashDriver.DashCount + " last=" + WL.Combat.DashDriver.LastInfo);
|
|
sb.AppendLine("AttackStep: count=" + WL.Combat.AttackStepDriver.StepCount + " last=" + (WL.Combat.AttackStepDriver.LastInfo ?? "(none)"));
|
|
sb.AppendLine(Budget("now"));
|
|
return sb.ToString();
|
|
}
|
|
|
|
public static string Budget(string label)
|
|
{
|
|
return "budget[" + label + "] setPass=" + UnityEditor.UnityStats.setPassCalls +
|
|
" batches=" + UnityEditor.UnityStats.batches +
|
|
" draw=" + UnityEditor.UnityStats.drawCalls +
|
|
" tris=" + UnityEditor.UnityStats.triangles +
|
|
" verts=" + UnityEditor.UnityStats.vertices;
|
|
}
|
|
|
|
// ── 코루틴 호스트 ────────────────────────────────────────────────────────
|
|
class Host : 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 Actor Target(Actor a)
|
|
{
|
|
var f = typeof(Actor).GetField("m_Target", BF);
|
|
return f != null ? f.GetValue(a) as Actor : null;
|
|
}
|
|
static void SuppressPets(bool s)
|
|
{
|
|
foreach (var p in UnityEngine.Object.FindObjectsByType<PetActor>(FindObjectsInactive.Include, FindObjectsSortMode.None))
|
|
if (p.gameObject.activeSelf == s) p.gameObject.SetActive(!s);
|
|
}
|
|
static double ASpd(Actor me)
|
|
{
|
|
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) return Convert.ToDouble(gm.Invoke(stat, new object[] { eStat.FinalAttackSpeed }));
|
|
}
|
|
catch { }
|
|
return 1.0;
|
|
}
|
|
static void SetTarget(Actor me, Actor t)
|
|
{
|
|
var m = typeof(Actor).GetMethod("Change_Target", BF);
|
|
if (m != null) { try { m.Invoke(me, new object[] { false, t, false }); } catch { } }
|
|
}
|
|
static MobActor Nearest(Actor me)
|
|
{
|
|
return UnityEngine.Object.FindObjectsByType<MobActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None)
|
|
.Where(m => HP(m) > 0)
|
|
.OrderBy(m => Vector3.Distance(m.transform.position, me.transform.position))
|
|
.FirstOrDefault();
|
|
}
|
|
static void WarpTo(Actor me, Vector3 pos)
|
|
{
|
|
var na = me.GetComponent<NavMeshAgent>();
|
|
NavMeshHit hit;
|
|
if (NavMesh.SamplePosition(pos, out hit, 4f, NavMesh.AllAreas)) pos = hit.position;
|
|
if (na != null && na.enabled) na.Warp(pos); else me.transform.position = pos;
|
|
}
|
|
static void Done(string tag, StringBuilder sb)
|
|
{
|
|
System.IO.Directory.CreateDirectory(OutDir);
|
|
System.IO.File.WriteAllText(System.IO.Path.Combine(OutDir, "p_" + tag + ".txt"), sb.ToString());
|
|
var go = GameObject.Find("__wl785probe");
|
|
if (go != null) UnityEngine.Object.Destroy(go);
|
|
}
|
|
IEnumerator Capture(string tag, string suffix, List<string> log, float el)
|
|
{
|
|
yield return new WaitForEndOfFrame();
|
|
Texture2D tex = null;
|
|
try { tex = ScreenCapture.CaptureScreenshotAsTexture(); } catch (Exception e) { log.Add("cap ex " + e.GetType().Name); }
|
|
if (tex != null)
|
|
{
|
|
System.IO.Directory.CreateDirectory(OutDir);
|
|
var path = System.IO.Path.Combine(OutDir, tag + "_" + suffix + ".png");
|
|
System.IO.File.WriteAllBytes(path, tex.EncodeToPNG());
|
|
UnityEngine.Object.Destroy(tex);
|
|
log.Add(el.ToString("F2") + "s CAP " + path);
|
|
}
|
|
}
|
|
|
|
// ── 리본 정합 측정 ───────────────────────────────────────────────────
|
|
class RibbonStat
|
|
{
|
|
public float maxTipErr, maxHiltErr, sumTipErr, sumHiltErr;
|
|
public int n, maxSamples, maxTris, maxVerts;
|
|
public string matInfo = "(none)", lastRib = "(none)";
|
|
public int interpolated;
|
|
public override string ToString()
|
|
{
|
|
return string.Format(
|
|
"리본정합 n={0} tip오차 max={1:F4}m avg={2:F4}m hilt오차 max={3:F4}m avg={4:F4}m " +
|
|
"샘플max={5} 삼각형max={6} 정점max={7} 보간샘플={8}\n mat={9}\n 마지막리브={10}",
|
|
n, maxTipErr, n > 0 ? sumTipErr / n : 0f, maxHiltErr, n > 0 ? sumHiltErr / n : 0f,
|
|
maxSamples, maxTris, maxVerts, interpolated, matInfo, lastRib);
|
|
}
|
|
}
|
|
// ★ 반드시 WaitForEndOfFrame 뒤에서 부른다. Update 타이밍에 재면 드라이버의 LateUpdate 가
|
|
// 아직 이번 프레임 샘플을 넣기 전이라, "칼날 현재 위치 vs 리본 마지막 점" 이 한 프레임 어긋난다.
|
|
static void SampleRibbon(RibbonStat rs)
|
|
{
|
|
var drivers = UnityEngine.Object.FindObjectsByType<WL.Combat.WeaponTrailDriver>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
|
foreach (var d in drivers)
|
|
{
|
|
if (!d.IsSwinging) continue;
|
|
for (int i = 0; i < d.ActiveSlotCount; i++)
|
|
{
|
|
var tr = d.GetTrail(i);
|
|
if (tr == null || tr.SampleCount < 1) continue;
|
|
int last = tr.SampleCount - 1;
|
|
float te = Vector3.Distance(d.GetTipWorld(i), tr.GetTip(last));
|
|
float he = Vector3.Distance(d.GetHiltWorld(i), tr.GetHilt(last));
|
|
rs.maxTipErr = Mathf.Max(rs.maxTipErr, te);
|
|
rs.maxHiltErr = Mathf.Max(rs.maxHiltErr, he);
|
|
rs.sumTipErr += te; rs.sumHiltErr += he; rs.n++;
|
|
rs.maxSamples = Mathf.Max(rs.maxSamples, tr.SampleCount);
|
|
rs.maxTris = Mathf.Max(rs.maxTris, tr.TriangleCount);
|
|
rs.interpolated = Mathf.Max(rs.interpolated, tr.InterpolatedSamples);
|
|
|
|
var mf = tr.GetComponent<MeshFilter>(); var mr = tr.GetComponent<MeshRenderer>();
|
|
if (mf != null && mf.sharedMesh != null)
|
|
{
|
|
rs.maxVerts = Mathf.Max(rs.maxVerts, mf.sharedMesh.vertexCount);
|
|
int vc = mf.sharedMesh.vertexCount;
|
|
if (vc >= 2)
|
|
{
|
|
var vs = mf.sharedMesh.vertices;
|
|
rs.lastRib = string.Format("inner={0} outer={1} 폭={2:F3}m | 실칼날 hilt={3} tip={4} 길이={5:F3}m",
|
|
vs[vc - 2].ToString("F3"), vs[vc - 1].ToString("F3"),
|
|
Vector3.Distance(vs[vc - 2], vs[vc - 1]),
|
|
d.GetHiltWorld(i).ToString("F3"), d.GetTipWorld(i).ToString("F3"),
|
|
Vector3.Distance(d.GetHiltWorld(i), d.GetTipWorld(i)));
|
|
}
|
|
}
|
|
if (mr != null && mr.sharedMaterial != null)
|
|
rs.matInfo = mr.sharedMaterial.name + " / " + mr.sharedMaterial.shader.name +
|
|
" materials=" + mr.sharedMaterials.Length + " enabled=" + mr.enabled;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── ⓐ 콤보 ───────────────────────────────────────────────────────────
|
|
public IEnumerator CoCombo(int classId, string tag, float dist)
|
|
{
|
|
var sb = new StringBuilder();
|
|
var me = UnityEngine.Object.FindObjectsByType<MyActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).First();
|
|
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("== #786 콤보 검증 class=" + classId + " ctrl=" +
|
|
(anim != null && anim.runtimeAnimatorController != null ? anim.runtimeAnimatorController.name : "(null)") + " ==");
|
|
|
|
var t = Nearest(me);
|
|
if (t == null) { sb.AppendLine("no live mob"); Done(tag, sb); yield break; }
|
|
var dir = t.transform.position - me.transform.position; dir.y = 0f;
|
|
WarpTo(me, t.transform.position - dir.normalized * dist);
|
|
me.transform.LookAt(new Vector3(t.transform.position.x, me.transform.position.y, t.transform.position.z));
|
|
SetTarget(me, t);
|
|
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) < 7f).ToList();
|
|
var lastHp = watch.ToDictionary(m => m, m => HP(m));
|
|
sb.AppendLine("target=" + t.name + " d=" + Vector3.Distance(me.transform.position, t.transform.position).ToString("F2") +
|
|
" watched=" + watch.Count);
|
|
|
|
me.Play_Attack(0, (float)ASpd(me));
|
|
float t0 = Time.time;
|
|
string lastClip = ""; int caps = 0;
|
|
var clipLog = new List<string>(); var hitLog = new List<string>(); var capLog = new List<string>();
|
|
var rs = new RibbonStat();
|
|
int maxSetPass = 0, maxBatch = 0, maxDraw = 0, maxTri = 0;
|
|
float nextCap = -1f; string capName = "";
|
|
float firstHit = -1f, lastHit = -1f;
|
|
|
|
while (Time.time - t0 < 4.0f)
|
|
{
|
|
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("F3") + "s " + clip + (anim.IsInTransition(0) ? " (전이중)" : ""));
|
|
lastClip = clip;
|
|
if (clip.IndexOf("Attack", StringComparison.OrdinalIgnoreCase) >= 0) { nextCap = el + 0.12f; capName = clip; }
|
|
}
|
|
foreach (var m in watch)
|
|
{
|
|
if (m == null) continue;
|
|
double hp = HP(m), prev = lastHp[m];
|
|
if (Math.Abs(hp - prev) > 0.01)
|
|
{
|
|
hitLog.Add(el.ToString("F3") + "s " + m.name + " " + prev.ToString("F0") + " -> " + hp.ToString("F0") +
|
|
" (clip=" + clip + " n=" + nt.ToString("F2") + ")");
|
|
lastHp[m] = hp;
|
|
if (firstHit < 0f) firstHit = el;
|
|
lastHit = el;
|
|
}
|
|
}
|
|
yield return new WaitForEndOfFrame();
|
|
SampleRibbon(rs);
|
|
maxSetPass = Mathf.Max(maxSetPass, UnityEditor.UnityStats.setPassCalls);
|
|
maxBatch = Mathf.Max(maxBatch, UnityEditor.UnityStats.batches);
|
|
maxDraw = Mathf.Max(maxDraw, UnityEditor.UnityStats.drawCalls);
|
|
maxTri = Mathf.Max(maxTri, UnityEditor.UnityStats.triangles);
|
|
|
|
if (nextCap > 0f && el >= nextCap)
|
|
{
|
|
nextCap = -1f;
|
|
yield return Capture(tag, (++caps) + "_" + capName.Replace("&", "n"), capLog, el);
|
|
continue;
|
|
}
|
|
yield return null;
|
|
}
|
|
|
|
sb.AppendLine("-- 클립 전이 --"); foreach (var s in clipLog) sb.AppendLine(" " + s);
|
|
sb.AppendLine("-- 타격(몹 HP 감소) 총 " + hitLog.Count + "회 --"); foreach (var s in hitLog) sb.AppendLine(" " + s);
|
|
sb.AppendLine("콤보 소요(첫 타격→마지막 타격) = " + (firstHit >= 0f ? (lastHit - firstHit).ToString("F3") + "s" : "?") +
|
|
" 첫 타격 " + firstHit.ToString("F3") + "s");
|
|
sb.AppendLine("-- " + rs + " --");
|
|
sb.AppendLine("budget(전투 중 최대) setPass=" + maxSetPass + " batches=" + maxBatch + " draw=" + maxDraw + " tris=" + maxTri);
|
|
sb.AppendLine("-- 캡처 --"); foreach (var s in capLog) sb.AppendLine(" " + s);
|
|
SuppressPets(false);
|
|
Done(tag, sb);
|
|
}
|
|
|
|
// ── ⓑ 4방향 ─────────────────────────────────────────────────────────
|
|
public IEnumerator CoDir(int classId, string tag)
|
|
{
|
|
var sb = new StringBuilder("== #785 4방향 공격 검증 class=" + classId + " ==\n");
|
|
var me = UnityEngine.Object.FindObjectsByType<MyActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).First();
|
|
SuppressPets(true);
|
|
var pc = me as PCActor;
|
|
if (classId > 0 && pc != null)
|
|
{
|
|
var data = table_classconfig.Ins.Get_Data_orNull(classId);
|
|
if (data != null) { pc.Change_Class(data, true); yield return new WaitForSeconds(1.5f); }
|
|
}
|
|
var anim = me.GetComponentInChildren<Animator>();
|
|
var t = Nearest(me);
|
|
if (t == null) { sb.AppendLine("no live mob"); Done(tag, sb); yield break; }
|
|
|
|
string[] names = { "N", "E", "S", "W" };
|
|
Vector3[] offs = { Vector3.forward, Vector3.right, Vector3.back, Vector3.left };
|
|
var capLog = new List<string>();
|
|
|
|
for (int k = 0; k < 4; k++)
|
|
{
|
|
// 몹의 북/동/남/서 1.2 m 지점에 서서 몹을 향해 때린다 → 스윙 방향이 4가지로 갈린다.
|
|
WarpTo(me, t.transform.position + offs[k] * 1.2f);
|
|
me.transform.LookAt(new Vector3(t.transform.position.x, me.transform.position.y, t.transform.position.z));
|
|
SetTarget(me, t);
|
|
yield return null;
|
|
yield return null;
|
|
|
|
var rs = new RibbonStat();
|
|
double hp0 = HP(t);
|
|
me.Play_Attack(0, (float)ASpd(me));
|
|
float t0 = Time.time; bool captured = false;
|
|
while (Time.time - t0 < 0.60f)
|
|
{
|
|
yield return new WaitForEndOfFrame();
|
|
SampleRibbon(rs);
|
|
if (!captured && Time.time - t0 > 0.38f)
|
|
{
|
|
captured = true;
|
|
yield return Capture(tag, names[k], capLog, Time.time - t0);
|
|
continue;
|
|
}
|
|
yield return null;
|
|
}
|
|
sb.AppendLine("[" + names[k] + "] pc=" + me.transform.position.ToString("F2") +
|
|
" fwd=" + me.transform.forward.ToString("F2") +
|
|
" dmg=" + (hp0 - HP(t)).ToString("F0"));
|
|
sb.AppendLine(" " + rs);
|
|
// 다음 방향 전에 콤보를 끊는다
|
|
me.Play_Idle();
|
|
yield return new WaitForSeconds(1.2f);
|
|
}
|
|
sb.AppendLine("-- 캡처 --"); foreach (var s in capLog) sb.AppendLine(" " + s);
|
|
SuppressPets(false);
|
|
Done(tag, sb);
|
|
}
|
|
|
|
// ── ⓒ 이동 중 공격 ───────────────────────────────────────────────────
|
|
public IEnumerator CoMoveAtk(string tag)
|
|
{
|
|
var sb = new StringBuilder("== #785 이동 중 공격(리본이 무기와 분리되지 않는가) ==\n");
|
|
var me = UnityEngine.Object.FindObjectsByType<MyActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).First();
|
|
SuppressPets(true);
|
|
var t = Nearest(me);
|
|
if (t == null) { sb.AppendLine("no live mob"); Done(tag, sb); yield break; }
|
|
var dir = t.transform.position - me.transform.position; dir.y = 0f;
|
|
WarpTo(me, t.transform.position - dir.normalized * 1.2f);
|
|
me.transform.LookAt(new Vector3(t.transform.position.x, me.transform.position.y, t.transform.position.z));
|
|
SetTarget(me, t);
|
|
yield return null;
|
|
|
|
var agent = me.GetComponent<NavMeshAgent>();
|
|
var rs = new RibbonStat();
|
|
var capLog = new List<string>();
|
|
Vector3 side = Vector3.Cross(Vector3.up, me.transform.forward).normalized;
|
|
|
|
me.Play_Attack(0, (float)ASpd(me));
|
|
float t0 = Time.time; bool cap = false;
|
|
Vector3 p0 = me.transform.position;
|
|
while (Time.time - t0 < 1.2f)
|
|
{
|
|
// 공격 도중 강제로 옆으로 민다 — 궤적이 캐릭터를 따라오는지 본다.
|
|
if (agent != null && agent.enabled && agent.isOnNavMesh) agent.Move(side * (2.5f * Time.deltaTime));
|
|
yield return new WaitForEndOfFrame();
|
|
SampleRibbon(rs);
|
|
if (!cap && Time.time - t0 > 0.32f) { cap = true; yield return Capture(tag, "moving", capLog, Time.time - t0); continue; }
|
|
yield return null;
|
|
}
|
|
sb.AppendLine("강제 측면 이동 = " + Vector3.Distance(p0, me.transform.position).ToString("F2") + "m");
|
|
sb.AppendLine(rs.ToString());
|
|
foreach (var s in capLog) sb.AppendLine(" " + s);
|
|
SuppressPets(false);
|
|
Done(tag, sb);
|
|
}
|
|
|
|
// ── 리본 룩 연속 캡처 ────────────────────────────────────────────────
|
|
public IEnumerator CoBurst(string tag, float from, float to, float step)
|
|
{
|
|
var sb = new StringBuilder("== 리본 연속 캡처 " + from + "~" + to + "s / " + step + "s ==\n");
|
|
var me = UnityEngine.Object.FindObjectsByType<MyActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).First();
|
|
SuppressPets(true);
|
|
var t = Nearest(me);
|
|
if (t == null) { sb.AppendLine("no live mob"); Done(tag, sb); yield break; }
|
|
var dir = t.transform.position - me.transform.position; dir.y = 0f;
|
|
WarpTo(me, t.transform.position - dir.normalized * 1.3f);
|
|
me.transform.LookAt(new Vector3(t.transform.position.x, me.transform.position.y, t.transform.position.z));
|
|
SetTarget(me, t);
|
|
me.Play_Idle();
|
|
yield return null; yield return null;
|
|
|
|
var rs = new RibbonStat();
|
|
var capLog = new List<string>();
|
|
me.Play_Attack(0, (float)ASpd(me));
|
|
float t0 = Time.time;
|
|
float next = from;
|
|
int idx = 0;
|
|
while (Time.time - t0 < to + 0.1f)
|
|
{
|
|
yield return new WaitForEndOfFrame();
|
|
SampleRibbon(rs);
|
|
float el = Time.time - t0;
|
|
if (el >= next && next <= to)
|
|
{
|
|
idx++;
|
|
yield return Capture(tag, idx.ToString("00") + "_" + Mathf.RoundToInt(el * 1000f) + "ms", capLog, el);
|
|
next += step;
|
|
continue;
|
|
}
|
|
yield return null;
|
|
}
|
|
sb.AppendLine(rs.ToString());
|
|
foreach (var s in capLog) sb.AppendLine(" " + s);
|
|
SuppressPets(false);
|
|
Done(tag, sb);
|
|
}
|
|
|
|
// ── ⓓ 대쉬 ──────────────────────────────────────────────────────────
|
|
public IEnumerator CoDash(string tag, float dist)
|
|
{
|
|
var sb = new StringBuilder("== #787 대쉬 검증 목표거리=" + dist.ToString("F1") + "m ==\n");
|
|
var me = UnityEngine.Object.FindObjectsByType<MyActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).First();
|
|
SuppressPets(true);
|
|
var t = Nearest(me);
|
|
if (t == null) { sb.AppendLine("no live mob"); Done(tag, sb); yield break; }
|
|
|
|
int dash0 = WL.Combat.DashDriver.DashCount;
|
|
var dir = t.transform.position - me.transform.position; dir.y = 0f;
|
|
WarpTo(me, t.transform.position - dir.normalized * dist);
|
|
me.transform.LookAt(new Vector3(t.transform.position.x, me.transform.position.y, t.transform.position.z));
|
|
me.Play_Idle();
|
|
SetTarget(me, t);
|
|
yield return null;
|
|
|
|
float d0 = Vector3.Distance(me.transform.position, t.transform.position);
|
|
double hp0 = HP(t);
|
|
sb.AppendLine("시작 실제거리=" + d0.ToString("F2") + "m pc=" + me.transform.position.ToString("F2") +
|
|
" target=" + t.name);
|
|
|
|
var anim = me.GetComponentInChildren<Animator>();
|
|
float t0 = Time.time;
|
|
string lastClip = ""; var log = new List<string>(); var capLog = new List<string>();
|
|
float dashStart = -1f, dashEnd = -1f, attackStart = -1f;
|
|
bool capped = false;
|
|
while (Time.time - t0 < 3.5f)
|
|
{
|
|
SuppressPets(true);
|
|
float el = Time.time - t0;
|
|
string clip = "(none)";
|
|
if (anim != null && anim.layerCount > 0)
|
|
{
|
|
var ci = anim.GetCurrentAnimatorClipInfo(0);
|
|
if (ci.Length > 0) clip = ci[0].clip.name;
|
|
}
|
|
if (clip != lastClip)
|
|
{
|
|
float dNow = Vector3.Distance(me.transform.position, t.transform.position);
|
|
log.Add(el.ToString("F3") + "s " + clip + " d=" + dNow.ToString("F2") + "m");
|
|
if (clip.IndexOf("Chase_Start", StringComparison.OrdinalIgnoreCase) >= 0 && dashStart < 0f) dashStart = el;
|
|
if (dashStart >= 0f && dashEnd < 0f && clip.IndexOf("Chase_Start", StringComparison.OrdinalIgnoreCase) < 0) dashEnd = el;
|
|
if (attackStart < 0f && clip.IndexOf("Attack", StringComparison.OrdinalIgnoreCase) >= 0) attackStart = el;
|
|
lastClip = clip;
|
|
}
|
|
if (!capped && dashStart >= 0f && el >= dashStart + 0.12f) { capped = true; yield return Capture(tag, "dash", capLog, el); continue; }
|
|
if (HP(t) < hp0 - 0.01) break; // 첫 타격이 들어가면 종료
|
|
yield return null;
|
|
}
|
|
float dEnd = Vector3.Distance(me.transform.position, t.transform.position);
|
|
sb.AppendLine("-- 클립 --"); foreach (var s in log) sb.AppendLine(" " + s);
|
|
sb.AppendLine("대쉬 진입=" + (dashStart >= 0f ? dashStart.ToString("F3") + "s" : "(없음)") +
|
|
" 대쉬 종료=" + (dashEnd >= 0f ? dashEnd.ToString("F3") + "s" : "-") +
|
|
" 공격 시작=" + (attackStart >= 0f ? attackStart.ToString("F3") + "s" : "(없음)"));
|
|
sb.AppendLine("이동 거리(거리 감소분)=" + (d0 - dEnd).ToString("F2") + "m 종료 거리=" + dEnd.ToString("F2") + "m" +
|
|
" 피해=" + (hp0 - HP(t)).ToString("F0"));
|
|
sb.AppendLine("DashDriver: count(+)=" + (WL.Combat.DashDriver.DashCount - dash0) +
|
|
" last=" + WL.Combat.DashDriver.LastInfo +
|
|
" startDist=" + WL.Combat.DashDriver.LastStartDistance.ToString("F2") +
|
|
" moved=" + WL.Combat.DashDriver.LastMovedDistance.ToString("F2") +
|
|
" endedInAttack=" + WL.Combat.DashDriver.LastEndedInAttack);
|
|
foreach (var s in capLog) sb.AppendLine(" " + s);
|
|
SuppressPets(false);
|
|
Done(tag, sb);
|
|
}
|
|
}
|
|
}
|