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