ThreeKingdomsSlingshot/Assets/Scripts/OutGame/UI/TradeScreen.cs

541 lines
32 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.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace TKS.OutGame
{
/// <summary>
/// 거래(img11 · 기획 S15) — ① 영웅·유닛 뽑기(픽업 · 천장 n/80 · 확률 공개 · 1회/10연 · 첫 10연 무료 · 결과 = 카드 순차 공개)
/// ② 보물 상자(PD #1093 C) = 상자 5종 · 보유 수 · 구매(chests.csv buy_gold/buy_gem/buy_points · 거래 할인 개발) · 열기는 보물 > 상자 탭
/// ③ 상점·패스 = 골드 승급 장비 상점(흰/녹 · 일일 · 새로고침 보옥 · 시장 개설 무료) · 성장 패스·월정액·챕터 팩·첫 결제 팩 = 「준비 중」 스텁(과금 없음).
/// </summary>
public class TradeScreen : OutGameScreen
{
int page;
readonly List<Button> tabs = new List<Button>();
readonly List<RectTransform> pages = new List<RectTransform>();
Text pityText;
RectTransform pityBar;
GameObject freeChip;
GameObject freeChip2;
Button singleBtn;
Button tenBtn;
RectTransform pickupArt;
RectTransform shopGrid;
ScrollRect shopScroll;
Text shopTimer;
Button refreshBtn;
GameObject resultRoot;
readonly List<GameObject> resultCards = new List<GameObject>();
Coroutine revealing;
public List<GachaPull> LastPulls { get; private set; }
public int Page
{
get { return page; }
}
public bool ResultVisible
{
get { return resultRoot != null && resultRoot.activeSelf; }
}
/// <summary>장비 상점 칸 수(스킬서 임시 판매 칸 제외).</summary>
public int ShopCardCount
{
get
{
int n = 0;
foreach (Transform c in shopGrid)
if (c.name != SkillBookCellName) n++;
return n;
}
}
public const string SkillBookCellName = "SkillBook";
/// <summary>스킬서 판매 칸이 있는가(PD #1093 B).</summary>
public bool SkillBookCellVisible
{
get { return shopGrid != null && shopGrid.Find(SkillBookCellName) != null; }
}
public override void Build()
{
Tab = 4;
if (UISkin.On) UiKit.Paint(UiKit.Fill("ListBg", Rt), UiKit.Col("list_bg", UiKit.Bg), false); // 목록형 바탕(Fable §5-3)
Header("거래", "뽑기 · 상자 · 상점 · 패스", true);
var body = Body();
string[] names = { "영웅·유닛 뽑기", "보물 상자", "상점·패스" };
for (int i = 0; i < names.Length; i++)
{
int k = i;
tabs.Add(UISkin.On
? UiKit.SubTab(body, "Tab" + i, names[i], 24f + i * 348f, 18f, 332f, 84f, () => SetPage(k))
: UiKit.Btn("Tab" + i, body, names[i], 32, 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));
}
if (UISkin.Has("panel_field") && UISkin.Has("btn_l_yellow")) BuildGachaKit(pages[0]);
else BuildGacha(pages[0]);
BuildShop(pages[2]);
}
// ---------- 뽑기(Fable 제안서 §4-4 · #1104) ----------
const float GaugeW = 820f;
Text pityBadge;
Text tenCost;
Text singleCost;
/// <summary>
/// 뽑기 = 배너(틀 유지 · 아래 문양 띠 흰 8 % · 오른쪽 위 「픽업」 뱃지) → 천장 섹션(레벨형 게이지 + 노랑 배지) →
/// 확률 섹션(제목 띠 · 행 3 = 별 아이콘 ×n + 바 + % Sen) → 1회(Secondary 하늘 + 보옥 비용) · 10연(Primary 노랑 + 「첫 무료」 꼬리 칩) · 각주 = ⓘ 팝업.
/// </summary>
void BuildGachaKit(RectTransform p)
{
var g = S.Db.Gacha;
// 배너
var banner = UiKit.At(p, "Banner", 24f, 12f, 1032f, 600f);
UiKit.Skin(banner, "banner", Color.white);
if (UISkin.Has("banner_pattern"))
{
var pat = UiKit.Skin(UiKit.At(banner, "Pattern", 18f, 600f - 112f, 1032f - 36f, 90f), "banner_pattern", UiKit.Col("banner_pattern", Color.clear));
pat.type = Image.Type.Tiled;
}
UiKit.InfoChip(banner, "Period", 30f, 26f, 180f, 52f, null, "기간 2주", 28); // 「픽업」 = 오른쪽 위 뱃지 하나만(검수 결함 3)
UiKit.Title(UiKit.Label(banner, "영웅·유닛 뽑기", 60, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(36f, -100f), new Vector2(600f, 90f)), true);
UiKit.Label(banner, "모든 영웅·병사 유닛\n= 뽑기로 획득", 32, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.UpperLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(36f, -200f), new Vector2(520f, 110f), false);
var free = UiKit.Skin(UiKit.At(banner, "Free", 36f, 600f - 104f, 300f, 64f), "tail", UiKit.Col("tail_red", UiKit.Red));
UiKit.Title(UiKit.FillLabel(free.transform, "첫 10연 무료", 30, UiKit.White), true);
freeChip = free.gameObject;
pickupArt = UiKit.Rect("PickupArt", banner, new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-20f, 20f), new Vector2(560f, 560f));
var hot = UiKit.Icon(banner, "badge_hot", new Vector2(1f, 1f), new Vector2(0.5f, 0.5f), new Vector2(-52f, -44f), 88f); // 배너 안쪽(검수 결함 3)
if (hot != null)
{
UiKit.DropShadow(hot, 4f);
UiKit.Title(UiKit.FillLabel(hot.transform, "픽업", 24, UiKit.White), true);
}
// 천장 게이지
RectTransform pr;
var pf = UiKit.SectionPanel(p, "Pity", 24f, 632f, 1032f, 176f, false, false, out pr);
UiKit.Title(UiKit.Label(pf, "천장", 34, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(26f, -12f), new Vector2(200f, 50f)), true);
pityText = UiKit.Label(pf, "", 28, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.MiddleRight, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-26f, -12f), new Vector2(620f, 50f), false);
pityBar = UiKit.Gauge(pf, "Gauge", 20f, 62f, GaugeW + 60f, "lv_badge_y", "lv_fill", out pityBadge);
// 확률 공개
RectTransform probRoot;
var prob = UiKit.SectionPanel(p, "Prob", 24f, 828f, 1032f, 400f, false, false, out probRoot);
UiKit.SectionTitle(probRoot, "확률 공개");
var info = UiKit.Rect("Info", probRoot, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-16f, -12f), new Vector2(64f, 64f));
UiKit.Skin(info, "badge_circle", UiKit.Col("chip_dark", UiKit.Dark), true);
var ii = UiKit.Icon(info, "info", new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 34f);
if (ii == null) UiKit.Title(UiKit.FillLabel(info, "i", 34, UiKit.White), true);
info.gameObject.AddComponent<Button>().onClick.AddListener(ShowGachaRules);
for (int t = 3; t >= 1; t--)
{
float y = 88f + (3 - t) * 98f;
var row = UiKit.Skin(UiKit.At(prob, "Row" + t, 20f, y, 1032f - 12f - 40f, 80f), "panel12", UiKit.Col("row_on_field", UiKit.Paper)).transform;
for (int s = 0; s < t; s++)
{
var st = UiKit.Icon(row, "star_on", new Vector2(0f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(40f + s * 40f, 0f), 40f);
if (st != null) UiKit.DropShadow(st, 2f);
}
var fill = UiKit.Bar("P" + t, row, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(180f, 0f), new Vector2(500f, 30f), UiKit.Slot, t == 3 ? UiKit.Col("grade_orange_card_bg", UiKit.Coin) : t == 2 ? UiKit.Col("grade_purple_card_bg", UiKit.Purple) : UiKit.Col("grade_blue_card_bg", UiKit.Jade));
UiKit.SetBar(fill, g.P(t));
var pt = UiKit.Title(UiKit.Label(row, (g.P(t) * 100f).ToString("0.#") + "%", 32, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(700f, 0f), new Vector2(140f, 50f)), true);
pt.horizontalOverflow = HorizontalWrapMode.Overflow;
if (t == 1) UiKit.Label(row, "병사 포함", 24, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.MiddleRight, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-20f, 0f), new Vector2(140f, 40f), false);
}
// 1회 · 10연
singleBtn = UiKit.SkinBtn(p, "Single", "btn_sky", UiKit.White, "1회", 42, UiKit.White, 24f, 1288f, 420f, 116f, () => DoDraw(1)); // 버튼 행 = 탭 바 위 24 고정(검수 결함 4)
singleCost = UiKit.BtnCost(singleBtn, "cur_gem", "", 44f, 36);
tenBtn = UiKit.SkinBtn(p, "Ten", "btn_l_yellow", UiKit.White, g.TenCount + "연", 46, UiKit.Col("text_on_yellow", UiKit.Ink), 1056f - 580f, 1282f, 580f, 122f, () => DoDraw(g.TenCount));
tenCost = UiKit.BtnCost(tenBtn, "cur_gem", "", 44f, 36);
var f2 = UiKit.Skin(UiKit.Rect("Free2", tenBtn.transform, new Vector2(1f, 1f), new Vector2(1f, 0.5f), new Vector2(-14f, 2f), new Vector2(200f, 50f)), "tail", UiKit.Col("tail_red", UiKit.Red));
UiKit.Title(UiKit.FillLabel(f2.transform, "첫 무료", 26, UiKit.White), true);
freeChip2 = f2.gameObject;
}
/// <summary>뽑기 규칙(구 각주 → ⓘ 팝업 · Fable §4-4).</summary>
void ShowGachaRules()
{
var e = S.Db.Economy;
var g = S.Db.Gacha;
Root.Modal("뽑기 규칙",
"중복 = 영웅 조각\n★★★ " + e.ShardDup(3) + " · ★★ " + e.ShardDup(2) + " · ★ " + e.ShardDup(1) + "\n병사 중복 = 골드 " + e.SoldierDupGold +
"\n\n" + g.TenCount + "연 = ★★ 이상 1 보장\n천장 " + g.Pity + "회 = ★★★ 확정\n확률·천장 전부 공개",
new OutGameRoot.ModalButton("확인", UiKit.Dark, null));
}
public void SetPage(int p)
{
page = Mathf.Clamp(p, 0, pages.Count - 1);
Refresh();
}
public void OpenShop()
{
Root.Show(this);
SetPage(2);
}
public override void OnShow()
{
S.EnsureShop();
}
// ---------- 뽑기 ----------
void BuildGacha(RectTransform p)
{
var g = S.Db.Gacha;
var banner = UiKit.TopBand("Banner", p, 12f, 440f, 24f, 24f);
if (UISkin.Has("banner")) UiKit.Skin(banner, "banner", Color.white); // 팩 배너 틀(PD #1100)
else UiKit.Paint(banner, new Color(0.27f, 0.14f, 0.12f));
var pick = UiKit.Box("Pickup", banner, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(30f, -24f), new Vector2(230f, 62f), UiKit.Red);
UiKit.FillLabel(pick.transform, "픽업 (2주)", 30, UiKit.White);
UiKit.Title(UiKit.Label(banner, "영웅·유닛 뽑기", 60, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(36f, -110f), new Vector2(600f, 90f)), true);
UiKit.Label(banner, "모든 영웅·병사 유닛\n= 뽑기로 획득", 32, new Color(0.9f, 0.82f, 0.78f), TextAnchor.UpperLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(36f, -210f), new Vector2(520f, 110f), false);
var free = UiKit.Box("Free", banner, new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(36f, 28f), new Vector2(300f, 78f), new Color(0.93f, 0.75f, 0.24f));
UiKit.FillLabel(free.transform, "첫 10연 무료", 34, UiKit.Ink);
freeChip = free.gameObject;
pickupArt = UiKit.Rect("PickupArt", banner, new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-20f, 10f), new Vector2(440f, 400f));
var pity = UiKit.TopBand("Pity", p, 468f, 130f, 24f, 24f);
var pimg = UiKit.Paint(pity, UiKit.Paper);
UiKit.Border(pimg, UiKit.Line, 3f);
UiKit.Label(pity, "천장 게이지", 36, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f, -10f), new Vector2(400f, 56f));
pityText = UiKit.Label(pity, "", 30, UiKit.Red, TextAnchor.MiddleRight, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-24f, -10f), new Vector2(560f, 56f));
pityBar = UiKit.Bar("PityBar", pity, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 18f), new Vector2(980f, 32f), UiKit.Slot, UiKit.Red);
var prob = UiKit.TopBand("Prob", p, 614f, 270f, 24f, 24f);
var primg = UiKit.Paint(prob, UiKit.Paper);
UiKit.Border(primg, UiKit.Line, 3f);
UiKit.Label(prob, "확률 공개", 36, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f, -10f), new Vector2(400f, 56f));
UiKit.Label(prob, "확률·천장 전부 공개", 26, UiKit.InkSoft, TextAnchor.MiddleRight, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-24f, -10f), new Vector2(400f, 56f), false);
for (int t = 3; t >= 1; t--)
{
float y = -84f - (3 - t) * 58f;
UiKit.Label(prob, UiKit.Stars(t, t), 38, UiKit.Star, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f, y), new Vector2(160f, 50f));
var fill = UiKit.Bar("P" + t, prob, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(200f, y - 12f), new Vector2(380f, 28f), UiKit.Slot, t == 3 ? UiKit.Red : new Color(0.47f, 0.38f, 0.32f));
UiKit.SetBar(fill, g.P(t));
UiKit.Label(prob, (g.P(t) * 100f).ToString("0.#") + " %" + (t == 1 ? " (병사 유닛 포함)" : ""), 32, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(600f, y), new Vector2(420f, 50f));
}
var btns = UiKit.TopBand("Buttons", p, 904f, 170f, 24f, 24f);
singleBtn = UiKit.Btn("Single", btns, "", 40, UiKit.Dark, UiKit.White, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), Vector2.zero, new Vector2(500f, 160f), () => DoDraw(1));
tenBtn = UiKit.Btn("Ten", btns, "", 40, UiKit.Red, UiKit.White, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), Vector2.zero, new Vector2(500f, 160f), () => DoDraw(g.TenCount));
var f2 = UiKit.Box("Free2", tenBtn.transform, new Vector2(1f, 1f), new Vector2(1f, 0.5f), new Vector2(-10f, 0f), new Vector2(240f, 56f), new Color(0.93f, 0.75f, 0.24f));
UiKit.FillLabel(f2.transform, "첫 10연 무료", 26, UiKit.Ink);
freeChip2 = f2.gameObject;
var e = S.Db.Economy;
UiKit.Label(p, "중복 = 영웅 조각(★★★ " + e.ShardDup(3) + " · ★★ " + e.ShardDup(2) + " · ★ " + e.ShardDup(1) + ") · 병사 중복 = 골드 " + e.SoldierDupGold +
" · 10연 = ★★ 이상 1 보장", 26, UiKit.InkSoft, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -1090f), new Vector2(1020f, 80f), false);
}
void RefreshGacha()
{
var g = S.Db.Gacha;
var d = S.Save;
bool free = GachaRules.FirstTenFree(S.Db, d);
freeChip.SetActive(free);
freeChip2.SetActive(free);
if (pityBadge != null)
{
pityText.text = d.gacha.pity + " / " + g.Pity + " · " + g.Pity + "회째 ★★★ 확정";
pityBadge.text = d.gacha.pity.ToString();
UiKit.SetGauge(pityBar, d.gacha.pity / (float)Mathf.Max(1, g.Pity), GaugeW + 60f);
singleCost.text = g.CostSingle.ToString("N0");
tenCost.text = free ? "무료" : g.CostTen.ToString("N0");
var ic = tenCost.transform.parent.GetComponentInChildren<Image>(true);
if (ic != null) ic.gameObject.SetActive(!free);
}
else
{
pityText.text = d.gacha.pity + " / " + g.Pity + " · " + g.Pity + "회 ★★★ 확정";
UiKit.SetBar(pityBar, d.gacha.pity / (float)Mathf.Max(1, g.Pity));
UiKit.LabelOf(singleBtn).text = "1회\n<size=32>보옥 " + g.CostSingle.ToString("N0") + "</size>";
UiKit.LabelOf(tenBtn).text = g.TenCount + "연\n<size=32>" + (free ? "무료" : "보옥 " + g.CostTen.ToString("N0")) + "</size>";
}
UiKit.Clear(pickupArt);
var picks = new List<HeroDb.Hero>();
foreach (var pe in g.pool)
if (pe.pickup && pe.inPool && S.Def(pe.heroId) != null) picks.Add(S.Def(pe.heroId));
if (pityBadge != null)
{
var arts = picks.FindAll(h => UiKit.ArtSprite(h.art) != null);
for (int i = 0; i < arts.Count && i < 2; i++)
UiKit.Portrait("Pick" + i, pickupArt, arts[i], new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-10f - i * 250f, 70f), new Vector2(300f, 440f), false, 1f, new Color(1f, 1f, 1f, 0f));
for (int i = 0; i < picks.Count && i < 2; i++)
UiKit.InfoChip(pickupArt, "Name" + i, 560f - (i + 1) * 250f, 498f, 230f, 50f, null, picks[i].nameKo + " ★" + picks[i].rarity, 26);
return;
}
for (int i = 0; i < picks.Count && i < 2; i++)
{
UiKit.Portrait("Pick" + i, pickupArt, picks[i], new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-i * 210f, 50f), new Vector2(230f, 330f), false, 1f, new Color(1f, 1f, 1f, 0f));
UiKit.Label(pickupArt, picks[i].nameKo + " ★" + picks[i].rarity, 26, UiKit.White, TextAnchor.MiddleCenter, new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-i * 210f, 6f), new Vector2(230f, 44f));
}
}
public void DoDraw(int count)
{
var why = GachaRules.CanDraw(S.Db, S.Save, count);
if (why != null)
{
Root.Toast(why);
return;
}
LastPulls = S.Draw(count);
ShowResult(LastPulls);
}
// ---------- 뽑기 결과(카드 순차 공개) ----------
void ShowResult(List<GachaPull> pulls)
{
CloseResult();
var dim = UiKit.Fill("GachaResult", Root.TopRoot);
UiLayer.Nest(dim.gameObject, UiRate.Event, true); // 위 덮개 = 자기 캔버스(재화 바와 따로 · PD 2026-11-10)
var dimg = UiKit.Paint(dim, new Color(0.08f, 0.05f, 0.04f, 0.9f), false, true);
dimg.raycastTarget = true;
var skip = dim.gameObject.AddComponent<Button>();
skip.transition = Selectable.Transition.None;
skip.onClick.AddListener(RevealAll);
resultRoot = dim.gameObject;
UiKit.Label(dim, "뽑기 결과", 64, UiKit.White, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -200f), new Vector2(900f, 100f));
int cols = Mathf.Min(5, Mathf.Max(1, pulls.Count));
int rows = (pulls.Count + cols - 1) / cols;
float cw = 196f, chh = 330f;
for (int i = 0; i < pulls.Count; i++)
{
int r = i / cols, c = i % cols;
int inRow = Mathf.Min(cols, pulls.Count - r * cols);
float x = (c - (inRow - 1) / 2f) * (cw + 12f);
float y = 120f - r * (chh + 20f) + (rows - 1) * (chh + 20f) / 2f;
resultCards.Add(ResultCard(dim, pulls[i], new Vector2(x, y), new Vector2(cw, chh)));
}
UiKit.Btn("Ok", dim, "확인", 40, UiKit.Red, UiKit.White, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 180f), new Vector2(420f, 110f), CloseResult);
revealing = Root.StartCoroutine(Reveal());
}
GameObject ResultCard(RectTransform parent, GachaPull p, Vector2 pos, Vector2 size)
{
var h = S.Def(p.heroId);
var back = UiKit.Box("Card", parent, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), pos, size, new Color(0.35f, 0.22f, 0.18f));
UiKit.Border(back, new Color(0.8f, 0.62f, 0.3f), 3f);
UiKit.FillLabel(back.transform, "?", 90, new Color(0.8f, 0.62f, 0.3f));
var front = UiKit.Fill("Front", back.transform);
UiKit.Paint(front, p.tier == 3 ? UiKit.Col("grade_orange_card_bg", UiKit.Coin) : p.tier == 2 ? UiKit.Purple : UiKit.Paper);
UiKit.Portrait("Face", front, h, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -10f), new Vector2(176f, 176f), false, 1.8f, new Color(0.9f, 0.86f, 0.8f));
UiKit.Label(front, UiKit.Stars(p.tier, p.tier), 30, UiKit.Star, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -188f), new Vector2(190f, 40f));
UiKit.Label(front, h != null ? h.nameKo : p.heroId, 32, UiKit.Ink, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -226f), new Vector2(190f, 44f));
string tag = p.isNew ? "<color=#b0301e>NEW</color>" : p.shards > 0 ? "조각 +" + p.shards : p.gold > 0 ? "골드 +" + p.gold : "";
if (p.pity) tag += " 천장";
if (p.pickup) tag += " 픽업";
UiKit.Label(front, tag, 26, UiKit.Ink, TextAnchor.MiddleCenter, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 10f), new Vector2(190f, 44f));
front.gameObject.SetActive(false);
return front.gameObject;
}
IEnumerator Reveal()
{
foreach (var c in resultCards)
{
yield return new WaitForSecondsRealtime(0.14f);
if (c != null) c.SetActive(true);
}
revealing = null;
}
public void RevealAll()
{
if (revealing != null) Root.StopCoroutine(revealing);
revealing = null;
foreach (var c in resultCards)
if (c != null) c.SetActive(true);
}
public void CloseResult()
{
if (revealing != null) Root.StopCoroutine(revealing);
revealing = null;
resultCards.Clear();
if (resultRoot != null)
{
resultRoot.SetActive(false);
Object.Destroy(resultRoot);
resultRoot = null;
}
Refresh();
}
public int RevealedCount
{
get
{
int n = 0;
foreach (var c in resultCards)
if (c != null && c.activeSelf) n++;
return n;
}
}
// ---------- 장비 상점 ----------
void BuildShop(RectTransform p)
{
var head = UiKit.TopBand("ShopHead", p, 12f, 110f, 24f, 24f);
UiKit.Label(head, "승급 장비 상점", 44, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), Vector2.zero, new Vector2(400f, 70f));
var e = S.Db.Economy;
var grades = new List<string>();
foreach (var g in e.ShopGrades) grades.Add(UiKit.GradeKo(g));
UiKit.Label(head, "골드 · " + string.Join("/", grades.ToArray()) + " · 매일 갱신", 26, UiKit.InkSoft, TextAnchor.MiddleLeft, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(330f, -4f), new Vector2(400f, 50f), false);
shopTimer = UiKit.Label(head, "", 28, UiKit.Red, TextAnchor.MiddleRight, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), Vector2.zero, new Vector2(320f, 50f));
shopGrid = UiKit.ScrollGrid("ShopGrid", p, 24f, 24f, 136f, 280f, new Vector2(326f, 400f), new Vector2(17f, 20f), 3, out shopScroll);
refreshBtn = UiKit.Btn("Refresh", p, "", 36, UiKit.Jade, UiKit.White, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 156f), new Vector2(640f, 96f), () =>
{
var why = S.RefreshShop();
Root.Toast(why ?? "상점 새로고침");
});
// 패스·팩 = 스텁(과금 없음 · PD #1093 C · 기획 S15)
string[] packs = { "성장 패스", "월정액", "챕터 팩", "첫 결제 팩" };
for (int i = 0; i < packs.Length; i++)
{
string name = packs[i];
UiKit.Btn("Pack" + i, p, name + "\n<size=22>준비 중</size>", 28, UiKit.Purple, UiKit.White, new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(24f + i * 262f, 30f), new Vector2(246f, 108f),
() => Root.Modal(name, "준비 중입니다.\n(과금·패스 = 스텁 · 결제 없음 · 시스템 기획 S15)", new OutGameRoot.ModalButton("확인", UiKit.Dark, null)));
}
}
void RefreshShop()
{
S.EnsureShop();
var d = S.Save;
var left = EconomyRules.UntilNextDay(S.Now);
shopTimer.text = "갱신까지 " + ((int)left.TotalHours).ToString("00") + ":" + left.Minutes.ToString("00");
int free = EconomyRules.FreeRefreshesLeft(S.Db, d);
UiKit.LabelOf(refreshBtn).text = free > 0 ? "새로고침 · 무료 " + free + "회(시장 개설)" : "새로고침 · 보옥 " + S.Db.Economy.ShopRefreshGem;
UiKit.Clear(shopGrid);
for (int i = 0; i < d.shop.items.Count; i++)
{
var it = d.shop.items[i];
var g = S.Db.Gear.Get(it.gearId);
int k = i;
var cell = UiKit.Cell(shopGrid, "Item" + i, it.sold ? UiKit.Slot : UiKit.Paper, null);
UiKit.Border(cell, UiKit.Line, 2f);
var strip = UiKit.TopBand("Grade", cell.transform, 0f, 64f);
UiKit.Paint(strip, UiKit.GradeColor(g != null ? g.grade : ""));
UiKit.FillLabel(strip, (g != null ? UiKit.GradeKo(g.grade) : "?") + " 등급", 28, UiKit.White);
UiKit.Label(cell.transform, g != null ? g.nameKo : it.gearId, 40, UiKit.Ink, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -86f), new Vector2(310f, 70f));
UiKit.Label(cell.transform, "보유 " + d.inv.GearCount(it.gearId) + " · 필요 영웅 " + NeedCount(it.gearId), 24, UiKit.InkSoft, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -160f), new Vector2(310f, 44f), false);
UiKit.Label(cell.transform, "골드 " + it.price.ToString("N0"), 36, UiKit.Coin, TextAnchor.MiddleCenter, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 120f), new Vector2(310f, 56f));
var b = UiKit.Btn("Buy", cell.transform, it.sold ? "품절" : "구매", 34, it.sold ? UiKit.Disabled : UiKit.Red, UiKit.White, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 20f), new Vector2(260f, 84f), () =>
{
var why = S.BuyShop(k);
Root.Toast(why ?? (g != null ? g.nameKo : "") + " 구매 → 가방");
});
b.interactable = !it.sold;
}
// 스킬서 임시 판매(B)는 제거 — 정식 획득처 = 사당 던전(PD #1093 C)
}
/// <summary>그 장비를 지금 등급 칸에서 기다리는(아직 안 채운) 보유 영웅 수.</summary>
int NeedCount(string gearId)
{
int n = 0;
foreach (var hs in S.Save.heroes)
{
var h = S.Def(hs.id);
var list = h != null ? S.Db.Heroes.GearFor(h, hs.grade) : null;
if (list == null) continue;
for (int i = 0; i < list.Count; i++)
if (list[i] == gearId && string.IsNullOrEmpty(hs.gear[i]))
{
n++;
break;
}
}
return n;
}
// ---------- 보물 상자(PD #1093 C) ----------
/// <summary>상자 구매 칸 수(테스트).</summary>
public int ChestRowCount { get; private set; }
void RefreshChests()
{
var p = pages[1];
UiKit.Clear(p);
var db = S.Db;
var d = S.Save;
UiKit.Label(p, "보물 상자 구매", 44, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(28f, -16f), new Vector2(500f, 70f));
UiKit.Label(p, "상자 포인트 " + d.inv.chestPoints + " · 열기 = 보물 > 상자", 28, UiKit.InkSoft, TextAnchor.MiddleRight, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-28f, -24f), new Vector2(560f, 54f), false);
ChestRowCount = 0;
for (int i = 0; i < db.Chests.chests.Count; i++)
{
var c = db.Chests.chests[i];
var row = UiKit.TopBand("Chest_" + c.id, p, 104f + i * 236f, 220f, 24f, 24f);
var img = UiKit.Paint(row, UiKit.Paper);
UiKit.Border(img, UiKit.Line, 3f);
var icon = UiKit.Box("Icon", row, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(20f, 0f), new Vector2(170f, 140f), TreasureScreen.ChestColor(c.id));
if (UiKit.Icon(icon.transform, "chest_" + c.id, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 8f), 120f) != null)
UiKit.Title(UiKit.Label(icon.transform, "×" + TreasureRules.ChestCount(d, c.id), 36, UiKit.White, TextAnchor.LowerRight, new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-8f, 2f), new Vector2(150f, 50f)), true);
else UiKit.FillLabel(icon.transform, "×" + TreasureRules.ChestCount(d, c.id), 40, UiKit.White);
var probs = new List<string>();
for (int g = 0; g < ChestDb.ProbGrades.Length; g++)
if (c.p[g] > 0f) probs.Add(TreasureRules.GradeKo(ChestDb.ProbGrades[g]) + " " + (c.p[g] * 100f).ToString("0.#") + "%");
UiKit.Label(row, c.nameKo, 40, UiKit.Ink, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(210f, -16f), new Vector2(420f, 60f));
UiKit.Label(row, string.Join(" · ", probs.ToArray()) + "\n포인트 +" + c.points + " · 획득처 " + c.source.Replace(";", "·"), 24, UiKit.InkSoft, TextAnchor.UpperLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(210f, -80f), new Vector2(440f, 120f), false);
int col = 0;
foreach (var cur in new[] { "gold", "gem", "points" })
{
int price = TreasureRules.Price(db, d, c, cur);
if (price <= 0) continue;
string id = c.id, currency = cur;
string label = (cur == "gold" ? "골드 " : cur == "gem" ? "보옥 " : "포인트 ") + price.ToString("N0");
var b = UiKit.Btn("Buy_" + cur, row, label, 28, cur == "gold" ? UiKit.Coin : cur == "gem" ? UiKit.Jade : UiKit.Grain, UiKit.White, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-16f - col * 186f, 0f), new Vector2(176f, 96f), () =>
{
var why = S.Do(() => TreasureRules.Buy(S.Db, S.Save, id, currency));
Root.Toast(why ?? S.Db.Chests.Get(id).nameKo + " +1 (보물 > 상자에서 열기)");
});
col++;
}
if (col == 0) UiKit.Label(row, c.source.Contains("event") ? "이벤트(적장 토벌)" : "판매 안 함", 28, UiKit.InkSoft, TextAnchor.MiddleRight, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-24f, 0f), new Vector2(300f, 60f), false);
ChestRowCount++;
}
UiKit.Btn("ToTreasure", p, "보물 > 상자 열기", 34, UiKit.Dark, UiKit.White, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 24f), new Vector2(560f, 96f), () =>
{
Root.Show(Root.Treasures);
Root.Treasures.SetPage(2);
});
}
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);
}
if (page == 0) RefreshGacha();
else if (page == 1) RefreshChests();
else if (page == 2) RefreshShop();
}
}
}