// ───────────────────────────────────────────────────────────────────────────── // WLBossArenaSettings.cs — 보스전 게이트 · 아레나 값의 단일 출처(SOT · C45) // // PD 지시 #813 · 발주서 WL-813d §1-4 (2026-09-09) // // 에셋 경로(고정): Assets/WL/Combat/Settings/Resources/WL/WLBossArenaSettings.asset // → Resources.Load("WL/WLBossArenaSettings") 로 1회 로드 후 캐시 // (WLCombatCoreSettings·WLLootSettings 등 기존 설정 SO 관례 그대로). // // ■ C8 롤백 (하나라도 false 면 BossArena 는 아무 일도 하지 않는다 = 813e 상시 배치 100%) // ① 에셋이 없다 → Active false // ② enabled = false → Active false // ③ RuntimeDisabled = true → Active false (진단·A/B 전용 · 도메인 리로드로 자동 해제) // ④ WLCombatCoreSettings.Enabled = false → Active false // 🔴 ④ 가 반드시 필요하다: 게이트는 CombatEvents.Killed/Spawned 구독으로만 열린다. // 코어가 꺼진 채 게이트를 걸면 이벤트가 0 이라 보스가 영원히 안 나온다. // 그래서 "코어가 꺼지면 게이트도 안 건다"(= 맵 로드 즉시 보스 배치)로 떨어뜨린다. // // ■ 아래 필드 기본값은 "SOT 부재 시의 실패 처리"가 아니라 에셋 생성 시 초기값이다. // 실제로 읽히는 값은 항상 .asset 이며, 코드에는 게임 수치 상수를 두지 않는다(C45). // // 🔴 어셈블리 주의: Assets/WL/Combat/ 에 .asmdef 를 만들지 말 것(FieldBossData/MobActor 가 Assembly-CSharp 에 있다). // ───────────────────────────────────────────────────────────────────────────── using UnityEngine; using WL.Combat.Core; namespace WL.Combat.Boss { /// 보스 등장 게이트 판정 방식. public enum eBossGateMode { /// 게이트 존이 「동시에」 생존 0 이 되는 순간(전멸). ZoneCleared = 0, /// 관측 존들의 누적 처치 수가 requiredKills 이상. KillCount = 1, /// 둘 중 먼저 만족하는 쪽(권장 — 리젠형 존에서 전멸이 안 잡혀도 열린다). Either = 2, /// 둘 다 만족해야 열린다. Both = 3, } [CreateAssetMenu(fileName = "WLBossArenaSettings", menuName = "WL/Boss Arena Settings", order = 326)] public sealed class WLBossArenaSettings : ScriptableObject { public const string ResourcesPath = "WL/WLBossArenaSettings"; static WLBossArenaSettings s_cached; static bool s_lookupDone; /// 설정 에셋(1회 로드 후 캐시). 없으면 null. public static WLBossArenaSettings Instance { get { if (!s_lookupDone) { s_cached = Resources.Load(ResourcesPath); s_lookupDone = true; } return s_cached; } } /// 진단·A/B 전용 런타임 스위치. 에셋을 건드리지 않고 게이트만 끈다(도메인 리로드로 자동 false). public static bool RuntimeDisabled; /// 게이트가 살아 있는가 = 에셋 존재 · enabled · 런타임 스위치 off · 코어 on. false 면 813e 상시 배치 그대로. public static bool Active { get { var i = Instance; return i != null && i.enabled && !RuntimeDisabled && WLCombatCoreSettings.Enabled; } } /// 테스트용: 캐시를 비워 다음 접근 때 다시 Resources.Load 한다(에셋 제거/복구 실험). public static void ClearCache() { s_cached = null; s_lookupDone = false; } // ─────────────────────────────────────────── 전체 스위치 [Header("전체 스위치")] [Tooltip("끄면 보스 등장 게이트·아레나 경계·처치 후 정리가 전부 비활성 — 813e 의 「맵 로드 즉시 상시 배치」 100%. 롤백 1순위.")] public bool enabled = true; [Tooltip("게이트 판정·리셋·정리를 Console 에 남긴다(검증용 · 기본 off).")] public bool verboseLog = false; // ─────────────────────────────────────────── 대상 보스 [Header("대상 보스 (WL_Nature · Anubis)")] [Tooltip("게이트를 걸 FieldBossData 의 n_SpawnerId. 다른 값의 FieldBossData 는 손대지 않는다(원본 즉시 배치). 0 이면 모든 FieldBossData 가 대상.")] public int bossSpawnerId = 813010; [Tooltip("보스의 MonsterList n_MonsterID. 추적 반경 데이터화 대상 행이다(0 이면 반경을 건드리지 않는다).")] public int bossMonsterId = 10006; // ─────────────────────────────────────────── 등장 게이트 [Header("등장 게이트 (존 전멸 → 보스 등장)")] [Tooltip("끄면 게이트를 걸지 않는다 — 보스는 맵 로드 즉시 등장(813e 그대로). 아레나 경계·정리만 쓰고 싶을 때.")] public bool gateEnabled = true; [Tooltip("생존/처치를 세는 존 SpawnerID 전체. 누적 처치(KillCount) 판정의 모수다.")] public int[] zoneSpawnerIds = new int[] { 813001, 813002, 813003, 813004, 813005 }; [Tooltip("「전멸」을 봐야 하는 존 SpawnerID. 이 존들이 동시에 생존 0 이 되면 게이트가 열린다(ZoneCleared).")] public int[] gateZoneSpawnerIds = new int[] { 813004, 813005 }; [Tooltip("판정 방식. 813e 존은 리젠형(Random/Order)이라 전멸이 순간적이다 → Either 권장.")] public eBossGateMode gateMode = eBossGateMode.Either; [Tooltip("KillCount 방식의 임계 — zoneSpawnerIds 누적 처치 수. 813e 존 동시 생존 합 = 12+15+14+8+5 = 54.")] public int requiredKills = 54; [Tooltip("true 면 「한 번도 스폰된 적 없는 존」은 전멸로 치지 않는다(맵 로드 직후 alive 0 오판 방지).")] public bool requireZoneSpawnedOnce = true; [Tooltip("조건 충족 후 실제 등장까지의 지연(초 · unscaled). 0 이면 같은 프레임에 등장. 컷인·배너를 앞세우려면 여기서 벌린다.")] public float gateOpenDelaySeconds = 0f; // ─────────────────────────────────────────── 아레나 [Header("아레나 경계 (813e 실측 중심 (139, 0.8, −20) · 반경 20)")] [Tooltip("끄면 추적 반경 데이터화·리쉬·리셋을 하지 않는다(게이트만 쓴다).")] public bool arenaEnabled = true; [Tooltip("true 면 아레나 중심 = FieldBossData 의 월드 좌표(813e 가 심은 그 자리). false 면 arenaCenter 를 쓴다.")] public bool useFieldBossPositionAsCenter = true; [Tooltip("useFieldBossPositionAsCenter = false 일 때의 아레나 중심(월드).")] public Vector3 arenaCenter = new Vector3(139f, 0.8f, -20f); [Tooltip("아레나 반경(m · XZ 평면). 813e 실측: 최근접 스포너까지 20.1 m.")] public float arenaRadius = 20f; [Tooltip("보스 등장 직전 MonsterList 행(bossMonsterId)의 f_BaseChaseRange 에 넣을 값. 0 이하면 테이블 값 그대로(원본 50).")] public float bossChaseRange = 20f; [Tooltip("보스 처치 후 정리 때 f_BaseChaseRange 를 원래 값으로 되돌린다(같은 세션에서 다른 맵이 같은 몬스터를 쓸 때 대비).")] public bool restoreChaseRangeOnCleanup = true; // ─────────────────────────────────────────── 리쉬 · 리셋 [Header("리쉬 · 리셋 (아레나 이탈)")] [Tooltip("끄면 보스가 아레나 밖으로 나가도 되돌리지 않는다(테이블 반경만으로 제어).")] public bool leashEnabled = true; [Tooltip("경계 검사 주기(초 · unscaled). 0 이하면 매 프레임.")] public float leashCheckIntervalSeconds = 0.25f; [Tooltip("보스가 아레나 중심에서 (반경 + 이 값) 을 넘으면 복귀시킨다.")] public float leashMargin = 2f; [Tooltip("PC 가 아레나 밖으로 나가면 보스를 리셋한다(발주서 §1-2 「SO 옵션」).")] public bool resetWhenPlayerLeavesArena = true; [Tooltip("PC 이탈 판정 여유 — PC 가 (반경 + 이 값) 밖일 때만 이탈로 본다.")] public float playerLeaveMargin = 4f; [Tooltip("PC 이탈이 이 시간(초 · unscaled) 이상 이어져야 리셋한다. 경계에서 왔다 갔다 할 때 리셋이 튀는 것을 막는다.")] public float playerLeaveGraceSeconds = 1.5f; [Tooltip("리셋 때 보스를 회복시킨다. On_Regen(reincarnation: true) 라 Spawned 이벤트는 다시 나지 않는다(연출 재발동 방지).")] public bool resetHealsBoss = true; [Tooltip("리셋 회복 비율(1 = 풀피).")] public float resetHealRate = 1f; // ─────────────────────────────────────────── 처치 후 정리 [Header("처치 후 정리")] [Tooltip("보스 처치 후 FieldBossData 의 RegenTime 재등장 코루틴을 끊는다(발주서 §1-3 「리젠 없음」).")] public bool suppressRegenAfterBossKill = true; [Tooltip("처치 → 정리까지의 지연(초 · unscaled). 킬캠(811d)·폭발 드랍(813f)·디졸브가 끝난 뒤에 손대려고 벌린다. RegenTime(60) 보다 작아야 한다.")] public float cleanupDelaySeconds = 3f; [Tooltip("정리 때 아레나 존 스포너(zoneSpawnerIds)를 비활성화한다. 기본 off — 켜면 남은 잡몹이 자식째 사라진다.")] public bool deactivateZonesAfterBossKill = false; [Tooltip("정리 때 게이트를 다시 잠근다(존을 다시 밀면 보스 재등장). 기본 off = 1회성.")] public bool rearmGateAfterBossKill = false; } }