// WL893_Probe.cs — PD 지시 #893 시험(Play 중 · 섬) // State : 몬스터 등장 이펙트 인스턴스 수 · 몹 수 · 더미 회수 수 · 피격 하이라이트 카운터 · 인식 거리 // PcHit : PC 피격을 원본 구독 경로(PcHitReaction.ProbeDamaged · 시험용 무적과 같은 invincible=true)로 흘려 하이라이트 값·남은 시간을 읽는다 // MobHit : 더미 몹 피격 확정을 원본 구독 경로(MobHitFlash.OnHitConfirmed)로 흘려 하이라이트 값·켜짐 시간을 읽는다 // MobLast: 자연 전투에서 마지막 몹 하이라이트의 켜짐 시간(blinkOffAt - blinkStartAt) using System.Collections; using System.Reflection; using System.Text; using UnityEngine; using WL.Combat.Core; using WL.Combat.Reaction; public static class WL893_Probe { static readonly int kHF = Shader.PropertyToID("_HitFlash"), kHFC = Shader.PropertyToID("_HitFlashColor"); 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(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 object Field(object o, string name) { if (o == null) return null; var f = o.GetType().GetField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); return f != null ? f.GetValue(o) : null; } static object StateOf(System.Type t, Actor a) { var f = t.GetField("s_states", BindingFlags.NonPublic | BindingFlags.Static); var dic = f != null ? f.GetValue(null) as IDictionary : null; return dic != null && a != null && dic.Contains(a) ? dic[a] : null; } static string Mpb(Renderer r) { var mpb = new MaterialPropertyBlock(); r.GetPropertyBlock(mpb); var c = mpb.GetColor(kHFC); return mpb.GetFloat(kHF).ToString("0.##") + "(" + c.r.ToString("0.##") + "," + c.g.ToString("0.##") + "," + c.b.ToString("0.##") + ")"; } static bool HasPass(Renderer r) { var mats = r.sharedMaterials; return mats != null && mats.Length > 0 && WL.Look.Character.WLCharacterBacklight.IsOurs(mats[mats.Length - 1]); } public static object State() { var sb = new StringBuilder(); int fxAll = 0, fxActive = 0; foreach (var go in Resources.FindObjectsOfTypeAll()) { if (go == null || !go.scene.IsValid() || !go.name.StartsWith("Effect_MonsterSpawn")) continue; var p = go.transform.parent; if (p != null && p.name.StartsWith("Effect_MonsterSpawn")) continue; fxAll++; if (go.activeInHierarchy) fxActive++; } int mobAll = 0, mobActive = 0; foreach (var m in Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None)) { mobAll++; if (m.isActiveAndEnabled) mobActive++; } sb.Append("등장 이펙트 인스턴스 ").Append(fxAll).Append("(켜짐 ").Append(fxActive).Append(") · 몹 켜짐 ").Append(mobActive).Append('/').Append(mobAll) .Append(" · Spawned ").Append(CombatEvents.Spawned.Raised) .Append(" · 더미 회수 ").Append(WL.IslandMob.WLTrailTest.Recalled).Append(" · 채택 ").Append(WL.IslandMob.WLTrailTest.Adopted).Append('\n'); sb.Append("몹 하이라이트 on ").Append(MobHitFlash.OnCount).Append(" · 덮기 ").Append(MobHitFlash.OverlayBlinks).Append(" · 예전 ").Append(MobHitFlash.LegacyBlinks) .Append(" | PC 피격 이벤트 ").Append(CombatEvents.Damaged.Raised).Append(" · PC 하이라이트 on ").Append(PcHitReaction.FlashOnCount).Append(" · 걸러짐 ").Append(PcHitReaction.SkippedFilter).Append(" · 덮기 렌더러 ").Append(PcHitReaction.OverlayRenderers).Append('\n'); sb.Append("인식 거리 고르기 ").Append(WL.Settings.WLTargetingSettings.RetargetSearchRange.ToString("0.##")).Append(" · 쫓기 ").Append(WL.Settings.WLTargetingSettings.ChaseRange.ToString("0.##")) .Append(" | ").Append(WL.Combat.Weapon.WLWeaponAttackPattern.Dump()); var pc = MyValue.MyPC; var d = Dummy(); if (pc != null && d != null) sb.Append("\n몹 ").Append(d.name).Append(" 거리 ").Append(Vector3.Distance(pc.Get_position(), d.Get_position()).ToString("0.##")) .Append(" · PC 타깃 ").Append(pc.isTarget ? pc.Get_Target().name : "없음"); return sb.ToString(); } public static object PcHit() { var pc = MyValue.MyPC; var d = Dummy(); if (pc == null) return "PC 없음"; int on0 = PcHitReaction.FlashOnCount, sk0 = PcHitReaction.SkippedFilter; PcHitReaction.ProbeDamaged(pc, d, 10d, true); var s = StateOf(typeof(PcHitReaction), pc); float off = s != null ? (float)Field(s, "flashOffAt") : -1f; var sb = new StringBuilder(); sb.Append("PC 하이라이트 on +").Append(PcHitReaction.FlashOnCount - on0).Append(" · 걸러짐 +").Append(PcHitReaction.SkippedFilter - sk0) .Append(" · 남은 시간 ").Append(s != null ? (off - ImpactRunner.Now).ToString("0.###") : "상태 없음").Append('\n'); int n = 0; foreach (var r in pc.GetComponentsInChildren()) { if (r == null || r is ParticleSystemRenderer || !HasPass(r)) continue; if (n < 4) sb.Append(r.name).Append(' ').Append(Mpb(r)).Append(" · "); n++; } sb.Append("덧그리기 패스 렌더러 ").Append(n); return sb.ToString(); } public static object MobHit() { var pc = MyValue.MyPC; var d = Dummy(); if (d == null) return "몹 없음"; var e = new HitConfirmedEvent { victim = d, attacker = pc, damage = 10d, byMainPC = true, time = Time.unscaledTime, frame = Time.frameCount }; var m = typeof(MobHitFlash).GetMethod("OnHitConfirmed", BindingFlags.NonPublic | BindingFlags.Static); if (m == null) return "OnHitConfirmed 없음"; m.Invoke(null, new object[] { e }); return MobLast(); } /// 캡처용 — 몹(흰색 · overlayOpacity)·PC(붉은색 · pcHitOverlayOpacity) 하이라이트를 켠 채로 둔다 / 끈다(실행 중만). public static object Look(string mode) { bool on = mode == "on"; var pc = MyValue.MyPC; var d = Dummy(); var st = WLHitBlinkSettings.Instance; var it = WLImpactTierSettings.Instance; string mob = d != null ? MobHitFlash.SetTellFlash(d, on ? st.overlayOpacity : 0f, on ? st.overlayColor : Color.white).ToString() : "몹 없음"; int n = 0; if (pc != null) foreach (var r in pc.GetComponentsInChildren(true)) { if (r == null || r is ParticleSystemRenderer || !HasPass(r)) continue; var mpb = new MaterialPropertyBlock(); r.GetPropertyBlock(mpb); mpb.SetFloat(kHF, on ? it.pcHitOverlayOpacity : 0f); mpb.SetColor(kHFC, it.pcHitOverlayColor); r.SetPropertyBlock(mpb); n++; } return "look " + mode + " · 몹 " + mob + " · PC 렌더러 " + n; } public static object Ring() { var pc = MyValue.MyPC; return "원 반지름 " + WL.Combat.RangeRing.WLRangeRing.LastRadius.ToString("0.##") + " · 공격 거리 " + WL.Combat.RangeRing.WLRangeRing.LastBaseStop.ToString("0.##") + " · 보임 " + WL.Combat.RangeRing.WLRangeRing.Visible + " · 알파 " + WL.Combat.RangeRing.WLRangeRing.Alpha.ToString("0.##") + " · PC 공격 거리(표) " + (pc != null ? pc.WL_AttackRange.ToString("0.##") : "-") + " · 인식 " + WL.Settings.WLTargetingSettings.RetargetSearchRange.ToString("0.##") + "/" + WL.Settings.WLTargetingSettings.ChaseRange.ToString("0.##") + " · PC 타깃 " + (pc != null && pc.isTarget ? pc.Get_Target().name : "없음"); } static int ActiveSpawnFx() { int n = 0; foreach (var go in Resources.FindObjectsOfTypeAll()) { if (go == null || !go.scene.IsValid() || !go.activeInHierarchy || !go.name.StartsWith("Effect_MonsterSpawn")) continue; var p = go.transform.parent; if (p != null && p.name.StartsWith("Effect_MonsterSpawn")) continue; n++; } return n; } /// /// 섬 시험 몹 자리의 꺼진 몹 n 마리를 원본 On_Regen 으로 깨워 「남은 몹 수」와 「같은 프레임에 켜진 등장 이펙트 수」를 비교한다. /// trail = WLTrailTest.Override(0 = 시험 몹 1마리 규칙 끔 → 존 동시 상한 3 이 거둔다 · -1 = 설정대로 = 전부 거둠). /// public static object SpawnTest(int n, int trail) { var cfg = WL.IslandMob.WLIslandMobSettings.Instance; int group = cfg != null ? cfg.spawnerId : 0; int before = ActiveSpawnFx(); int prev = WL.IslandMob.WLTrailTest.Override; WL.IslandMob.WLTrailTest.Override = trail; int regen = 0, kept = 0; try { foreach (var m in Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None)) { if (regen >= n) break; if (m == null || m.gameObject.activeSelf || m.Get_Group() != group) continue; m.On_Regen(); regen++; if (m.gameObject.activeSelf) kept++; } } finally { WL.IslandMob.WLTrailTest.Override = prev; } return "깨움 " + regen + " · 남은 몹 " + kept + " · 켜진 등장 이펙트 " + before + " → " + ActiveSpawnFx(); } /// SpawnTest 로 남긴 몹(시험 몹 외)을 다시 끈다. public static object Cleanup() { int n = 0; var d = WL.IslandMob.WLTrailTest.Dummy; foreach (var m in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) if (m != null && !ReferenceEquals(m, d)) { m.Off(); n++; } return "끔 " + n; } /// 시험 몹을 PC 정면 dist(m)에 세우고 멈춘다(실행 중만) — 대쉬·공격 전진이 어디서 멈추는지 재기 위해. public static object Freeze(float dist) { var pc = MyValue.MyPC; var d = Dummy(); if (pc == null || d == null) return "PC/몹 없음"; 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 "몹 " + Vector3.Distance(pc.Get_position(), d.Get_position()).ToString("0.##") + " m 에 세움 · 대쉬 수 " + WL.Combat.DashDriver.DashCount + " · 원 " + WL.Combat.RangeRing.WLRangeRing.AttackReach(pc).ToString("0.##") + " · 멈춤 거리 " + WL.Combat.WLCombatMotionSettings.ApproachStop(pc, -1f).ToString("0.##"); } public static object Unfreeze() { var d = Dummy(); if (d == null) return "몹 없음"; d.Stop(false); return "몹 다시 움직임"; } public static object Dist() { var pc = MyValue.MyPC; var d = Dummy(); if (pc == null || d == null) return "PC/몹 없음"; return "거리 " + Vector3.Distance(pc.Get_position(), d.Get_position()).ToString("0.##") + " · " + pc.Get_CurAnim() + " | 대쉬 " + WL.Combat.DashDriver.DashCount + " · " + WL.Combat.DashDriver.LastInfo + " | 원 반지름 " + WL.Combat.RangeRing.WLRangeRing.LastRadius.ToString("0.##") + " · 알파 " + WL.Combat.RangeRing.WLRangeRing.Alpha.ToString("0.##") + " · 궤적·대쉬 게이트 사거리 " + WL.Combat.WeaponTrailDriver.LastAttackReach.ToString("0.##") + " · 대쉬 억제 " + WL.Combat.DashDriver.BlockedByReachCount; } public static object EquipGroup(int groupNo) { if (!WL.Equip.WLInventory.EnsureLoaded()) return "인벤토리 로드 실패"; foreach (var it in WL.Equip.WLInventory.SortedItems()) if (it != null && it.itemJson != null && it.itemJson.GroupNo == groupNo) return "장착 " + groupNo + " → " + WL.Equip.WLInventory.Equip(it.rawItem._id); return "인벤토리에 없음 " + groupNo; } public static object MobLast() { var d = Dummy(); if (d == null) return "몹 없음"; var s = StateOf(typeof(MobHitFlash), d); if (s == null) return "상태 없음(아직 안 맞음)"; float start = (float)Field(s, "blinkStartAt"), off = (float)Field(s, "blinkOffAt"); var rs = Field(s, "renderers") as Renderer[]; var sb = new StringBuilder(); sb.Append(d.name).Append(" 켜짐 시간 ").Append((off - start).ToString("0.###")).Append(" · 켜짐 ").Append(Field(s, "blinkOn")) .Append(" · 덮기 패스 ").Append(rs != null && rs.Length > 0 && WL.Look.Character.WLCharacterBacklight.HasOverlay(rs[0])).Append(" · "); if (rs != null) for (int i = 0; i < rs.Length && i < 2; i++) if (rs[i] != null) sb.Append(rs[i].name).Append(' ').Append(Mpb(rs[i])).Append(' '); return sb.ToString(); } }