246 lines
13 KiB
C#
246 lines
13 KiB
C#
// 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<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 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<GameObject>())
|
|
{
|
|
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<MobActor>(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<Renderer>())
|
|
{
|
|
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();
|
|
}
|
|
|
|
/// <summary>캡처용 — 몹(흰색 · overlayOpacity)·PC(붉은색 · pcHitOverlayOpacity) 하이라이트를 켠 채로 둔다 / 끈다(실행 중만).</summary>
|
|
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<Renderer>(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<GameObject>())
|
|
{
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 섬 시험 몹 자리의 꺼진 몹 n 마리를 원본 On_Regen 으로 깨워 「남은 몹 수」와 「같은 프레임에 켜진 등장 이펙트 수」를 비교한다.
|
|
/// trail = WLTrailTest.Override(0 = 시험 몹 1마리 규칙 끔 → 존 동시 상한 3 이 거둔다 · -1 = 설정대로 = 전부 거둠).
|
|
/// </summary>
|
|
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<MobActor>(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();
|
|
}
|
|
|
|
/// <summary>SpawnTest 로 남긴 몹(시험 몹 외)을 다시 끈다.</summary>
|
|
public static object Cleanup()
|
|
{
|
|
int n = 0; var d = WL.IslandMob.WLTrailTest.Dummy;
|
|
foreach (var m in Object.FindObjectsByType<MobActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
|
|
if (m != null && !ReferenceEquals(m, d)) { m.Off(); n++; }
|
|
return "끔 " + n;
|
|
}
|
|
|
|
/// <summary>시험 몹을 PC 정면 dist(m)에 세우고 멈춘다(실행 중만) — 대쉬·공격 전진이 어디서 멈추는지 재기 위해.</summary>
|
|
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();
|
|
}
|
|
}
|