564 lines
34 KiB
C#
564 lines
34 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
|
||
namespace TKS.OutGame
|
||
{
|
||
/// <summary>
|
||
/// 보물(하단 탭 · 기획 S8 · 목업 img07·16·17 · 카피바라 수집품 구조 · PD #1093 C) — 상단 탭 3:
|
||
/// ① 보유 중 = 그리드 4열 · 등급 색 · ★ · 미보유 회색 · 탭 = 상세(기본 효과 · 조건 효과 n/max · 강화 ★)
|
||
/// ② 세트 = 세트마다 구성 · 조각 게이지 · [세트]/[3성]/[6성]/[10성] 효과 ③ 상자 = 상자 포인트 게이지 → 명장 상자 · 선택 상자 + 확률 · 보유 수 · 열기(상자는 이 탭에서만 연다).
|
||
/// </summary>
|
||
public class TreasureScreen : OutGameScreen
|
||
{
|
||
public static readonly string[] PageNames = { "보유 중", "세트", "상자" };
|
||
|
||
int page;
|
||
int chestIdx;
|
||
bool chestPicked;
|
||
readonly List<Button> tabs = new List<Button>();
|
||
readonly List<RectTransform> pages = new List<RectTransform>();
|
||
|
||
public int Page
|
||
{
|
||
get { return page; }
|
||
}
|
||
|
||
public int GridCount { get; private set; }
|
||
public int SetCount { get; private set; }
|
||
public ChestResult LastOpen { get; private set; }
|
||
|
||
public string SelectedChest
|
||
{
|
||
get
|
||
{
|
||
var l = S.Db.Chests.chests;
|
||
return l.Count > 0 ? l[Mathf.Clamp(chestIdx, 0, l.Count - 1)].id : "";
|
||
}
|
||
}
|
||
|
||
public override void Build()
|
||
{
|
||
Tab = 1;
|
||
if (UISkin.On) UiKit.Paint(UiKit.Fill("ListBg", Rt), UiKit.Col("list_bg", UiKit.Bg), false); // 목록형 바탕(Fable §5-3)
|
||
Header("보물", "", true); // 부제 삭제(설계서 §2-5)
|
||
var body = Body();
|
||
for (int i = 0; i < PageNames.Length; i++)
|
||
{
|
||
int k = i;
|
||
tabs.Add(UISkin.On
|
||
? UiKit.SubTab(body, "Tab" + i, PageNames[i], 24f + i * 348f, 18f, 332f, 84f, () => SetPage(k))
|
||
: UiKit.Btn("Tab" + i, body, PageNames[i], 36, UiKit.Paper, UiKit.Ink, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f + i * 348f, -18f), new Vector2(332f, 84f), () => SetPage(k)));
|
||
pages.Add(UiKit.Fill("Page" + i, body, 0f, 0f, 118f, 0f));
|
||
}
|
||
}
|
||
|
||
/// <summary>새 상자 화면 부품(Fable §4-5)이 테마에 있는가.</summary>
|
||
static bool Kit
|
||
{
|
||
get { return UISkin.Has("card3_bg") && UISkin.Has("banner_point") && UISkin.Has("btn_l_yellow"); }
|
||
}
|
||
|
||
/// <summary>보물 아이콘 id — 납품 그림(tre_<id>)이 있으면 그것 · 없으면 기본 스탯별 팩 임시 아이콘(tre_stat_* · Fable §6 N3 전까지).</summary>
|
||
static string IconId(TreasureDb.Item it)
|
||
{
|
||
if (UiKit.HasFinalIcon("tre_" + it.id)) return "tre_" + it.id;
|
||
return UISkin.Icon("tre_stat_" + it.baseStat) != null ? "tre_stat_" + it.baseStat : null;
|
||
}
|
||
|
||
public void SetPage(int p)
|
||
{
|
||
page = Mathf.Clamp(p, 0, pages.Count - 1);
|
||
Refresh();
|
||
}
|
||
|
||
public void SelectChest(string id)
|
||
{
|
||
int i = S.Db.Chests.IndexOf(id);
|
||
if (i >= 0) chestIdx = i;
|
||
chestPicked = true;
|
||
Refresh();
|
||
}
|
||
|
||
public override void Refresh()
|
||
{
|
||
for (int i = 0; i < tabs.Count; i++)
|
||
{
|
||
bool on = i == page;
|
||
if (UISkin.On) UiKit.SetSubTab(tabs[i], on);
|
||
else
|
||
{
|
||
tabs[i].GetComponent<Image>().color = on ? UiKit.Red : UiKit.Paper; // 선택 = 붉은 옻칠(다른 화면 탭과 같음)
|
||
UiKit.LabelOf(tabs[i]).color = on ? UiKit.White : UiKit.Ink;
|
||
}
|
||
pages[i].gameObject.SetActive(on);
|
||
}
|
||
UiKit.Clear(pages[page]);
|
||
if (page == 0) BuildOwned(pages[0]);
|
||
else if (page == 1) BuildSets(pages[1]);
|
||
else if (Kit) BuildChestsKit(pages[2]);
|
||
else BuildChests(pages[2]);
|
||
}
|
||
|
||
/// <summary>보물 등급 대표 색(테마 tre_<등급> · 희귀 청 · 영웅 자 · 전설 주 · 신화 적 · 명장 금).</summary>
|
||
public static Color GradeColor(string g)
|
||
{
|
||
return UiKit.Col("tre_" + g, UiKit.Slot);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 보물 칸(UI 상업용 재설계 ⑥ · 설계서 §3⑥) = 팩 ItemFrame 5층 등급색 + 아이콘(납품 tre_* · 없으면 이름 첫 글자) + ★ 또는 조각 바 + 아래 이름 판 ·
|
||
/// 미보유 = 진청 빈 칸 + 흰 25 % 실루엣.
|
||
/// </summary>
|
||
void TreasureCell(Transform parent, TreasureDb.Item it, bool showShards)
|
||
{
|
||
var st = S.Save.inv.Treasure(it.id);
|
||
bool owned = st != null;
|
||
var cell = UiKit.Cell(parent, "T_" + it.id, Color.clear, owned ? (System.Action)(() => Detail(it.id)) : null);
|
||
cell.sprite = null; // 바닥 = 클릭 판(투명)
|
||
var t = cell.transform;
|
||
UiKit.ItemSlot(t, owned ? "tre_" + it.grade + "_" : "tre_none_", owned);
|
||
var art = UiKit.Rect("Art", t, new Vector2(0.5f, 1f), new Vector2(0.5f, 0.5f), new Vector2(0f, -100f), new Vector2(140f, 140f));
|
||
string icon = IconId(it);
|
||
if (icon != null)
|
||
{
|
||
// 아이콘 = 납품 그림 또는 기본 스탯별 임시(Fable §4-5 「글자 → 보물 아이콘」) · 미보유 = 흰 25 % 실루엣
|
||
var ic = UiKit.Icon(art, icon, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, icon.StartsWith("tre_stat_") ? 120f : 140f);
|
||
if (owned) UiKit.DropShadow(ic, 5f);
|
||
else ic.color = UiKit.Col("tre_none_icon", UiKit.InkSoft);
|
||
}
|
||
else if (owned) UiKit.Title(UiKit.FillLabel(art, it.nameKo.Substring(0, 1), 84, UiKit.White), true);
|
||
if (owned)
|
||
{
|
||
if (showShards && st.star < it.starMax)
|
||
{
|
||
int need, iron;
|
||
TreasureRules.EnhanceCost(S.Db, it, st.star + 1, out need, out iron);
|
||
var fill = UiKit.Bar("Shards", t, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 76f), new Vector2(150f, 26f), UiKit.Slot, GradeColor(it.grade));
|
||
UiKit.SetBar(fill, st.shards / (float)Mathf.Max(1, need));
|
||
UiKit.FillLabel(fill.parent, st.shards + "/" + need, 18, UiKit.White);
|
||
}
|
||
else if (st.star > 0) UiKit.StarRow(t, st.star, st.star, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 72f), 30f);
|
||
}
|
||
// 이름 띠 = 칸 아래에 붙은 카드 띠(CardFrame01_Bottom · Fable §4-5) · 테마가 없으면 둥근 판
|
||
var band = UISkin.Has("card_bottom")
|
||
? UiKit.Layer(t, "Name", "card_bottom", UiKit.Col("card_bottom", UiKit.Dark), 6f, 0f, 6f, 6f)
|
||
: UiKit.Skin(UiKit.Rect("Name", t, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 12f), new Vector2(206f, 52f)), "plate12", UiKit.Col("gear_plate", UiKit.Dark));
|
||
if (UISkin.Has("card_bottom"))
|
||
{
|
||
band.rectTransform.anchorMax = new Vector2(1f, 0f);
|
||
band.rectTransform.offsetMax = new Vector2(-6f, 62f);
|
||
}
|
||
var nt = UiKit.FillLabel(band.transform, owned ? it.nameKo : "미보유", 26, owned ? UiKit.White : UiKit.Col("text_soft", UiKit.InkSoft));
|
||
nt.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
}
|
||
|
||
// ---------- ① 보유 중 ----------
|
||
|
||
void BuildOwned(RectTransform p)
|
||
{
|
||
var db = S.Db;
|
||
int owned = 0;
|
||
foreach (var it in db.Treasures.items)
|
||
if (S.Save.inv.Treasure(it.id) != null) owned++;
|
||
var cnt = UiKit.Title(UiKit.Label(p, "보유 " + owned + " / " + db.Treasures.items.Count, 36, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(28f, -8f), new Vector2(360f, 56f)), UISkin.On);
|
||
cnt.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
var grades = db.Economy.TreasureGrades;
|
||
for (int i = 0; i < grades.Count; i++)
|
||
{
|
||
// 등급 범례 칩 = 서브탭 틀 소형(Round10 · Fable §4-5 「120×56 통일」) · 등급 대표 색
|
||
var pos = new Vector2(-24f - (grades.Count - 1 - i) * 126f, -8f);
|
||
var chip = UISkin.Has("subtab")
|
||
? UiKit.Skin(UiKit.Rect("G" + i, p, new Vector2(1f, 1f), new Vector2(1f, 1f), pos, new Vector2(118f, 56f)), "subtab", GradeColor(grades[i].Key))
|
||
: UiKit.Box("G" + i, p, new Vector2(1f, 1f), new Vector2(1f, 1f), pos, new Vector2(110f, 48f), GradeColor(grades[i].Key));
|
||
UiKit.Title(UiKit.FillLabel(chip.transform, TreasureRules.GradeKo(grades[i].Key), 26, UiKit.White), UISkin.On);
|
||
}
|
||
ScrollRect sr;
|
||
var grid = UiKit.ScrollGrid("Grid", p, 24f, 24f, 76f, 64f, new Vector2(240f, 290f), new Vector2(16f, 16f), 4, out sr);
|
||
// 정렬 = 보유 먼저 · 등급 높은 순
|
||
var list = new List<TreasureDb.Item>(db.Treasures.items);
|
||
list.Sort((a, b) =>
|
||
{
|
||
bool oa = S.Save.inv.Treasure(a.id) != null, ob = S.Save.inv.Treasure(b.id) != null;
|
||
if (oa != ob) return oa ? -1 : 1;
|
||
return db.Economy.TreasureGradeOrder(b.grade).CompareTo(db.Economy.TreasureGradeOrder(a.grade));
|
||
});
|
||
GridCount = 0;
|
||
foreach (var it in list)
|
||
{
|
||
TreasureCell(grid, it, false);
|
||
GridCount++;
|
||
}
|
||
var bonus = new List<string>();
|
||
foreach (var s in TreasureDb.BaseStats)
|
||
{
|
||
float v = TreasureRules.StatPct(db, S.Save, s);
|
||
if (v > 0f) bonus.Add(TreasureRules.StatKo(s) + " +" + v.ToString("0.#") + "%");
|
||
}
|
||
string fx = bonus.Count > 0 ? "전체 효과 " + string.Join(" · ", bonus.ToArray()) : "보물을 모으면 전 유닛에 상시 효과";
|
||
if (UISkin.Has("chip"))
|
||
{
|
||
// 아래 효과 줄 = 정보 칩(§5-6)
|
||
var c =UiKit.Skin(UiKit.Rect("Effects", p, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 4f), new Vector2(1032f, 54f)), "chip", UiKit.Col("chip_dark", UiKit.Dark));
|
||
var ft = UiKit.FillLabel(c.transform, fx, 26, UiKit.White, TextAnchor.MiddleCenter, false, 20f);
|
||
ft.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
}
|
||
else UiKit.Label(p, fx, 26, UiKit.InkSoft, TextAnchor.MiddleCenter, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 8f), new Vector2(1030f, 50f), false);
|
||
}
|
||
|
||
public void Detail(string id)
|
||
{
|
||
var db = S.Db;
|
||
var d = S.Save;
|
||
var it = db.Treasures.Get(id);
|
||
var st = d.inv.Treasure(id);
|
||
if (it == null || st == null) return;
|
||
var sb = new System.Text.StringBuilder();
|
||
sb.Append(TreasureRules.GradeKo(it.grade)).Append(" · ★").Append(st.star).Append("/").Append(it.starMax);
|
||
var tiers = it.setId.Length > 0 ? db.TreasureSets.TiersOf(it.setId) : null;
|
||
if (tiers != null && tiers.Count > 0) sb.Append(" · 세트 「").Append(tiers[0].nameKo).Append("」");
|
||
sb.Append("\n\n<b>기본 효과</b> ").Append(TreasureRules.StatKo(it.baseStat)).Append(" +").Append(TreasureRules.BasePct(db, it, st.star).ToString("0.##")).Append("%");
|
||
int stacks = TreasureRules.CondStacks(d, it);
|
||
sb.Append("\n<b>조건 효과</b> ").Append(TreasureRules.CondKo(it.condKey)).Append(" ").Append(it.condEvery).Append("회마다 +").Append(it.condPct.ToString("0.##")).Append("% (")
|
||
.Append(stacks).Append("/").Append(it.condMax).Append(" · 누적 ").Append(TreasureRules.Counter(d, it.condKey)).Append(")");
|
||
sb.Append("\n<size=28>= 지금 ").Append(TreasureRules.StatKo(it.baseStat)).Append(" +").Append(TreasureRules.Value(db, d, it).ToString("0.##")).Append("%</size>");
|
||
string btn;
|
||
var why = TreasureRules.CanEnhance(db, d, id);
|
||
if (st.star < it.starMax)
|
||
{
|
||
int shards, iron;
|
||
TreasureRules.EnhanceCost(db, it, st.star + 1, out shards, out iron);
|
||
sb.Append("\n\n<b>강화 ★").Append(st.star + 1).Append("</b> 조각 ").Append(st.shards).Append("/").Append(shards).Append(" · 운철 ").Append(d.inv.iron).Append("/").Append(iron);
|
||
btn = why == null ? "강화" : why;
|
||
}
|
||
else btn = "최고 ★";
|
||
Root.Modal(it.nameKo, sb.ToString(),
|
||
new OutGameRoot.ModalButton(btn, why == null ? UiKit.Red : UiKit.Disabled, () =>
|
||
{
|
||
var w = S.Do(() => TreasureRules.Enhance(S.Db, S.Save, id));
|
||
if (w == null) Detail(id);
|
||
else Root.Toast(w);
|
||
}, true),
|
||
new OutGameRoot.ModalButton("닫기", UiKit.Dark, null));
|
||
}
|
||
|
||
// ---------- ② 세트 ----------
|
||
|
||
void BuildSets(RectTransform p)
|
||
{
|
||
var db = S.Db;
|
||
var d = S.Save;
|
||
ScrollRect sr;
|
||
var list = UiKit.ScrollGrid("Sets", p, 24f, 24f, 6f, 12f, new Vector2(1032f, 640f), new Vector2(0f, 24f), 1, out sr);
|
||
SetCount = 0;
|
||
foreach (var setId in db.TreasureSets.SetIds)
|
||
{
|
||
var tiers = db.TreasureSets.TiersOf(setId);
|
||
if (tiers.Count == 0) continue;
|
||
var head = tiers[0];
|
||
var block = UiKit.Cell(list, "Set_" + setId, new Color(0f, 0f, 0f, 0f), null);
|
||
block.raycastTarget = false;
|
||
var band = UiKit.Box("Band", block.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), Vector2.zero, new Vector2(700f, 70f), UiKit.Dark);
|
||
UiKit.FillLabel(band.transform, head.nameKo, 40, UiKit.White);
|
||
int owned = TreasureRules.OwnedIn(d, head);
|
||
UiKit.Label(block.transform, "보유 " + owned + "/" + head.members.Count, 30, owned == head.members.Count ? UiKit.Ink : UiKit.Red, TextAnchor.MiddleRight, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(0f, -10f), new Vector2(200f, 50f));
|
||
var row = UiKit.Rect("Members", block.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -84f), new Vector2(1032f, 290f));
|
||
var hl = row.gameObject.AddComponent<HorizontalLayoutGroup>();
|
||
hl.spacing = 12f;
|
||
hl.childAlignment = TextAnchor.UpperCenter;
|
||
hl.childControlWidth = hl.childControlHeight = false;
|
||
foreach (var m in head.members)
|
||
{
|
||
var it = db.Treasures.Get(m);
|
||
if (it == null) continue;
|
||
var holder = UiKit.Rect("M", row, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(196f, 290f));
|
||
TreasureCell(holder, it, true);
|
||
var c = holder.GetChild(0) as RectTransform;
|
||
c.anchorMin = Vector2.zero;
|
||
c.anchorMax = Vector2.one;
|
||
c.offsetMin = c.offsetMax = Vector2.zero;
|
||
}
|
||
var fx = UiKit.Box("Effects", block.transform, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), Vector2.zero, new Vector2(1032f, 250f), UiKit.Paper);
|
||
UiKit.Border(fx, UiKit.Line, 2f);
|
||
var best = TreasureRules.SetTier(db, d, setId);
|
||
var sb = new System.Text.StringBuilder("<b>기본 효과</b> <size=26>(가장 높은 달성 단계 1개)</size>");
|
||
string okHex = "#" + ColorUtility.ToHtmlStringRGB(UiKit.Ok), offHex = "#" + ColorUtility.ToHtmlStringRGB(UiKit.InkSoft);
|
||
foreach (var t in tiers)
|
||
{
|
||
bool ok = TreasureRules.TierReached(d, t);
|
||
string label = t.stars == 0 ? "[세트]" : "[" + t.stars + "성]";
|
||
string stat = TreasureRules.StatKo(t.effectKey.Replace("_pct", ""));
|
||
sb.Append("\n").Append(ok ? (t == best ? "<color=" + okHex + "><b>" : "<color=" + okHex + ">") : "<color=" + offHex + ">").Append(label).Append(" ").Append(stat).Append(" +").Append(t.pct.ToString("0.#")).Append("%")
|
||
.Append(ok ? (t == best ? " ✔ 적용</b>" : " 달성") : " 미달성").Append("</color>");
|
||
}
|
||
UiKit.FillLabel(fx.transform, sb.ToString(), 30, UiKit.Ink, TextAnchor.MiddleLeft, false, 24f);
|
||
SetCount++;
|
||
}
|
||
}
|
||
|
||
// ---------- ③ 상자 ----------
|
||
|
||
void BuildChests(RectTransform p)
|
||
{
|
||
var db = S.Db;
|
||
var d = S.Save;
|
||
var e = db.Economy;
|
||
var goalBox = UiKit.TopBand("Points", p, 6f, 150f, 24f, 24f);
|
||
var gimg = UiKit.Paint(goalBox, UiKit.Paper);
|
||
UiKit.Border(gimg, UiKit.Line, 3f);
|
||
if (UiKit.Icon(goalBox, "chest_point", new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(16f, 0f), 100f) == null)
|
||
UiKit.FillLabel(UiKit.Dot("P", goalBox, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(20f, 0f), 90f, UiKit.Coin).transform, "P", 44, UiKit.Ink);
|
||
var reward = db.Chests.Get(e.ChestPointReward);
|
||
UiKit.Label(goalBox, (e.ChestPointGoal - d.inv.chestPoints) + "포인트 더 모으면 " + (reward != null ? reward.nameKo : ""), 34, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(130f, -12f), new Vector2(800f, 54f));
|
||
var fill = UiKit.Bar("Bar", goalBox, new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(130f, 52f), new Vector2(700f, 30f), UiKit.Slot, UiKit.Coin);
|
||
UiKit.SetBar(fill, d.inv.chestPoints / (float)Mathf.Max(1, e.ChestPointGoal));
|
||
UiKit.Label(goalBox, "상자 포인트 " + d.inv.chestPoints + " / " + e.ChestPointGoal, 26, UiKit.InkSoft, TextAnchor.MiddleLeft, new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(130f, 8f), new Vector2(700f, 40f), false);
|
||
|
||
var c = db.Chests.Get(SelectedChest);
|
||
if (c == null) return;
|
||
int count = TreasureRules.ChestCount(d, c.id);
|
||
var banner = UiKit.Box("Banner", p, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -180f), new Vector2(560f, 470f), ChestColor(c.id));
|
||
UiKit.Border(banner, new Color(0.25f, 0.2f, 0.18f), 4f);
|
||
UiKit.Title(UiKit.Label(banner.transform, c.nameKo, 60, UiKit.White, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -40f), new Vector2(520f, 90f)), true);
|
||
UiKit.Icon(banner.transform, "chest_" + c.id, new Vector2(0f, 1f), new Vector2(0.5f, 0.5f), new Vector2(10f, -10f), 150f);
|
||
int line = 0;
|
||
for (int i = 0; i < ChestDb.ProbGrades.Length; i++)
|
||
{
|
||
if (c.p[i] <= 0f) continue;
|
||
var chip = UiKit.Box("P" + i, banner.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -150f - line * 62f), new Vector2(360f, 54f), GradeColor(ChestDb.ProbGrades[i]));
|
||
UiKit.FillLabel(chip.transform, TreasureRules.GradeKo(ChestDb.ProbGrades[i]) + " " + (c.p[i] * 100f).ToString("0.#") + " %", 30, UiKit.White);
|
||
line++;
|
||
}
|
||
var extra = new List<string>();
|
||
if (c.goldMax > 0) extra.Add("골드 " + c.goldMin + "~" + c.goldMax);
|
||
if (c.gemMax > 0) extra.Add("보옥 " + c.gemMin + "~" + c.gemMax);
|
||
if (c.ironMax > 0) extra.Add("운철 " + c.ironMin + "~" + c.ironMax);
|
||
extra.Add("포인트 +" + c.points);
|
||
UiKit.Label(banner.transform, string.Join(" · ", extra.ToArray()), 26, UiKit.White, TextAnchor.MiddleCenter, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 18f), new Vector2(540f, 50f), false);
|
||
UiKit.Btn("Prev", p, "◁", 70, new Color(0f, 0f, 0f, 0f), UiKit.Ink, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(40f, -360f), new Vector2(120f, 140f), () => Step(-1));
|
||
UiKit.Btn("Next", p, "▷", 70, new Color(0f, 0f, 0f, 0f), UiKit.Ink, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-40f, -360f), new Vector2(120f, 140f), () => Step(1));
|
||
|
||
var open = UiKit.Btn("Open", p, count > 0 ? "열기\n<size=28>보유 " + count + "</size>" : "열기\n<size=28>상자 없음</size>", 46, count > 0 ? UiKit.Red : UiKit.Disabled, UiKit.White,
|
||
new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -680f), new Vector2(480f, 140f), () => DoOpen(SelectedChest));
|
||
open.interactable = count > 0;
|
||
UiKit.Label(p, "상자는 이 탭에서만 엽니다 · 중복 = 조각 · 천기 = 적장 토벌 전용", 26, UiKit.InkSoft, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -836f), new Vector2(1000f, 44f), false);
|
||
|
||
int n = db.Chests.chests.Count;
|
||
float w = (1032f - (n - 1) * 12f) / n;
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
var ch = db.Chests.chests[i];
|
||
int k = i;
|
||
var b = UiKit.Btn("Chest_" + ch.id, p, "", 28, i == chestIdx ? UiKit.Red : UiKit.Paper, UiKit.Ink, new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(24f + i * (w + 12f), 24f), new Vector2(w, 220f), () =>
|
||
{
|
||
chestIdx = k;
|
||
chestPicked = true;
|
||
Refresh();
|
||
});
|
||
UiKit.Border(b.GetComponent<Image>(), i == chestIdx ? UiKit.Coin : UiKit.Line, i == chestIdx ? 5f : 2f);
|
||
var box = UiKit.Box("Icon", b.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -20f), new Vector2(w * 0.7f, 90f), ChestColor(ch.id));
|
||
box.raycastTarget = false;
|
||
UiKit.Icon(box.transform, "chest_" + ch.id, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 4f), 96f);
|
||
UiKit.Label(b.transform, ch.nameKo + "\n<size=34>×" + TreasureRules.ChestCount(d, ch.id) + "</size>", 26, UiKit.Ink, TextAnchor.MiddleCenter, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 14f), new Vector2(w, 100f));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 상자 탭(Fable 제안서 §4-5 · #1104) — 포인트 띠(BannerFrame05 · 가는 주황 바 · 「85 / 120」 · 오른쪽 보상 상자) →
|
||
/// 상자 카드(CardFrame03 상자 색 + 안쪽 + 광택 · 상자 아이콘 300 + 뒤 금빛 · 등급 칩 + % 한 줄 · 내용물 줄) · 좌우 화살표 →
|
||
/// 「열기」 Primary 노랑 + 보유 칩(없으면 회색) → 아래 상자 종류 행(ItemFrame 5층 상자 색 + 수량 칩 · 선택 = FocusLine 깜빡).
|
||
/// </summary>
|
||
void BuildChestsKit(RectTransform p)
|
||
{
|
||
var db = S.Db;
|
||
var d = S.Save;
|
||
var e = db.Economy;
|
||
var soft = UiKit.Col("text_soft", UiKit.InkSoft);
|
||
|
||
// 포인트 띠
|
||
var band = UiKit.Skin(UiKit.At(p, "Points", 24f, 6f, 1032f, 92f), "banner_point", UiKit.Col("banner_point", UiKit.White)).transform;
|
||
var pi = UiKit.Icon(band, "chest_point", new Vector2(0f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(46f, 2f), 76f);
|
||
if (pi != null) UiKit.DropShadow(pi, 3f);
|
||
var reward = db.Chests.Get(e.ChestPointReward);
|
||
UiKit.Label(band, Mathf.Max(0, e.ChestPointGoal - d.inv.chestPoints) + "포인트 더 모으면 " + (reward != null ? reward.nameKo : ""), 28, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(112f, -6f), new Vector2(640f, 44f));
|
||
var bg = UiKit.Skin(UiKit.At(band, "Bar", 112f, 50f, 600f, 30f), "bar4_bg", UiKit.White);
|
||
var fill = UiKit.Skin(UiKit.At(bg.transform, "Fill", 4f, 4f, 592f, 20f), "bar4_fill", UiKit.White).rectTransform;
|
||
float v = Mathf.Clamp01(d.inv.chestPoints / (float)Mathf.Max(1, e.ChestPointGoal));
|
||
fill.sizeDelta = new Vector2(Mathf.Max(16f, 592f * v), 20f);
|
||
fill.gameObject.SetActive(v > 0.001f);
|
||
var pt = UiKit.Title(UiKit.Label(band, d.inv.chestPoints + " / " + e.ChestPointGoal, 30, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(730f, -38f), new Vector2(180f, 50f)), true);
|
||
pt.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
if (reward != null)
|
||
{
|
||
var ri = UiKit.Icon(band, "chest_" + reward.id, new Vector2(1f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(-62f, 4f), 80f);
|
||
if (ri != null) UiKit.DropShadow(ri, 3f);
|
||
}
|
||
|
||
if (!chestPicked && TreasureRules.ChestCount(d, SelectedChest) == 0)
|
||
for (int i = 0; i < db.Chests.chests.Count; i++)
|
||
if (TreasureRules.ChestCount(d, db.Chests.chests[i].id) > 0)
|
||
{
|
||
chestIdx = i;
|
||
break;
|
||
}
|
||
var c = db.Chests.Get(SelectedChest);
|
||
if (c == null) return;
|
||
int count = TreasureRules.ChestCount(d, c.id);
|
||
|
||
// 무대 = 카드 뒤 문양 흰 5 % + 바닥 그라데이션(검수2 결함 4)
|
||
if (UISkin.Has("bg_pattern"))
|
||
{
|
||
var stage = UiKit.At(p, "Stage", 0f, 108f, 1080f, 1000f);
|
||
var pat = UiKit.Skin(stage, "bg_pattern", UiKit.Col("stage_pattern", Color.clear));
|
||
pat.type = Image.Type.Tiled;
|
||
if (UISkin.Has("scroll_fade"))
|
||
{
|
||
var g = UiKit.Skin(UiKit.At(stage, "Ground", 0f, 1000f - 160f, 1080f, 160f), "scroll_fade", UiKit.Col("panel_ground", Color.clear));
|
||
g.rectTransform.localScale = new Vector3(1f, -1f, 1f);
|
||
g.rectTransform.pivot = new Vector2(0f, 0f);
|
||
}
|
||
}
|
||
|
||
// 상자 카드
|
||
const float Cw = 680f, Ch = 760f, Cy = 120f;
|
||
var card = UiKit.At(p, "Card", (1080f - Cw) * 0.5f, Cy, Cw, Ch);
|
||
UiKit.Skin(card, "card3_bg", ChestColor(c.id));
|
||
UiKit.Layer(card, "Inner", "card3_inner", UiKit.Col("card3_inner", Color.clear), 18f, 104f, 18f, 18f);
|
||
UiKit.Layer(card, "Light", "card3_light", UiKit.Col("card3_light", Color.clear), 6f, 6f, 6f, 10f);
|
||
var name = UiKit.Title(UiKit.Label(card, c.nameKo, 56, UiKit.White, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -16f), new Vector2(Cw - 40f, 80f)), true);
|
||
name.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
if (UISkin.Has("glow_circle"))
|
||
{
|
||
var glow = UiKit.Skin(UiKit.Rect("Glow", card, new Vector2(0.5f, 1f), new Vector2(0.5f, 0.5f), new Vector2(0f, -320f), new Vector2(600f, 600f)), "glow_circle", UiKit.Col("chest_glow", Color.clear));
|
||
var pulse = glow.gameObject.AddComponent<UiPulse>();
|
||
pulse.min = 0.7f;
|
||
pulse.period = 1.6f;
|
||
}
|
||
var ci = UiKit.Icon(card, "chest_" + c.id, new Vector2(0.5f, 1f), new Vector2(0.5f, 0.5f), new Vector2(0f, -320f), 320f);
|
||
if (ci != null) UiKit.DropShadow(ci, 8f);
|
||
var probs = new List<int>();
|
||
for (int i = 0; i < ChestDb.ProbGrades.Length; i++)
|
||
if (c.p[i] > 0f) probs.Add(i);
|
||
const float Pw = 178f, Pg = 14f;
|
||
float px = (Cw - probs.Count * Pw - (probs.Count - 1) * Pg) * 0.5f;
|
||
for (int k = 0; k < probs.Count; k++)
|
||
{
|
||
int i = probs[k];
|
||
string g = ChestDb.ProbGrades[i];
|
||
var chip = UiKit.Skin(UiKit.At(card, "P" + i, px + k * (Pw + Pg), 528f, Pw, 60f), "para_orange", GradeColor(g));
|
||
var ct = UiKit.Title(UiKit.FillLabel(chip.transform, TreasureRules.GradeKo(g) + " " + (c.p[i] * 100f).ToString("0.#") + "%", 30, UiKit.White), true);
|
||
ct.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
}
|
||
var extra = new List<string>();
|
||
if (c.goldMax > 0) extra.Add("골드 " + c.goldMin + "~" + c.goldMax);
|
||
if (c.gemMax > 0) extra.Add("보옥 " + c.gemMin + "~" + c.gemMax);
|
||
if (c.ironMax > 0) extra.Add("운철 " + c.ironMin + "~" + c.ironMax);
|
||
extra.Add("포인트 +" + c.points);
|
||
// 내용물 줄 = 카드 아래 어두운 띠 위 흰 글자(검수2 결함 10)
|
||
var eb = UiKit.Layer(card, "ExtraBand", "card_bottom", UiKit.Col("card_bottom", UiKit.Dark), 6f, 0f, 6f, 6f);
|
||
eb.rectTransform.anchorMax = new Vector2(1f, 0f);
|
||
eb.rectTransform.offsetMax = new Vector2(-6f, 76f);
|
||
var et = UiKit.Title(UiKit.FillLabel(eb.transform, string.Join(" · ", extra.ToArray()), 26, UiKit.White, TextAnchor.MiddleCenter, true, 16f), true);
|
||
et.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
|
||
// 좌우 화살표(카드 가운데 높이)
|
||
Arrow(p, "Prev", "arrow_prev", 30f, Cy + Ch * 0.5f - 70f, () => Step(-1));
|
||
Arrow(p, "Next", "arrow_next", 1080f - 30f - 100f, Cy + Ch * 0.5f - 70f, () => Step(1));
|
||
|
||
// 열기
|
||
var open = UiKit.SkinBtn(p, "Open", count > 0 ? "btn_l_yellow" : "btn_gray", UiKit.White, count > 0 ? "열기" : "상자 없음", 46,
|
||
count > 0 ? UiKit.Col("text_on_yellow", UiKit.Ink) : UiKit.White, (1080f - 460f) * 0.5f, Cy + Ch + 26f, 460f, 116f, () => DoOpen(SelectedChest));
|
||
open.interactable = count > 0;
|
||
if (count > 0) UiKit.BtnCost(open, "chest_" + c.id, "x" + count, 56f, 34);
|
||
UiKit.Label(p, "상자는 이 탭에서만 엽니다 · 중복 = 조각 · 천기 = 적장 토벌 전용", 26, soft, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -(Cy + Ch + 156f)), new Vector2(1000f, 44f), false);
|
||
|
||
// 상자 종류 행(아래)
|
||
int n = db.Chests.chests.Count;
|
||
const float Tw = 150f;
|
||
float gap = n > 1 ? Mathf.Min(64f, (1032f - n * Tw) / (n - 1)) : 0f;
|
||
float tx0 = (1080f - n * Tw - (n - 1) * gap) * 0.5f;
|
||
float ty = OutGameRoot.BodyH - 118f - 24f - 40f - Tw;
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
var ch = db.Chests.chests[i];
|
||
int k = i;
|
||
bool sel = i == chestIdx;
|
||
var cell = UiKit.At(p, "Chest_" + ch.id, tx0 + i * (Tw + gap), ty, Tw, Tw);
|
||
UiKit.ItemSlot(cell, "chest_" + ch.id + "_", sel);
|
||
var hit = cell.gameObject.AddComponent<Image>();
|
||
hit.color = Color.clear;
|
||
cell.gameObject.AddComponent<Button>().onClick.AddListener(() =>
|
||
{
|
||
chestIdx = k;
|
||
chestPicked = true;
|
||
Refresh();
|
||
});
|
||
var ic = UiKit.Icon(cell, "chest_" + ch.id, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 6f), 104f);
|
||
if (ic != null) UiKit.DropShadow(ic, 4f);
|
||
UiKit.QtyChip(cell, "x" + TreasureRules.ChestCount(d, ch.id), 28);
|
||
if (sel && UISkin.Has("focus_line"))
|
||
{
|
||
var f = UiKit.Layer(cell, "Focus", "focus_line", UiKit.Col("chest_select", UiKit.White), -8f, -8f, -8f, -8f);
|
||
f.gameObject.AddComponent<UiPulse>();
|
||
cell.localScale = Vector3.one * 1.08f;
|
||
}
|
||
else UiKit.Layer(cell, "Dim", "item_bg", UiKit.Col("chest_dim", new Color(0f, 0f, 0f, 0.25f)));
|
||
var nl = UiKit.Label(p, ch.nameKo, 26, sel ? UiKit.White : soft, TextAnchor.UpperCenter, new Vector2(0f, 1f), new Vector2(0.5f, 1f), new Vector2(tx0 + i * (Tw + gap) + Tw * 0.5f, -(ty + Tw + 10f)), new Vector2(Tw + gap, 36f), sel);
|
||
nl.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
}
|
||
}
|
||
|
||
/// <summary>화살표 버튼(투명 판 + 팩 화살표 아이콘 · 없으면 글자).</summary>
|
||
static void Arrow(Transform p, string name, string icon, float x, float y, System.Action onClick)
|
||
{
|
||
var rt = UiKit.At(p, name, x, y, 100f, 140f);
|
||
var img = rt.gameObject.AddComponent<Image>();
|
||
img.color = Color.clear;
|
||
rt.gameObject.AddComponent<Button>().onClick.AddListener(() => onClick());
|
||
var ic = UiKit.Icon(rt, icon, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 96f);
|
||
if (ic != null) UiKit.DropShadow(ic, 4f);
|
||
else UiKit.Title(UiKit.FillLabel(rt, name == "Prev" ? "◁" : "▷", 70, UiKit.White), true);
|
||
}
|
||
|
||
void Step(int dir)
|
||
{
|
||
int n = Mathf.Max(1, S.Db.Chests.chests.Count);
|
||
chestIdx = ((chestIdx + dir) % n + n) % n;
|
||
chestPicked = true;
|
||
Refresh();
|
||
}
|
||
|
||
public void DoOpen(string id)
|
||
{
|
||
var r = S.OpenChest(id);
|
||
LastOpen = r;
|
||
if (r == null)
|
||
{
|
||
Root.Toast("상자 없음");
|
||
return;
|
||
}
|
||
var it = r.treasureId != null ? S.Db.Treasures.Get(r.treasureId) : null;
|
||
var sb = new System.Text.StringBuilder();
|
||
if (it != null)
|
||
sb.Append("<size=48><b>").Append(it.nameKo).Append("</b></size>\n").Append(TreasureRules.GradeKo(it.grade)).Append(r.gradeUp ? " (상위 등급!)" : "")
|
||
.Append(r.isNew ? " <color=#b0301e>NEW</color>" : " 조각 +" + r.shards).Append("\n\n");
|
||
var parts = new List<string>();
|
||
if (r.gold > 0) parts.Add("골드 +" + r.gold.ToString("N0"));
|
||
if (r.gem > 0) parts.Add("보옥 +" + r.gem.ToString("N0"));
|
||
if (r.iron > 0) parts.Add("운철 +" + r.iron);
|
||
parts.Add("상자 포인트 +" + r.points);
|
||
sb.Append(string.Join(" · ", parts.ToArray()));
|
||
if (r.bonusChests > 0) sb.Append("\n<color=#b0301e>포인트 달성 → ").Append(S.Db.Chests.Get(S.Db.Economy.ChestPointReward).nameKo).Append(" +").Append(r.bonusChests).Append("</color>");
|
||
Root.Modal(S.Db.Chests.Get(id).nameKo + " 열기", sb.ToString(),
|
||
new OutGameRoot.ModalButton("한 번 더", UiKit.Red, () => DoOpen(id)),
|
||
new OutGameRoot.ModalButton("확인", UiKit.Dark, null));
|
||
}
|
||
|
||
/// <summary>상자 대표 색 = 테마 chest_<id>(구 코드 상수 → ui_theme.csv · C45 · #1104).</summary>
|
||
public static Color ChestColor(string id)
|
||
{
|
||
return UiKit.Col("chest_" + id, UiKit.Slot);
|
||
}
|
||
}
|
||
}
|