182 lines
11 KiB
C#
182 lines
11 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// 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;
|
||
|
||
// ─────────────────────────────────────────── ⑤ 동시 드랍 오브젝트 상한
|
||
[Header("⑤ 동시 드랍 오브젝트 상한 (모바일 과밀 방지)")]
|
||
[Tooltip("살아 있는 드랍 오브젝트가 이 수를 넘으면 폭발 드랍의 추가 건을 더 내지 않는다(원본 기본 드랍은 절대 막지 않는다). " +
|
||
"0 이면 제한 없음. 참고: 원본 풀 자체가 코인 30 · 아이템 2 로 이미 상한이다(DropItemInfo.Start).")]
|
||
public int maxConcurrentDrops = 24;
|
||
|
||
[Tooltip("살아 있는 드랍이 이 수를 넘으면 등급 빔도 내지 않는다(EffectBudget 과 별개의 2차 방어). 0 이면 제한 없음.")]
|
||
public int beamSuppressAboveDrops = 12;
|
||
}
|
||
}
|