2026-11-13 13:34:16 +00:00
using System ;
2026-11-05 12:11:10 +00:00
using System.Collections ;
using System.Collections.Generic ;
using UnityEngine ;
using UnityEngine.UI ;
namespace TKS.OutGame
{
/// <summary>
2026-11-13 13:34:16 +00:00
/// 거래(img11 · 기획 S15) — ① 명장 등용(뽑기 · PD 2026-11-13: 배너 2쪽 = 픽업(영웅 1명 강조 일러 · 기간 · 상세 정보) / 국가별 상시(지도 위 진영 깃발 4 = 위·촉·오·군 · 주요 영웅 5) ·
/// 좌우 화살표로 쪽 넘김 · 천장 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>
2026-11-13 13:34:16 +00:00
/// <summary>
/// 명장 등용 배너 배치 표(Resources/UI/recruit_banner.csv · 형식 = 로비 장면 표 LobbyScene.Load) — 지도·깃발·영웅 자리·이름표 좌표 = 배너 그림 px.
/// 누가 나오는지·이름·기간은 OutGame 표 gacha_banners.csv(GachaConfig.Banner).
/// </summary>
public static class RecruitBanner
{
public const string TablePath = "UI/recruit_banner" ;
static List < LobbyScene . Row > rows ;
public static void Reload ( )
{
rows = null ;
}
public static List < LobbyScene . Row > Rows
{
get
{
if ( rows = = null ) rows = LobbyScene . Load ( TablePath ) ;
return rows ;
}
}
public static LobbyScene . Row First ( string kind )
{
return DomesticScene . First ( Rows , kind ) ;
}
/// <summary>인원 n 의 자리 줄(i_slot 순) — 그 인원 줄이 없으면 n 보다 큰 가장 작은 인원 줄.</summary>
public static List < LobbyScene . Row > Slots ( string kind , int n )
{
int best = int . MaxValue ;
foreach ( var r in Rows )
if ( r . kind = = kind & & r . n > = n & & r . n < best ) best = r . n ;
var list = new List < LobbyScene . Row > ( ) ;
foreach ( var r in Rows )
if ( r . kind = = kind & & r . n = = best ) list . Add ( r ) ;
list . Sort ( ( a , b ) = > a . slot . CompareTo ( b . slot ) ) ;
return list ;
}
}
2026-11-05 12:11:10 +00:00
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 ;
2026-11-13 13:34:16 +00:00
// 명장 등용 배너(PD 2026-11-13) — 쪽 = 픽업 배너 1개씩 + 상시 배너 묶음 1쪽(진영 깃발로 고름)
GachaConfig . Banner banner ;
string standingId ;
readonly List < List < GachaConfig . Banner > > bannerPages = new List < List < GachaConfig . Banner > > ( ) ;
RectTransform bannerPx ;
RectTransform bannerDots ;
Text pityNote ;
readonly Text [ ] probNote = new Text [ 4 ] ;
/// <summary>지금 고른 등용 배너.</summary>
public GachaConfig . Banner Banner
{
get { return banner ; }
}
public int BannerPageCount
{
get { return bannerPages . Count ; }
}
/// <summary>지금 배너가 있는 쪽(0부터).</summary>
public int BannerPage
{
get
{
for ( int i = 0 ; i < bannerPages . Count ; i + + )
if ( bannerPages [ i ] . Contains ( banner ) ) return i ;
return 0 ;
}
}
/// <summary>배너에 세운 주요 영웅 수 · 진영 깃발 버튼 수(테스트·캡처).</summary>
public int FeaturedShown { get ; private set ; }
public int FlagCount { get ; private set ; }
2026-11-05 12:11:10 +00:00
RectTransform shopGrid ;
ScrollRect shopScroll ;
Text shopTimer ;
Button refreshBtn ;
GameObject resultRoot ;
readonly List < GameObject > resultCards = new List < GameObject > ( ) ;
Coroutine revealing ;
2026-11-13 11:29:46 +00:00
GachaCutscene cutscene ;
2026-11-05 12:11:10 +00:00
public List < GachaPull > LastPulls { get ; private set ; }
2026-11-13 11:29:46 +00:00
/// <summary>지금 재생 중인 뽑기 연출(삼고초려 장면 → 카드 획득 · 없으면 null).</summary>
public GachaCutscene Cutscene
{
get { return cutscene ; }
}
2026-11-05 12:11:10 +00:00
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-13 13:34:16 +00:00
Header ( "거래" , "등용 · 상자 · 상점 · 패스" , true ) ;
2026-11-05 12:11:10 +00:00
var body = Body ( ) ;
2026-11-13 13:34:16 +00:00
string [ ] names = { "명장 등용" , "보물 상자" , "상점·패스" } ;
2026-11-05 12:11:10 +00:00
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-13 13:34:16 +00:00
BuildBannerPages ( ) ;
banner = GachaRules . Default ( S . Db , S . Now ) ;
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-13 13:34:16 +00:00
// ---------- 명장 등용(뽑기 · Fable 제안서 §4-4 · #1104 → PD 2026-11-13 배너 개편) ----------
2026-11-09 09:03:43 +00:00
const float GaugeW = 820f ;
2026-11-13 13:34:16 +00:00
const float BannerW = 1032f , BannerH = 640f , BannerInset = 8f ;
2026-11-09 09:03:43 +00:00
Text pityBadge ;
Text tenCost ;
Text singleCost ;
2026-11-13 13:34:16 +00:00
void BuildBannerPages ( )
{
bannerPages . Clear ( ) ;
List < GachaConfig . Banner > standing = null ;
foreach ( var b in S . Db . Gacha . banners )
{
if ( b . IsPickup )
{
bannerPages . Add ( new List < GachaConfig . Banner > { b } ) ;
continue ;
}
if ( standing = = null )
{
standing = new List < GachaConfig . Banner > ( ) ;
bannerPages . Add ( standing ) ;
}
standing . Add ( b ) ;
}
}
2026-11-09 09:03:43 +00:00
/// <summary>
2026-11-13 13:34:16 +00:00
/// 명장 등용 = 배너(틀 + 안쪽 그림 · 좌우 화살표 = 쪽 넘김 · 아래 쪽 점) → 천장 섹션(레벨형 게이지 + 노랑 배지 · 배너 묶음) →
/// 확률 섹션(제목 띠 · 행 3 = 별 아이콘 × n + 바 + % + 배너별 메모) → 등용 1회(Secondary 하늘 + 보옥 비용) · 10회(Primary 노랑 + 「첫 무료」 꼬리 칩) · 각주 = ⓘ 팝업.
/// 배너 안쪽 내용은 고를 때마다 다시 그린다(DrawBanner · 좌표 = 표 recruit_banner.csv).
2026-11-09 09:03:43 +00:00
/// </summary>
void BuildGachaKit ( RectTransform p )
{
var g = S . Db . Gacha ;
2026-11-13 13:34:16 +00:00
// 배너 = 틀 + 안쪽(둥근 마스크 · 그림 px 좌표계)
var frame = UiKit . At ( p , "Banner" , 24f , 12f , BannerW , BannerH ) ;
UiKit . Skin ( frame , "banner" , Color . white ) ;
var clip = UiKit . Fill ( "Clip" , frame , BannerInset , BannerInset , BannerInset , BannerInset ) ;
UiKit . Skin ( clip , "panel_field" , Color . white ) ;
clip . gameObject . AddComponent < Mask > ( ) . showMaskGraphic = false ;
var view = RecruitBanner . First ( "view" ) ;
float aw = BannerW - 2f * BannerInset , ah = BannerH - 2f * BannerInset ;
float vw = view ! = null & & view . w > 1f ? view . w : aw , vh = view ! = null & & view . h > 1f ? view . h : ah ;
bannerPx = UiKit . At ( clip , "Px" , 0f , 0f , vw , vh ) ;
bannerPx . localScale = new Vector3 ( aw / vw , aw / vw , 1f ) ;
if ( bannerPages . Count > 1 )
2026-11-09 09:03:43 +00:00
{
2026-11-13 13:34:16 +00:00
BannerArrow ( frame , "Prev" , "arrow_prev" , false ) ;
BannerArrow ( frame , "Next" , "arrow_next" , true ) ;
2026-11-09 09:03:43 +00:00
}
2026-11-13 13:34:16 +00:00
bannerDots = UiKit . At ( p , "Dots" , 24f , 12f + BannerH + 3f , BannerW , 18f ) ;
2026-11-09 09:03:43 +00:00
// 천장 게이지
RectTransform pr ;
2026-11-13 13:34:16 +00:00
var pf = UiKit . SectionPanel ( p , "Pity" , 24f , 676f , 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 ( 100f , 50f ) ) , true ) ;
pityNote = UiKit . Label ( pf , "" , 24 , UiKit . Col ( "text_soft" , UiKit . InkSoft ) , TextAnchor . MiddleLeft , new Vector2 ( 0f , 1f ) , new Vector2 ( 0f , 1f ) , new Vector2 ( 120f , - 14f ) , new Vector2 ( 300f , 46f ) , false ) ;
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 ( 560f , 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-13 13:34:16 +00:00
var prob = UiKit . SectionPanel ( p , "Prob" , 24f , 864f , 1032f , 384f , 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-13 13:34:16 +00:00
float y = 84f + ( 3 - t ) * 94f ;
2026-11-09 09:19:06 +00:00
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 ) ;
}
2026-11-13 13:34:16 +00:00
var fill = UiKit . Bar ( "P" + t , row , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 180f , 0f ) , new Vector2 ( 460f , 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 ) ) ;
2026-11-09 09:03:43 +00:00
UiKit . SetBar ( fill , g . P ( t ) ) ;
2026-11-13 13:34:16 +00:00
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 ( 660f , 0f ) , new Vector2 ( 120f , 50f ) ) , true ) ;
2026-11-09 09:03:43 +00:00
pt . horizontalOverflow = HorizontalWrapMode . Overflow ;
2026-11-13 13:34:16 +00:00
probNote [ t ] = 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 ( 200f , 40f ) , false ) ;
probNote [ t ] . horizontalOverflow = HorizontalWrapMode . Overflow ;
2026-11-09 09:03:43 +00:00
}
2026-11-13 13:34:16 +00:00
// 등용 1회 · 10회
singleBtn = UiKit . SkinBtn ( p , "Single" , "btn_sky" , UiKit . White , "1회 등용" , 38 , UiKit . White , 24f , 1276f , 420f , 116f , ( ) = > DoDraw ( 1 ) ) ; // 버튼 행 = 탭 바 위 24 고정(검수 결함 4)
singleCost = UiKit . BtnCost ( singleBtn , "cur_gem" , "" , 44f , 34 ) ;
tenBtn = UiKit . SkinBtn ( p , "Ten" , "btn_l_yellow" , UiKit . White , g . TenCount + "회 등용" , 44 , UiKit . Col ( "text_on_yellow" , UiKit . Ink ) , 1056f - 580f , 1270f , 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 ;
}
2026-11-13 13:34:16 +00:00
/// <summary>배너 좌우 화살표(쪽 넘김 · 틀 가장자리에 걸침 · 어두운 원 바탕 + 화살표).</summary>
void BannerArrow ( Transform frame , string name , string icon , bool right )
{
var side = new Vector2 ( right ? 1f : 0f , 0.5f ) ;
var hit = UiKit . Rect ( name , frame , side , side , new Vector2 ( right ? - 4f : 4f , 0f ) , new Vector2 ( 68f , 124f ) ) ;
hit . gameObject . AddComponent < Image > ( ) . color = Color . clear ;
hit . gameObject . AddComponent < Button > ( ) . onClick . AddListener ( ( ) = > StepBanner ( right ? 1 : - 1 ) ) ;
var c = new Vector2 ( 0.5f , 0.5f ) ;
UiKit . Skin ( UiKit . Rect ( "Bg" , hit , c , c , Vector2 . zero , new Vector2 ( 60f , 60f ) ) , "badge_circle" , UiKit . Col ( "chip_dark" , UiKit . Dark ) ) ;
var ic = UiKit . Icon ( hit , icon , c , c , new Vector2 ( right ? 2f : - 2f , 0f ) , 38f ) ;
if ( ic ! = null ) ic . preserveAspect = true ;
else UiKit . Title ( UiKit . FillLabel ( hit , right ? ">" : "<" , 40 , UiKit . White ) , true ) ;
}
/// <summary>배너 고르기(진영 깃발 · 캡처·테스트) — 상시 배너면 그 진영을 기억한다(쪽을 넘겼다 돌아와도 유지).</summary>
public void SelectBanner ( string id )
{
var b = S . Db . Gacha . Get ( id ) ;
if ( b = = null ) return ;
banner = b ;
if ( ! b . IsPickup ) standingId = b . id ;
Refresh ( ) ;
}
/// <summary>좌우 화살표 = 쪽 넘김(끝에서 처음으로 돈다).</summary>
public void StepBanner ( int d )
{
int n = bannerPages . Count ;
if ( n = = 0 ) return ;
var page = bannerPages [ ( ( BannerPage + d ) % n + n ) % n ] ;
var pick = page [ 0 ] ;
foreach ( var b in page )
if ( b . id = = standingId ) pick = b ;
SelectBanner ( pick . id ) ;
}
/// <summary>「상세 정보」 = 이 배너 주요 영웅(픽업 영웅)의 풀 성장 스펙·스킬 팝업.</summary>
public void ShowBannerInfo ( )
{
if ( banner ! = null & & banner . featured . Count > 0 ) ShowHeroInfo ( banner . featured [ 0 ] ) ;
}
public bool ShowHeroInfo ( string heroId )
{
return HeroInfoPopup . Show ( Root , heroId ) ;
}
// ---------- 배너 안쪽(고를 때마다 다시 그림 · 좌표 = 표 recruit_banner.csv 의 그림 px) ----------
static readonly Vector2 TL = new Vector2 ( 0f , 1f ) ;
void DrawBanner ( )
{
UiKit . Clear ( bannerPx ) ;
FeaturedShown = FlagCount = 0 ;
var bg = UiKit . Fill ( "ArtBg" , bannerPx ) . gameObject . AddComponent < Image > ( ) ;
bg . raycastTarget = false ;
bg . sprite = UISkin . Icon ( banner . art ) ;
if ( bg . sprite = = null ) bg . color = UiKit . Col ( "panel_field" , UiKit . Dark ) ;
if ( banner . IsPickup ) DrawPickup ( ) ;
else DrawStanding ( ) ;
UiKit . Clear ( bannerDots ) ;
if ( bannerPages . Count < 2 ) return ;
int at = BannerPage ;
float dx = - ( ( bannerPages . Count - 1 ) * 30f + 22f ) / 2f ;
for ( int i = 0 ; i < bannerPages . Count ; i + + )
{
// 지금 쪽 = 긴 흰 알약 · 나머지 = 옅은 점(QA r1 M5 「쪽 점이 작고 흐림」)
float w = i = = at ? 40f : 18f ;
var dot = UiKit . Rect ( "Dot" + i , bannerDots , new Vector2 ( 0.5f , 0.5f ) , new Vector2 ( 0f , 0.5f ) , new Vector2 ( dx , 0f ) , new Vector2 ( w , 18f ) ) ;
UiKit . Skin ( dot , "chip" , i = = at ? UiKit . White : UiKit . Col ( "text_soft" , UiKit . Disabled ) ) ;
dx + = w + 12f ;
}
}
/// <summary>
/// 국가별 상시 배너(PD 2026-11-13 참고 1 + 추가 지시): 제목 줄 · 왼쪽 = 중국 대륙 지도 두루마리 위에 진영 깃발 4(마름모꼴 · ↖위 ↙촉 ↗군 ↘오 = 버튼) ·
/// 오른쪽 = 그 진영 주요 영웅 5명이 한 무리로 서고 저마다 세로 이름표(영웅을 누르면 상세 정보).
/// </summary>
void DrawStanding ( )
{
var px = bannerPx ;
UiKit . InfoChip ( px , "Kind" , 22f , 20f , 150f , 50f , null , "상시 등용" , 26 ) ;
var title = UiKit . Title ( UiKit . Label ( px , banner . nameKo , 54 , UiKit . White , TextAnchor . MiddleLeft , TL , TL , new Vector2 ( 188f , - 8f ) , new Vector2 ( 520f , 76f ) ) , true ) ;
title . horizontalOverflow = HorizontalWrapMode . Overflow ;
var sub = UiKit . Title ( UiKit . Label ( px , banner . descKo , 24 , UiKit . White , TextAnchor . MiddleRight , new Vector2 ( 1f , 1f ) , new Vector2 ( 1f , 1f ) , new Vector2 ( - 24f , - 26f ) , new Vector2 ( 430f , 40f ) ) , true ) ;
sub . horizontalOverflow = HorizontalWrapMode . Overflow ;
var m = RecruitBanner . First ( "map" ) ;
if ( m ! = null )
{
var map = UiKit . At ( px , "Map" , m . x , m . y , m . w , m . h ) ;
var mi = map . gameObject . AddComponent < Image > ( ) ;
mi . sprite = UISkin . Icon ( m . sprite ) ;
mi . raycastTarget = false ;
mi . enabled = mi . sprite ! = null ;
var glow = RecruitBanner . First ( "flag_glow" ) ;
var page = bannerPages [ BannerPage ] ;
// 고르지 않은 깃발 먼저 · 고른 깃발은 맨 위(크게 · 밑에 맥동하는 금빛)
for ( int pass = 0 ; pass < 2 ; pass + + )
foreach ( var b in page )
{
bool on = b = = banner ;
if ( on ! = ( pass = = 1 ) ) continue ;
var r = RecruitBanner . First ( "flag_" + b . faction ) ;
if ( r = = null ) continue ;
var at = new Vector2 ( r . x , - r . y ) ;
var c = new Vector2 ( 0.5f , 0.5f ) ;
if ( on & & glow ! = null & & UISkin . Has ( glow . sprite ) )
{
var gl = UiKit . Skin ( UiKit . Rect ( "FlagGlow" , map , TL , c , at , new Vector2 ( glow . w , glow . w ) ) , glow . sprite , glow . color ) ;
var pulse = gl . gameObject . AddComponent < UiPulse > ( ) ;
pulse . min = glow . min ;
pulse . max = glow . max ;
pulse . period = glow . period ;
}
float size = r . w * ( on ? r . max : r . min ) ;
var f = UiKit . Rect ( "Flag_" + b . id , map , TL , c , at , new Vector2 ( size , size ) ) ;
var fi = f . gameObject . AddComponent < Image > ( ) ;
fi . sprite = UISkin . Icon ( r . sprite ) ;
fi . preserveAspect = true ;
float sh = on | | r . shade < 0f ? 1f : r . shade ;
fi . color = fi . sprite ! = null ? new Color ( sh , sh , sh , 1f ) : Color . clear ;
if ( fi . sprite ! = null ) UiKit . DropShadow ( fi , 4f ) ;
else UiKit . FactionBadge ( f , b . faction , c , c , Vector2 . zero , size * 0.8f ) ;
string id = b . id ;
f . gameObject . AddComponent < Button > ( ) . onClick . AddListener ( ( ) = > SelectBanner ( id ) ) ;
FlagCount + + ;
}
UiKit . InfoChip ( px , "Hint" , m . x + 30f , m . y + m . h + 10f , m . w - 60f , 46f , null , "깃발을 눌러 진영 선택" , 24 ) ;
int heroes , soldiers ;
CountPool ( banner , out heroes , out soldiers ) ;
UiKit . InfoChip ( px , "Pool" , m . x + 6f , m . y + m . h + 62f , m . w - 12f , 42f , null , UiKit . FactionKo ( banner . faction ) + " 진영 영웅 " + heroes + "명" + ( soldiers > 0 ? " · 병사 " + soldiers + "종" : "" ) + " 등장" , 22 ) ;
}
DrawFeatured ( ) ;
}
/// <summary>이 배너에서 나오는 영웅 수 · 병사 유닛 종 수(풀 표 기준).</summary>
void CountPool ( GachaConfig . Banner b , out int heroes , out int soldiers )
{
heroes = soldiers = 0 ;
for ( int t = 1 ; t < = 3 ; t + + )
foreach ( var e in GachaRules . Candidates ( S . Db , b , t ) )
{
var h = S . Def ( e . heroId ) ;
if ( h ! = null & & h . isSoldier ) soldiers + + ;
else heroes + + ;
}
}
/// <summary>주요 영웅 무리(표 hero 줄 = 발 자리 · 뒤 → 앞 순서로 그림) + 세로 이름표(표 tag 줄) · 영웅 누름 = 상세 정보.</summary>
void DrawFeatured ( )
{
var px = bannerPx ;
int n = banner . featured . Count ;
var order = RecruitBanner . Slots ( "hero" , n ) ;
order . Sort ( ( a , b ) = > a . y . CompareTo ( b . y ) ) ;
var hit = RecruitBanner . First ( "hit" ) ;
var shadow = RecruitBanner . First ( "shadow" ) ;
foreach ( var r in order )
{
var h = r . slot < n ? S . Def ( banner . featured [ r . slot ] ) : null ;
if ( h = = null ) continue ;
var foot = new Vector2 ( r . x , - r . y ) ;
if ( shadow ! = null & & UISkin . Has ( shadow . sprite ) )
UiKit . Skin ( UiKit . Rect ( "Shadow" , px , TL , new Vector2 ( 0.5f , 0.5f ) , foot , new Vector2 ( r . h * shadow . w , r . h * shadow . h ) ) , shadow . sprite , shadow . color ) ;
if ( UiKit . UnitStand ( "Hero_" + h . id , px , h , TL , foot , r . h , r . max ) = = null )
UiKit . Portrait ( "Hero_" + h . id , px , h , TL , new Vector2 ( 0.5f , 0f ) , foot , new Vector2 ( r . h , r . h ) , false , 1f , Color . clear ) ;
FeaturedShown + + ;
var hb = UiKit . Rect ( "Hit_" + h . id , px , TL , new Vector2 ( 0.5f , 0f ) , foot , new Vector2 ( hit ! = null ? hit . w : r . h , hit ! = null ? hit . h : r . h ) ) ;
hb . gameObject . AddComponent < Image > ( ) . color = Color . clear ;
string id = h . id ;
hb . gameObject . AddComponent < Button > ( ) . onClick . AddListener ( ( ) = > ShowHeroInfo ( id ) ) ;
}
foreach ( var r in RecruitBanner . Slots ( "tag" , n ) )
{
var h = r . slot < n ? S . Def ( banner . featured [ r . slot ] ) : null ;
if ( h ! = null ) NameTagV ( px , r , h . nameKo ) ;
}
}
/// <summary>
/// 세로 이름표(PD 참고 1 = 영웅마다 세로 이름표) — 표 tag 줄: 목패(가로 그림을 세워서 씀 · 9 분할 유지) · 판 색 · 글자 = 한 자씩 세로로.
/// f_x = 가운데 · f_y = 윗변(i_count 1 = 아랫변 · 머리 바로 위에 붙이는 이름표) · f_w 폭 · f_h 글자 1자 높이 · f_scale 글자 크기 · f_min 위아래 여백.
/// </summary>
static void NameTagV ( Transform px , LobbyScene . Row r , string name )
{
float w = r . w > 1f ? r . w : 46f , ch = r . h > 1f ? r . h : 30f , pad = r . min > 1f ? r . min : 12f ;
int fs = r . scale > 1f ? Mathf . RoundToInt ( r . scale ) : 26 ;
float h = name . Length * ch + pad * 2f ;
var box = UiKit . Rect ( "Tag" , px , TL , new Vector2 ( 0.5f , r . count = = 1 ? 0f : 1f ) , new Vector2 ( r . x , - r . y ) , new Vector2 ( w , h ) ) ; // i_count 1 = f_y 가 아랫변
var c = new Vector2 ( 0.5f , 0.5f ) ;
var plate = UiKit . Rect ( "Plate" , box , c , c , Vector2 . zero , new Vector2 ( h , w ) ) ;
plate . localEulerAngles = new Vector3 ( 0f , 0f , 90f ) ;
var img = plate . gameObject . AddComponent < Image > ( ) ;
img . sprite = UISkin . Icon ( r . sprite ) ;
img . color = r . color ;
img . raycastTarget = false ;
if ( img . sprite ! = null & & img . sprite . border . sqrMagnitude > 0f ) img . type = Image . Type . Sliced ;
for ( int i = 0 ; i < name . Length ; i + + )
{
var t = UiKit . Title ( UiKit . Label ( box , name . Substring ( i , 1 ) , fs , UiKit . White , TextAnchor . MiddleCenter , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0f , - ( pad + i * ch ) ) , new Vector2 ( w , ch ) ) , true ) ;
t . horizontalOverflow = HorizontalWrapMode . Overflow ;
t . verticalOverflow = VerticalWrapMode . Overflow ;
}
}
/// <summary>
/// 픽업 배너(PD 2026-11-13 참고 3 구성 = 왼쪽 정보 판 + 오른쪽 큰 일러스트 + 아래 이름표): 그림(영웅 일러 + 왼쪽 어둡게)은 한 장 ·
/// 왼쪽 = 종류 칩 → 제목(영웅 이름 강조) → ★ → 남은 기간 → 설명 → 인게임 그림 + 스킬 아이콘 → 천장 안내 · 오른쪽 아래 = 이름 명패 + 「상세 정보」.
/// </summary>
void DrawPickup ( )
{
var px = bannerPx ;
var g = S . Db . Gacha ;
var h = banner . featured . Count > 0 ? S . Def ( banner . featured [ 0 ] ) : null ;
var now = S . Now ;
bool open = GachaRules . IsOpen ( banner , now ) ;
var kind = UiKit . Skin ( UiKit . At ( px , "Kind" , 74f , 24f , 176f , 52f ) , "chip" , UiKit . Purple ) ;
UiKit . Title ( UiKit . FillLabel ( kind . transform , "픽업 등용" , 28 , UiKit . White ) , true ) ;
string title = banner . nameKo ;
var names = new List < string > ( ) ;
foreach ( var id in banner . featured )
{
var fh = S . Def ( id ) ;
if ( fh = = null ) continue ;
names . Add ( fh . nameKo ) ;
title = title . Replace ( fh . nameKo , "<color=#" + ColorUtility . ToHtmlStringRGB ( UiKit . Star ) + ">" + fh . nameKo + "</color>" ) ;
}
var tt = UiKit . Title ( UiKit . Label ( px , title , 68 , UiKit . White , TextAnchor . MiddleLeft , TL , TL , new Vector2 ( 74f , - 82f ) , new Vector2 ( 440f , 92f ) ) , true ) ;
tt . supportRichText = true ;
tt . horizontalOverflow = HorizontalWrapMode . Overflow ;
if ( h ! = null )
for ( int i = 0 ; i < h . rarity ; i + + )
UiKit . DropShadow ( UiKit . Icon ( px , "star_l" , TL , TL , new Vector2 ( 76f + i * 54f , - 180f ) , 52f ) , 3f ) ;
var clock = UiKit . Icon ( px , "clock" , TL , TL , new Vector2 ( 76f , - 246f ) , 40f ) ;
string when = ! open ? "등용 기간 종료" : GachaRules . EndOf ( banner ) = = null ? "기간 제한 없음" : "남은 기간 " + RemainText ( GachaRules . Remaining ( banner , now ) ) ;
var wt = UiKit . Title ( UiKit . Label ( px , when , 30 , UiKit . White , TextAnchor . MiddleLeft , TL , TL , new Vector2 ( clock ! = null ? 126f : 76f , - 245f ) , new Vector2 ( 360f , 42f ) ) , true ) ;
wt . horizontalOverflow = HorizontalWrapMode . Overflow ;
string desc = banner . descKo + "\n★★★ 등용 시 " + ( g . PickupShare * 100f ) . ToString ( "0.#" ) + "% 확률로 " + string . Join ( " · " , names . ToArray ( ) ) ;
var dt = UiKit . Label ( px , desc , 26 , UiKit . White , TextAnchor . UpperLeft , TL , TL , new Vector2 ( 76f , - 302f ) , new Vector2 ( 390f , 80f ) ) ;
dt . lineSpacing = 1.15f ;
UiKit . DropShadow ( dt , 2f ) ;
if ( h ! = null )
{
// 인게임 그림 + 스킬 아이콘 2(액티브 · 패시브) — 누르면 상세 정보
const float y = 396f , cell = 108f ;
var face = UiKit . Portrait ( "InGame" , px , h , TL , TL , new Vector2 ( 76f , - y ) , new Vector2 ( cell , cell ) , false , 1f , UiKit . Col ( "header" , UiKit . Dark ) ) ;
face . gameObject . AddComponent < Button > ( ) . onClick . AddListener ( ShowBannerInfo ) ;
Caption ( px , "인게임" , 76f , y + cell + 4f , cell ) ;
string tk = UiKit . TypeKey ( h . type ) ;
for ( int i = 0 ; i < 2 ; i + + )
{
var sk = SkillRules . Skill ( S . Db , h , i ) ;
if ( sk = = null | | UISkin . Icon ( "skill_" + sk . id ) = = null ) continue ;
float x = 76f + ( i + 1 ) * ( cell + 14f ) ;
UiKit . Icon ( px , "skill_" + sk . id , TL , TL , new Vector2 ( x + cell * 0.082f , - ( y + cell * 0.082f ) ) , cell * 0.836f ) ;
if ( tk ! = null ) UiKit . DropShadow ( UiKit . Icon ( px , "skill_ring_" + tk , TL , TL , new Vector2 ( x , - y ) , cell ) , 3f ) ;
Caption ( px , i = = 0 ? "액티브" : "패시브" , x , y + cell + 4f , cell ) ;
}
}
int left = Mathf . Max ( 1 , g . Pity - GachaRules . Pity ( S . Save , banner ) ) ;
UiKit . InfoChip ( px , "PityNote" , 74f , 558f , 400f , 48f , null , "앞으로 " + left + "회 안에 ★★★ 확정" , 26 ) ;
// 오른쪽 아래 = 이름 명패(병종 기호 · 이름 · ★) + 상세 정보
if ( h ! = null )
{
var plate = UiKit . At ( px , "Name" , 500f , 530f , 348f , 82f ) ;
var pimg = plate . gameObject . AddComponent < Image > ( ) ;
pimg . raycastTarget = false ;
pimg . sprite = UISkin . Icon ( "tre_plaque" ) ;
if ( pimg . sprite ! = null )
{
if ( pimg . sprite . border . sqrMagnitude > 0f ) pimg . type = Image . Type . Sliced ;
}
else
{
UISkin . Apply ( pimg , "chip" ) ;
pimg . color = UiKit . Col ( "chip_dark" , UiKit . Dark ) ;
}
var row = UiKit . Fill ( "Row" , plate , 62f , 62f , 6f , 12f ) ;
var hl = row . gameObject . AddComponent < HorizontalLayoutGroup > ( ) ;
hl . childAlignment = TextAnchor . MiddleCenter ;
hl . spacing = 8f ;
hl . childControlWidth = true ;
hl . childControlHeight = false ;
hl . childForceExpandWidth = hl . childForceExpandHeight = false ;
var sym = UiKit . Icon ( row , UiKit . TypeSymbolId ( h . type ) , new Vector2 ( 0.5f , 0.5f ) , new Vector2 ( 0.5f , 0.5f ) , Vector2 . zero , 46f ) ;
if ( sym ! = null ) Fixed ( sym , 46f ) ;
var nt = UiKit . Title ( UiKit . Label ( row , h . nameKo , 40 , UiKit . White , TextAnchor . MiddleCenter , new Vector2 ( 0.5f , 0.5f ) , new Vector2 ( 0.5f , 0.5f ) , Vector2 . zero , new Vector2 ( 120f , 60f ) ) , true ) ;
nt . horizontalOverflow = HorizontalWrapMode . Overflow ;
for ( int i = 0 ; i < h . rarity ; i + + )
{
var st = UiKit . Icon ( row , "star_on" , new Vector2 ( 0.5f , 0.5f ) , new Vector2 ( 0.5f , 0.5f ) , Vector2 . zero , 30f ) ;
if ( st ! = null ) Fixed ( st , 26f ) ;
}
UiKit . SkinBtn ( px , "BannerInfo" , "btn_sky" , UiKit . White , "상세 정보" , 28 , UiKit . White , 856f , 530f , 150f , 80f , ShowBannerInfo ) ;
}
}
static void Fixed ( Graphic g , float w )
{
var le = g . gameObject . AddComponent < LayoutElement > ( ) ;
le . minWidth = le . preferredWidth = w ;
}
static void Caption ( Transform px , string s , float x , float y , float w )
{
UiKit . DropShadow ( UiKit . Label ( px , s , 22 , UiKit . Col ( "text_soft" , UiKit . White ) , TextAnchor . MiddleCenter , TL , TL , new Vector2 ( x , - y ) , new Vector2 ( w , 30f ) , false ) , 2f ) ;
}
/// <summary>남은 기간 글자(하루 이상 = n일 n시간 · 하루 미만 = n시간 n분).</summary>
public static string RemainText ( TimeSpan r )
{
if ( r . TotalDays > = 1.0 ) return ( int ) r . TotalDays + "일 " + r . Hours + "시간" ;
return ( int ) r . TotalHours + "시간 " + r . Minutes + "분" ;
}
/// <summary>같은 천장 묶음을 쓰는 배너 수.</summary>
int PityShare ( GachaConfig . Banner b )
{
int n = 0 ;
foreach ( var o in S . Db . Gacha . banners )
if ( o . pityGroup = = b . pityGroup ) n + + ;
return n ;
}
/// <summary>등용 규칙(구 각주 → ⓘ 팝업 · Fable §4-4) — 배너별 풀 · 천장 묶음 포함.</summary>
2026-11-09 09:03:43 +00:00
void ShowGachaRules ( )
{
var e = S . Db . Economy ;
var g = S . Db . Gacha ;
2026-11-13 13:34:16 +00:00
string scope = "" ;
if ( banner ! = null )
{
if ( banner . IsPickup )
{
var names = new List < string > ( ) ;
foreach ( var id in banner . featured )
if ( S . Def ( id ) ! = null ) names . Add ( S . Def ( id ) . nameKo ) ;
scope = "픽업 배너 = ★★★ 의 " + ( g . PickupShare * 100f ) . ToString ( "0.#" ) + "% 가 " + string . Join ( " · " , names . ToArray ( ) ) + "\n" ;
}
else scope = "상시 배너 = " + UiKit . FactionKo ( banner . faction ) + " 진영 영웅만 등장\n" ;
if ( g . PickupExclusive ) scope + = "픽업 영웅은 픽업 배너에서만 등장\n" ;
int share = PityShare ( banner ) ;
scope + = "천장 " + g . Pity + "회 = ★★★ 확정 (" + ( share > 1 ? "배너 " + share + "개가 같이 셈" : "이 배너만 셈" ) + ")\n\n" ;
}
Root . Modal ( "등용 규칙" ,
scope + "중복 = 영웅 조각\n★★★ " + e . ShardDup ( 3 ) + " · ★★ " + e . ShardDup ( 2 ) + " · ★ " + e . ShardDup ( 1 ) + "\n병사 중복 = 골드 " + e . SoldierDupGold +
"\n\n" + g . TenCount + "회 등용 = ★★ 이상 1 보장\n확률·천장 전부 공개" ,
2026-11-09 09:03:43 +00:00
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 ( ) ;
}
2026-11-13 13:34:16 +00:00
// ---------- 명장 등용(테마가 없을 때의 예전 화면) ----------
2026-11-05 12:11:10 +00:00
void BuildGacha ( RectTransform p )
{
var g = S . Db . Gacha ;
2026-11-13 13:34:16 +00:00
var bn = UiKit . TopBand ( "Banner" , p , 12f , 440f , 24f , 24f ) ;
if ( UISkin . Has ( "banner" ) ) UiKit . Skin ( bn , "banner" , Color . white ) ; // 팩 배너 틀(PD #1100)
else UiKit . Paint ( bn , new Color ( 0.27f , 0.14f , 0.12f ) ) ;
var pick = UiKit . Box ( "Pickup" , bn , new Vector2 ( 0f , 1f ) , new Vector2 ( 0f , 1f ) , new Vector2 ( 30f , - 24f ) , new Vector2 ( 230f , 62f ) , UiKit . Red ) ;
UiKit . FillLabel ( pick . transform , "등용 배너" , 30 , UiKit . White ) ;
UiKit . Title ( UiKit . Label ( bn , "명장 등용" , 60 , UiKit . White , TextAnchor . MiddleLeft , new Vector2 ( 0f , 1f ) , new Vector2 ( 0f , 1f ) , new Vector2 ( 36f , - 110f ) , new Vector2 ( 600f , 90f ) ) , true ) ;
UiKit . Label ( bn , "모든 영웅·병사 유닛\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" , bn , 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 ) ;
2026-11-05 12:11:10 +00:00
freeChip = free . gameObject ;
2026-11-13 13:34:16 +00:00
pickupArt = UiKit . Rect ( "PickupArt" , bn , new Vector2 ( 1f , 0f ) , new Vector2 ( 1f , 0f ) , new Vector2 ( - 20f , 10f ) , new Vector2 ( 440f , 400f ) ) ;
2026-11-05 12:11:10 +00:00
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 ) ) ;
2026-11-13 13:34:16 +00:00
UiKit . FillLabel ( f2 . transform , "첫 10회 무료" , 26 , UiKit . Ink ) ;
2026-11-05 12:11:10 +00:00
freeChip2 = f2 . gameObject ;
var e = S . Db . Economy ;
UiKit . Label ( p , "중복 = 영웅 조각(★★★ " + e . ShardDup ( 3 ) + " · ★★ " + e . ShardDup ( 2 ) + " · ★ " + e . ShardDup ( 1 ) + ") · 병사 중복 = 골드 " + e . SoldierDupGold +
2026-11-13 13:34:16 +00:00
" · 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 ) ;
2026-11-05 12:11:10 +00:00
}
void RefreshGacha ( )
{
var g = S . Db . Gacha ;
var d = S . Save ;
2026-11-13 13:34:16 +00:00
if ( banner = = null ) return ;
2026-11-05 12:11:10 +00:00
bool free = GachaRules . FirstTenFree ( S . Db , d ) ;
2026-11-13 13:34:16 +00:00
bool open = GachaRules . IsOpen ( banner , S . Now ) ;
int pity = GachaRules . Pity ( d , banner ) ;
if ( freeChip ! = null ) freeChip . SetActive ( free ) ;
2026-11-05 12:11:10 +00:00
freeChip2 . SetActive ( free ) ;
2026-11-13 13:34:16 +00:00
singleBtn . interactable = tenBtn . interactable = open ;
2026-11-09 09:03:43 +00:00
if ( pityBadge ! = null )
{
2026-11-13 13:34:16 +00:00
pityText . text = pity + " / " + g . Pity + " · " + g . Pity + "회째 ★★★ 확정" ;
pityBadge . text = pity . ToString ( ) ;
int share = PityShare ( banner ) ;
pityNote . text = share > 1 ? "배너 " + share + "개 공용" : "이 배너 전용" ;
UiKit . SetGauge ( pityBar , pity / ( float ) Mathf . Max ( 1 , g . Pity ) , GaugeW + 60f ) ;
2026-11-09 09:03:43 +00:00
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 ) ;
2026-11-13 13:34:16 +00:00
// 확률 행 메모 = 배너별(★★★ = 픽업 영웅 몫 · ★ = 병사 포함)
var names = new List < string > ( ) ;
if ( banner . IsPickup )
foreach ( var id in banner . featured )
if ( S . Def ( id ) ! = null ) names . Add ( S . Def ( id ) . nameKo ) ;
probNote [ 3 ] . text = names . Count > 0 ? string . Join ( "·" , names . ToArray ( ) ) + " " + ( g . PickupShare * 100f ) . ToString ( "0.#" ) + "%" : "" ;
probNote [ 2 ] . text = "" ;
probNote [ 1 ] . text = banner . soldiers ? "병사 포함" : "" ;
DrawBanner ( ) ;
2026-11-09 09:19:06 +00:00
return ;
}
2026-11-13 13:34:16 +00:00
pityText . text = pity + " / " + g . Pity + " · " + g . Pity + "회 ★★★ 확정" ;
UiKit . SetBar ( pityBar , 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 ) ;
for ( int i = 0 ; i < banner . featured . Count & & i < 2 ; i + + )
2026-11-05 12:11:10 +00:00
{
2026-11-13 13:34:16 +00:00
var h = S . Def ( banner . featured [ i ] ) ;
if ( h = = null ) continue ;
UiKit . Portrait ( "Pick" + i , pickupArt , h , 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 , h . nameKo + " ★" + h . rarity , 26 , UiKit . White , TextAnchor . MiddleCenter , new Vector2 ( 1f , 0f ) , new Vector2 ( 1f , 0f ) , new Vector2 ( - i * 210f , 6f ) , new Vector2 ( 230f , 44f ) ) ;
2026-11-05 12:11:10 +00:00
}
}
public void DoDraw ( int count )
{
2026-11-13 13:34:16 +00:00
var why = GachaRules . CanDraw ( S . Db , S . Save , banner , count , S . Now ) ;
2026-11-05 12:11:10 +00:00
if ( why ! = null )
{
Root . Toast ( why ) ;
return ;
}
2026-11-13 13:34:16 +00:00
LastPulls = S . Draw ( banner , count ) ;
2026-11-05 12:11:10 +00:00
ShowResult ( LastPulls ) ;
}
// ---------- 뽑기 결과(카드 순차 공개) ----------
void ShowResult ( List < GachaPull > pulls )
{
CloseResult ( ) ;
2026-11-13 11:29:46 +00:00
// 삼고초려 연출(PD 2026-11-13 · 문이 덜컹 → 서서히 열리며 등급 빛 → 카드 획득 · 표 gacha_scene.csv + 배경 bg_gacha_hut 이 있을 때) · 없으면 아래 예전 결과 화면
if ( pulls . Count > 0 & & GachaScene . Ready )
{
cutscene = GachaCutscene . Show ( Root , S , pulls , CloseResult ) ;
resultRoot = cutscene . gameObject ;
return ;
}
2026-11-05 12:11:10 +00:00
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 ;
2026-11-13 13:34:16 +00:00
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 ) ) ;
2026-11-05 12:11:10 +00:00
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 ( )
{
2026-11-13 11:29:46 +00:00
if ( cutscene ! = null )
{
cutscene . LandAll ( ) ; // 장면을 건너뛰고 카드 전부 공개
return ;
}
2026-11-05 12:11:10 +00:00
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 ( ) ;
2026-11-13 11:29:46 +00:00
cutscene = null ;
2026-11-05 12:11:10 +00:00
if ( resultRoot ! = null )
{
resultRoot . SetActive ( false ) ;
2026-11-13 13:34:16 +00:00
UnityEngine . Object . Destroy ( resultRoot ) ;
2026-11-05 12:11:10 +00:00
resultRoot = null ;
}
Refresh ( ) ;
}
public int RevealedCount
{
get
{
2026-11-13 11:29:46 +00:00
if ( cutscene ! = null ) return cutscene . CardsLanded ;
2026-11-05 12:11:10 +00:00
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 ( ) ;
}
}
}