Project_WL/Assets/WL/Equip/WLEquipTables.cs

340 lines
17 KiB
C#
Raw Permalink 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.

// ─────────────────────────────────────────────────────────────────────────────
// 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();
}
}
}