Project_WL/Assets/WL/Equip/WLEquipGui.RefLayout.cs

368 lines
27 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.

// ─────────────────────────────────────────────────────────────────────────────
// WLEquipGui.RefLayout.cs — PD 레퍼런스 레이아웃(#881 · 2026-10-16)
//
// PD 원문: 「장비 아이템에 텍스트가 너무 많아. 첨부한 레퍼런스 게임과 같이 장비 UI 팝업의 레이아웃을 수정해줘」
// + 「인벤토리 화면도 예시 이미지와 같이 레이아웃을 개선하도록 해.」
// - 장비 상세 팝업 V2: 등급 리본 · 제목 띠 · 아이콘 + 「레벨: n/max」 · 능력치(+다음 레벨 증가분) + 합성 · 강등 작은 버튼 · 짧은 설명 ·
// 「등급 스킬」 목록(등급 색 점 · 잠긴 등급 = 자물쇠 + 흐림 · 마지막 줄 = 시너지) · 비용 줄 · 버튼 3(장비 · 레벨업 · 일괄 레벨업).
// 예전 긴 글(FR 옵션 · 계산식 · 설명 문단)은 넣지 않는다.
// - 인벤토리 머리줄: 부위 탭 대신 「정렬 · 나의 장비 · 합성」(부위 거르기 = 빈 슬롯 누르기 · 제목 누르면 전체).
// 치수 · 색 · 글자 · 아이콘 = Resources/WL/WLEquipGui.json itemPopup · hub(C45).
// ─────────────────────────────────────────────────────────────────────────────
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using WL.PartSkill;
namespace WL.Equip
{
public partial class WLEquipGui
{
WLGuiItemPopup IP;
bool IpOn { get { return IP != null && IP.enabled != 0; } }
TextMeshProUGUI _ipLevel, _ipStat, _ipDesc, _ipSection, _ipGold, _ipStone;
Image _ipStatIcon, _ipGoldIcon, _ipStoneIcon;
RectTransform _ipRowsRoot, _ipStoneGroup;
sealed class IpRow { public RectTransform root; public Image dot, lockImg; public TextMeshProUGUI text; }
readonly List<IpRow> _ipRows = new List<IpRow>();
public static int ItemPopupV2Refreshes;
// ── 인벤토리 머리줄 · 정렬 ──────────────────────────────────────────
int _sortMode;
TextMeshProUGUI _sortLabel, _gridTitle;
void BuildGridHeader()
{
var hdr = NewRT(_hub, "GridHeader");
hdr.anchorMin = new Vector2(0f, 1f); hdr.anchorMax = new Vector2(1f, 1f); hdr.pivot = new Vector2(0.5f, 1f);
hdr.anchoredPosition = new Vector2(0f, -H.tabTop); hdr.sizeDelta = new Vector2(0f, H.headerH);
var sortBtn = NewButton(hdr, SortLabel(), Spr("btnPrimary"), Color.white, H.headerBtnW, H.headerH, () => { _sortMode = (_sortMode + 1) % Mathf.Max(1, H.sortLabels != null ? H.sortLabels.Length : 1); Refresh(); }, H.headerBtnFontPx, DarkCol());
Place((RectTransform)sortBtn.transform, new Vector2(0f, 0.5f), new Vector2(H.edge, 0f), new Vector2(H.headerBtnW, H.headerH), new Vector2(0f, 0.5f));
_sortLabel = sortBtn.GetComponentInChildren<TextMeshProUGUI>();
_composeOpenBtn = NewButton(hdr, "합성", Spr("btnPrimary"), Color.white, H.headerBtnW, H.headerH, () => OpenCompose(-1), H.headerBtnFontPx, DarkCol());
Place((RectTransform)_composeOpenBtn.transform, new Vector2(1f, 0.5f), new Vector2(-H.edge, 0f), new Vector2(H.headerBtnW, H.headerH), new Vector2(1f, 0.5f));
_gridTitle = NewText(hdr, "Title", H.headerFontPx, TextAlignmentOptions.Midline, TextCol());
_gridTitle.fontStyle = FontStyles.Bold; ApplyOutline(_gridTitle); _gridTitle.raycastTarget = true;
var trt = _gridTitle.rectTransform; trt.anchorMin = new Vector2(0f, 0f); trt.anchorMax = new Vector2(1f, 1f);
trt.offsetMin = new Vector2(H.edge + H.headerBtnW + 16f, 0f); trt.offsetMax = new Vector2(-(H.edge + H.headerBtnW + 16f), 0f);
var tb = _gridTitle.gameObject.AddComponent<Button>(); tb.targetGraphic = _gridTitle; tb.onClick.AddListener(() => { _tab = 0; Refresh(); }); // 제목 = 부위 거르기 해제
}
string SortLabel()
{
var l = H.sortLabels;
return l != null && l.Length > 0 ? l[Mathf.Clamp(_sortMode, 0, l.Length - 1)] : "정렬";
}
void RefreshGridHeader()
{
if (_sortLabel != null) _sortLabel.text = SortLabel();
if (_gridTitle != null) _gridTitle.text = H.gridTitle + (_tab != 0 ? " · " + WLEquipTables.SlotName((eFREquipmentType)_tab) : "");
}
static int TierOfItem(Item it)
{
WLGradeTier t; int plus;
return it != null && it.itemJson != null && it.itemJson.IsEquipment && WLEquipGrades.TryMap(it.itemJson, out t, out plus) ? t.tier * 100 + plus : 0;
}
/// <summary>그리드 목록 — 장착분 빼기(hub.gridHideEquipped) + 정렬(0 = 등급순 · 1 = 레벨순 · 장비 먼저 · 소모품 뒤).</summary>
List<Item> GridItems(List<Item> src)
{
if (H.headerMode == 0 && H.gridHideEquipped == 0) return src;
var list = new List<Item>(src.Count);
for (int i = 0; i < src.Count; i++)
{
var it = src[i];
if (H.gridHideEquipped != 0 && it.itemJson.IsEquipment && WLInventory.IsEquipped(it.rawItem._id)) continue;
list.Add(it);
}
if (H.headerMode == 0) return list;
int mode = _sortMode;
list.Sort((a, b) =>
{
bool qa = a.itemJson.IsEquipment, qb = b.itemJson.IsEquipment;
if (qa != qb) return qa ? -1 : 1;
int ta = TierOfItem(a), tb = TierOfItem(b), la = a.rawItem.level, lb = b.rawItem.level;
int c = mode == 1 ? lb.CompareTo(la) : tb.CompareTo(ta);
if (c != 0) return c;
c = mode == 1 ? tb.CompareTo(ta) : lb.CompareTo(la);
if (c != 0) return c;
c = a.itemJson.EquipmentType.CompareTo(b.itemJson.EquipmentType);
if (c != 0) return c;
return a.rawItem._id.CompareTo(b.rawItem._id);
});
return list;
}
// ── 장비 상세 팝업 V2 ────────────────────────────────────────────────
void BuildItemPopupV2()
{
_popup = new GameObject("DetailPopup", typeof(RectTransform)); _popup.layer = UILayer; _popup.transform.SetParent(_root, false);
var prt = (RectTransform)_popup.transform; Stretch(prt);
var dim = NewImage(prt, "Dim", null, Col("dim", new Color(0f, 0f, 0f, 0.7f)), Image.Type.Simple); Stretch(dim.rectTransform); dim.raycastTarget = true;
var dimBtn = dim.gameObject.AddComponent<Button>(); dimBtn.targetGraphic = dim; dimBtn.onClick.AddListener(() => _popup.SetActive(false));
var box = NewPopupBox(prt, "Box", new Vector2(IP.w, IP.h)); var b = box.rectTransform;
const float rim = 14f;
// 제목 띠 + 등급 리본 + 닫기
var titleBar = NewImage(b, "TitleBar", Spr("titleBar"), Col("popupTitleBar", new Color(1f, 0.78f, 0.2f, 1f)));
var tb = titleBar.rectTransform; tb.anchorMin = new Vector2(0f, 1f); tb.anchorMax = new Vector2(1f, 1f); tb.pivot = new Vector2(0.5f, 1f);
tb.offsetMin = new Vector2(rim, -IP.titleH - rim); tb.offsetMax = new Vector2(-rim, -rim);
_popupName = NewText(tb, "Name", IP.titleFontPx, TextAlignmentOptions.Midline, Col("popupTitleText", Color.white)); _popupName.fontStyle = FontStyles.Bold; ApplyOutline(_popupName);
Stretch(_popupName.rectTransform, 100f, 4f, 100f, 4f);
_popupRarityImg = NewImage(b, "Ribbon", Spr("gradeLabel"), Color.white);
Place(_popupRarityImg.rectTransform, new Vector2(0.5f, 1f), new Vector2(0f, IP.ribbonH * 0.35f), new Vector2(IP.ribbonW, IP.ribbonH), new Vector2(0.5f, 0.5f));
_popupRarity = NewText(_popupRarityImg.rectTransform, "Rarity", IP.ribbonFontPx, TextAlignmentOptions.Midline, Color.white); _popupRarity.fontStyle = FontStyles.Bold; ApplyOutline(_popupRarity); Stretch(_popupRarity.rectTransform);
var close = NewIconButton(b, "Close", "closeIcon", L.closeBtnW, () => _popup.SetActive(false), "closeBtnBg");
Place((RectTransform)close.transform, new Vector2(1f, 1f), new Vector2(10f, 10f), new Vector2(L.closeBtnW, L.closeBtnH), new Vector2(1f, 1f));
// 아이콘 + 레벨
float x0 = rim + IP.pad, y0 = rim + IP.titleH + IP.pad;
_popupForm = NewCell(b, "Form", IP.iconSize, L.cellIconInset, L.cellFontPx, true, false, null);
Place(_popupForm.root, new Vector2(0f, 1f), new Vector2(x0, -y0), new Vector2(IP.iconSize, IP.iconSize), new Vector2(0f, 1f));
_ipLevel = NewText(b, "Level", IP.levelFontPx, TextAlignmentOptions.Midline, TextCol()); _ipLevel.fontStyle = FontStyles.Bold;
Place(_ipLevel.rectTransform, new Vector2(0f, 1f), new Vector2(x0, -(y0 + IP.iconSize + 4f)), new Vector2(IP.iconSize, IP.levelFontPx + 12f), new Vector2(0f, 1f));
// 능력치 상자 + 작은 버튼(합성 · 강등)
float rx = x0 + IP.iconSize + IP.gap, rw = IP.w - rx - x0;
float statW = rw - (IP.smallBtn + IP.gap) * 2f;
var statBox = NewImage(b, "StatBox", Spr("innerBox"), Col("statBox", new Color(0.1f, 0.14f, 0.3f, 0.92f)));
Place(statBox.rectTransform, new Vector2(0f, 1f), new Vector2(rx, -y0), new Vector2(statW, IP.statH), new Vector2(0f, 1f));
_ipStatIcon = NewImage(statBox.rectTransform, "Icon", null, Color.white, Image.Type.Simple); _ipStatIcon.preserveAspect = true;
Place(_ipStatIcon.rectTransform, new Vector2(0f, 0.5f), new Vector2(10f, 0f), new Vector2(IP.statIcon, IP.statIcon), new Vector2(0f, 0.5f));
_ipStat = NewText(statBox.rectTransform, "Value", IP.statFontPx, TextAlignmentOptions.MidlineRight, TextCol()); _ipStat.fontStyle = FontStyles.Bold; ApplyOutline(_ipStat);
_ipStat.enableWordWrapping = false; _ipStat.overflowMode = TextOverflowModes.Ellipsis;
var srt = _ipStat.rectTransform; srt.anchorMin = Vector2.zero; srt.anchorMax = Vector2.one; srt.offsetMin = new Vector2(IP.statIcon + 18f, 0f); srt.offsetMax = new Vector2(-16f, 0f);
_composeBtn = NewButton(b, "합성", Spr("btnBlue"), Color.white, IP.smallBtn, IP.statH, () => OpenCompose(_selectedId), IP.smallBtnFontPx, TextCol());
Place((RectTransform)_composeBtn.transform, new Vector2(0f, 1f), new Vector2(rx + statW + IP.gap, -y0), new Vector2(IP.smallBtn, IP.statH), new Vector2(0f, 1f));
_downBtn = NewButton(b, "강등", Spr("btnDanger"), Color.white, IP.smallBtn, IP.statH, () => Act(() => WLInventory.Downgrade(_selectedId)), IP.smallBtnFontPx, TextCol());
Place((RectTransform)_downBtn.transform, new Vector2(0f, 1f), new Vector2(rx + statW + IP.gap * 2f + IP.smallBtn, -y0), new Vector2(IP.smallBtn, IP.statH), new Vector2(0f, 1f));
// 짧은 설명
float dy = y0 + IP.statH + IP.gap;
float dh = Mathf.Max(IP.descH, IP.iconSize + IP.levelFontPx + 16f - IP.statH - IP.gap);
var desc = NewImage(b, "Desc", Spr("innerBox"), Col("descBox", new Color(0f, 0f, 0f, 0.28f)));
Place(desc.rectTransform, new Vector2(0f, 1f), new Vector2(rx, -dy), new Vector2(rw, dh), new Vector2(0f, 1f));
_ipDesc = NewText(desc.rectTransform, "Text", IP.descFontPx, TextAlignmentOptions.MidlineLeft, Col("descText", new Color(0.92f, 0.94f, 1f, 1f)));
_ipDesc.enableWordWrapping = true; _ipDesc.overflowMode = TextOverflowModes.Ellipsis; Stretch(_ipDesc.rectTransform, 16f, 8f, 16f, 8f);
// 등급 스킬
float bottomBlock = rim + IP.pad + IP.btnH + IP.gap + IP.costH + IP.gap;
float sy = Mathf.Max(dy + dh, y0 + IP.iconSize + IP.levelFontPx + 16f) + IP.gap;
var sec = NewImage(b, "Section", Spr("innerBox"), Col("sectionBox", new Color(0f, 0f, 0f, 0.26f)));
var secRt = sec.rectTransform; secRt.anchorMin = Vector2.zero; secRt.anchorMax = Vector2.one; secRt.offsetMin = new Vector2(x0, bottomBlock); secRt.offsetMax = new Vector2(-x0, -sy);
var head = NewImage(secRt, "Head", Spr("innerBox"), Col("sectionHead", new Color(0f, 0f, 0f, 0.3f)));
var hrt = head.rectTransform; hrt.anchorMin = new Vector2(0f, 1f); hrt.anchorMax = new Vector2(1f, 1f); hrt.pivot = new Vector2(0.5f, 1f); hrt.offsetMin = new Vector2(0f, -IP.sectionTitleH); hrt.offsetMax = Vector2.zero;
_ipSection = NewText(hrt, "Title", IP.sectionTitleFontPx, TextAlignmentOptions.MidlineLeft, TextCol()); _ipSection.fontStyle = FontStyles.Bold; Stretch(_ipSection.rectTransform, 20f, 0f, 20f, 0f);
_ipSection.text = IP.sectionTitle;
_ipRowsRoot = NewRT(secRt, "Rows");
_ipRowsRoot.anchorMin = Vector2.zero; _ipRowsRoot.anchorMax = Vector2.one; _ipRowsRoot.offsetMin = new Vector2(16f, 8f); _ipRowsRoot.offsetMax = new Vector2(-16f, -IP.sectionTitleH - 8f);
var vl = _ipRowsRoot.gameObject.AddComponent<VerticalLayoutGroup>(); vl.spacing = 2f;
vl.childControlWidth = true; vl.childControlHeight = true; vl.childForceExpandWidth = true; vl.childForceExpandHeight = false;
var circle = WLPartSkillHud.CircleSprite();
for (int i = 0; i < 8; i++)
{
var r = new IpRow();
r.root = NewRT(_ipRowsRoot, "Row" + i); Pref(r.root, 0f, IP.rowH);
r.dot = NewImage(r.root, "Dot", circle, Color.white, Image.Type.Simple);
Place(r.dot.rectTransform, new Vector2(0f, 0.5f), new Vector2(6f + (IP.lockSize - IP.dotSize) * 0.5f, 0f), new Vector2(IP.dotSize, IP.dotSize), new Vector2(0f, 0.5f));
r.lockImg = NewImage(r.root, "Lock", Spr("lockIcon"), Col("lockTint", new Color(1f, 0.75f, 0.3f, 1f)), Image.Type.Simple); r.lockImg.preserveAspect = true;
Place(r.lockImg.rectTransform, new Vector2(0f, 0.5f), new Vector2(6f, 0f), new Vector2(IP.lockSize, IP.lockSize), new Vector2(0f, 0.5f));
r.text = NewText(r.root, "Text", IP.rowFontPx, TextAlignmentOptions.MidlineLeft, TextCol()); r.text.enableWordWrapping = false; r.text.overflowMode = TextOverflowModes.Ellipsis;
var t = r.text.rectTransform; t.anchorMin = Vector2.zero; t.anchorMax = Vector2.one; t.offsetMin = new Vector2(IP.lockSize + 20f, 0f); t.offsetMax = Vector2.zero;
_ipRows.Add(r);
}
// 비용 줄(골드 · 강화석)
var cost = NewRT(b, "Cost");
cost.anchorMin = new Vector2(0f, 0f); cost.anchorMax = new Vector2(1f, 0f); cost.pivot = new Vector2(0.5f, 0f);
cost.offsetMin = new Vector2(x0, rim + IP.pad + IP.btnH + IP.gap); cost.offsetMax = new Vector2(-x0, rim + IP.pad + IP.btnH + IP.gap + IP.costH);
var hl = cost.gameObject.AddComponent<HorizontalLayoutGroup>(); hl.spacing = 10f; hl.childAlignment = TextAnchor.MiddleCenter;
hl.childControlWidth = true; hl.childControlHeight = true; hl.childForceExpandWidth = false; hl.childForceExpandHeight = false;
_ipGoldIcon = NewImage(cost, "GoldIcon", Spr("goldIcon"), Color.white, Image.Type.Simple); _ipGoldIcon.preserveAspect = true; Pref(_ipGoldIcon.rectTransform, IP.costIcon, IP.costIcon);
_ipGold = NewText(cost, "Gold", IP.costFontPx, TextAlignmentOptions.MidlineLeft, TextCol()); _ipGold.fontStyle = FontStyles.Bold; ApplyOutline(_ipGold);
_ipGold.enableWordWrapping = false; var gle = _ipGold.gameObject.AddComponent<LayoutElement>(); gle.minHeight = IP.costH;
_ipStoneGroup = NewRT(cost, "StoneGroup");
var shl = _ipStoneGroup.gameObject.AddComponent<HorizontalLayoutGroup>(); shl.spacing = 10f; shl.padding = new RectOffset(30, 0, 0, 0); shl.childAlignment = TextAnchor.MiddleCenter;
shl.childControlWidth = true; shl.childControlHeight = true; shl.childForceExpandWidth = false; shl.childForceExpandHeight = false;
_ipStoneIcon = NewImage(_ipStoneGroup, "StoneIcon", Spr("stoneIcon"), Color.white, Image.Type.Simple); _ipStoneIcon.preserveAspect = true; Pref(_ipStoneIcon.rectTransform, IP.costIcon, IP.costIcon);
_ipStone = NewText(_ipStoneGroup, "Stone", IP.costFontPx, TextAlignmentOptions.MidlineLeft, TextCol()); _ipStone.fontStyle = FontStyles.Bold; ApplyOutline(_ipStone);
_ipStone.enableWordWrapping = false; var sle = _ipStone.gameObject.AddComponent<LayoutElement>(); sle.minHeight = IP.costH;
// 버튼 3 — 장비 · 레벨업 · 일괄 레벨업
var row = NewRT(b, "Buttons");
row.anchorMin = new Vector2(0f, 0f); row.anchorMax = new Vector2(1f, 0f); row.pivot = new Vector2(0.5f, 0f);
row.offsetMin = new Vector2(x0, rim + IP.pad); row.offsetMax = new Vector2(-x0, rim + IP.pad + IP.btnH);
var bl = row.gameObject.AddComponent<HorizontalLayoutGroup>(); bl.spacing = IP.gap;
bl.childControlWidth = true; bl.childControlHeight = true; bl.childForceExpandWidth = false; bl.childForceExpandHeight = true;
_equipBtn = NewButton(row, "장비", Spr("btnBlue"), Color.white, 0f, IP.btnH, OnEquipToggle, IP.btnFontPx, TextCol()); Flex((RectTransform)_equipBtn.transform, 1f);
_equipLabel = _equipBtn.GetComponentInChildren<TextMeshProUGUI>();
_lvBtn = NewButton(row, "레벨업", Spr("btnPrimary"), Color.white, 0f, IP.btnH, () => Act(() => WLInventory.ItemLevelUp(_selectedId, false)), IP.btnFontPx, DarkCol()); Flex((RectTransform)_lvBtn.transform, 1f);
_lvAllBtn = NewButton(row, "일괄 레벨업", Spr("btnSecondary"), Color.white, 0f, IP.btnH, () => Act(() => WLInventory.ItemLevelUp(_selectedId, true)), IP.btnFontPx, DarkCol()); Flex((RectTransform)_lvAllBtn.transform, 1f);
_popup.SetActive(false);
}
void RefreshItemPopupV2(Item sel)
{
ItemPopupV2Refreshes++;
var j = sel.itemJson;
bool isEq = j.IsEquipment;
BindCell(_popupForm, sel, false);
_popupName.text = j.Item_name;
int tier = 1, plus = 0; WLGradeTier t = null;
if (isEq && WLEquipGrades.TryMap(j, out t, out plus)) tier = t.tier;
_popupRarityImg.color = isEq ? TierCol(tier) : Col("frameEmpty", Color.gray);
_popupRarity.text = isEq ? WLEquipGrades.Label(j, false) : "소모품";
_ipLevel.text = isEq ? IP.levelPrefix + sel.rawItem.level + "/" + j.ItemMaxLv : "×" + sel.rawItem.count;
string iconKey = null;
_ipStat.text = isEq ? StatText(sel, out iconKey) : "";
string sprName; _ipStatIcon.sprite = iconKey != null && IP.statIcons != null && IP.statIcons.TryGetValue(iconKey, out sprName) ? WLEquipGuiAssets.Get(sprName) : null;
_ipStatIcon.color = _ipStatIcon.sprite != null ? Color.white : new Color(1f, 1f, 1f, 0f);
_ipDesc.text = DescText(sel);
FillGradeRows(sel, isEq ? tier : 0);
FillCost(sel, isEq);
bool equipped = isEq && WLInventory.IsEquipped(sel.rawItem._id);
_equipLabel.text = equipped ? "해제" : "장비";
SetEnabled(_equipBtn, isEq, "btnBlue");
bool canLv = isEq && WLInventory.NextLevelCost(sel) != null;
SetEnabled(_lvBtn, canLv, "btnPrimary"); SetEnabled(_lvAllBtn, canLv, "btnSecondary");
ItemComposeJson cj; ItemJson rj;
SetEnabled(_composeBtn, isEq && WLInventory.ComposeInfo(sel, out cj, out rj), "btnBlue");
SetEnabled(_downBtn, isEq && WLEquipTables.FindComposeByResult(j.ItemGrade, j.GradeLv) != null, "btnDanger");
}
/// <summary>능력치 한 줄 — 평평한 값 = 「7.0<초록>+0.3</초록>」(다음 레벨 증가분) · 반지 = 근접/원거리 · 신발 · 목걸이 = 배율 문구.</summary>
string StatText(Item sel, out string iconKey)
{
iconKey = null;
var part = WLEquipGrades.Part(sel.itemJson.EquipmentType);
if (part == null) return "";
if (WLEquipGrades.IsRateOutput(part)) { iconKey = part.output; return WLPartStats.ItemStatLine(sel, true); }
eStat o; double bv, bonus; string line;
if (!WLEquipStats.ItemValues(sel, WLEquipStats.CurrentMainStat(), out o, out bv, out bonus, out line)) return "";
double cur = bv + bonus;
iconKey = WLEquipGrades.IsRingOutput(part) ? "RING" : o.ToString();
string head = "";
if (WLEquipGrades.IsRingOutput(part))
{
string rn; bool ok = WLPartStats.RingMatches(sel, out rn);
head = (rn.Length > 0 ? rn.Split(' ')[0] : rn) + " ";
if (!ok) return "<color=#FF8A8A>" + head + cur.ToString("0.#") + "</color>";
}
string s = head + cur.ToString("0.#");
if (WLInventory.NextLevelCost(sel) != null)
{
eStat o2; double bv2, bonus2; string line2;
if (WLEquipStats.ItemValues(sel, WLEquipStats.CurrentMainStat(), out o2, out bv2, out bonus2, out line2, sel.rawItem.level + 1))
{
double d = bv2 + bonus2 - cur;
if (d > 0.05) s += "<color=#" + ColorUtility.ToHtmlStringRGB(Col("statDelta", new Color(0.49f, 0.92f, 0.38f, 1f))) + ">+" + d.ToString("0.#") + "</color>";
}
}
return s;
}
/// <summary>짧은 설명 — 파츠 스킬 설명 · 무기 = 주력/각성 스킬 이름 · 그 밖 = 표 설명.</summary>
string DescText(Item sel)
{
var j = sel.itemJson;
if (!j.IsEquipment) return j.Desc ?? "";
var d = WLPartSkillData.Enabled ? WLPartSkillData.DefForGroup(j.GroupNo) : null;
if (d != null && (d.kind == WLPartKind.Active || d.kind == WLPartKind.Passive)) return d.desc;
if (j.EquipmentType == eFREquipmentType.Weapon)
{
var rows = WL.Combat.Weapon.WLWeaponSkillTable.Rows(j.GroupNo);
if (rows != null && rows.Count > 0)
{
var sb = new System.Text.StringBuilder();
foreach (var r in WL.Combat.Weapon.WLWeaponSkillTable.ByUnlock(rows)) { if (sb.Length > 0) sb.Append(" · "); sb.Append(WL.Combat.Weapon.WLWeaponSkillTable.SlotName(r.slot)).Append(" [").Append(r.skillName).Append(']'); } // #973 기본 → 주력 → 각성
return sb.ToString();
}
}
return j.Desc ?? "";
}
/// <summary>등급 스킬 줄 — 파츠 스킬 = 등급별 단계 · 무기 = 무기 스킬 등급표 · 마지막 = 시너지. 지금 등급보다 높은 줄 = 자물쇠 + 흐림.</summary>
void FillGradeRows(Item sel, int tier)
{
var j = sel.itemJson;
var texts = new List<string>(); var tiers = new List<int>(); var open = new List<bool>();
var d = j.IsEquipment && WLPartSkillData.Enabled ? WLPartSkillData.DefForGroup(j.GroupNo) : null;
int maxParts = Mathf.Max(1, IP.rowMaxParts);
if (d != null && (d.kind == WLPartKind.Active || d.kind == WLPartKind.Passive))
{
for (int t = 1; t <= WLEquipGrades.MaxTier; t++) { texts.Add(WLPartSkillText.TierRow(d, t, maxParts)); tiers.Add(t); open.Add(t <= tier); } // #973 G1~G8
}
else if (j.IsEquipment && j.EquipmentType == eFREquipmentType.Weapon)
{
var rows = WL.Combat.Weapon.WLWeaponSkillTable.Rows(j.GroupNo);
string main = "", awaken = "", basic = "";
if (rows != null) foreach (var r in rows) { if (r.slot == 1) main = r.skillName; else if (r.slot == 2) awaken = r.skillName; else if (r.slot == 3) basic = r.skillName; } // #973 3 = 기본
var wt = IP.weaponTierText;
if (rows != null && rows.Count > 0 && wt != null && wt.Length > 1)
for (int t = 1; t <= WLEquipGrades.MaxTier && t < wt.Length; t++) { texts.Add(wt[t].Replace("{main}", main).Replace("{awaken}", awaken).Replace("{basic}", basic.Length > 0 ? basic : "준비 중")); tiers.Add(t); open.Add(t <= tier); }
}
// 시너지 줄(파츠 액티브 · 무기 시너지 · 결합)
if (d != null && d.HasSynergy && d.kind != WLPartKind.Passive)
{
var s = SlotFor(sel, d);
var p = WLPartSkillData.Def(!string.IsNullOrEmpty(d.synKey) ? d.synKey : d.combineKey);
int need = WLPartSkillData.S.synergyLevel;
texts.Add("★ 시너지 [" + (string.IsNullOrEmpty(d.synName) ? d.name : d.synName) + "] · " + (p != null ? p.name : "?") + " " + need + "단계");
tiers.Add(0); open.Add(s != null && s.synergy);
}
for (int i = 0; i < _ipRows.Count; i++)
{
var r = _ipRows[i];
bool on = i < texts.Count;
SetOn(r.root, on || (i == 0 && texts.Count == 0));
if (!on)
{
if (i == 0 && texts.Count == 0) { SetOn(r.dot, false); SetOn(r.lockImg, false); r.text.text = IP.noSkillText; r.text.color = Col("textDim", Color.gray); }
continue;
}
bool isOpen = open[i];
SetOn(r.dot, isOpen); SetOn(r.lockImg, !isOpen);
r.dot.color = tiers[i] > 0 ? TierCol(tiers[i]) : Col("synergyOn", new Color(1f, 0.8f, 0.25f, 1f));
r.lockImg.color = tiers[i] > 0 ? TierCol(tiers[i]) : Col("synergyOn", new Color(1f, 0.8f, 0.25f, 1f));
r.text.text = texts[i];
var c = TextCol(); if (!isOpen) c.a = 0.45f;
r.text.color = c;
}
}
void FillCost(Item sel, bool isEq)
{
var cost = isEq ? WLInventory.NextLevelCost(sel) : null;
SetOn(_ipGoldIcon, cost != null);
if (cost == null) { _ipGold.text = isEq ? IP.maxLevelText : ""; SetOn(_ipStoneGroup, false); return; }
long g = WLInventory.GetGold();
string lack = "#" + ColorUtility.ToHtmlStringRGB(Col("costLack", new Color(1f, 0.45f, 0.45f, 1f)));
_ipGold.text = (g < cost.MoneyCountRequired ? "<color=" + lack + ">" : "") + Short(g) + "/" + Short(cost.MoneyCountRequired) + (g < cost.MoneyCountRequired ? "</color>" : "");
bool needStone = cost.ItemKeyRequired > 0 && cost.ItemCountRequired > 0;
SetOn(_ipStoneGroup, needStone);
if (needStone)
{
long have = WLInventory.GetItemCountByIndex(cost.ItemKeyRequired);
_ipStone.text = (have < cost.ItemCountRequired ? "<color=" + lack + ">" : "") + have + "/" + cost.ItemCountRequired + (have < cost.ItemCountRequired ? "</color>" : "");
}
}
public string ItemPopupStateForProbe
{
get
{
if (!IpOn || _popup == null) return "v2 off";
int rows = 0; foreach (var r in _ipRows) if (r.root.gameObject.activeSelf) rows++;
return "v2 open=" + _popup.activeSelf + " name='" + (_popupName != null ? _popupName.text : "") + "' stat='" + (_ipStat != null ? _ipStat.text : "") + "' rows=" + rows + " gold='" + (_ipGold != null ? _ipGold.text : "") + "'";
}
}
}
}