// ───────────────────────────────────────────────────────────────────────────── // WLSkillSlotPreset.cs — 전투 패드 4슬롯 장착 **로컬 오버라이드** 값의 단일 출처 (WL-813g §3 · #813) // // 813c 완료보고 §5 이월분 + 813n 완료보고(슬롯 SkillID 16행 · 쿨 6/8/12/20 s · MP 10/15/25/40). // 기준서 v1 §B 요소 3 「4슬롯 · 쿨·자원 차등」. // // ■ 왜 필요한가 (813n §1 「슬롯 장착은 서버 프리셋이라 813c 와 묶어야 함」) // 장착 슬롯의 SOT 는 서버(`ServerData.Equip.Skill[Preset]`)다. 서버 프리셋을 바꾸지 않고도 // 전투 패드가 813n 프리셋(클래스별 Active 3 + 광역 1)으로 도는지 보려면 **클라 로컬 오버라이드**가 필요하다. // // ■ 어디에 쓰는가 // WLSkillSlotOverride(UI) 가 이 표에서 내 클래스 행을 찾아 4슬롯 SkillID 를 // `ServerData.Set_Equip(eItem.Skill, id, slot)`(클라 전용 공개 API · 서버 호출 0)로 써 넣는다. // 그 뒤 `PCActor` 가 `Get_EquipSkillID(i)` 로 읽는 경로 = 기존 `Use_Skill` 경로 그대로다(핫스팟 0). // // ■ 롤백(C8) // 이 에셋이 없거나 enabled = false 면 아무 것도 쓰지 않는다 = **서버 프리셋 원본** 그대로. // restoreOnDisable 이 켜져 있으면 패드가 꺼질 때 원래 SkillID 를 되돌린다. // // 🔴 이 오버라이드는 **클라이언트 메모리만** 바꾼다. 서버 저장 호출(`Save_*`)은 하지 않는다. // ───────────────────────────────────────────────────────────────────────────── using System; using UnityEngine; namespace WL.UI { /// 한 클래스(ClassConfig n_ClassID)의 4슬롯 장착 SkillID. [Serializable] public struct WLSkillSlotRow { [Tooltip("ClassConfig 의 n_ClassID (예: 10101 / 10102 / 10501 / 10502).")] public int classId; [Tooltip("메모용 이름(런타임 미사용).")] public string note; [Tooltip("슬롯 0~3 에 장착할 SkillID. 0 이면 그 슬롯은 건드리지 않는다(서버 값 유지).")] public int[] skillIds; } [CreateAssetMenu(fileName = "WLSkillSlotPreset", menuName = "WL/Skill Slot Preset", order = 32)] public sealed class WLSkillSlotPreset : ScriptableObject { /// Resources 경로 — 런타임 로드 키. public const string ResourcesPath = "WL/WLSkillSlotPreset"; private static WLSkillSlotPreset s_instance; private static bool s_tried; /// 에셋이 없으면 null(호출부는 null 이면 서버 프리셋 원본 유지 = C8 롤백). public static WLSkillSlotPreset Instance { get { if (s_instance == null && !s_tried) { s_tried = true; s_instance = Resources.Load(ResourcesPath); } return s_instance; } } public static void ClearCache() { s_instance = null; s_tried = false; } /// 진단·A/B 전용 런타임 스위치. public static bool RuntimeDisabled; /// 오버라이드가 살아 있는가 = 에셋 존재 + enabled + 런타임 스위치. public static bool Enabled { get { var i = Instance; return i != null && i.overrideEnabled && !RuntimeDisabled; } } // ── 스위치 ──────────────────────────────────────────────────────────── [Header("스위치")] [Tooltip("끄면 서버 프리셋을 그대로 쓴다(오버라이드 0). 롤백 1순위.")] public bool overrideEnabled = true; [Tooltip("적용/차단 결정을 Console 에 남긴다(검증용).")] public bool verboseLog = false; [Tooltip("패드가 꺼질 때(씬 이동·컴포넌트 비활성) 원래 SkillID 를 되돌린다.")] public bool restoreOnDisable = true; [Tooltip("오버라이드할 슬롯 수(0~4). 원본 배열은 6칸이고 813c 가 쓰는 활성 슬롯은 4개다.")] public int slotCount = 4; // ── 클래스별 4슬롯 (813n 완료보고 16행) ─────────────────────────────── [Header("클래스별 4슬롯 SkillID (813n 프리셋 · 슬롯 1~3 = 클래스 Active · 슬롯 4 = 광역)")] [Tooltip("클래스 행. 내 클래스(Actor.Get_ID())와 classId 가 같은 행을 쓴다. 없으면 오버라이드 0.")] public WLSkillSlotRow[] rows = new WLSkillSlotRow[] { new WLSkillSlotRow { classId = 10101, note = "onehand · 703101/703102/703103 + 광역 702006 돌개바람", skillIds = new[] { 703101, 703102, 703103, 702006 } }, new WLSkillSlotRow { classId = 10102, note = "spellblade · 703106/703105/703104 + 광역 702009 마나폭발", skillIds = new[] { 703106, 703105, 703104, 702009 } }, new WLSkillSlotRow { classId = 10501, note = "bluntshield · 703502/703503/703501 + 광역 702005 파도", skillIds = new[] { 703502, 703503, 703501, 702005 } }, new WLSkillSlotRow { classId = 10502, note = "paladin · 703505/703506/703504 + 광역 702012 심판의빛", skillIds = new[] { 703505, 703506, 703504, 702012 } }, }; // ── MP 부족 시 버튼 흐림 (발주서 §3) ────────────────────────────────── [Header("MP 게이트 — 부족하면 버튼 흐림 (Actor.cs:733-741 과 같은 식)")] [Tooltip("MP 부족 표시 사용 여부.")] public bool mpDimEnabled = true; [Tooltip("MP 가 모자랄 때 버튼 알파(0~1 · 1 = 흐리지 않음).")] public float mpDimAlpha = 0.4f; [Tooltip("MP 가 충분할 때 버튼 알파(보통 1).")] public float mpFullAlpha = 1f; [Tooltip("알파가 바뀌는 데 걸리는 시간(초 · 0 이면 즉시).")] public float mpDimLerpSeconds = 0.12f; [Tooltip("MP 확인 주기(초). 매 프레임 확인하지 않는다.")] public float mpCheckInterval = 0.1f; [Tooltip("MP 부족 표시를 쿨타임 중에도 유지한다(off 면 쿨 중에는 쿨 링만 보여 준다).")] public bool mpDimDuringCooldown = true; // ── 헬퍼 ────────────────────────────────────────────────────────────── /// 클래스 ID 의 행을 찾는다. 없으면 -1. public int FindRow(int classId) { if (rows == null) return -1; for (int i = 0; i < rows.Length; i++) if (rows[i].classId == classId) return i; return -1; } /// 클래스 ID · 슬롯의 SkillID. 행이 없거나 슬롯이 비면 0(= 서버 값 유지). public int SkillIdFor(int classId, int slot) { int r = FindRow(classId); if (r < 0) return 0; var ids = rows[r].skillIds; if (ids == null || slot < 0 || slot >= ids.Length) return 0; return ids[slot]; } /// 실제로 오버라이드할 슬롯 수(설정 값과 행 길이 중 작은 쪽). public int SlotCountFor(int classId) { int r = FindRow(classId); if (r < 0) return 0; var ids = rows[r].skillIds; return ids == null ? 0 : Mathf.Clamp(slotCount, 0, ids.Length); } } }