Project_WL/AgentScripts/WL923_Probe.cs

215 lines
18 KiB
C#

// WL923_Probe.cs — PD 지시 #923 시험(Play 중 · 섬)
// Arrows(seconds, dodgeOnCharge, dodgeEvery) : 활 기본 화살(Projectile_1040x)이 켜질 때마다(첫 프레임) 겉모습 · 원래 화살 메시 · 3타 겉모습 · PC 동작 ·
// PC 정면-화살 방향 각 · PC 정면-대상 방향 각을 기록 · dodgeOnCharge 1 = 3타 모으기 시작 순간 대상 반대쪽으로 구르기(3타 끊기) ·
// dodgeEvery > 0 = 그 간격(초)마다 대상 반대쪽으로 구르기 · 끝에 풀 인스턴스 수 · 「#923 전 방식이면 안 붙었을 새 인스턴스」 수
// Log() : 기록
using System.Text;
using UnityEngine;
public static class WL923_Probe
{
const string Key = "wl923log";
static StringBuilder Buf()
{
var sb = System.AppDomain.CurrentDomain.GetData(Key) as StringBuilder;
if (sb == null) { sb = new StringBuilder(); System.AppDomain.CurrentDomain.SetData(Key, sb); }
return sb;
}
public static object Log() { return Buf().ToString(); }
static float FlatAngle(Vector3 a, Vector3 b) { a.y = 0f; b.y = 0f; if (a.sqrMagnitude < 1e-6f || b.sqrMagnitude < 1e-6f) return -1f; return Vector3.Angle(a, b); }
static System.Collections.Generic.List<ProjectileBase> Pool(string name)
{
var f = typeof(ProjectileInfo).GetField("dic_projectile", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
var dic = f != null && ProjectileInfo.Ins != null ? f.GetValue(ProjectileInfo.Ins) as System.Collections.Generic.Dictionary<string, System.Collections.Generic.List<ProjectileBase>> : null;
System.Collections.Generic.List<ProjectileBase> list = null;
if (dic != null) dic.TryGetValue(name, out list);
return list;
}
static bool MeshShown(ProjectileBase pb)
{
foreach (var mr in pb.GetComponentsInChildren<MeshRenderer>(false)) if (mr.enabled && (mr.transform == pb.transform || mr.transform.parent == pb.transform)) return true;
return false;
}
/// <summary>PC 상태(공격이 안 나갈 때) — IsStop · CC · 피격 시간 · 대상 · 거리 · 조이스틱 · Auto.</summary>
public static object State()
{
var pc = MyValue.MyPC as PCActor; if (pc == null) return "PC 없음";
var bf = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public;
System.Func<string, object> F = n => { for (var t = pc.GetType(); t != null; t = t.BaseType) { var f = t.GetField(n, bf); if (f != null) return f.GetValue(pc); var p = t.GetProperty(n, bf); if (p != null) return p.GetValue(pc); } return "?"; };
var tg = pc.Get_Target();
return "동작 " + pc.Get_CurAnim() + " · IsStop " + F("IsStop") + " · CC " + pc.IsCC() + " · m_HitTime " + F("m_HitTime") + " · m_Auto " + F("m_Auto") + " · 조이 " + pc.JoystickStatus + " · 대상 " + (DSUtil.CheckNull(tg) ? "-" : tg.name + " 거리 " + Vector3.Distance(pc.Get_position(), tg.Get_position()).ToString("F2")) + " · isFollowOwner " + F("isFollowOwner") + " · 대쉬 " + WL.Combat.DashDriver.IsDashingNow(pc) + " · timeScale " + Time.timeScale + " · enabled " + pc.enabled;
}
/// <summary>대검 각성(칼날 폭풍 · WLBladeStorm) — slot 2 발동 · moveAt 초부터 moveSec 초 이동(PC 오른쪽) · dodgeAt > 0 이면 그때 구르기 · 0.25 초마다 상태 기록 · 끝 요약.</summary>
public static object Storm(float seconds, float moveAt, float moveSec, float dodgeAt)
{
var pc = MyValue.MyPC as PCActor; if (pc == null) return "PC 없음";
var sb = Buf(); sb.Length = 0; float t0 = Time.time, tl = -1f; bool moving = false, moved = false, dodged = false; var p0 = pc.Get_position(); Vector3 pm = Vector3.zero;
int k0 = WL.Combat.Weapon.WLBladeStorm.Ticks, h0 = WL.Combat.Weapon.WLBladeStorm.Hits, f0 = WL.Combat.Weapon.WLBladeStorm.FxSpawned, e0 = WL.Combat.Weapon.WLBladeStorm.Ends, a0 = WL.Combat.Weapon.WLBladeStorm.Aborts;
var right = pc.transform.right; right.y = 0f; right.Normalize(); float lastYaw = pc.transform.eulerAngles.y, yawSum = 0f; var d = WL.IslandMob.WLTrailTest.Dummy;
string res = WL.Combat.Weapon.WLSkillHotkey.Fire(2);
UnityEngine.Events.UnityAction act = null;
act = () =>
{
float t = Time.time - t0; var an = pc.m_animation; var si = an.GetCurrentAnimatorStateInfo(0);
float yaw = pc.transform.eulerAngles.y; yawSum += Mathf.Abs(Mathf.DeltaAngle(lastYaw, yaw)); lastYaw = yaw;
if (!moved && moveSec > 0f && t >= moveAt)
{
if (!moving) { moving = true; pm = pc.Get_position(); sb.AppendFormat("{0:F2} ▶ 이동 시작 · 조이 {1}", t, pc.JoystickStatus).Append('\n'); }
pc.Run_byJoystick(right);
if (t >= moveAt + moveSec) { moving = false; moved = true; var mv = pc.Get_position() - pm; mv.y = 0f; sb.AppendFormat("{0:F2} ■ 이동 끝 · {1:F2} m · 동작 {2}", t, mv.magnitude, pc.Get_CurAnim()).Append('\n'); }
}
if (!dodged && dodgeAt > 0f && t >= dodgeAt) { dodged = true; sb.AppendFormat("{0:F2} 구르기 {1}", t, DodgeAway(3f)).Append('\n'); }
if (t - tl >= 0.25f || t > seconds)
{
tl = t;
sb.AppendFormat("{0:F2} {1} · 상태 {2} n={3:F2} 속도 {4:F2} · 조이 {5} · 회전 누적 {6:F0}° · 판정 {7} 명중 {8} · 이펙트 {9}{10}", t, pc.Get_CurAnim(), si.IsName("Attack2") ? "Attack2" : si.IsName("idle") ? "idle" : si.IsName("dodge") ? "dodge" : si.shortNameHash.ToString(), si.normalizedTime, an.speed, pc.JoystickStatus, yawSum,
WL.Combat.Weapon.WLBladeStorm.Ticks - k0, WL.Combat.Weapon.WLBladeStorm.Hits - h0, WL.Combat.Weapon.WLBladeStorm.FxSpawned - f0, d != null ? " · 더미 거리 " + Vector3.Distance(d.Get_position(), pc.Get_position()).ToString("F2") : "").Append('\n');
}
if (t > seconds)
{
Application.onBeforeRender -= act; if (moving) { }
sb.AppendFormat("끝 · 판정 {0} · 명중 {1} · 이펙트 {2} · 정상 끝 +{3} · 끊김 +{4} · 마지막 회전 {5:F0}° / {6:F2} 초 · {7} · {8}", WL.Combat.Weapon.WLBladeStorm.Ticks - k0, WL.Combat.Weapon.WLBladeStorm.Hits - h0, WL.Combat.Weapon.WLBladeStorm.FxSpawned - f0,
WL.Combat.Weapon.WLBladeStorm.Ends - e0, WL.Combat.Weapon.WLBladeStorm.Aborts - a0, WL.Combat.Weapon.WLBladeStorm.LastSpinTotal, WL.Combat.Weapon.WLBladeStorm.LastSeconds, WL.Combat.Weapon.WLBladeStorm.LastLog, res).Append('\n');
}
};
Application.onBeforeRender += act;
return "칼날 폭풍 관찰 " + seconds + " 초 · " + res;
}
/// <summary>이펙트 프리팹을 y 1000 에 띄워 시각 t 마다 Simulate — 입자 있는 렌더러별 경계(루트 기준 중심 · 크기) · 전체 경계 · 「ground」 이름 렌더러 경계.</summary>
public static object FxBounds(string path, string timesCsv)
{
var prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(path); if (prefab == null) return "없음 " + path;
var root = new Vector3(0f, 1000f, 0f); var go = Object.Instantiate(prefab, root, prefab.transform.rotation); var sb = new StringBuilder();
foreach (var ts in timesCsv.Split(','))
{
float t = float.Parse(ts, System.Globalization.CultureInfo.InvariantCulture);
foreach (var ps in go.GetComponentsInChildren<ParticleSystem>(true)) if (ps.transform.parent == null || ps.transform.parent.GetComponentInParent<ParticleSystem>() == null) ps.Simulate(t, true, true, false);
Bounds all = new Bounds(); bool has = false; Bounds gr = new Bounds(); bool hg = false;
sb.Append("■ t ").Append(ts).Append('\n');
foreach (var r in go.GetComponentsInChildren<ParticleSystemRenderer>(true))
{
var ps = r.GetComponent<ParticleSystem>(); if (ps == null || ps.particleCount == 0 || !r.enabled) continue;
var b = r.bounds; b.center -= root;
if (!has) { all = b; has = true; } else all.Encapsulate(b);
if (r.name.StartsWith("ground") || r.name.StartsWith("floor")) { if (!hg) { gr = b; hg = true; } else gr.Encapsulate(b); }
sb.Append(" ").Append(r.transform.parent != null ? r.transform.parent.name + "/" : "").Append(r.name).Append(" n ").Append(ps.particleCount).Append(" 중심 ").Append(b.center.ToString("F2")).Append(" 크기 ").Append(b.size.ToString("F2")).Append('\n');
}
sb.Append(" 전체 중심 ").Append(has ? all.center.ToString("F2") : "-").Append(" 크기 ").Append(has ? all.size.ToString("F2") : "-").Append(" · 바닥 중심 ").Append(hg ? gr.center.ToString("F2") : "-").Append(" 크기 ").Append(hg ? gr.size.ToString("F2") : "-").Append('\n');
}
Object.Destroy(go);
return sb.ToString();
}
/// <summary>마지막 궤적(호) 배치 — 이벤트 · 종류 · 목표 법선/이등분 · 배치 법선 · 카메라 정면과의 |내적|(1 = 정면으로 보임 · 0 = 옆면 선) · PC 정면.</summary>
public static object Arc()
{
var p = WL.Combat.WeaponTrailDriver.LastPlacement; var cam = Camera.main; var pc = MyValue.MyPC as PCActor;
if (cam == null || pc == null) return "카메라/PC 없음";
var cf = cam.transform.forward;
return p.source + " → " + p.effect + " · " + p.kind + " · 목표 법선 " + p.targetNormal.ToString("F2") + " 이등분 " + p.targetBisector.ToString("F2") + " · 배치 법선 " + p.arcNormal.ToString("F2")
+ " · |법선·카메라| " + Mathf.Abs(Vector3.Dot(p.arcNormal.normalized, cf)).ToString("F2") + " · 카메라 정면 " + cf.ToString("F2") + " · PC 정면 " + pc.transform.forward.ToString("F2") + " · 호 기울임 " + WL.Combat.WeaponTrailDriver.ArcTilted;
}
/// <summary>시험용 — WLAttackFacing 를 이번 Play 동안만 끄고 켠다(비교 측정 · JSON 은 그대로).</summary>
public static object FacingOn(int on)
{
var f = typeof(WL.Combat.WLAttackFacing).GetField("s_cfg", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
var cfg = f != null ? f.GetValue(null) as WL.Combat.WLAttackFacingJson : null; if (cfg == null) return "설정 없음";
cfg.enabled = on; return "WLAttackFacing enabled = " + on;
}
/// <summary>시험용 — 대기 중인 스킬(m_Magic)을 비운다(NoAuto 로 쿨이 999 가 되면 걸린 스킬이 못 나가 기본 공격까지 막힌다).</summary>
public static object ClearMagic()
{
var pc = MyValue.MyPC as PCActor; if (pc == null) return "PC 없음";
var bf = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public;
for (var t = pc.GetType(); t != null; t = t.BaseType) { var f = t.GetField("m_Magic", bf); if (f != null) { var was = f.GetValue(pc); f.SetValue(pc, null); return "m_Magic " + (was == null ? "null" : "있음") + " → null"; } }
return "필드 없음";
}
/// <summary>시험 더미를 PC 정면 dist m 에 세운다(멈춤) · PC 상태 · 대상.</summary>
public static object Near(float dist)
{
var pc = MyValue.MyPC as PCActor; var d = WL.IslandMob.WLTrailTest.Dummy; if (pc == null) return "PC 없음";
if (d == null) return "더미 없음 · PC " + pc.Get_CurAnim() + " · 대상 " + (DSUtil.CheckNull(pc.Get_Target()) ? "-" : pc.Get_Target().name);
var f = pc.transform.forward; f.y = 0f; if (f.sqrMagnitude < 1e-4f) f = Vector3.forward; f.Normalize();
var want = pc.Get_position() + f * dist; UnityEngine.AI.NavMeshHit hit;
if (UnityEngine.AI.NavMesh.SamplePosition(want, out hit, 1.5f, UnityEngine.AI.NavMesh.AllAreas)) want = hit.position;
d.Stop(true); d.Set_Warp(want);
return "더미 " + d.name + " → 거리 " + Vector3.Distance(pc.Get_position(), d.Get_position()).ToString("F2") + " · PC " + pc.Get_CurAnim() + " · 대상 " + (DSUtil.CheckNull(pc.Get_Target()) ? "-" : pc.Get_Target().name);
}
/// <summary>대상 반대쪽으로 구르기(원본 WLDoubleTapDodge.TryDodge — 두 번 탭과 같은 경로).</summary>
public static string DodgeAway(float dist)
{
var pc = MyValue.MyPC as PCActor; var cam = Camera.main; if (pc == null || cam == null) return "PC/카메라 없음";
var t = pc.Get_Target(); Vector3 away = -pc.transform.forward;
if (!DSUtil.CheckNull(t)) { away = pc.Get_position() - t.Get_position(); away.y = 0f; }
if (away.sqrMagnitude < 1e-4f) away = -pc.transform.forward;
var sp = cam.WorldToScreenPoint(pc.Get_position() + away.normalized * dist);
var m = typeof(WL.Combat.WLDoubleTapDodge).GetMethod("TryDodge", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public);
if (m == null) return "TryDodge 없음";
m.Invoke(null, new object[] { new Vector2(sp.x, sp.y) });
return WL.Combat.WLDoubleTapDodge.LastLog;
}
public static object Arrows(float seconds, int dodgeOnCharge, float dodgeEvery)
{
var pc = MyValue.MyPC as PCActor; if (pc == null) return "PC 없음";
var sb = Buf(); sb.Length = 0; float t0 = Time.time, nextDodge = dodgeEvery > 0f ? dodgeEvery : -1f;
var wasActive = new System.Collections.Generic.Dictionary<int, bool>(); var everSeen = new System.Collections.Generic.HashSet<int>();
int ch0 = WL.Combat.Weapon.WLBowChargeReceiver.Charges, chBase = ch0, cons0 = WL.Combat.Weapon.WLBowChargeReceiver.Consumed, drop0 = WL.Combat.Weapon.WLBowChargeReceiver.Dropped;
int snap0 = WL.Combat.WLAttackFacing.Snaps;
int n = 0, noVis = 0, mesh = 0, chargedOther = 0, wouldMiss = 0, faceBad = 0, dodges = 0, chargedAll = 0; float faceMax = 0f;
UnityEngine.Events.UnityAction act = null;
act = () =>
{
float t = Time.time - t0;
if (dodgeOnCharge != 0 && WL.Combat.Weapon.WLBowChargeReceiver.Charges > ch0) { ch0 = WL.Combat.Weapon.WLBowChargeReceiver.Charges; dodges++; sb.AppendFormat("{0:F2} 3타 모으기 → 구르기 {1}", t, DodgeAway(3f)).Append('\n'); }
var dm = WL.IslandMob.WLTrailTest.Dummy; if (dm != null && pc.Get_CurAnim() != eAnim.Dodge && Vector3.Distance(dm.Get_position(), pc.Get_position()) > 6f) Near(4f);
if (nextDodge > 0f && t >= nextDodge) { nextDodge += dodgeEvery; dodges++; sb.AppendFormat("{0:F2} 구르기 {1}", t, DodgeAway(3f)).Append('\n'); }
foreach (var name in new[] { "Projectile_10402", "Projectile_10401" })
{
var list = Pool(name); if (list == null) continue;
foreach (var pb in list)
{
if (pb == null) continue; int id = pb.GetInstanceID(); bool on = pb.gameObject.activeInHierarchy; bool was; wasActive.TryGetValue(id, out was); wasActive[id] = on;
if (!on || was) continue;
var pd = pb.Get_ProjectTileData(); if (pd == null || !ReferenceEquals(pd.shooter, pc)) continue;
bool first = everSeen.Add(id);
if (first)
{ // #923 전 방식(HitboxSpawned 때 풀에서 찾기)은 이 순간 풀에 이미 붙은 인스턴스가 하나라도 있으면 새 인스턴스를 건너뛰었다
int attachedOthers = 0; foreach (var o in list) if (o != null && o != pb && o.transform.Find("~WL_Visual") != null) attachedOthers++;
if (attachedOthers > 0) wouldMiss++;
}
var vis = pb.transform.Find("~WL_Visual"); var chg = pb.transform.Find("~WL_Charged");
bool v = vis != null && vis.gameObject.activeSelf, c = chg != null && chg.gameObject.activeSelf, m = MeshShown(pb);
var cur = pc.Get_CurAnim(); var tg = pc.Get_Target();
float aArrow = FlatAngle(pc.transform.forward, pb.transform.forward);
float aTarget = DSUtil.CheckNull(tg) ? -1f : FlatAngle(pc.transform.forward, tg.Get_position() - pc.Get_position());
n++; if (!v && !c) noVis++; if (m) mesh++; if (c) chargedAll++; var ci = pc.m_animation.GetCurrentAnimatorClipInfo(0); string clip = ci.Length > 0 && ci[0].clip != null ? ci[0].clip.name : "-"; if (c && clip.IndexOf("Bow3") < 0) chargedOther++;
if (aArrow > faceMax) faceMax = aArrow; if (aArrow > 45f) faceBad++;
sb.AppendFormat("{0:F2} 화살 #{1} {2}{3} · 겉모습 {4} · 3타 {5} · 원래 메시 {6} · 동작 {7} {10} · 정면-화살 {8:F0}° · 정면-대상 {9:F0}°", t, n, name.Substring(11), first ? " 새 인스턴스" : "", v ? "O" : "X", c ? "O" : "-", m ? "보임" : "-", cur, aArrow, aTarget, clip).Append('\n');
}
}
if (t > seconds)
{
Application.onBeforeRender -= act;
var p2 = Pool("Projectile_10402"); var p1 = Pool("Projectile_10401");
sb.AppendFormat("끝 · 화살 {0} · 겉모습 없음 {1} · 원래 메시 보임 {2} · 3타 겉모습 {3}(3타 클립 아닐 때 {4}) · 정면-화살 45° 넘음 {5}(최대 {6:F0}°) · 구르기 {7} · 풀 10402 {8} / 10401 {9} · 옛 방식이면 안 붙었을 새 인스턴스 {10} · 강화 창 모으기 +{11} 사용 +{12} 닫음 +{13} · 방향 맞춤 +{14} 최근 {15:F0}°",
n, noVis, mesh, chargedAll, chargedOther, faceBad, faceMax, dodges, p2 != null ? p2.Count : 0, p1 != null ? p1.Count : 0, wouldMiss,
WL.Combat.Weapon.WLBowChargeReceiver.Charges - chBase, WL.Combat.Weapon.WLBowChargeReceiver.Consumed - cons0, WL.Combat.Weapon.WLBowChargeReceiver.Dropped - drop0,
WL.Combat.WLAttackFacing.Snaps - snap0, WL.Combat.WLAttackFacing.LastSnapDeg).Append('\n');
}
};
Application.onBeforeRender += act;
return "화살 관찰 " + seconds + " 초";
}
}