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

135 lines
8.0 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.

// ─────────────────────────────────────────────────────────────────────────────
// 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;
}
}