Project_WL/Assets/WL/Combat/Reaction/WLReactionSettings.cs

130 lines
7.1 KiB
C#

// ─────────────────────────────────────────────────────────────────────────────
// WLReactionSettings.cs — 전투 반응(연쇄 처치 · 군집 폭발 · 데미지 합산 윈도우) 값의 단일 출처(SOT · C45)
//
// PD 지시 #813 · 발주서 WL-811def §1-2 · 설계안 WL-811a §C 811e 행 · 기준서 v1 §B 요소 2 (2026-09-09)
//
// 에셋 경로(고정): Assets/WL/Combat/Settings/Resources/WL/WLReactionSettings.asset
// → Resources.Load<WLReactionSettings>("WL/WLReactionSettings") 로 1회 로드 후 캐시.
// 에셋이 없거나 enabled = false 또는 RuntimeDisabled 면 KillChain · DamageAggregator 가 아무 것도 하지 않는다(C8).
// HUD 표시는 하지 않는다 — 이 파일의 시스템은 이벤트(KillChain.TierReached · DamageAggregator.Flushed)만 낸다(표시 = 813g U).
// ─────────────────────────────────────────────────────────────────────────────
using System;
using UnityEngine;
namespace WL.Combat.Reaction
{
[Serializable]
public struct KillChainTier
{
[Tooltip("티어 이름(데이터 · HUD 문구는 813g 가 이 이름을 키로 쓴다).")]
public string name;
[Tooltip("이 티어에 도달하는 연쇄 처치 수.")]
public int kills;
}
[CreateAssetMenu(fileName = "WLReactionSettings", menuName = "WL/Reaction Settings", order = 322)]
public sealed class WLReactionSettings : ScriptableObject
{
public const string ResourcesPath = "WL/WLReactionSettings";
static WLReactionSettings s_cached;
static bool s_lookupDone;
/// <summary>설정 에셋(1회 로드 후 캐시). 없으면 null.</summary>
public static WLReactionSettings Instance
{
get
{
if (!s_lookupDone)
{
s_cached = Resources.Load<WLReactionSettings>(ResourcesPath);
s_lookupDone = true;
}
return s_cached;
}
}
/// <summary>진단·A/B 전용 런타임 스위치. 에셋을 건드리지 않고 이 반응만 끈다. Play 종료 시 자동 false.</summary>
public static bool RuntimeDisabled;
public static bool Enabled
{
get { var i = Instance; return i != null && i.enabled && !RuntimeDisabled; }
}
public static void ClearCache() { s_cached = null; s_lookupDone = false; }
// ─────────────────────────────────────────── 전체 스위치
[Header("전체 스위치")]
[Tooltip("끄면 연쇄 처치 카운터 · 군집 폭발 · 데미지 합산이 전부 비활성(이벤트 0). 롤백 1순위.")]
public bool enabled = true;
[Tooltip("티어 도달 · 폭발 · 합산 플러시를 Console 에 남긴다(검증용 · GC 발생).")]
public bool verboseLog = false;
// ─────────────────────────────────────────── 연쇄 처치
[Header("연쇄 처치 (Killed · 윈도우 안 연속 처치 카운터)")]
[Tooltip("연쇄 처치 카운터 사용 여부.")]
public bool killChainEnabled = true;
[Tooltip("연쇄 유지 시간(초 · unscaled). 마지막 처치로부터 이 시간 안에 다음 처치가 오면 연쇄가 이어진다. 기준서 §B 요소 2 = 3 s.")]
public float killChainWindowSeconds = 3f;
[Tooltip("메인 PC 가 직접 처치한 것만 센다(killer == 메인 PC). off 면 도트·펫·소환수 처치도 센다.")]
public bool killChainRequireMainPCKiller = false;
[Tooltip("몬스터 소환수(eSubRol.Summon) 처치도 센다.")]
public bool killChainCountSummons = true;
[Tooltip("연쇄 위치(무게중심) 계산에 기억하는 최근 처치 수(버퍼 용량 · 첫 사용 때 할당).")]
public int killChainBufferSize = 16;
[Tooltip("티어 표(오름차순). 연쇄 수가 kills 와 같아지는 순간 TierReached 를 1회 낸다. 기준서 제안 2/3/5/8.")]
public KillChainTier[] tiers = new KillChainTier[]
{
new KillChainTier { name = "DOUBLE", kills = 2 },
new KillChainTier { name = "TRIPLE", kills = 3 },
new KillChainTier { name = "RAMPAGE", kills = 5 },
new KillChainTier { name = "MASSACRE", kills = 8 },
};
// ─────────────────────────────────────────── 군집 폭발
[Header("군집 폭발 (티어 도달 → 처치 무게중심에 VFX 1개 · EffectBudget.ClusterBurst 상한 1)")]
[Tooltip("군집 폭발 사용 여부.")]
public bool clusterBurstEnabled = true;
[Tooltip("폭발을 내는 최소 티어 인덱스(tiers 배열 기준 · 0 = DOUBLE · 1 = TRIPLE).")]
public int clusterBurstMinTier = 1;
[Tooltip("Assets/Res_Addr/Effect/<이름>.prefab — InGameInfo.Show_Effect 로 풀에서 꺼낸다(에셋 인벤토리 실존 확인 = BoomHit · 대안 FireballHit · Effect_STR_EXTRA_SPLASH).")]
public string clusterBurstPrefab = "BoomHit";
[Tooltip("폭발 슬롯(EffectBudget.ClusterBurst)을 잡아 두는 시간(초 · unscaled). 이 시간 안의 다음 폭발은 스킵(동시 ≤ 1).")]
public float clusterBurstHoldSeconds = 1f;
[Tooltip("무게중심에서 위로 띄우는 높이(m).")]
public float clusterBurstHeightOffset = 0.3f;
[Tooltip("무게중심이 화면 밖(EffectBudget.IsInView)이면 스킵.")]
public bool clusterBurstRequireInView = true;
// ─────────────────────────────────────────── 데미지 합산
[Header("데미지 합산 윈도우 (HitConfirmed → 대상별 합산 → Flushed 이벤트 · 표시는 813g)")]
[Tooltip("합산 사용 여부.")]
public bool damageMergeEnabled = true;
[Tooltip("첫 타격부터 이 시간(초 · unscaled) 안의 같은 대상 타격을 하나로 합쳐 Flushed 를 낸다. 기준서 §B 요소 2 = 0.25 s.")]
public float damageMergeWindowSeconds = 0.25f;
[Tooltip("처치 타격(isKill)은 윈도우를 기다리지 않고 즉시 플러시한다.")]
public bool damageMergeFlushOnKill = true;
[Tooltip("동시에 합산 중일 수 있는 대상 수(슬롯 · 첫 사용 때 할당). 초과하면 가장 오래된 슬롯을 먼저 플러시한다.")]
public int damageMergeSlots = 32;
[Tooltip("메인 PC 가 때린 것만 합산한다. off 면 모든 타격(PC 피격 포함)을 대상별로 합산.")]
public bool damageMergeMainPCOnly = false;
}
}