368 lines
27 KiB
C#
368 lines
27 KiB
C#
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|||
|
|
// 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();
|
|||
|
|
for (int i = 0; i < rows.Count; i++) { if (i > 0) sb.Append(" · "); sb.Append(rows[i].slot == 1 ? "주력 [" : "각성 [").Append(rows[i].skillName).Append(']'); }
|
|||
|
|
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 <= 7; t++) { texts.Add(WLPartSkillText.TierRow(d, t, maxParts)); tiers.Add(t); open.Add(t <= tier); }
|
|||
|
|
}
|
|||
|
|
else if (j.IsEquipment && j.EquipmentType == eFREquipmentType.Weapon)
|
|||
|
|
{
|
|||
|
|
var rows = WL.Combat.Weapon.WLWeaponSkillTable.Rows(j.GroupNo);
|
|||
|
|
string main = "", awaken = "";
|
|||
|
|
if (rows != null) foreach (var r in rows) { if (r.slot == 1) main = r.skillName; else if (r.slot == 2) awaken = r.skillName; }
|
|||
|
|
var wt = IP.weaponTierText;
|
|||
|
|
if (rows != null && rows.Count > 0 && wt != null && wt.Length > 1)
|
|||
|
|
for (int t = 1; t <= 7 && t < wt.Length; t++) { texts.Add(wt[t].Replace("{main}", main).Replace("{awaken}", awaken)); 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 : "") + "'";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|