2026-11-05 12:11:10 +00:00
using System.Collections.Generic ;
using UnityEngine ;
using UnityEngine.UI ;
namespace TKS.OutGame
{
/// <summary>영웅·병사 카드(2줄 · 기획 S12 v4) — 좌 초상(★ 는 초상 아래) · 1줄 = 병종 배지 · Lv · 출전 중 · 2줄 = 이름.</summary>
public static class HeroCards
{
public static Image Card ( Transform content , OutGameSession s , HeroDb . Hero h , bool deployed , bool owned , System . Action onTap )
{
var cell = UiKit . Cell ( content , "Card_" + h . id , owned ? UiKit . Paper : new Color ( 0.86f , 0.84f , 0.8f ) , onTap ) ;
UiKit . Border ( cell , deployed ? UiKit . Red : UiKit . Line , deployed ? 4f : 2f ) ;
var lv = s . Save . Level ( h . id ) ;
var hs = s . Save . Hero ( h . id ) ;
UiKit . Portrait ( "Face" , cell . transform , h , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 12f , 8f ) , new Vector2 ( 116f , 108f ) , false , 2f , new Color ( 0.9f , 0.86f , 0.8f ) ) ;
int star = hs ! = null ? hs . star : h . rarity ;
UiKit . Label ( cell . transform , UiKit . Stars ( star , star ) , 22 , UiKit . Star , TextAnchor . MiddleCenter , new Vector2 ( 0f , 0f ) , new Vector2 ( 0f , 0f ) , new Vector2 ( 12f , 2f ) , new Vector2 ( 116f , 30f ) ) ;
UiKit . TypeBadge ( cell . transform , h . type , new Vector2 ( 0f , 1f ) , new Vector2 ( 0f , 1f ) , new Vector2 ( 142f , - 16f ) , 40f ) ;
UiKit . Label ( cell . transform , owned ? "Lv " + ( lv ! = null ? lv . lv : 1 ) : "미보유" , 30 , UiKit . Ink , TextAnchor . MiddleLeft , new Vector2 ( 0f , 1f ) , new Vector2 ( 0f , 1f ) , new Vector2 ( 204f , - 12f ) , new Vector2 ( 150f , 48f ) ) ;
if ( deployed )
{
var b = UiKit . Box ( "Deployed" , cell . transform , new Vector2 ( 1f , 1f ) , new Vector2 ( 1f , 1f ) , new Vector2 ( - 12f , - 14f ) , new Vector2 ( 124f , 44f ) , UiKit . Red ) ;
UiKit . FillLabel ( b . transform , "출전 중" , 24 , UiKit . White ) ;
}
if ( hs ! = null )
{
var g = UiKit . Box ( "Grade" , cell . transform , new Vector2 ( 1f , 0f ) , new Vector2 ( 1f , 0f ) , new Vector2 ( - 12f , 14f ) , new Vector2 ( 56f , 40f ) , UiKit . GradeColor ( hs . grade ) ) ;
UiKit . FillLabel ( g . transform , UiKit . GradeKo ( hs . grade ) , 24 , UiKit . White ) ;
}
var name = UiKit . Label ( cell . transform , h . nameKo , 44 , owned ? UiKit . Ink : UiKit . InkSoft , TextAnchor . MiddleLeft , new Vector2 ( 0f , 0f ) , new Vector2 ( 0f , 0f ) , new Vector2 ( 142f , 12f ) , new Vector2 ( 290f , 66f ) ) ;
name . horizontalOverflow = HorizontalWrapMode . Overflow ;
if ( owned & & s . CanGrowAny ( h . id ) ) UiKit . Dot ( "New" , cell . transform , new Vector2 ( 0f , 1f ) , new Vector2 ( 0.5f , 0.5f ) , new Vector2 ( 14f , - 10f ) , 26f , UiKit . Red ) ;
return cell ;
}
}
/// <summary>영웅 탭 = 보유 영웅·병사 목록(장수|병사 · 2줄 카드) → 탭 = 영웅 상세 · 도감.</summary>
public class HeroListScreen : OutGameScreen
{
bool soldiers ;
RectTransform content ;
ScrollRect scroll ;
Button tabHero ;
Button tabSoldier ;
public bool ShowingSoldiers
{
get { return soldiers ; }
}
public override void Build ( )
{
Tab = 0 ;
Header ( "영웅" , "레벨 · 승급 · 진화 · 스킬" , true ) ;
var body = Body ( ) ;
tabHero = UiKit . Btn ( "TabHero" , body , "장수" , 36 , UiKit . Red , UiKit . White , new Vector2 ( 0f , 1f ) , new Vector2 ( 0f , 1f ) , new Vector2 ( 24f , - 20f ) , new Vector2 ( 220f , 84f ) , ( ) = > SetFilter ( false ) ) ;
tabSoldier = UiKit . Btn ( "TabSoldier" , body , "병사" , 36 , UiKit . Paper , UiKit . Ink , new Vector2 ( 0f , 1f ) , new Vector2 ( 0f , 1f ) , new Vector2 ( 258f , - 20f ) , new Vector2 ( 220f , 84f ) , ( ) = > SetFilter ( true ) ) ;
UiKit . Btn ( "Codex" , body , "도감" , 36 , UiKit . Dark , UiKit . White , new Vector2 ( 1f , 1f ) , new Vector2 ( 1f , 1f ) , new Vector2 ( - 24f , - 20f ) , new Vector2 ( 220f , 84f ) , ( ) = > HeroDetailScreen . ShowCodex ( Root ) ) ;
content = UiKit . ScrollGrid ( "List" , body , 24f , 24f , 124f , 16f , new Vector2 ( 506f , 150f ) , new Vector2 ( 20f , 18f ) , 2 , out scroll ) ;
}
public void SetFilter ( bool soldier )
{
soldiers = soldier ;
Refresh ( ) ;
}
public override void Refresh ( )
{
tabHero . GetComponent < Image > ( ) . color = soldiers ? UiKit . Paper : UiKit . Red ;
UiKit . LabelOf ( tabHero ) . color = soldiers ? UiKit . Ink : UiKit . White ;
tabSoldier . GetComponent < Image > ( ) . color = soldiers ? UiKit . Red : UiKit . Paper ;
UiKit . LabelOf ( tabSoldier ) . color = soldiers ? UiKit . White : UiKit . Ink ;
UiKit . LabelOf ( tabHero ) . text = "장수 " + S . Owned ( false ) . Count ;
UiKit . LabelOf ( tabSoldier ) . text = "병사 " + S . Owned ( true ) . Count ;
UiKit . Clear ( content ) ;
foreach ( var h in S . Owned ( soldiers ) )
{
var id = h . id ;
HeroCards . Card ( content , S , h , S . InActivePreset ( id ) , true , ( ) = > Root . ShowHero ( id ) ) ;
}
}
public int CardCount
{
get { return content . childCount ; }
}
}
/// <summary>
/// 영웅 상세(img06 · 기획 S6 v4) — 전투력 · 일러 + 양옆 장비 6칸(빈 칸 탭 = 가방에서 장착 · 1칸 = 1강) · 이름 띠(병종 · 이름 · ★) ·
/// 스킬 정보(액티브·패시브 = 이름·효과·Lv·쿨 · 표시만 · 스킬 레벨업 = B 단계) · Lv/경험 바 + 레벨업(골드) · 승급(6칸) · 조각 바 + 진화 · 상세정보 · 도감 · ◀ ▶ 전환.
/// </summary>
public class HeroDetailScreen : OutGameScreen
{
string id ;
readonly List < string > order = new List < string > ( ) ;
Text power ;
RectTransform art ;
readonly Image [ ] slotImg = new Image [ 6 ] ;
readonly Text [ ] slotName = new Text [ 6 ] ;
readonly Text [ ] slotState = new Text [ 6 ] ;
Image typeDot ;
Text typeMark ;
Text nameText ;
Text subText ;
Text starText ;
Text [ ] skillName = new Text [ 2 ] ;
Text [ ] skillDesc = new Text [ 2 ] ;
Text [ ] skillChip = new Text [ 2 ] ;
Text lvText ;
Text expText ;
RectTransform expBar ;
Button levelBtn ;
Button promoteBtn ;
Text shardText ;
RectTransform shardBar ;
Button evolveBtn ;
Button skillBtn ;
public string HeroId
{
get { return id ; }
}
public void Select ( string heroId )
{
id = heroId ;
var h = S . Def ( heroId ) ;
order . Clear ( ) ;
if ( h ! = null )
foreach ( var o in S . Owned ( h . isSoldier ) ) order . Add ( o . id ) ;
}
public override void Build ( )
{
Tab = 0 ;
Header ( "영웅" , "레벨 · 승급 · 진화 · 스킬" , true ) ;
var body = Body ( ) ;
// ① 카드: 전투력 · 일러 · 장비 6칸 · 이름 띠
var card = UiKit . TopBand ( "Card" , body , 16f , 720f , 24f , 24f ) ;
var cimg = UiKit . Paint ( card , UiKit . Paper ) ;
UiKit . Border ( cimg , UiKit . Line , 3f ) ;
UiKit . Dot ( "Halo" , card , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0f , - 100f ) , 500f , new Color ( 0.93f , 0.9f , 0.84f ) ) ;
power = UiKit . Label ( card , "" , 44 , UiKit . Ink , TextAnchor . MiddleCenter , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0f , - 16f ) , new Vector2 ( 560f , 70f ) ) ;
UiKit . Btn ( "Prev" , card , "◀" , 40 , UiKit . Dark , UiKit . White , new Vector2 ( 0f , 1f ) , new Vector2 ( 0f , 1f ) , new Vector2 ( 20f , - 16f ) , new Vector2 ( 96f , 72f ) , ( ) = > Step ( - 1 ) ) ;
UiKit . Btn ( "Next" , card , "▶" , 40 , UiKit . Dark , UiKit . White , new Vector2 ( 1f , 1f ) , new Vector2 ( 1f , 1f ) , new Vector2 ( - 20f , - 16f ) , new Vector2 ( 96f , 72f ) , ( ) = > Step ( 1 ) ) ;
art = UiKit . Rect ( "Art" , card , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0f , - 96f ) , new Vector2 ( 560f , 500f ) ) ;
for ( int i = 0 ; i < 6 ; i + + )
{
int k = i ;
bool left = i < 3 ;
int row = i % 3 ;
var a = left ? new Vector2 ( 0f , 1f ) : new Vector2 ( 1f , 1f ) ;
var b = UiKit . Btn ( "Slot" + i , card , "" , 24 , UiKit . Slot , UiKit . Ink , a , a , new Vector2 ( left ? 22f : - 22f , - 104f - row * 172f ) , new Vector2 ( 196f , 156f ) , ( ) = > OnSlot ( k ) ) ;
slotImg [ i ] = b . GetComponent < Image > ( ) ;
UiKit . LabelOf ( b ) . text = "" ;
slotName [ i ] = UiKit . Label ( b . transform , "" , 30 , UiKit . Ink , TextAnchor . MiddleCenter , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0f , - 12f ) , new Vector2 ( 186f , 84f ) ) ;
slotState [ i ] = UiKit . Label ( b . transform , "" , 24 , UiKit . InkSoft , TextAnchor . MiddleCenter , new Vector2 ( 0.5f , 0f ) , new Vector2 ( 0.5f , 0f ) , new Vector2 ( 0f , 10f ) , new Vector2 ( 186f , 44f ) , false ) ;
}
var band = UiKit . Rect ( "NameBand" , card , new Vector2 ( 0.5f , 0f ) , new Vector2 ( 0.5f , 0f ) , Vector2 . zero , new Vector2 ( 0f , 116f ) ) ;
band . anchorMin = new Vector2 ( 0f , 0f ) ;
band . anchorMax = new Vector2 ( 1f , 0f ) ;
band . offsetMin = new Vector2 ( 0f , 0f ) ;
band . offsetMax = new Vector2 ( 0f , 116f ) ;
UiKit . Paint ( band , UiKit . Dark ) ;
typeDot = UiKit . Dot ( "TypeDot" , band , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 24f , 0f ) , 80f , UiKit . Red ) ;
typeMark = UiKit . FillLabel ( typeDot . transform , "" , 40 , UiKit . White ) ;
nameText = UiKit . Label ( band , "" , 60 , UiKit . White , TextAnchor . MiddleLeft , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 124f , 4f ) , new Vector2 ( 300f , 90f ) ) ;
nameText . horizontalOverflow = HorizontalWrapMode . Overflow ;
subText = UiKit . Label ( band , "" , 28 , new Color ( 0.85f , 0.8f , 0.75f ) , TextAnchor . MiddleLeft , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 420f , 0f ) , new Vector2 ( 330f , 80f ) , false ) ;
starText = UiKit . Label ( band , "" , 52 , UiKit . Star , TextAnchor . MiddleRight , new Vector2 ( 1f , 0.5f ) , new Vector2 ( 1f , 0.5f ) , new Vector2 ( - 24f , 0f ) , new Vector2 ( 300f , 80f ) ) ;
// ② 스킬 정보
var skill = UiKit . TopBand ( "Skill" , body , 752f , 400f , 24f , 24f ) ;
var simg = UiKit . Paint ( skill , UiKit . Paper ) ;
UiKit . Border ( simg , UiKit . Line , 3f ) ;
UiKit . Label ( skill , "스킬" , 40 , UiKit . Ink , TextAnchor . MiddleLeft , new Vector2 ( 0f , 1f ) , new Vector2 ( 0f , 1f ) , new Vector2 ( 28f , - 14f ) , new Vector2 ( 300f , 60f ) ) ;
UiKit . Label ( skill , "액티브 1 · 패시브 1" , 26 , UiKit . InkSoft , TextAnchor . MiddleRight , new Vector2 ( 1f , 1f ) , new Vector2 ( 1f , 1f ) , new Vector2 ( - 28f , - 14f ) , new Vector2 ( 400f , 60f ) , false ) ;
for ( int i = 0 ; i < 2 ; i + + )
{
var c = UiKit . TopBand ( "Skill" + i , skill , 86f + i * 152f , 140f , 20f , 20f ) ;
var ci = UiKit . Paint ( c , new Color ( 0.98f , 0.96f , 0.9f ) ) ;
UiKit . Border ( ci , UiKit . Line , 2f ) ;
var d = UiKit . Dot ( "Kind" , c , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 18f , 0f ) , 92f , i = = 0 ? UiKit . Red : UiKit . Dark ) ;
UiKit . FillLabel ( d . transform , i = = 0 ? "액" : "패" , 40 , UiKit . White ) ;
skillName [ i ] = UiKit . Label ( c , "" , 34 , UiKit . Ink , TextAnchor . MiddleLeft , new Vector2 ( 0f , 1f ) , new Vector2 ( 0f , 1f ) , new Vector2 ( 128f , - 8f ) , new Vector2 ( 560f , 56f ) ) ;
skillName [ i ] . horizontalOverflow = HorizontalWrapMode . Overflow ;
skillDesc [ i ] = UiKit . Label ( c , "" , 26 , UiKit . InkSoft , TextAnchor . UpperLeft , new Vector2 ( 0f , 1f ) , new Vector2 ( 0f , 1f ) , new Vector2 ( 128f , - 64f ) , new Vector2 ( 840f , 72f ) , false ) ;
var chip = UiKit . Box ( "Chip" , c , new Vector2 ( 1f , 1f ) , new Vector2 ( 1f , 1f ) , new Vector2 ( - 14f , - 12f ) , new Vector2 ( 240f , 46f ) , i = = 0 ? UiKit . Red : UiKit . Dark ) ;
skillChip [ i ] = UiKit . FillLabel ( chip . transform , "" , 24 , UiKit . White ) ;
}
// ③ 레벨 · 승급
var lvRow = UiKit . TopBand ( "Level" , body , 1168f , 110f , 24f , 24f ) ;
lvText = UiKit . Label ( lvRow , "" , 42 , UiKit . Ink , TextAnchor . MiddleLeft , new Vector2 ( 0f , 1f ) , new Vector2 ( 0f , 1f ) , new Vector2 ( 4f , 0f ) , new Vector2 ( 330f , 60f ) ) ;
lvText . horizontalOverflow = HorizontalWrapMode . Overflow ;
expText = UiKit . Label ( lvRow , "" , 28 , UiKit . Ink , TextAnchor . MiddleRight , new Vector2 ( 0f , 1f ) , new Vector2 ( 0f , 1f ) , new Vector2 ( 330f , 0f ) , new Vector2 ( 230f , 60f ) ) ;
expBar = UiKit . Bar ( "ExpBar" , lvRow , new Vector2 ( 0f , 0f ) , new Vector2 ( 0f , 0f ) , new Vector2 ( 4f , 10f ) , new Vector2 ( 556f , 30f ) , new Color ( 0.86f , 0.82f , 0.75f ) , new Color ( 0.47f , 0.38f , 0.32f ) ) ;
levelBtn = UiKit . Btn ( "LevelUp" , lvRow , "" , 30 , UiKit . Red , UiKit . White , new Vector2 ( 1f , 0.5f ) , new Vector2 ( 1f , 0.5f ) , new Vector2 ( - 252f , 0f ) , new Vector2 ( 204f , 106f ) , OnLevelUp ) ;
promoteBtn = UiKit . Btn ( "Promote" , lvRow , "" , 30 , UiKit . Disabled , UiKit . White , new Vector2 ( 1f , 0.5f ) , new Vector2 ( 1f , 0.5f ) , new Vector2 ( 0f , 0f ) , new Vector2 ( 236f , 106f ) , OnPromote ) ;
// ④ 조각 · 진화
var shRow = UiKit . TopBand ( "Shard" , body , 1290f , 96f , 24f , 24f ) ;
var sd = UiKit . Dot ( "ShardDot" , shRow , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 0f , - 14f ) , 54f , UiKit . Purple ) ;
UiKit . FillLabel ( sd . transform , "조" , 28 , UiKit . White ) ;
UiKit . Label ( shRow , "영웅 조각" , 32 , UiKit . Ink , TextAnchor . MiddleLeft , new Vector2 ( 0f , 1f ) , new Vector2 ( 0f , 1f ) , new Vector2 ( 64f , 0f ) , new Vector2 ( 300f , 46f ) ) ;
shardBar = UiKit . Bar ( "ShardBar" , shRow , new Vector2 ( 0f , 0f ) , new Vector2 ( 0f , 0f ) , new Vector2 ( 64f , 4f ) , new Vector2 ( 690f , 42f ) , new Color ( 0.86f , 0.82f , 0.75f ) , UiKit . Purple ) ;
shardText = UiKit . Label ( shardBar . parent , "" , 28 , UiKit . Ink , TextAnchor . MiddleCenter , new Vector2 ( 0.5f , 0.5f ) , new Vector2 ( 0.5f , 0.5f ) , Vector2 . zero , new Vector2 ( 680f , 42f ) ) ;
evolveBtn = UiKit . Btn ( "Evolve" , shRow , "" , 30 , UiKit . Red , UiKit . White , new Vector2 ( 1f , 0.5f ) , new Vector2 ( 1f , 0.5f ) , new Vector2 ( 0f , 0f ) , new Vector2 ( 236f , 96f ) , OnEvolve ) ;
// ⑤ 상세정보 · 도감 · 스킬 레벨업(B)
var btnRow = UiKit . TopBand ( "Buttons" , body , 1402f , 96f , 24f , 24f ) ;
float bw = ( 1032f - 40f ) / 3f ;
UiKit . Btn ( "Detail" , btnRow , "상세정보" , 36 , UiKit . Dark , UiKit . White , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 0f , 0.5f ) , Vector2 . zero , new Vector2 ( bw , 96f ) , ShowStats ) ;
UiKit . Btn ( "Codex" , btnRow , "도감" , 36 , UiKit . Dark , UiKit . White , new Vector2 ( 0.5f , 0.5f ) , new Vector2 ( 0.5f , 0.5f ) , Vector2 . zero , new Vector2 ( bw , 96f ) , ( ) = > ShowCodex ( Root ) ) ;
2026-11-05 14:31:27 +00:00
skillBtn = UiKit . Btn ( "SkillUp" , btnRow , "스킬 레벨업" , 32 , UiKit . Red , UiKit . White , new Vector2 ( 1f , 0.5f ) , new Vector2 ( 1f , 0.5f ) , Vector2 . zero , new Vector2 ( bw , 96f ) , OnSkillUp ) ;
2026-11-05 12:11:10 +00:00
}
void Step ( int d )
{
if ( order . Count = = 0 ) return ;
int i = Mathf . Max ( 0 , order . IndexOf ( id ) ) ;
id = order [ ( i + d + order . Count ) % order . Count ] ;
Refresh ( ) ;
}
public override void Refresh ( )
{
var h = S . Def ( id ) ;
if ( h = = null ) return ;
var db = S . Db ;
var lv = S . Save . Level ( id ) ;
var hs = S . Save . Hero ( id ) ;
var st = S . StatsOf ( id ) ;
power . text = "전투력 " + st . power . ToString ( "N0" ) ;
UiKit . Clear ( art ) ;
UiKit . Portrait ( "Hero" , art , h , new Vector2 ( 0.5f , 0.5f ) , new Vector2 ( 0.5f , 0.5f ) , Vector2 . zero , new Vector2 ( 560f , 500f ) , false , 1f , new Color ( 1f , 1f , 1f , 0f ) ) ;
for ( int i = 0 ; i < 6 ; i + + )
{
string need = h . isSoldier ? null : HeroRules . RequiredGear ( db , h , hs , i ) ;
var g = db . Gear . Get ( need ) ;
bool on = hs ! = null & & i < hs . gear . Count & & ! string . IsNullOrEmpty ( hs . gear [ i ] ) ;
int have = need ! = null ? S . Save . inv . GearCount ( need ) : 0 ;
slotImg [ i ] . gameObject . SetActive ( need ! = null ) ;
if ( need = = null ) continue ;
slotName [ i ] . text = g ! = null ? g . nameKo : need ;
if ( on )
{
slotImg [ i ] . color = new Color ( 0.9f , 0.95f , 0.86f ) ;
slotState [ i ] . text = "<color=#3a6a30>○ 장착</color> · " + UiKit . GradeKo ( g ! = null ? g . grade : "" ) ;
}
else if ( have > 0 )
{
slotImg [ i ] . color = new Color ( 1f , 0.93f , 0.86f ) ;
slotState [ i ] . text = "<color=#a03a22>탭하여 장착 × " + have + "</color>" ;
}
else
{
slotImg [ i ] . color = UiKit . Slot ;
slotState [ i ] . text = "없음 · " + UiKit . GradeKo ( g ! = null ? g . grade : "" ) ;
}
}
typeDot . color = UiKit . TypeColor ( h . type ) ;
typeMark . text = UnitTypeInfo . Mark ( h . type ) ;
nameText . text = h . nameKo ;
subText . text = UnitTypeInfo . Korean ( h . type ) + " · " + ( h . isSoldier ? "병사 유닛" : UiKit . GradeKo ( hs . grade ) + " 등급" ) ;
starText . text = h . isSoldier ? UiKit . Stars ( h . rarity , 3 ) : UiKit . Stars ( hs . star , db . Evolve . MaxStar ) ;
var skills = new [ ] { db . Skills . Get ( h . skillActive ) , db . Skills . Get ( h . skillPassive ) } ;
for ( int i = 0 ; i < 2 ; i + + )
{
2026-11-05 14:31:27 +00:00
int cap = SkillRules . Cap ( db , h , hs , i ) ; // 등급 + ★3 액티브 +1(PD #1093 B)
2026-11-05 12:11:10 +00:00
var sk = skills [ i ] ;
if ( sk = = null )
{
skillName [ i ] . text = h . isSoldier ? "병사 유닛 — 스킬 없음" : "—" ;
skillDesc [ i ] . text = "" ;
skillChip [ i ] . transform . parent . gameObject . SetActive ( false ) ;
continue ;
}
skillChip [ i ] . transform . parent . gameObject . SetActive ( true ) ;
skillName [ i ] . text = sk . nameKo + ( string . IsNullOrEmpty ( sk . hanja ) ? "" : "(" + sk . hanja + ")" ) + " · " + ( sk . kind = = "active" ? "액티브" : "패시브" ) ;
skillDesc [ i ] . text = SkillInfoDb . DisplayDesc ( sk ) ;
int slv = hs ! = null & & hs . skillLv ! = null & & hs . skillLv . Length > i ? hs . skillLv [ i ] : 1 ;
2026-11-05 14:31:27 +00:00
skillChip [ i ] . text = "Lv " + slv + "/" + cap + ( sk . kind = = "active" ? " · 쿨 " + sk . cooldown + "턴" : ( hs ! = null & & SkillRules . PassiveMult ( db , hs . star ) > 1f ? " · ★×" + SkillRules . PassiveMult ( db , hs . star ) . ToString ( "0.##" ) : "" ) ) ;
2026-11-05 12:11:10 +00:00
}
2026-11-05 14:31:27 +00:00
bool canSkill = ! h . isSoldier & & hs ! = null & & ( SkillRules . CanLevelUp ( db , S . Save , h , hs , 0 ) = = null | | SkillRules . CanLevelUp ( db , S . Save , h , hs , 1 ) = = null ) ;
UiKit . LabelOf ( skillBtn ) . text = "스킬 레벨업\n<size=22>" + ( h . isSoldier ? "병사 = 스킬 없음" : "스킬서 " + S . Save . inv . books . skill ) + "</size>" ;
skillBtn . GetComponent < Image > ( ) . color = canSkill ? UiKit . Red : UiKit . Disabled ;
2026-11-05 12:11:10 +00:00
int lvCap = HeroRules . LevelCap ( db , S . Save ) ;
var row = db . Levels . Get ( lv . lv ) ;
lvText . text = "Lv " + lv . lv + " / " + lvCap + ( lvCap < db . Levels . MaxLevel ? "" : "" ) ;
float pct = row ! = null & & row . expToNext > 0 ? lv . exp / ( float ) row . expToNext : 1f ;
expText . text = lv . lv > = db . Levels . MaxLevel ? "최고 레벨" : pct > = 1f ? "경험치 100% ↑" : "경험치 " + ( pct * 100f ) . ToString ( "0.00" ) + "%" ;
UiKit . SetBar ( expBar , pct ) ;
var lvWhy = HeroRules . CanLevelUp ( db , S . Save , lv ) ;
UiKit . LabelOf ( levelBtn ) . text = "레벨업\n<size=24>" + ( row ! = null & & lv . lv < db . Levels . MaxLevel ? "골드 " + row . goldCost . ToString ( "N0" ) : "최고" ) + "</size>" ;
levelBtn . GetComponent < Image > ( ) . color = lvWhy = = null ? UiKit . Red : UiKit . Disabled ;
var promoWhy = HeroRules . CanPromote ( db , h , hs ) ;
var gearList = h . isSoldier ? null : db . Heroes . GearFor ( h , hs . grade ) ;
UiKit . LabelOf ( promoteBtn ) . text = h . isSoldier ? "승급\n<size=24>병사 없음</size>"
: gearList = = null ? "승급\n<size=24>최고 등급</size>"
: "승급\n<size=24>장비 " + HeroRules . EquippedCount ( hs ) + "/" + gearList . Count + "칸</size>" ;
promoteBtn . GetComponent < Image > ( ) . color = promoWhy = = null ? UiKit . Red : UiKit . Disabled ;
int need2 = HeroRules . ShardsForNext ( db , hs ) ;
if ( h . isSoldier )
{
shardText . text = "병사 유닛 — 진화 없음 (중복 = 골드)" ;
UiKit . SetBar ( shardBar , 0f ) ;
UiKit . LabelOf ( evolveBtn ) . text = "진화\n<size=24>없음</size>" ;
evolveBtn . GetComponent < Image > ( ) . color = UiKit . Disabled ;
}
else
{
shardText . text = need2 > 0 ? hs . shards + " / " + need2 : hs . shards + " · 최고 ★" ;
UiKit . SetBar ( shardBar , need2 > 0 ? hs . shards / ( float ) need2 : 1f ) ;
UiKit . LabelOf ( evolveBtn ) . text = hs . star > = db . Evolve . MaxStar ? "진화\n<size=24>최고 ★</size>" : "진화\n<size=24>다음 ★" + ( hs . star + 1 ) + "</size>" ;
evolveBtn . GetComponent < Image > ( ) . color = HeroRules . CanEvolve ( db , h , hs ) = = null ? UiKit . Red : UiKit . Disabled ;
}
}
void OnSlot ( int i )
{
var h = S . Def ( id ) ;
var hs = S . Save . Hero ( id ) ;
if ( h = = null | | hs = = null ) return ;
var need = HeroRules . RequiredGear ( S . Db , h , hs , i ) ;
var g = S . Db . Gear . Get ( need ) ;
if ( need = = null ) return ;
if ( ! string . IsNullOrEmpty ( hs . gear [ i ] ) )
{
Root . Toast ( "장착됨: " + ( g ! = null ? g . nameKo : need ) + " — 6칸을 모두 채우면 승급" ) ;
return ;
}
var why = S . Equip ( id , i ) ;
if ( why = = null )
{
Root . Toast ( ( g ! = null ? g . nameKo : need ) + " 장착 · 1칸 = 1강(누적 " + hs . gearTotal + ")" ) ;
return ;
}
string from = g ! = null ? g . dropFromChapter + "장~ 스테이지 드랍" : "" ;
Root . Modal ( "필요 장비" , ( g ! = null ? g . nameKo + " (" + UiKit . GradeKo ( g . grade ) + " 등급)" : need ) + "\n보유 0\n\n획득: " + from + " · 거래 > 상점(흰/녹)" +
( g ! = null & & g . craftCount > 0 ? "\n합성 = 하위 " + g . craftCount + "개 + 도면 " + g . craftBlueprint + " (2차)" : "" ) ,
new OutGameRoot . ModalButton ( "상점 가기" , UiKit . Red , ( ) = > Root . Trade . OpenShop ( ) ) ,
new OutGameRoot . ModalButton ( "닫기" , UiKit . Dark , null ) ) ;
}
void OnLevelUp ( )
{
var why = S . LevelUp ( id ) ;
Root . Toast ( why ? ? S . Def ( id ) . nameKo + " Lv " + S . Save . Level ( id ) . lv + "!" ) ;
}
void OnPromote ( )
{
var h = S . Def ( id ) ;
var why = S . Promote ( id ) ;
Root . Toast ( why ? ? h . nameKo + " 승급 → " + UiKit . GradeKo ( S . Save . Hero ( id ) . grade ) + " 등급 · 새 장비 6칸" ) ;
}
2026-11-05 14:31:27 +00:00
/// <summary>스킬 레벨업(PD #1093 B): 액티브·패시브 중 고르기 → 스킬서 + 골드(skill_levels.csv) · 상한 = 등급 + ★3 액티브 +1.</summary>
public void OnSkillUp ( )
{
var h = S . Def ( id ) ;
var hs = S . Save . Hero ( id ) ;
if ( h = = null | | hs = = null | | h . isSoldier )
{
Root . Toast ( "병사 유닛은 스킬이 없습니다" ) ;
return ;
}
var db = S . Db ;
var buttons = new List < OutGameRoot . ModalButton > ( ) ;
var body = new System . Text . StringBuilder ( "보유 스킬서 " + S . Save . inv . books . skill + " · 골드 " + S . Save . gold . ToString ( "N0" ) + "\n" ) ;
for ( int i = 0 ; i < 2 ; i + + )
{
int k = i ;
var sk = SkillRules . Skill ( db , h , i ) ;
if ( sk = = null ) continue ;
int lv = SkillRules . Level ( hs , i ) ;
var cost = db . Skills . CostTo ( lv + 1 ) ;
var why = SkillRules . CanLevelUp ( db , S . Save , h , hs , i ) ;
body . Append ( "\n" ) . Append ( i = = 0 ? "액티브 " : "패시브 " ) . Append ( sk . nameKo ) . Append ( " Lv " ) . Append ( lv ) . Append ( " / " ) . Append ( SkillRules . Cap ( db , h , hs , i ) ) ;
if ( cost ! = null & & lv < sk . lvMax ) body . Append ( " → 스킬서 " ) . Append ( cost . books ) . Append ( " · 골드 " ) . Append ( cost . gold . ToString ( "N0" ) ) ;
if ( why ! = null ) body . Append ( "\n <color=#a03a22>" ) . Append ( why ) . Append ( "</color>" ) ;
buttons . Add ( new OutGameRoot . ModalButton ( ( i = = 0 ? "액티브" : "패시브" ) + " Lv+1" , why = = null ? UiKit . Red : UiKit . Disabled , ( ) = >
{
var r = SkillRules . LevelUp ( S . Db , S . Save , h , hs , k ) ;
if ( r = = null ) S . Commit ( ) ;
Refresh ( ) ;
if ( r = = null ) OnSkillUp ( ) ; // 이어서 올릴 수 있게 다시 연다
Root . Toast ( r ? ? sk . nameKo + " Lv " + SkillRules . Level ( hs , k ) + "!" ) ;
} ) ) ;
}
buttons . Add ( new OutGameRoot . ModalButton ( "닫기" , UiKit . Dark , null ) ) ;
Root . Modal ( "스킬 레벨업 — " + h . nameKo , body . ToString ( ) , buttons . ToArray ( ) ) ;
}
2026-11-05 12:11:10 +00:00
void OnEvolve ( )
{
var why = S . Evolve ( id ) ;
Root . Toast ( why ? ? S . Def ( id ) . nameKo + " 진화 → ★" + S . Save . Hero ( id ) . star ) ;
}
void ShowStats ( )
{
var h = S . Def ( id ) ;
var st = S . StatsOf ( id ) ;
var e = S . Db . Economy ;
var hs = S . Save . Hero ( id ) ;
string body =
"기본(" + UnitTypeInfo . Korean ( h . type ) + ") HP " + st . baseHp + " · 공 " + st . baseAtk + " · 방 " + st . baseDef + "\n" +
"영웅 배율 × " + st . hpMult + " · × " + st . atkMult + " · × " + st . defMult + "\n" +
"레벨 Lv " + S . Save . Level ( id ) . lv + " +" + st . lvPct . ToString ( "0.#" ) + "%\n" +
"승급 " + ( hs ! = null ? UiKit . GradeKo ( hs . grade ) : "—" ) + " +" + st . promoPct . ToString ( "0.#" ) + "% · 장비 누적 " + ( hs ! = null ? hs . gearTotal : 0 ) + "개 +" + st . gearPct . ToString ( "0.#" ) + "%\n" +
"진화 " + ( hs ! = null ? "★" + hs . star : "—" ) + " +" + st . starPct . ToString ( "0.#" ) + "%\n" +
2026-11-05 15:22:54 +00:00
"훈련(" + TrainingRules . RankName ( S . Db , TrainingRules . Rank ( S . Save , h . type ) ) + ") HP +" + st . trainHpPct . ToString ( "0.#" ) + "% · 공 +" + st . trainPct . ToString ( "0.#" ) + "% · 방 +" + st . trainDefPct . ToString ( "0.#" ) + "%\n" +
"보물(수집품·세트) HP +" + st . treasureHpPct . ToString ( "0.#" ) + "% · 공 +" + st . treasureAtkPct . ToString ( "0.#" ) + "% · 방 +" + st . treasureDefPct . ToString ( "0.#" ) + "%\n\n" +
2026-11-05 12:11:10 +00:00
"<size=40>HP " + st . Hp + " · 공 " + st . Atk + " · 방 " + st . Def + "</size>\n" +
"전투력 " + st . power . ToString ( "N0" ) + " = HP/" + e . PowerHpDiv + " + 공×" + e . PowerAtkW + " + 방×" + e . PowerDefW ;
Root . Modal ( "상세정보 — " + h . nameKo , body , new OutGameRoot . ModalButton ( "닫기" , UiKit . Dark , null ) ) ;
}
/// <summary>도감 = 전 영웅·병사(미보유 회색).</summary>
public static void ShowCodex ( OutGameRoot root )
{
var s = root . Session ;
int owned = 0 ;
foreach ( var h in s . Db . Heroes . heroes )
if ( s . Save . Owns ( h . id ) ) owned + + ;
var content = root . ModalCustom ( "도감 " + owned + " / " + s . Db . Heroes . heroes . Count , 1560f , new OutGameRoot . ModalButton ( "닫기" , UiKit . Dark , null ) ) ;
ScrollRect sr ;
var grid = UiKit . ScrollGrid ( "Codex" , content , 0f , 0f , 0f , 0f , new Vector2 ( 420f , 140f ) , new Vector2 ( 20f , 14f ) , 2 , out sr ) ;
foreach ( var h in s . Db . Heroes . heroes )
{
bool own = s . Save . Owns ( h . id ) ;
var hero = h ;
var cell = HeroCards . Card ( grid , s , h , false , own , ( ) = > root . Toast ( hero . nameKo + ( string . IsNullOrEmpty ( hero . hanja ) ? "" : "(" + hero . hanja + ")" ) + " · " +
UnitTypeInfo . Korean ( hero . type ) + " · ★" + hero . rarity + ( hero . isSoldier ? " · 병사 유닛" : "" ) + ( own ? " · 보유" : " · 미보유 — 거래 > 뽑기" ) ) ) ;
if ( ! own ) cell . color = new Color ( 0.78f , 0.76f , 0.73f ) ;
}
}
}
}