Project_WL/Assets/WL/Combat/Run/WLRunSettings.cs

135 lines
8.0 KiB
C#
Raw Normal View History

// ─────────────────────────────────────────────────────────────────────────────
// WLRunSettings.cs — 3분 런 구조 값의 단일 출처(SOT · C45)
//
// PD 지시 #813 · 발주서 WL-813p §1 (2026-09-09)
//
// 에셋 경로(고정): Assets/WL/Combat/Settings/Resources/WL/WLRunSettings.asset
// → Resources.Load<WLRunSettings>("WL/WLRunSettings") 로 1회 로드 후 캐시
// (WLBossArenaSettings·WLLootSettings 등 기존 설정 SO 관례 그대로).
//
// ■ C8 롤백 (하나라도 false 면 RunDirector 는 아무 일도 하지 않는다 = 런 구조 이전 100%)
// ① 에셋이 없다 → Enabled false
// ② enabled = false → Enabled false
// ③ RuntimeDisabled = true → Enabled false (진단·A/B 전용 · 도메인 리로드로 자동 해제)
// ④ WLCombatCoreSettings.Enabled = false → Enabled false
// 🔴 ④ 가 필요한 이유: 런 집계·승리 판정은 전부 CombatEvents(Killed/Spawned/SkillCast) 구독이다.
// 코어가 꺼진 채 런을 걸면 처치가 0 으로 보여 「무조건 시간 종료」가 된다.
// 그래서 "코어가 꺼지면 런 구조도 안 건다"(= 결과 화면도 안 뜬다)로 떨어뜨린다.
//
// ■ 아래 필드 기본값은 "SOT 부재 시의 실패 처리"가 아니라 에셋 생성 시 초기값이다.
// 실제로 읽히는 값은 항상 .asset 이며, 코드에는 게임 수치 상수를 두지 않는다(C45).
//
// 🔴 어셈블리 주의: Assets/WL/Combat/ 에 .asmdef 를 만들지 말 것(Actor/PCActor 가 Assembly-CSharp 에 있다).
// ─────────────────────────────────────────────────────────────────────────────
using UnityEngine;
using WL.Combat.Core;
namespace WL.Combat.Run
{
[CreateAssetMenu(fileName = "WLRunSettings", menuName = "WL/Run Settings", order = 327)]
public sealed class WLRunSettings : ScriptableObject
{
public const string ResourcesPath = "WL/WLRunSettings";
static WLRunSettings s_cached;
static bool s_lookupDone;
/// <summary>설정 에셋(1회 로드 후 캐시). 없으면 null.</summary>
public static WLRunSettings Instance
{
get
{
if (!s_lookupDone)
{
s_cached = Resources.Load<WLRunSettings>(ResourcesPath);
s_lookupDone = true;
}
return s_cached;
}
}
/// <summary>진단·A/B 전용 런타임 스위치. 에셋을 건드리지 않고 런 구조만 끈다(도메인 리로드로 자동 false).</summary>
public static bool RuntimeDisabled;
/// <summary>런 구조가 살아 있는가 = 에셋 존재 · enabled · 런타임 스위치 off · 코어 on.</summary>
public static bool Enabled
{
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("끄면 런 상태 머신·집계·RunEvents 가 전부 비활성 — 맵은 기존 필드 맵 그대로다. 롤백 1순위.")]
public bool enabled = true;
[Tooltip("런 시작·존 클리어·게이트·종료를 Console 에 남긴다(검증용 · 기본 off).")]
public bool verboseLog = false;
// ─────────────────────────────────────────── 런 길이
[Header("런 길이 (기준서 §D-2 3분 런)")]
[Tooltip("런 제한 시간(초). 이 시간을 넘기면 보스를 못 잡았어도 Timeout 으로 끝난다.")]
public float runSeconds = 180f;
[Tooltip("RunTick 발행 주기(초 · unscaled). 1 = 1 Hz(UI 타이머). 0 이하면 매 프레임(권장하지 않는다).")]
public float tickIntervalSeconds = 1f;
[Tooltip("진입 경계·타이머 폴링 주기(초 · unscaled). AutoCombatOnEnter 와 같은 폴링 방식이다.")]
public float pollSeconds = 0.1f;
[Tooltip("813i DeathFlow 가 세계를 멈춘 동안(부활 팝업)은 런 시계를 세운다. 끄면 실시간으로 계속 흐른다.")]
public bool pauseTimerWhileWorldHeld = true;
// ─────────────────────────────────────────── 존 진행
[Header("존 진행 (813e 존 스포너 · MonsterAppear.json 실측 ID)")]
[Tooltip("런이 진행 순서로 보는 존 SpawnerID. zoneClearKills 와 길이가 같아야 한다(짧은 쪽 기준으로 잘린다).")]
public int[] zoneSpawnerIds = new int[] { 813001, 813002, 813003, 813004 };
[Tooltip("존 클리어 임계 — 런 시작 시점 대비 그 스포너의 누적 처치 수. 기본 = MonsterAppear 의 n_MaxMonsterCount × 2.")]
public int[] zoneClearKills = new int[] { 24, 30, 28, 16 };
// ─────────────────────────────────────────── 보스 게이트
[Header("보스 게이트 (813d BossArena)")]
[Tooltip("보스 FieldBossData 의 n_SpawnerId(MonsterAppear 실측 813010). 0 이면 모든 보스 스폰을 이 런의 보스로 본다.")]
public int bossSpawnerId = 813010;
[Tooltip("존을 전부 클리어하면 BossArena.ForceOpenGate() 로 게이트를 연다.")]
public bool openGateOnZonesCleared = true;
[Tooltip("경과 시간이 이 값(초)을 넘으면 존이 남았어도 게이트를 연다(3분 안에 보스를 만나게 하는 안전판). 0 이하면 시간 개방 없음.")]
public float openGateAtSec = 120f;
// ─────────────────────────────────────────── 런 시작 · 종료
[Header("런 시작 · 종료 경계")]
[Tooltip("맵(런) 진입 경계에서 런을 자동 시작한다. 끄면 RunDirector.StartRun() 을 직접 불러야 한다(813q 버튼 등).")]
public bool autoStartOnMapEnter = true;
[Tooltip("로비에서는 런을 시작하지 않는다(AutoCombatOnEnter 와 같은 판정).")]
public bool skipLobby = true;
[Tooltip("런 도중 맵을 뜨거나 로비로 돌아가면 RunEnded(Abandon) 1회 후 정지한다.")]
public bool endOnMapLeave = true;
// ─────────────────────────────────────────── 다음 런(재시작)
[Header("다음 런 (RunDirector.Restart)")]
[Tooltip("재시작 때 PC 를 존 시작점으로 되돌린다 — MyActor.On_Regen(false, rate) = 전회복 + Set_Warp(Get_ReincanationPos) 경로(813i DeathFlow 와 같다).")]
public bool restartWarpsPlayer = true;
[Tooltip("재시작 회복 비율(1 = 풀피). restartWarpsPlayer 가 꺼져 있으면 쓰이지 않는다.")]
public float restartHealRate = 1f;
[Tooltip("재시작 때 BossArena 게이트를 다시 잠근다(BossArena.ResetGate · 존 누적 카운터는 건드리지 않는다).")]
public bool restartRearmsBossGate = true;
[Tooltip("재시작 때 813i 물약 잔량을 런 기준으로 되돌린다(PotionUse.ResetRun).")]
public bool restartResetsPotions = true;
}
}