// ───────────────────────────────────────────────────────────────────────────── // WLTrailTest.cs — 궤적(검기) 테스트 모드: 섬 몬스터 1마리만 등장 + 무적 // // PD 지시 (2026-10-10): 「우선 궤적 테스트를 위해 몬스터를 1마리만 등장시키고 무적 상태로 만들어줘.」 // // ■ 켜기/끄기 = PlayerPrefs `WL_TrailTest` (1/0) — 에디터 프로브 AgentScripts/staging/WL823/p23_trailtest_{on,off,state}.cs. // 빌드·세이브·표·프리팹 무변경. 끄면 원본 존 동작 그대로(C8 롤백 경로). // ■ 2026-10-15 PD 「섬에 타격감을 체크할 수 있도록 무적 몬스터 1기를 다시 추가」 — 상시 스위치 = `WLIslandMobSettings.hitTestDummy` // (몬스터 존이 꺼져 있으면 WLIslandMobZones 가 이 1마리 전용 자리만 만든다) · 종류 = `hitTestDummyMobId`(다른 종류는 회수하고 기다린다). // ■ 동작 — WLIslandMobZones.OnSpawned 의 두 줄 훅: // ① 더미가 살아 있는데 몹이 또 등장하면 즉시 회수(Actor.Off · 존 누적에 안 센다). 리젠은 원본대로 돌되 화면엔 1마리. // ② 첫 몹을 더미로 채택 — MaxHP·HP 를 큰 값으로 덮고(ApplyMobHp 뒤) 매 LateUpdate 되채운다. // 🔴 왜 m_bImmortal(Is_Immortal) 을 쓰지 않는가 — ActorInfo.Get_EnemyActors(:328)·Get_AlliesActors(:365) 가 // Is_Immortal 액터를 대상 목록에서 **제외**한다 → PC 자동 전투가 그 몹을 아예 때리지 않는다. // 궤적을 보려면 맞아야 하므로 「체력 되채움」으로 무적을 만든다. // 🔴 Actor.Get_Damage 는 HP 를 깎고 0 이하면 그 자리에서 죽이므로 LateUpdate 되채움만으론 한 방 사망을 못 막는다 // → MaxHP 자체를 큰 값으로 올린다(플레이어 한 방 피해보다 충분히 큼). // 🔴 Debug.LogError 금지(원본 ErrorLogHook 이 팝업을 띄운다). // ───────────────────────────────────────────────────────────────────────────── using UnityEngine; namespace WL.IslandMob { public static class WLTrailTest { public const string PrefsKey = "WL_TrailTest"; /// 더미 체력. 게임 표 값이 아니라 테스트 장치 값 — 플레이어 한 방 피해보다 충분히 크면 된다. public static double DummyHp = 1e9; /// 검증 전용 — 0/1 이면 PlayerPrefs 대신 이 값을 쓴다(-1 = PlayerPrefs). public static int Override = -1; public static Actor Dummy; public static int Adopted, Recalled, Refills; public static string LastLog = ""; /// 켜짐 = PlayerPrefs WL_TrailTest(에디터 프로브) 또는 설정 `WLIslandMobSettings.hitTestDummy`(2026-10-15 PD 「타격감 체크용 무적 몬스터 1기」 · 상시 스위치). public static bool Enabled { get { if (Override >= 0) return Override != 0; if (PlayerPrefs.GetInt(PrefsKey, 0) != 0) return true; var c = WLIslandMobSettings.Instance; return c != null && c.hitTestDummy != 0; } } public static bool DummyAlive { get { return Dummy != null && Dummy.isActiveAndEnabled && !Dummy.IsDead(); } } /// 더미로 쓸 종류인가 — 설정 `hitTestDummyMobId`(0 = 아무 몹). public static bool Wanted(Actor a) { var c = WLIslandMobSettings.Instance; int want = c != null ? c.hitTestDummyMobId : 0; return a != null && (want <= 0 || MonsterId(a) == want); } static System.Reflection.FieldInfo s_mobData; /// /// 몹의 MonsterList ID(n_MonsterID). 🔴 실측(2026-10-15): `Actor.Get_ID()` 는 섬 스포너 몹 전부 1 을 돌려준다(몬스터 ID 아님) → /// 원본 `MobActor.m_MonsterTableData`(protected · 공개 접근자 없음)를 읽는다. 못 읽으면 0. /// public static int MonsterId(Actor a) { var mob = a as MobActor; if (mob == null) return 0; if (s_mobData == null) s_mobData = typeof(MobActor).GetField("m_MonsterTableData", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); var d = s_mobData != null ? s_mobData.GetValue(mob) as MonsterTableData : null; return d != null ? d.n_MonsterID : 0; } /// /// 등장한 몹을 회수해야 하는가 — ① 더미가 이미 살아 있고 이 몹이 그 더미가 아닐 때 ② 더미가 없는데 원하는 종류가 아닐 때(다음 리젠을 기다린다). /// 반환 2 = ①(이 존의 리젠 예약도 멈춘다) · 1 = ②(리젠은 계속) · 0 = 회수 안 함. /// public static int RecallKind(Actor a) { if (!Enabled || a == null) return 0; if (DummyAlive) return ReferenceEquals(Dummy, a) ? 0 : 2; return Wanted(a) ? 0 : 1; } public static bool ShouldRecall(Actor a) { return RecallKind(a) != 0; } /// 첫 몹을 더미로 채택한다. ApplyMobHp 뒤에 불러야 HP 덮기가 남는다. public static void Adopt(Actor a) { if (!Enabled || a == null) return; if (DummyAlive && !ReferenceEquals(Dummy, a)) return; if (!Wanted(a)) return; Dummy = a; var d = a.GetComponent(); if (d == null) d = a.gameObject.AddComponent(); d.Begin(a); Adopted++; LastLog = a.name + "(몬스터 " + MonsterId(a) + ") 더미 채택 · HP " + a.Get_HP().ToString("F0") + "/" + a.Get_MaxHP().ToString("F0") + " · " + a.Get_position().ToString("F1"); } /// 보고·프로브용 한 줄. public static string Describe() { return "enabled=" + Enabled + " dummy=" + (Dummy != null ? Dummy.name : "-") + " alive=" + DummyAlive + " hp=" + (DummyAlive ? Dummy.Get_HP().ToString("F0") + "/" + Dummy.Get_MaxHP().ToString("F0") : "-") + " adopted=" + Adopted + " recalled=" + Recalled + " refills=" + Refills + " · " + LastLog; } } /// 더미 몹에 붙어 체력을 되채운다. 모드를 끄면 아무것도 하지 않는다(그때부터 평범하게 죽는다). public sealed class WLTrailTestDummy : MonoBehaviour { Actor _a; double _hp; public void Begin(Actor a) { _a = a; _hp = WLTrailTest.DummyHp; Fill(); } void Fill() { if (_a == null) return; var st = _a.Get_StatInfo(); if (st == null) return; if (_a.Get_MaxHP() < _hp) st.Set_Stat(eStat.MaxHP, _hp); if (_a.Get_HP() < _hp) { st.Set_Stat(eStat.HP, _hp); WLTrailTest.Refills++; } } void LateUpdate() { if (_a == null || !WLTrailTest.Enabled || _a.IsDead()) return; Fill(); } } }