Project_WL/Assets/WL/Equip/WLEquipTables.cs

340 lines
17 KiB
C#
Raw Permalink Normal View History

// ─────────────────────────────────────────────────────────────────────────────
// WLEquipTables.cs — FR 장비 테이블 로더 (#821)
//
// FR 은 xlsm → json → Cook(prefab) 3단이지만 WL 에서는 **json 을 Resources 에서 바로** 읽는다.
// FR 표는 엑셀 셀 병합 때문에 묶음의 첫 행에만 Index 가 있다 → FR `ItemLevelTable.LoadFromText` ·
// `JsonUtil.ReadItemDropPackageJson` · `ItemOptionTable.LoadFromText` 의 묶기 규칙을 그대로 재현한다.
// 파일 이름은 FR 원본 그대로(`WLEquipSettings.tableFolder` 아래).
// ─────────────────────────────────────────────────────────────────────────────
using System.Collections.Generic;
using Newtonsoft.Json;
using UnityEngine;
namespace WL.Equip
{
public static class WLEquipTables
{
public static bool Loaded { get; private set; }
public static string LastLog { get; private set; } = "";
static readonly Dictionary<int, ItemJson> s_items = new Dictionary<int, ItemJson>();
static readonly List<ItemJson> s_itemList = new List<ItemJson>();
static readonly Dictionary<int, ItemLevelJson> s_levels = new Dictionary<int, ItemLevelJson>();
static readonly Dictionary<int, ItemComposeJson> s_compose = new Dictionary<int, ItemComposeJson>();
static readonly Dictionary<int, ItemOptionJson> s_options = new Dictionary<int, ItemOptionJson>();
static readonly Dictionary<int, ItemDropGroupJson> s_dropGroups = new Dictionary<int, ItemDropGroupJson>();
static readonly Dictionary<int, ItemDropPackageJson> s_dropPackages = new Dictionary<int, ItemDropPackageJson>();
public static IReadOnlyList<ItemJson> Items { get { EnsureLoaded(); return s_itemList; } }
public static int ItemCount { get { return s_items.Count; } }
public static int LevelTableCount { get { return s_levels.Count; } }
public static int OptionCount { get { return s_options.Count; } }
public static bool EnsureLoaded()
{
if (Loaded) return true;
var cfg = WLEquipSettings.Instance;
string folder = cfg != null ? cfg.tableFolder : "WL/FR";
try
{
LoadItems(folder + "/ItemTable");
LoadCompose(folder + "/ItemComposeTable");
LoadOptions(folder + "/ItemOptionTable");
string[] levelFiles = { "Item_Weapon", "Item_Necklace", "Item_Gloves", "Item_Amor", "Item_Belt", "Item_Shoes" };
for (int i = 0; i < levelFiles.Length; i++) LoadLevels(folder + "/" + levelFiles[i]);
LoadDropGroups(folder + "/ItemDropGroupTable");
LoadDropPackages(folder + "/ItemDropPackageTable");
Loaded = true;
LastLog = "FR 표 로드 — 아이템 " + s_items.Count + " · 레벨표 " + s_levels.Count + " · 합성 " + s_compose.Count +
" · 옵션 " + s_options.Count + " · 드랍그룹 " + s_dropGroups.Count + " · 드랍패키지 " + s_dropPackages.Count;
if (cfg != null && cfg.verboseLog != 0) Debug.Log("[WL-821] " + LastLog);
}
catch (System.Exception ex)
{
LastLog = "FR 표 로드 실패 — " + ex.Message;
Debug.LogError("[WL-821] " + LastLog);
}
return Loaded;
}
static string ReadText(string path)
{
var ta = Resources.Load<TextAsset>(path);
if (ta == null) throw new System.IO.FileNotFoundException("Resources/" + path + ".json 없음");
return ta.text;
}
public static int ExtraItems { get; private set; }
static void LoadItems(string path)
{
s_items.Clear(); s_itemList.Clear(); ExtraItems = 0;
var arr = JsonConvert.DeserializeObject<ItemJson[]>(ReadText(path));
for (int i = 0; i < arr.Length; i++)
{
if (arr[i].Index <= 0) continue;
if (s_items.ContainsKey(arr[i].Index)) continue;
s_items.Add(arr[i].Index, arr[i]);
s_itemList.Add(arr[i]);
}
// #825 WL 덧씌움 — FR 표에 없는 무기 그룹(창 9006·9007 · PD 「6종」)을 Resources/WL/WLItemTableExtra.json 에서 보탠다(FR 원본 파일 무수정)
var extra = Resources.Load<TextAsset>("WL/WLItemTableExtra");
if (extra != null)
{
try
{
var more = JsonConvert.DeserializeObject<ItemJson[]>(extra.text);
for (int i = 0; more != null && i < more.Length; i++)
{
if (more[i].Index <= 0 || s_items.ContainsKey(more[i].Index)) continue;
s_items.Add(more[i].Index, more[i]); s_itemList.Add(more[i]); ExtraItems++;
}
}
catch (System.Exception ex) { Debug.LogWarning("[WL-825] 장비 표 덧씌움 실패 — " + ex.Message); }
}
// #879 PD 10-16 벨트 슬롯 → 반지 — 표시 이름만 바꾼다(FR 원본 파일 무수정 · 규칙 = WLPartStats.json renameItems)
try { WL.PartSkill.WLPartStats.RenameItems(s_itemList); }
catch (System.Exception ex) { Debug.LogWarning("[WL-879] 부위 이름 바꾸기 실패 — " + ex.Message); }
}
static void LoadCompose(string path)
{
s_compose.Clear();
var arr = JsonConvert.DeserializeObject<ItemComposeJson[]>(ReadText(path));
for (int i = 0; i < arr.Length; i++) if (!s_compose.ContainsKey(arr[i].Index)) s_compose.Add(arr[i].Index, arr[i]);
}
static void LoadOptions(string path)
{
s_options.Clear();
var readers = JsonConvert.DeserializeObject<ItemOptionReader[]>(ReadText(path));
ItemOptionJson cur = null;
for (int i = 0; i < readers.Length; i++)
{
var r = readers[i];
if (cur == null || (r.Index != 0 && cur.Index != r.Index))
{
cur = new ItemOptionJson(r.Index, r.Desc);
if (!s_options.ContainsKey(cur.Index)) s_options.Add(cur.Index, cur);
}
cur.ItemOptions.Add(new ItemOptionElement(r));
}
}
static void LoadLevels(string path)
{
var infos = JsonConvert.DeserializeObject<ItemLevelInfo[]>(ReadText(path));
ItemLevelJson cur = null;
for (int i = 0; i < infos.Length; i++)
{
int idx = infos[i].Index;
if (cur == null || (idx != 0 && cur.Index != idx))
{
cur = new ItemLevelJson(idx);
if (!s_levels.ContainsKey(idx)) s_levels.Add(idx, cur); else cur = s_levels[idx];
}
else if (idx == 0) infos[i].Index = cur.Index;
cur.LevelInfos.Add(infos[i]);
}
}
static void LoadDropGroups(string path)
{
s_dropGroups.Clear();
var infos = JsonConvert.DeserializeObject<ItemDropGroupInfo[]>(ReadText(path));
ItemDropGroupJson cur = null;
for (int i = 0; i < infos.Length; i++)
{
int idx = infos[i].Index;
if (cur == null || (idx != 0 && cur.Index != idx))
{
cur = new ItemDropGroupJson(idx);
if (!s_dropGroups.ContainsKey(idx)) s_dropGroups.Add(idx, cur); else cur = s_dropGroups[idx];
}
else if (idx == 0) infos[i].Index = cur.Index;
cur.ItemDropGroupInfos.Add(infos[i]);
}
}
static void LoadDropPackages(string path)
{
s_dropPackages.Clear();
var infos = JsonConvert.DeserializeObject<ItemDropPackageInfo[]>(ReadText(path));
ItemDropPackageJson cur = null;
for (int i = 0; i < infos.Length; i++)
{
int idx = infos[i].Index;
if (cur == null || (idx != 0 && cur.Index != idx))
{
cur = new ItemDropPackageJson(idx);
if (!s_dropPackages.ContainsKey(idx)) s_dropPackages.Add(idx, cur); else cur = s_dropPackages[idx];
}
else if (idx == 0) infos[i].Index = cur.Index;
cur.ItemDropPackageInfos.Add(infos[i]);
cur.TotalRate += infos[i].Rate;
}
}
// ── 조회 ───────────────────────────────────────────────────────────
public static ItemJson FindItemJson(int index) { EnsureLoaded(); ItemJson j; return s_items.TryGetValue(index, out j) ? j : null; }
public static ItemLevelJson FindItemLevelJson(int index) { EnsureLoaded(); ItemLevelJson j; return s_levels.TryGetValue(index, out j) ? j : null; }
public static ItemOptionJson FindItemOptionJson(int groupNo) { EnsureLoaded(); ItemOptionJson j; return s_options.TryGetValue(groupNo, out j) ? j : null; }
public static ItemDropGroupJson FindDropGroup(int index) { EnsureLoaded(); ItemDropGroupJson j; return s_dropGroups.TryGetValue(index, out j) ? j : null; }
public static ItemDropPackageJson FindDropPackage(int index) { EnsureLoaded(); ItemDropPackageJson j; return s_dropPackages.TryGetValue(index, out j) ? j : null; }
/// <summary>FR `ItemComposeTable.FindItemComposeJson` — 키 = 등급×1000 + 등급 단계.</summary>
public static ItemComposeJson FindItemComposeJson(eFRItemGrade grade, int gradeLv)
{
EnsureLoaded();
ItemComposeJson j;
return s_compose.TryGetValue((int)grade * 1000 + gradeLv, out j) ? j : null;
}
/// <summary>강등용 역조회 — 결과(등급·단계)가 주어진 값인 합성 행.</summary>
public static ItemComposeJson FindComposeByResult(eFRItemGrade resultGrade, int resultGradeLv)
{
EnsureLoaded();
foreach (var kv in s_compose)
if (kv.Value.ResultGrade == resultGrade && kv.Value.ResultGradeLevel == resultGradeLv) return kv.Value;
return null;
}
/// <summary>FR `ComposePopup.FindResultItemJson` — 같은 그룹에서 결과 등급·단계의 장비.</summary>
public static ItemJson FindResultItemJson(ItemJson source, ItemComposeJson compose)
{
return FindGroupItem(source.GroupNo, compose.ResultGrade, compose.ResultGradeLevel);
}
public static ItemJson FindGroupItem(int groupNo, eFRItemGrade grade, int gradeLv)
{
EnsureLoaded();
for (int i = 0; i < s_itemList.Count; i++)
{
var j = s_itemList[i];
if (j.ItemType != eFRItemType.Equipment) continue;
if (j.GroupNo == groupNo && j.ItemGrade == grade && j.GradeLv == gradeLv) return j;
}
return null;
}
/// <summary>표의 등급 옵션 중 지금 등급까지 열린 것(FR `PlayerManager.GetTotalAbilities` 규칙 — 등급 오름차순, 초과 시 중단).</summary>
public static void CollectOpenedOptions(ItemJson item, List<ItemOptionElement> into)
{
var oj = FindItemOptionJson(item.GroupNo);
if (oj == null) return;
for (int i = 0; i < oj.ItemOptions.Count; i++)
{
var e = oj.ItemOptions[i];
if (e == null) continue;
if (e.ItemGrade > item.ItemGrade) break;
into.Add(e);
}
}
// ── 드랍 (FR `Data.GetRandomDropPackageInfoInGroup` + `ItemDropPackageTable.GetRandomDropPackageInfo`) ──
public struct Drop { public int itemId; public int count; }
public static void RollDropGroup(int groupId, List<Drop> into, System.Random rng)
{
var g = FindDropGroup(groupId);
if (g == null) return;
for (int i = 0; i < g.ItemDropGroupInfos.Count; i++)
{
var gi = g.ItemDropGroupInfos[i];
if (rng.Next(1, 10001) > gi.Rate) continue;
var pkg = FindDropPackage(gi.Package_ID);
if (pkg == null) continue;
if (gi.DropType == eFRDropType.All)
{
for (int d = 0; d < pkg.ItemDropPackageInfos.Count; d++) into.Add(ToDrop(pkg.ItemDropPackageInfos[d], rng));
}
else
{
var p = RollPackage(pkg, rng);
if (p != null) into.Add(ToDrop(p, rng));
}
}
}
public static ItemDropPackageInfo RollPackage(ItemDropPackageJson pkg, System.Random rng)
{
if (pkg == null || pkg.TotalRate <= 0) return null;
int r = rng.Next(1, pkg.TotalRate + 1);
for (int i = 0; i < pkg.ItemDropPackageInfos.Count; i++)
{
r -= pkg.ItemDropPackageInfos[i].Rate;
if (r <= 0) return pkg.ItemDropPackageInfos[i];
}
return null;
}
static Drop ToDrop(ItemDropPackageInfo p, System.Random rng)
{
int min = p.MinCount <= 0 ? 1 : p.MinCount;
int max = p.MaxCount < min ? min : p.MaxCount;
return new Drop { itemId = p.Item_ID, count = rng.Next(min, max + 1) };
}
public static string GradeName(eFRItemGrade g)
{
switch (g)
{
case eFRItemGrade.Normal: return "일반";
case eFRItemGrade.Magic: return "매직";
case eFRItemGrade.Rare: return "레어";
case eFRItemGrade.Elite: return "엘리트";
case eFRItemGrade.Epic: return "에픽";
case eFRItemGrade.Legend: return "전설";
}
return g.ToString();
}
public static string SlotName(eFREquipmentType t)
{
// #879 부위 이름 = 등급 표 parts[].name(데이터 · 벨트 → 반지) · 표가 없을 때만 아래 기본값
var p = WLEquipGrades.Loaded || WLEquipGrades.EnsureLoaded() ? WLEquipGrades.Part(t) : null;
if (p != null && !string.IsNullOrEmpty(p.name)) return p.name;
switch (t)
{
case eFREquipmentType.Weapon: return "무기";
case eFREquipmentType.Necklace: return "목걸이";
case eFREquipmentType.Glove: return "장갑";
case eFREquipmentType.Armor: return "갑옷";
case eFREquipmentType.Belt: return "벨트";
case eFREquipmentType.Shose: return "신발";
}
return t.ToString();
}
public static string AbilityName(eFRAbility a)
{
switch (a)
{
case eFRAbility.ItemRange: return "획득 범위 %";
case eFRAbility.Health: return "체력 %";
case eFRAbility.RecoveryHealth: return "체력 회복 %";
case eFRAbility.Speed: return "이동 속도 %";
case eFRAbility.Defence: return "피해 감소 %";
case eFRAbility.Attack: return "공격력 %";
case eFRAbility.IncreaseDrop: return "획득량 %";
case eFRAbility.Rolling: return "구르기 %";
case eFRAbility.ProjectileSpeed: return "투사체 속도 %";
case eFRAbility.SkillRange: return "스킬 범위 %";
case eFRAbility.SkillDuration: return "스킬 지속 %";
case eFRAbility.Cooltime: return "쿨타임 감소 %";
case eFRAbility.AttackDefence: return "공격·방어 %";
case eFRAbility.Passive: return "패시브 효과 %";
case eFRAbility.Critical: return "치명타 확률 %";
case eFRAbility.CriticalDamage: return "치명타 피해 %";
case eFRAbility.Revive: return "부활 횟수";
case eFRAbility.HeartRecover: return "하트 회복 %";
case eFRAbility.SpeedValue: return "이동 속도 +";
case eFRAbility.Excellent: return "액설런트 확률 %";
case eFRAbility.ExcellentDamage: return "액설런트 피해 %";
}
return a.ToString();
}
}
}