2026-11-05 12:11:10 +00:00
using System.Collections ;
using System.Collections.Generic ;
using UnityEngine ;
using UnityEngine.UI ;
namespace TKS.OutGame
{
/// <summary>
/// 거래(img11 · 기획 S15) — ① 영웅·유닛 뽑기(픽업 · 천장 n/80 · 확률 공개 · 1회/10연 · 첫 10연 무료 · 결과 = 카드 순차 공개)
2026-11-05 15:22:54 +00:00
/// ② 보물 상자(PD #1093 C) = 상자 5종 · 보유 수 · 구매(chests.csv buy_gold/buy_gem/buy_points · 거래 할인 개발) · 열기는 보물 > 상자 탭
/// ③ 상점·패스 = 골드 승급 장비 상점(흰/녹 · 일일 · 새로고침 보옥 · 시장 개설 무료) · 성장 패스·월정액·챕터 팩·첫 결제 팩 = 「준비 중」 스텁(과금 없음).
2026-11-05 12:11:10 +00:00
/// </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 ; }
}
2026-11-05 14:31:27 +00:00
/// <summary>장비 상점 칸 수(스킬서 임시 판매 칸 제외).</summary>
2026-11-05 12:11:10 +00:00
public int ShopCardCount
{
2026-11-05 14:31:27 +00:00
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 ; }
2026-11-05 12:11:10 +00:00
}
public override void Build ( )
{
Tab = 4 ;
2026-11-09 09:03:43 +00:00
if ( UISkin . On ) UiKit . Paint ( UiKit . Fill ( "ListBg" , Rt ) , UiKit . Col ( "list_bg" , UiKit . Bg ) , false ) ; // 목록형 바탕(Fable §5-3)
2026-11-05 12:11:10 +00:00
Header ( "거래" , "뽑기 · 상자 · 상점 · 패스" , true ) ;
var body = Body ( ) ;
string [ ] names = { "영웅·유닛 뽑기" , "보물 상자" , "상점·패스" } ;
for ( int i = 0 ; i < names . Length ; i + + )
{
int k = i ;
2026-11-09 09:03:43 +00:00
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 ) ) ) ;
2026-11-05 12:11:10 +00:00
pages . Add ( UiKit . Fill ( "Page" + i , body , 0f , 0f , 118f , 0f ) ) ;
}
2026-11-09 09:03:43 +00:00
if ( UISkin . Has ( "panel_field" ) & & UISkin . Has ( "btn_l_yellow" ) ) BuildGachaKit ( pages [ 0 ] ) ;
else BuildGacha ( pages [ 0 ] ) ;
2026-11-05 12:11:10 +00:00
BuildShop ( pages [ 2 ] ) ;
}
2026-11-09 09:03:43 +00:00
// ---------- 뽑기(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 ;
// 배너
2026-11-09 09:19:06 +00:00
var banner = UiKit . At ( p , "Banner" , 24f , 12f , 1032f , 600f ) ;
2026-11-09 09:03:43 +00:00
UiKit . Skin ( banner , "banner" , Color . white ) ;
if ( UISkin . Has ( "banner_pattern" ) )
{
2026-11-09 09:19:06 +00:00
var pat = UiKit . Skin ( UiKit . At ( banner , "Pattern" , 18f , 600f - 112f , 1032f - 36f , 90f ) , "banner_pattern" , UiKit . Col ( "banner_pattern" , Color . clear ) ) ;
2026-11-09 09:03:43 +00:00
pat . type = Image . Type . Tiled ;
}
2026-11-09 09:19:06 +00:00
UiKit . InfoChip ( banner , "Period" , 30f , 26f , 180f , 52f , null , "기간 2주" , 28 ) ; // 「픽업」 = 오른쪽 위 뱃지 하나만(검수 결함 3)
2026-11-09 09:03:43 +00:00
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 ) ;
2026-11-09 09:19:06 +00:00
var free = UiKit . Skin ( UiKit . At ( banner , "Free" , 36f , 600f - 104f , 300f , 64f ) , "tail" , UiKit . Col ( "tail_red" , UiKit . Red ) ) ;
2026-11-09 09:03:43 +00:00
UiKit . Title ( UiKit . FillLabel ( free . transform , "첫 10연 무료" , 30 , UiKit . White ) , true ) ;
freeChip = free . gameObject ;
2026-11-09 09:19:06 +00:00
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)
2026-11-09 09:03:43 +00:00
if ( hot ! = null )
{
UiKit . DropShadow ( hot , 4f ) ;
2026-11-09 09:19:06 +00:00
UiKit . Title ( UiKit . FillLabel ( hot . transform , "픽업" , 24 , UiKit . White ) , true ) ;
2026-11-09 09:03:43 +00:00
}
// 천장 게이지
RectTransform pr ;
2026-11-09 09:19:06 +00:00
var pf = UiKit . SectionPanel ( p , "Pity" , 24f , 632f , 1032f , 176f , false , false , out pr ) ;
2026-11-09 09:03:43 +00:00
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 ) ;
2026-11-09 09:19:06 +00:00
pityBar = UiKit . Gauge ( pf , "Gauge" , 20f , 62f , GaugeW + 60f , "lv_badge_y" , "lv_fill" , out pityBadge ) ;
2026-11-09 09:03:43 +00:00
// 확률 공개
RectTransform probRoot ;
2026-11-09 09:19:06 +00:00
var prob = UiKit . SectionPanel ( p , "Prob" , 24f , 828f , 1032f , 400f , false , false , out probRoot ) ;
2026-11-09 09:03:43 +00:00
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 - - )
{
2026-11-09 09:19:06 +00:00
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 ;
2026-11-09 09:03:43 +00:00
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연
2026-11-09 09:19:06 +00:00
singleBtn = UiKit . SkinBtn ( p , "Single" , "btn_sky" , UiKit . White , "1회" , 42 , UiKit . White , 24f , 1288f , 420f , 116f , ( ) = > DoDraw ( 1 ) ) ; // 버튼 행 = 탭 바 위 24 고정(검수 결함 4)
2026-11-09 09:03:43 +00:00
singleCost = UiKit . BtnCost ( singleBtn , "cur_gem" , "" , 44f , 36 ) ;
2026-11-09 09:19:06 +00:00
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 ) ) ;
2026-11-09 09:03:43 +00:00
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 ) ) ;
}
2026-11-05 12:11:10 +00:00
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 ) ;
2026-11-06 14:20:57 +00:00
if ( UISkin . Has ( "banner" ) ) UiKit . Skin ( banner , "banner" , Color . white ) ; // 팩 배너 틀(PD #1100)
else UiKit . Paint ( banner , new Color ( 0.27f , 0.14f , 0.12f ) ) ;
2026-11-05 12:11:10 +00:00
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 ) ;
2026-11-06 14:20:57 +00:00
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 ) ;
2026-11-05 12:11:10 +00:00
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 ) ) ;
2026-11-09 03:20:32 +00:00
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 ) ;
2026-11-05 12:11:10 +00:00
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 ) ) ;
2026-11-09 03:20:32 +00:00
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 ) ) ;
2026-11-05 12:11:10 +00:00
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 ) ;
2026-11-09 09:03:43 +00:00
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>" ;
}
2026-11-05 12:11:10 +00:00
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 ) ) ;
2026-11-09 09:19:06 +00:00
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 ;
}
2026-11-05 12:11:10 +00:00
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 ) ;
2026-11-09 16:05:47 +00:00
UiLayer . Nest ( dim . gameObject , UiRate . Event , true ) ; // 위 덮개 = 자기 캔버스(재화 바와 따로 · PD 2026-11-10)
2026-11-05 12:11:10 +00:00
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 ) ;
2026-11-09 03:20:32 +00:00
UiKit . Paint ( front , p . tier = = 3 ? UiKit . Col ( "grade_orange_card_bg" , UiKit . Coin ) : p . tier = = 2 ? UiKit . Purple : UiKit . Paper ) ;
2026-11-05 12:11:10 +00:00
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 ) ;
2026-11-05 15:22:54 +00:00
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 ) , ( ) = >
2026-11-05 12:11:10 +00:00
{
var why = S . RefreshShop ( ) ;
Root . Toast ( why ? ? "상점 새로고침" ) ;
} ) ;
2026-11-05 15:22:54 +00:00
// 패스·팩 = 스텁(과금 없음 · 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 ) ) ) ;
}
2026-11-05 12:11:10 +00:00
}
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" ) ;
2026-11-05 15:22:54 +00:00
int free = EconomyRules . FreeRefreshesLeft ( S . Db , d ) ;
UiKit . LabelOf ( refreshBtn ) . text = free > 0 ? "새로고침 · 무료 " + free + "회(시장 개설)" : "새로고침 · 보옥 " + S . Db . Economy . ShopRefreshGem ;
2026-11-05 12:11:10 +00:00
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 ;
2026-11-09 03:20:32 +00:00
var cell = UiKit . Cell ( shopGrid , "Item" + i , it . sold ? UiKit . Slot : UiKit . Paper , null ) ;
2026-11-05 12:11:10 +00:00
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 ;
}
2026-11-05 15:22:54 +00:00
// 스킬서 임시 판매(B)는 제거 — 정식 획득처 = 사당 던전(PD #1093 C)
2026-11-05 12:11:10 +00:00
}
/// <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 ;
}
2026-11-05 15:22:54 +00:00
// ---------- 보물 상자(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 ) ) ;
2026-11-06 14:20:57 +00:00
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 ) ;
2026-11-05 15:22:54 +00:00
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 ) ;
} ) ;
}
2026-11-05 12:11:10 +00:00
public override void Refresh ( )
{
for ( int i = 0 ; i < tabs . Count ; i + + )
{
bool on = i = = page ;
2026-11-09 09:03:43 +00:00
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 ;
}
2026-11-05 12:11:10 +00:00
pages [ i ] . gameObject . SetActive ( on ) ;
}
if ( page = = 0 ) RefreshGacha ( ) ;
2026-11-05 15:22:54 +00:00
else if ( page = = 1 ) RefreshChests ( ) ;
2026-11-05 12:11:10 +00:00
else if ( page = = 2 ) RefreshShop ( ) ;
}
}
}