184 lines
11 KiB
C#
184 lines
11 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
|
// WLEquipGrades.cs — WL 아이템 등급 표 로더 (PD 2026-10-09 · #823 7단계 → 2026-10-23 #973 8단계 G1~G8 · 천상 = FR Legend-3)
|
|
//
|
|
// 표 = `Resources/WL/WLGradeTable.json`(WLEquipSettings.gradeTablePath · C45 — 코드는 값을 갖지 않는다)
|
|
// tiers : 등급 7단계(일반·고급·희귀·에픽·전설(S)·불멸·신화) 이름 · 색 · 주스탯 반영 %
|
|
// frMap : FR 이식 데이터의 (ItemGrade, GradeLv) 14단계 → WL 등급(+단계) 대응
|
|
// parts : 부위별 출력 스탯(공격 부위 → ATK · 체력 부위 → MaxHP) · 레벨표 필드 · 주스탯 1당 환산 계수
|
|
// FR 표(ItemTable · 레벨표 · 합성표)는 수정하지 않는다 — 등급은 이 대응표로 읽는다.
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
using System.Collections.Generic;
|
|
using Newtonsoft.Json;
|
|
using UnityEngine;
|
|
|
|
namespace WL.Equip
|
|
{
|
|
[System.Serializable] public class WLGradeTier { public int tier; public string code; public string name; public string color; public double statPercent; public int isS; } // #973 code = G1~G8(표시 「전설(G5)」)
|
|
[System.Serializable] public class WLGradeFrMap { public int frGrade; public int gradeLv; public int tier; public int plus; }
|
|
/// <summary>부위 출력 — output = 힘민지 eStat 이름(ATK · MaxHP · DEF) 또는 WL 특수값(#879 PD 10-16): RING = 반지 근접/원거리 공격력(무기 계열이 맞을 때만 ATK) · MOVE = 신발 이동 속도 · RANDOM = 목걸이 랜덤 스탯(둘 다 WL.PartSkill.WLPartStats 가 배율로 더한다).
|
|
/// levelFactor = FR 레벨표 값에 곱하는 환산(예: 장갑 공격 칸 → 생명력 · 갑옷 체력 칸 → 방어력).</summary>
|
|
[System.Serializable] public class WLGradePart { public int equipType; public string name; public string output; public string levelField; public double statFactor = 1.0; public double levelFactor = 1.0;
|
|
/// <summary>#895 이 부위가 아무 효과도 주지 않는 등급들(능력치 · 파츠 스킬 0) — PD 「모든 무기의 일반 등급은 아무런 효과가 없어야」 = 무기 [1].</summary>
|
|
public int[] noEffectTiers; }
|
|
[System.Serializable] public class WLGradeTableJson { public List<WLGradeTier> tiers; public List<WLGradeFrMap> frMap; public List<WLGradePart> parts; }
|
|
|
|
public static class WLEquipGrades
|
|
{
|
|
public static bool Loaded { get; private set; }
|
|
public static string LastLog { get; private set; } = "";
|
|
public static int MaxTier { get; private set; }
|
|
|
|
static readonly Dictionary<int, WLGradeTier> s_tiers = new Dictionary<int, WLGradeTier>();
|
|
static readonly Dictionary<int, WLGradeFrMap> s_frMap = new Dictionary<int, WLGradeFrMap>(); // key = frGrade*1000 + gradeLv
|
|
static readonly Dictionary<int, WLGradeFrMap> s_tierBase = new Dictionary<int, WLGradeFrMap>(); // tier → plus 0 인 FR 단계
|
|
static readonly Dictionary<int, WLGradePart> s_parts = new Dictionary<int, WLGradePart>();
|
|
static readonly Dictionary<string, eStat> s_statCache = new Dictionary<string, eStat>();
|
|
|
|
public static bool EnsureLoaded()
|
|
{
|
|
if (Loaded) return true;
|
|
var cfg = WLEquipSettings.Instance;
|
|
string path = cfg != null && !string.IsNullOrEmpty(cfg.gradeTablePath) ? cfg.gradeTablePath : "WL/WLGradeTable";
|
|
try
|
|
{
|
|
var ta = Resources.Load<TextAsset>(path);
|
|
if (ta == null) throw new System.IO.FileNotFoundException("Resources/" + path + ".json 없음");
|
|
var t = JsonConvert.DeserializeObject<WLGradeTableJson>(ta.text);
|
|
s_tiers.Clear(); s_frMap.Clear(); s_tierBase.Clear(); s_parts.Clear(); MaxTier = 0;
|
|
if (t.tiers != null) for (int i = 0; i < t.tiers.Count; i++) { var g = t.tiers[i]; if (g == null) continue; s_tiers[g.tier] = g; if (g.tier > MaxTier) MaxTier = g.tier; }
|
|
if (t.frMap != null) for (int i = 0; i < t.frMap.Count; i++)
|
|
{
|
|
var m = t.frMap[i]; if (m == null) continue;
|
|
s_frMap[m.frGrade * 1000 + m.gradeLv] = m;
|
|
WLGradeFrMap cur;
|
|
if (!s_tierBase.TryGetValue(m.tier, out cur) || m.plus < cur.plus) s_tierBase[m.tier] = m;
|
|
}
|
|
if (t.parts != null) for (int i = 0; i < t.parts.Count; i++) { var p = t.parts[i]; if (p != null) s_parts[p.equipType] = p; }
|
|
Loaded = s_tiers.Count > 0;
|
|
LastLog = "등급 표 로드 — 등급 " + s_tiers.Count + " · FR 대응 " + s_frMap.Count + " · 부위 " + s_parts.Count;
|
|
if (cfg != null && cfg.verboseLog != 0) Debug.Log("[WL-823] " + LastLog);
|
|
}
|
|
catch (System.Exception ex)
|
|
{
|
|
LastLog = "등급 표 로드 실패 — " + ex.Message;
|
|
Debug.LogError("[WL-823] " + LastLog);
|
|
}
|
|
return Loaded;
|
|
}
|
|
|
|
public static WLGradeTier Tier(int tier) { EnsureLoaded(); WLGradeTier g; return s_tiers.TryGetValue(tier, out g) ? g : null; }
|
|
public static WLGradePart Part(eFREquipmentType t) { EnsureLoaded(); WLGradePart p; return s_parts.TryGetValue((int)t, out p) ? p : null; }
|
|
|
|
/// <summary>#895 — 그 부위가 이 등급에서 아무 효과도 없는가(parts[].noEffectTiers).</summary>
|
|
public static bool NoEffect(WLGradePart part, int tier)
|
|
{
|
|
if (part == null || part.noEffectTiers == null) return false;
|
|
for (int i = 0; i < part.noEffectTiers.Length; i++) if (part.noEffectTiers[i] == tier) return true;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>#895 — 이 장비가 지금 등급에서 아무 효과도 없는가(무기 일반 = 능력치 · 파츠 스킬 0).</summary>
|
|
public static bool NoEffect(ItemJson j)
|
|
{
|
|
if (j == null || !EnsureLoaded()) return false;
|
|
WLGradeTier t; int plus;
|
|
return TryMap(j, out t, out plus) && NoEffect(Part(j.EquipmentType), t.tier);
|
|
}
|
|
/// <summary>WL 등급의 기본(plus 0) FR 단계 — 상자 지급 아이템을 고를 때 쓴다.</summary>
|
|
public static WLGradeFrMap BaseStepOfTier(int tier) { EnsureLoaded(); WLGradeFrMap m; return s_tierBase.TryGetValue(tier, out m) ? m : null; }
|
|
|
|
/// <summary>FR (ItemGrade, GradeLv) → WL 등급(+단계). 대응이 없으면 false.</summary>
|
|
public static bool TryMap(ItemJson j, out WLGradeTier tier, out int plus)
|
|
{
|
|
tier = null; plus = 0;
|
|
if (j == null || !EnsureLoaded()) return false;
|
|
WLGradeFrMap m;
|
|
if (!s_frMap.TryGetValue((int)j.ItemGrade * 1000 + j.GradeLv, out m)) return false;
|
|
tier = Tier(m.tier);
|
|
plus = m.plus;
|
|
return tier != null;
|
|
}
|
|
|
|
public static eStat OutputStat(WLGradePart p)
|
|
{
|
|
if (p == null || string.IsNullOrEmpty(p.output)) return eStat.ATK;
|
|
eStat s;
|
|
if (s_statCache.TryGetValue(p.output, out s)) return s;
|
|
if (p.output == "RING") s = eStat.ATK; // 반지 = 근접/원거리 공격력(무기 계열이 맞을 때만)
|
|
else if (!System.Enum.TryParse<eStat>(p.output, out s)) s = eStat.ATK;
|
|
s_statCache[p.output] = s;
|
|
return s;
|
|
}
|
|
|
|
/// <summary>평평한 값(공격 · 생명 · 방어 · 반지)이 아닌 배율 출력(신발 MOVE · 목걸이 RANDOM) — WLEquipStats 합계에서 빠지고 WLPartStats 가 맡는다.</summary>
|
|
public static bool IsRateOutput(WLGradePart p) { return p != null && (p.output == "MOVE" || p.output == "RANDOM"); }
|
|
public static bool IsRingOutput(WLGradePart p) { return p != null && p.output == "RING"; }
|
|
|
|
public static string OutputName(eStat s)
|
|
{
|
|
switch (s)
|
|
{
|
|
case eStat.ATK: return "공격";
|
|
case eStat.MaxHP: return "생명";
|
|
case eStat.DEF: return "방어";
|
|
}
|
|
return s.ToString();
|
|
}
|
|
|
|
/// <summary>표시용 등급 라벨 — 「전설(S)+1」. rich = TMP 색 태그 포함. 대응이 없으면 FR 등급 이름.</summary>
|
|
public static string Label(ItemJson j, bool rich)
|
|
{
|
|
WLGradeTier t; int plus;
|
|
if (!TryMap(j, out t, out plus))
|
|
{
|
|
if (j == null) return "?";
|
|
return WLEquipTables.GradeName(j.ItemGrade) + (j.GradeLv > 0 ? "+" + j.GradeLv : "");
|
|
}
|
|
return Compose(t, plus, rich);
|
|
}
|
|
|
|
public static string TierLabel(int tier, bool rich)
|
|
{
|
|
var t = Tier(tier);
|
|
return t == null ? ("등급" + tier) : Compose(t, 0, rich);
|
|
}
|
|
|
|
/// <summary>FR (등급, 단계) 값만으로 라벨 — 합성표 행처럼 ItemJson 이 없는 자리에 쓴다.</summary>
|
|
public static string LabelOf(eFRItemGrade g, int gradeLv, bool rich)
|
|
{
|
|
if (EnsureLoaded())
|
|
{
|
|
WLGradeFrMap m;
|
|
if (s_frMap.TryGetValue((int)g * 1000 + gradeLv, out m))
|
|
{
|
|
var t = Tier(m.tier);
|
|
if (t != null) return Compose(t, m.plus, rich);
|
|
}
|
|
}
|
|
return WLEquipTables.GradeName(g) + (gradeLv > 0 ? "+" + gradeLv : "");
|
|
}
|
|
|
|
static string Compose(WLGradeTier t, int plus, bool rich)
|
|
{
|
|
string s = t.name + (!string.IsNullOrEmpty(t.code) ? "(" + t.code + ")" : t.isS != 0 ? "(S)" : "") + (plus > 0 ? "+" + plus : ""); // #973 PD 용어 「전설(G5)」 — 코드가 있으면 S 표시 대신
|
|
if (!rich || string.IsNullOrEmpty(t.color)) return s;
|
|
return "<color=" + t.color + ">" + s + "</color>";
|
|
}
|
|
|
|
/// <summary>등급 표 전체를 한 줄로(UI 안내용).</summary>
|
|
public static string Legend(bool rich)
|
|
{
|
|
if (!EnsureLoaded()) return LastLog;
|
|
var sb = new System.Text.StringBuilder();
|
|
for (int i = 1; i <= MaxTier; i++)
|
|
{
|
|
var t = Tier(i); if (t == null) continue;
|
|
if (sb.Length > 0) sb.Append(" ");
|
|
sb.Append(Compose(t, 0, rich)).Append(' ').Append(t.statPercent.ToString("F0")).Append('%');
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
}
|