Project_WL/Assets/WL/Combat/Boss/WLBossArenaSettings.cs

181 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ─────────────────────────────────────────────────────────────────────────────
// WLBossArenaSettings.cs — 보스전 게이트 · 아레나 값의 단일 출처(SOT · C45)
//
// PD 지시 #813 · 발주서 WL-813d §1-4 (2026-09-09)
//
// 에셋 경로(고정): Assets/WL/Combat/Settings/Resources/WL/WLBossArenaSettings.asset
// → Resources.Load<WLBossArenaSettings>("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
{
/// <summary>보스 등장 게이트 판정 방식.</summary>
public enum eBossGateMode
{
/// <summary>게이트 존이 「동시에」 생존 0 이 되는 순간(전멸).</summary>
ZoneCleared = 0,
/// <summary>관측 존들의 누적 처치 수가 requiredKills 이상.</summary>
KillCount = 1,
/// <summary>둘 중 먼저 만족하는 쪽(권장 — 리젠형 존에서 전멸이 안 잡혀도 열린다).</summary>
Either = 2,
/// <summary>둘 다 만족해야 열린다.</summary>
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;
/// <summary>설정 에셋(1회 로드 후 캐시). 없으면 null.</summary>
public static WLBossArenaSettings Instance
{
get
{
if (!s_lookupDone)
{
s_cached = Resources.Load<WLBossArenaSettings>(ResourcesPath);
s_lookupDone = true;
}
return s_cached;
}
}
/// <summary>진단·A/B 전용 런타임 스위치. 에셋을 건드리지 않고 게이트만 끈다(도메인 리로드로 자동 false).</summary>
public static bool RuntimeDisabled;
/// <summary>게이트가 살아 있는가 = 에셋 존재 · enabled · 런타임 스위치 off · 코어 on. false 면 813e 상시 배치 그대로.</summary>
public static bool Active
{
get
{
var i = Instance;
return i != null && i.enabled && !RuntimeDisabled && WLCombatCoreSettings.Enabled;
}
}
/// <summary>테스트용: 캐시를 비워 다음 접근 때 다시 Resources.Load 한다(에셋 제거/복구 실험).</summary>
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;
}
}