Project_WL/AgentScripts/WL909_Probe.cs

229 lines
14 KiB
C#

// WL909_Probe.cs — PD 지시 #909 시험(Play 중 · 섬)
// QTest(dist, sec) : 시험 몹을 PC 정면 dist(m)에 세우고(멈춤) Q(주력) → 매 프레임 상태 · 거리 + 결과(핫키 문장 · 대쉬 결과 · 방패 치기 명중)
// StunWatch(sec) : 켜진 몹의 기절(원본 CC Stun) 시작 순간마다 그때 PC 콤보 단계 · 직전 명중 단계 · PC 기절 능력치
// JumpTrace(dist, pauseAt) : 해머 Q 점프 — 매 프레임 PC 높이 · 상태 · 정규화 시간 · 속도 · 스킬 발동 · pauseAt 초 > 0 이면 그 순간 에디터 일시 정지
// Log()
using System.Text;
using UnityEngine;
using WL.Combat.Core;
public static class WL909_Probe
{
const string Key = "wl909log";
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 Actor Dummy()
{
var d = WL.IslandMob.WLTrailTest.Dummy;
if (d != null && d.isActiveAndEnabled) return d;
var pc = MyValue.MyPC; Actor best = null; float bd = float.MaxValue;
foreach (var m in Object.FindObjectsByType<MobActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
{
if (m == null || m.IsDead()) continue;
float dd = pc != null ? Vector3.Distance(pc.Get_position(), m.Get_position()) : 0f;
if (dd < bd) { bd = dd; best = m; }
}
return best;
}
static string Clip(Animator a)
{
if (a == null) return "-";
var ci = a.GetCurrentAnimatorClipInfo(0);
string s = ci.Length > 0 && ci[0].clip != null ? ci[0].clip.name : "-";
if (a.IsInTransition(0)) { var ni = a.GetNextAnimatorClipInfo(0); s += "→" + (ni.Length > 0 && ni[0].clip != null ? ni[0].clip.name : "-"); }
return s;
}
// ── 방패 치기 명중 기록 ──
static int s_bashHits; static string s_bashLast = "";
static void OnHit(in HitConfirmedEvent e)
{
if (e.projectile == null || e.projectile.m_SkillListTableData == null) return;
if (e.projectile.m_SkillListTableData.e_Skill != eEffect.Skill_ShieldAttack) return;
s_bashHits++; s_bashLast = e.victim != null ? e.victim.name : "-";
}
public static object QTest(float dist, float seconds)
{
var pc = MyValue.MyPC as PCActor; var d = Dummy();
if (pc == null || d == null) return "PC/몹 없음";
var f0 = pc.transform.forward; f0.y = 0f; if (f0.sqrMagnitude < 1e-4f) f0 = Vector3.forward; f0.Normalize();
// 가로막힘(물가 · 경계) 없는 방향을 고른다 — 45° 씩 돌려 NavMesh.Raycast 가 안 막히는 첫 방향
Vector3 want = pc.Get_position() + f0 * dist; UnityEngine.AI.NavMeshHit hit;
for (int k = 0; k < 8; k++)
{
var dir = Quaternion.Euler(0f, 45f * k, 0f) * f0;
if (!UnityEngine.AI.NavMesh.Raycast(pc.Get_position(), pc.Get_position() + dir * dist, out hit, UnityEngine.AI.NavMesh.AllAreas)) { want = pc.Get_position() + dir * dist; pc.transform.rotation = Quaternion.LookRotation(dir); break; }
}
if (UnityEngine.AI.NavMesh.SamplePosition(want, out hit, 1.5f, UnityEngine.AI.NavMesh.AllAreas)) want = hit.position;
d.Stop(true); d.Set_Warp(want);
pc.Change_Target(true, d);
CombatEvents.HitConfirmed.Remove(OnHit); CombatEvents.HitConfirmed.Add(OnHit);
int h0 = s_bashHits, a0 = WL.Combat.Weapon.WLSkillApproach.Arrivals, to0 = WL.Combat.Weapon.WLSkillApproach.Timeouts, br0 = WL.Combat.Weapon.WLSkillApproach.Broken, sf0 = CombatEvents.SkillFired.Raised;
var sb = Buf(); sb.Length = 0;
float t0 = Time.time; string last = "";
sb.Append("시작 거리 ").Append(Vector3.Distance(pc.Get_position(), d.Get_position()).ToString("F2"))
.Append(" · 인식 거리 ").Append(WL.Settings.WLTargetingSettings.RetargetSearchRange.ToString("0.##")).Append(" · 쫓기 ").Append(WL.Settings.WLTargetingSettings.ChaseRange.ToString("0.##"))
.Append(" · PC 대상 ").Append(pc.isTarget ? pc.Get_Target().name : "없음").Append(" · 상태 ").Append(pc.Get_CurAnim()).Append('\n');
UnityEngine.Events.UnityAction act = null;
act = () =>
{
if (pc == null || pc.m_animation == null || d == null) { Application.onBeforeRender -= act; return; }
float t = Time.time - t0;
string key = Clip(pc.m_animation) + " " + pc.Get_CurAnim();
if (key != last) { last = key; sb.AppendFormat("{0:F2} {1} · 거리 {2:F2} · 발동 +{3}", t, key, Vector3.Distance(pc.Get_position(), d.Get_position()), CombatEvents.SkillFired.Raised - sf0).Append('\n'); }
if (t > seconds)
{
Application.onBeforeRender -= act;
sb.AppendFormat("끝 · 거리 {0:F2} · 대쉬 도착 +{1} · 시간초과 +{2} · 끊김 +{3} · 방패 치기 명중 +{4}({5}) · 접근 {6}", Vector3.Distance(pc.Get_position(), d.Get_position()),
WL.Combat.Weapon.WLSkillApproach.Arrivals - a0, WL.Combat.Weapon.WLSkillApproach.Timeouts - to0, WL.Combat.Weapon.WLSkillApproach.Broken - br0, s_bashHits - h0, s_bashLast, WL.Combat.Weapon.WLSkillApproach.LastLog).Append('\n');
}
};
Application.onBeforeRender += act;
string r = WL.Combat.Weapon.WLSkillHotkey.Fire(1);
return "Q: " + r;
}
// ── 기절 출처 ──
static int s_lastHitStage = -1; static float s_lastHitAt = -1f; static string s_lastHitProj = "";
static void OnPcHit(in HitConfirmedEvent e)
{
var pc = e.attacker as PCActor; if (pc == null || !pc.IsMainPC()) return;
var ct = pc.GetComponent<WL.Combat.Core.ComboTracker>();
s_lastHitStage = ct != null ? ct.LastStage : -1; s_lastHitAt = Time.time;
s_lastHitProj = e.projectile != null && e.projectile.m_SkillListTableData != null ? e.projectile.m_SkillListTableData.e_Skill.ToString() : "기본";
}
static System.Collections.Generic.Dictionary<int, float> s_riseAt = new System.Collections.Generic.Dictionary<int, float>();
public static object StunWatch(float seconds)
{
var pc = MyValue.MyPC as PCActor; if (pc == null) return "PC 없음";
CombatEvents.HitConfirmed.Remove(OnPcHit); CombatEvents.HitConfirmed.Add(OnPcHit);
var sb = Buf(); sb.Length = 0;
var st = pc.Get_StatInfo();
sb.Append("PC 기절 확률 ").Append(st != null ? st.Get_Stat(eStat.Stun).ToString("0.###") : "-").Append(" · 누적 ").Append(st != null ? st.Get_Stat(eStat.StunAdd).ToString("0.###") : "-")
.Append(" · 시간 ").Append(st != null ? st.Get_Stat(eStat.StunTime).ToString("0.##") : "-").Append('\n');
var was = new System.Collections.Generic.Dictionary<int, bool>();
float t0 = Time.time; int n = 0;
UnityEngine.Events.UnityAction act = null;
act = () =>
{
float t = Time.time - t0;
foreach (var m in Object.FindObjectsByType<MobActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
{
if (m == null || m.IsDead()) continue;
bool on = m.Get_CCData(eCC.Stun).CCTime > 0f;
bool prev; was.TryGetValue(m.GetInstanceID(), out prev);
if (on && !prev && n < 40)
{
n++;
var ct = pc.GetComponent<WL.Combat.Core.ComboTracker>();
sb.AppendFormat("{0:F2} 기절 시작 {1} {2:F2}s · PC 단계 {3} · 직전 명중 단계 {4}({5}) {6:F2}s 전 · 띄움 {7} · 넉백 {8} · 날림 {9}", t, m.name, m.Get_CCData(eCC.Stun).CCTime,
ct != null ? ct.LastStage : -1, s_lastHitStage, s_lastHitProj, Time.time - s_lastHitAt, WL.Combat.Weapon.WLLaunch.Launches, WL.Combat.Weapon.WLLaunch.Knockbacks, WL.Combat.Weapon.WLLaunch.Flings).Append('\n');
}
if (!on && prev && n < 40) { float st0; if (s_riseAt.TryGetValue(m.GetInstanceID(), out st0)) sb.AppendFormat("{0:F2} 기절 끝 {1} · 유지 {2:F2}s", t, m.name, Time.time - st0).Append('\n'); }
if (on && !prev) s_riseAt[m.GetInstanceID()] = Time.time;
was[m.GetInstanceID()] = on;
}
if (t > seconds)
{
Application.onBeforeRender -= act;
var ty = System.Type.GetType("WL.Combat.Weapon.WLStageStunCancel, Assembly-CSharp");
var fc = ty != null ? ty.GetField("Cancelled") : null;
sb.Append("끝 · 기절 ").Append(n).Append(" · 1타 기절 풀기 ").Append(fc != null ? fc.GetValue(null).ToString() : "?").Append('\n');
}
};
Application.onBeforeRender += act;
return "기절 관찰 " + seconds + " s";
}
// ── 점프 타이밍 ──
public static object JumpTrace(float dist, float pauseAt)
{
var pc = MyValue.MyPC as PCActor; var d = Dummy();
if (pc == null || d == null) return "PC/몹 없음";
var f0 = pc.transform.forward; f0.y = 0f; if (f0.sqrMagnitude < 1e-4f) f0 = Vector3.forward; f0.Normalize();
d.Stop(true); d.Set_Warp(pc.Get_position() + f0 * dist);
var sb = Buf(); sb.Length = 0;
float t0 = Time.time, y0 = pc.transform.position.y; int sf0 = CombatEvents.SkillFired.Raised; bool paused = false;
UnityEngine.Events.UnityAction act = null;
act = () =>
{
if (pc == null || pc.m_animation == null) { Application.onBeforeRender -= act; return; }
float t = Time.time - t0; var a = pc.m_animation; var si = a.GetCurrentAnimatorStateInfo(0);
var ag = pc.Get_Agent();
sb.AppendFormat("{0:F2} 높이 {1:F2} · {2} n={3:F2} · 속도 {4:F2} · 발동 +{5} · {6}", t, pc.transform.position.y - y0, Clip(a), si.normalizedTime, a.speed, CombatEvents.SkillFired.Raised - sf0, pc.Get_CurAnim()).Append('\n');
if (!paused && pauseAt > 0f && t >= pauseAt) { paused = true; UnityEditor.EditorApplication.isPaused = true; sb.Append("■ 일시 정지").Append('\n'); }
if (t > 1.6f) Application.onBeforeRender -= act;
};
Application.onBeforeRender += act;
return "Q: " + WL.Combat.Weapon.WLSkillHotkey.Fire(1);
}
static string Snap(Actor d)
{
return "g" + WL.Combat.Impact.WLImpactFx.GroundShown + " sr" + WL.Combat.Weapon.WLStageHitReaction.Applied + " la" + WL.Combat.Weapon.WLLaunch.Launches + " kb" + WL.Combat.Weapon.WLLaunch.Knockbacks
+ " fl" + WL.Combat.Weapon.WLLaunch.Flings + " hc" + CombatEvents.HitConfirmed.Raised + " st" + WL.Combat.Reaction.MobStagger.StaggerCount + " ext" + WL.Combat.Reaction.MobStagger.Extended
+ " push" + WL.Combat.Reaction.MobStagger.Pushes + " vfxL" + WL.Combat.Weapon.WLSkillVfx.Launched + " kbCC" + (d != null ? d.Get_CCData(eCC.KnockBack).CCTime.ToString("F2") : "-");
}
/// <summary>시험 몹 1마리의 기절 시작 프레임 전후 카운터 비교(어느 계통이 걸었나).</summary>
public static object StunSource(float seconds)
{
var pc = MyValue.MyPC as PCActor; var d = Dummy(); if (pc == null || d == null) return "PC/몹 없음";
var sb = Buf(); sb.Length = 0;
float t0 = Time.time; bool was = false; string prev = Snap(d); int n = 0;
UnityEngine.Events.UnityAction act = null;
act = () =>
{
if (d == null) { Application.onBeforeRender -= act; return; }
float t = Time.time - t0; string now = Snap(d);
bool on = d.Get_CCData(eCC.Stun).CCTime > 0f;
if (on && !was && n < 12)
{
n++;
var ct = pc.GetComponent<WL.Combat.Core.ComboTracker>();
sb.AppendFormat("{0:F2} 기절 {1:F2}s · 단계 {2} · 이전 [{3}] → 지금 [{4}]", t, d.Get_CCData(eCC.Stun).CCTime, ct != null ? ct.LastStage : -1, prev, now).Append('\n');
}
was = on; prev = now;
if (t > seconds) { Application.onBeforeRender -= act; sb.Append("끝").Append('\n'); }
};
Application.onBeforeRender += act;
return "기절 출처 관찰";
}
/// <summary>시험 몹의 원본 StunAfterKnockBackTime(넉백 끝 기절 예약)이 바뀌는 프레임 — 그때 PC 단계 · 클립 · 스킬 발동 · 명중 수.</summary>
public static object StunArm(float seconds)
{
var pc = MyValue.MyPC as PCActor; var d = Dummy(); if (pc == null || d == null) return "PC/몹 없음";
var f = typeof(Actor).GetField("StunAfterKnockBackTime", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
if (f == null) return "필드 없음";
var sb = Buf(); sb.Length = 0;
float t0 = Time.time, last = (float)f.GetValue(d); int n = 0, sf0 = CombatEvents.SkillFired.Raised;
UnityEngine.Events.UnityAction act = null;
act = () =>
{
if (d == null) { Application.onBeforeRender -= act; return; }
float t = Time.time - t0, v = (float)f.GetValue(d);
if (v != last && n < 20)
{
n++;
var ct = pc.GetComponent<WL.Combat.Core.ComboTracker>();
sb.AppendFormat("{0:F2} 예약 {1:F2} → {2:F2} · 단계 {3} · {4} · 스킬발동 +{5} · 명중 {6} · 기절 {7:F2}", t, last, v, ct != null ? ct.LastStage : -1, Clip(pc.m_animation),
CombatEvents.SkillFired.Raised - sf0, CombatEvents.HitConfirmed.Raised, d.Get_CCData(eCC.Stun).CCTime).Append('\n');
last = v;
}
if (t > seconds) { Application.onBeforeRender -= act; sb.Append("끝").Append('\n'); }
};
Application.onBeforeRender += act;
return "예약 관찰 · 지금 " + last;
}
}