Project_WL/Assets/WL/Combat/Loot/WLLootSettings.cs

193 lines
12 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.

// ─────────────────────────────────────────────────────────────────────────────
// WLLootSettings.cs — 전리품 연출 값의 단일 출처(SOT · C45)
//
// PD 지시 #813 · 발주서 WL-813f §1 (2026-09-09) · 기준서 v1 §B 요소 4
//
// 에셋 경로(고정): Assets/WL/Combat/Settings/Resources/WL/WLLootSettings.asset
// → Resources.Load<WLLootSettings>("WL/WLLootSettings") 1회 로드 후 캐시(WLGrowthSettings 관례 그대로).
// 에셋이 없거나 enabled = false 면 Enabled == false → DropItem.cs 의 훅 2줄이 즉시 반환하고
// 산포·빔·사운드·자동 줍기·폭발 드랍이 전부 꺼진다 = 원본 드랍 100%(C8 롤백 1순위 스위치).
// 아래 필드 기본값은 "에셋 부재 시의 폴백" 이 아니라 에셋 생성 시 초기값이다.
//
// 🔴 어셈블리 주의: Assets/WL/Combat/ 에 .asmdef 를 만들지 말 것(DropItem/MobActor 가 Assembly-CSharp 에 있다).
// ─────────────────────────────────────────────────────────────────────────────
using UnityEngine;
namespace WL.Combat.Loot
{
[CreateAssetMenu(fileName = "WLLootSettings", menuName = "WL/Loot Settings", order = 322)]
public sealed class WLLootSettings : ScriptableObject
{
public const string ResourcesPath = "WL/WLLootSettings";
/// <summary>등급 배열 길이 = eItemGrade 8단계 + 여유 1(기준서 §B 요소 4 등급 색 9행).</summary>
public const int GradeSlots = 9;
static WLLootSettings s_cached;
static bool s_lookupDone;
/// <summary>설정 에셋(1회 로드 후 캐시). 없으면 null.</summary>
public static WLLootSettings Instance
{
get
{
if (!s_lookupDone)
{
s_cached = Resources.Load<WLLootSettings>(ResourcesPath);
s_lookupDone = true;
}
return s_cached;
}
}
/// <summary>진단·A/B 전용 런타임 스위치(WLCombatCoreSettings.RuntimeDisabled 선례). Play 종료 시 도메인 리로드로 자동 false.</summary>
public static bool RuntimeDisabled;
/// <summary>전리품 연출이 살아 있는가 = 에셋 존재 · enabled · 런타임 스위치 off.</summary>
public static bool Enabled
{
get { var i = Instance; return i != null && i.enabled && !RuntimeDisabled; }
}
/// <summary>테스트용: 캐시를 비워 다음 접근 때 다시 Resources.Load 한다(에셋 제거/복구 실험).</summary>
public static void ClearCache() { s_cached = null; s_lookupDone = false; }
// ─────────────────────────────────────────── 전체 스위치
[Header("전체 스위치")]
[Tooltip("끄면 산포·빔·사운드·자동 줍기·폭발 드랍이 전부 비활성 — 원본 DropItem 동작 100%. 롤백 1순위.")]
public bool enabled = true;
[Tooltip("드랍 처리 결과를 Console 에 남긴다(검증용 · 기본 off · GC 가 발생하므로 서비스 빌드에서 켜지 말 것).")]
public bool verboseLog = false;
// ─────────────────────────────────────────── ① 드랍 산포
[Header("① 드랍 산포 (기준서 §B: 산포 반경 0.6~1.2 m)")]
[Tooltip("끄면 원본이 계산한 위치를 그대로 쓴다(골드는 원본대로 2~3 m).")]
public bool scatterEnabled = true;
[Tooltip("드랍 지점(몹 위치)에서 떨어지는 최소 거리(m).")]
public float scatterRadiusMin = 0.6f;
[Tooltip("드랍 지점에서 떨어지는 최대 거리(m).")]
public float scatterRadiusMax = 1.2f;
[Tooltip("NavMesh 스냅 반경(m). 표집점이 NavMesh 밖이면 이 반경 안에서 가장 가까운 점으로 당긴다. 0 이면 스냅하지 않는다.")]
public float scatterNavSnapRadius = 0.8f;
[Tooltip("포물선 시작 높이(m · 등급 1 기준). Co_Move_Split 의 시작점을 올려 튀는 높이를 만든다.")]
public float bounceHeightBase = 0.35f;
[Tooltip("등급 1 을 넘는 등급 1단계마다 더해지는 높이(m).")]
public float bounceHeightPerGrade = 0.08f;
[Tooltip("튀는 높이 상한(m).")]
public float bounceHeightMax = 1.0f;
[Tooltip("한 번의 처치에서 여러 개가 떨어질 때 서로 다른 방향으로 부채꼴로 흩는다(같은 자리 겹침 방지).")]
public bool fanOutSameKill = true;
// ─────────────────────────────────────────── ② 등급 빔 + 사운드
[Header("② 등급 빔 + 사운드 (기준서 §B: 등급 4 이상만)")]
[Tooltip("빔·사운드를 낼 최소 아이템 등급(table_itemlist.n_ItemGrade). 기준서 = 4.")]
public int beamMinGrade = 4;
[Tooltip("등급별 빔 프리팹(인덱스 0 = 등급 1 … 인덱스 8 = 등급 9). 비워 두면 그 등급은 빔 없음. 보유 FX_LootDrop_* 만 쓴다(새 아트 0).")]
public GameObject[] beamByGrade = new GameObject[GradeSlots];
[Tooltip("빔을 드랍 오브젝트의 자식으로 붙인다(자석으로 끌려갈 때 같이 따라온다). 끄면 드랍 지점에 고정.")]
public bool beamParentToDrop = true;
[Tooltip("빔 스폰 오프셋(m · 드랍 오브젝트 기준).")]
public Vector3 beamOffset = new Vector3(0f, 0f, 0f);
[Tooltip("빔 프리팹 스케일 배율(1 = 원본).")]
public float beamScale = 0.6f;
[Tooltip("빔 최대 수명(초). 드랍 소멸(원본 20 s)보다 길게 잡아야 줍기 전에 꺼지지 않는다. 드랍이 사라지면 이 시간과 무관하게 즉시 파괴한다.")]
public float beamMaxLifetimeSeconds = 21f;
[Tooltip("EffectBudget.Heavy 슬롯을 잡고 빔을 스폰한다(상한 초과 = 빔만 스킵 · 드랍 자체는 그대로). 코어가 꺼져 있으면 제한 없음.")]
public bool beamUseEffectBudget = true;
[Tooltip("등급 빔과 함께 낼 사운드(원본 eSound · 보유 클립만). 기준서 = s015_GoldPowerUp.")]
public eSound beamSound = eSound.s015_GoldPowerUp;
[Tooltip("빔 사운드 최대 볼륨(거리 감쇠 전). 0 이면 사운드 없음.")]
[Range(0f, 1f)] public float beamSoundVolume = 0.5f;
[Tooltip("빔 사운드를 다시 낼 최소 간격(초 · unscaled). 폭발 드랍이 겹칠 때 소리가 뭉치는 것을 막는다.")]
public float beamSoundMinIntervalSeconds = 0.12f;
// ─────────────────────────────────────────── ③ 자동 줍기 반경
[Header("③ 자동 줍기 반경 (기준서 §B: 1.5 m · 원본 자석 상태기 재사용)")]
[Tooltip("끄면 원본 그대로 접촉해야만 줍는다.")]
public bool autoPickupEnabled = true;
[Tooltip("PC 중심에서 이 거리 안에 들어온 드랍을 자석 상태로 바꾼다(m).")]
public float autoPickupRadius = 1.5f;
[Tooltip("자석 판정 주기(초). 0 이면 매 프레임. 드랍이 많을 때 비용을 줄인다.")]
public float autoPickupScanIntervalSeconds = 0.1f;
[Tooltip("자석 1단계에서 위로 띄우는 높이(m · 원본 주석 코드 값 3).")]
public float magnetRiseHeight = 3f;
[Tooltip("자석 1단계 속도(원본 주석 코드 값 5). 2단계는 원본이 15 로 올린다.")]
public float magnetRiseSpeed = 5f;
// ─────────────────────────────────────────── ④ 보스/엘리트 폭발 드랍
[Header("④ 폭발 드랍 (기준서 §B: 엘리트 1~2건 · 보스 3~5건)")]
[Tooltip("끄면 원본대로 처치당 1건만 추첨한다.")]
public bool burstEnabled = true;
[Tooltip("엘리트 처치 시 추가 드랍 최소 건수.")]
public int eliteBurstMin = 1;
[Tooltip("엘리트 처치 시 추가 드랍 최대 건수(포함).")]
public int eliteBurstMax = 2;
[Tooltip("보스 처치 시 추가 드랍 최소 건수.")]
public int bossBurstMin = 3;
[Tooltip("보스 처치 시 추가 드랍 최대 건수(포함).")]
public int bossBurstMax = 5;
[Tooltip("일반 잡몹 추가 드랍 건수(기준서 = 0 · 잡몹은 골드 100% + 백 15~25% 로 충분).")]
public int normalBurstCount = 0;
[Tooltip("폭발 드랍 1건 사이의 간격(초). 0 이면 같은 프레임에 한꺼번에.")]
public float burstIntervalSeconds = 0.07f;
[Tooltip("추가 드랍 1건마다 몹의 RandomBag 을 다시 추첨한다. 추첨이 빈손이면 아래 골드로 대체.")]
public bool burstRollsBag = true;
[Tooltip("백 추첨이 빈손일 때 대신 떨어뜨리는 골드 = 몹 n_DropGold × 이 비율(최소 1). 0 이면 빈손일 때 아무것도 떨어지지 않는다. " +
"⚠ 이 값이 0 보다 크면 보스/엘리트 골드 수입이 건수만큼 늘어난다(경제 영향 · PD 튜닝 지점).")]
[Range(0f, 1f)] public float burstGoldShare = 0.25f;
[Tooltip("EffectBudget.ClusterBurst 슬롯을 잡고 폭발 드랍을 시작한다. 811e 군집 폭발과 슬롯(상한 1)을 공유하므로 기본 off.")]
public bool burstUseClusterBudget = false;
// ─────────────────────────────────────────── ④-B 등급 하한 백 (기준서 §E 4-1 · #813f2)
[Header("④-B 등급 하한 백 (엘리트 ≥3 · 보스 ≥5)")]
[Tooltip("엘리트 폭발 드랍 앞 N 건이 쓰는 RandomBag ID(등급 3~5 확정 · 8130002). 0 = 존 백 그대로.")]
public int eliteBagId = 8130002;
[Tooltip("엘리트 폭발 드랍 중 eliteBagId 를 쓰는 건수(앞에서부터). 나머지는 몹의 존 백.")]
public int eliteFloorDrops = 1;
[Tooltip("보스 폭발 드랍이 쓰는 RandomBag ID(등급 5~7 확정 · 8130003 · 전부 beamMinGrade 4 이상). 0 = 존 백 그대로.")]
public int bossBagId = 8130003;
// ─────────────────────────────────────────── ⑤ 동시 드랍 오브젝트 상한
[Header("⑤ 동시 드랍 오브젝트 상한 (모바일 과밀 방지)")]
[Tooltip("살아 있는 드랍 오브젝트가 이 수를 넘으면 폭발 드랍의 추가 건을 더 내지 않는다(원본 기본 드랍은 절대 막지 않는다). " +
"0 이면 제한 없음. 참고: 원본 풀 자체가 코인 30 · 아이템 2 로 이미 상한이다(DropItemInfo.Start).")]
public int maxConcurrentDrops = 24;
[Tooltip("살아 있는 드랍이 이 수를 넘으면 등급 빔도 내지 않는다(EffectBudget 과 별개의 2차 방어). 0 이면 제한 없음.")]
public int beamSuppressAboveDrops = 12;
}
}