명장 등용(뽑기 메뉴 개편) — 배너 5종(픽업 여포 + 국가별 상시 4) · 등용 버튼 · 상세 정보 팝업(PD 2026-11-13)
- 이름: 「영웅·유닛 뽑기」 → 「명장 등용」 · 버튼 「1회/10회 등용」 · 결과 제목 「등용 결과」 - 표: gacha_banners.csv 신설(배너 5 = type·faction·pool·soldiers·featured·pity_group·기간·art) gacha_pool.csv pickup 열 삭제(픽업 영웅 SOT = 배너 featured) · gacha.csv pickup_exclusive 추가 - 규칙: 배너별 풀(상시 = 그 진영 + 병사) · 픽업 = ★★★ 의 pickup_share · 천장 = pity_group 공유 · 기간(start+days) 밖 등용 불가 · 세이브 gacha.pities(구 pity 는 상시 묶음으로 이관) - 화면: 배너 2쪽 좌우 넘김 · 상시 = 지도 위 진영 깃발 4(↖위 ↙촉 ↗군 ↘오) + 주요 영웅 5 + 세로 이름표 · 픽업 = 정보 판 + 여포 일러 + 이름 명패 + 「상세 정보」 · 자리 = Resources/UI/recruit_banner.csv - 팝업 HeroInfoPopup: 풀 성장 능력치(최고 레벨·등급·★·장비) + 액티브/패시브 스킬 - 그림: UI/Samguk/recruit/(픽업 1 · 진영 배경 4 · 지도 1) · 아틀라스 UI_Recruit - 테스트: RecruitTests 9/9 · OutGameTests·GachaCutsceneTests·UILayerTests 통과 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
0188f55202
commit
a6bd9c0f66
|
|
@ -6,6 +6,7 @@ p_r2,0.15
|
|||
p_r1,0.82
|
||||
pity,80
|
||||
pickup_share,0.5
|
||||
pickup_exclusive,1
|
||||
free_first_ten,1
|
||||
daily_free,1
|
||||
ten_count,10
|
||||
|
|
|
|||
|
|
|
@ -0,0 +1,6 @@
|
|||
banner_id,type,faction,name_ko,desc_ko,pool,soldiers,featured,pity_group,start,days,art
|
||||
pickup_lvbu,pickup,other,여포 등용,픽업 영웅의 등장 확률 상승,all,1,lvbu,pickup,2026-11-13,14,recruit_pickup_lvbu
|
||||
wei,standing,wei,위 명장 등용,위나라의 명장을 등용합니다,wei,1,caocao;xiahoudun;zhangliao;simayi;xuchu,standing,,0,recruit_bg_wei
|
||||
shu,standing,shu,촉 명장 등용,촉나라의 명장을 등용합니다,shu,1,liubei;guanyu;zhangfei;zhaoyun;zhugeliang,standing,,0,recruit_bg_shu
|
||||
wu,standing,wu,오 명장 등용,오나라의 명장을 등용합니다,wu,1,sunquan;zhouyu;sunce;luxun;sunshangxiang,standing,,0,recruit_bg_wu
|
||||
other,standing,other,군웅 명장 등용,천하를 다툰 군웅을 등용합니다,other,1,dongzhuo;yuanshao;diaochan;menghuo;zhurong,standing,,0,recruit_bg_other
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3449dfa5d4016d04aa104617f4119126
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,88 +1,88 @@
|
|||
hero_id,in_pool,pickup
|
||||
liubei,1,0
|
||||
guanyu,1,0
|
||||
zhangfei,1,0
|
||||
zhaoyun,1,1
|
||||
huangzhong,1,0
|
||||
zhugeliang,1,0
|
||||
zhoucang,1,0
|
||||
guanping,1,0
|
||||
xushu,1,0
|
||||
taishici,1,0
|
||||
machao,1,0
|
||||
weiyan,1,0
|
||||
pangtong,1,0
|
||||
jiangwei,1,0
|
||||
liufeng,1,0
|
||||
jianyong,1,0
|
||||
sunqian,1,0
|
||||
mizhu,1,0
|
||||
caocao,1,0
|
||||
xiahoudun,1,0
|
||||
xuchu,1,0
|
||||
zhangliao,1,1
|
||||
xuhuang,1,0
|
||||
zhanghe,1,0
|
||||
caoren,1,0
|
||||
yujin,1,0
|
||||
simayi,1,0
|
||||
xiahouyuan,1,0
|
||||
guojia,1,0
|
||||
jiaxu,1,0
|
||||
dianwei,1,0
|
||||
xunyu,1,0
|
||||
xiahoumao,1,0
|
||||
wanglang,1,0
|
||||
xiahouen,1,0
|
||||
sunjian,1,0
|
||||
zhouyu,1,0
|
||||
huanggai,1,0
|
||||
ganning,1,0
|
||||
chengpu,1,0
|
||||
sunce,1,0
|
||||
sunquan,1,0
|
||||
lusu,1,0
|
||||
lvmeng,1,0
|
||||
luxun,1,0
|
||||
sunshangxiang,1,0
|
||||
xiaoqiao,1,0
|
||||
lingtong,1,0
|
||||
zhoutai,1,0
|
||||
zhugejin,1,0
|
||||
dongxi,1,0
|
||||
lvbu,1,0
|
||||
zhangjiao,1,0
|
||||
huaxiong,1,0
|
||||
yanliang,1,0
|
||||
wenchou,1,0
|
||||
gongsunzan,1,0
|
||||
dongzhuo,1,0
|
||||
lijue,1,0
|
||||
yuanshao,1,0
|
||||
yuanshu,1,0
|
||||
diaochan,1,0
|
||||
zhurong,1,0
|
||||
menghuo,1,0
|
||||
gaoshun,1,0
|
||||
zhanglu,1,0
|
||||
yanbaihu,1,0
|
||||
hanxuan,1,0
|
||||
jinxuan,1,0
|
||||
zhaofan,1,0
|
||||
zhangren,1,0
|
||||
chunyuqiong,1,0
|
||||
soldier_infantry_1,1,0
|
||||
soldier_infantry_2,1,0
|
||||
soldier_infantry_3,1,0
|
||||
soldier_spear_1,1,0
|
||||
soldier_spear_2,1,0
|
||||
soldier_spear_3,1,0
|
||||
soldier_cavalry_1,1,0
|
||||
soldier_cavalry_2,1,0
|
||||
soldier_cavalry_3,1,0
|
||||
soldier_archer_1,1,0
|
||||
soldier_archer_2,1,0
|
||||
soldier_archer_3,1,0
|
||||
soldier_strategist_1,1,0
|
||||
soldier_strategist_2,1,0
|
||||
soldier_strategist_3,1,0
|
||||
hero_id,in_pool
|
||||
liubei,1
|
||||
guanyu,1
|
||||
zhangfei,1
|
||||
zhaoyun,1
|
||||
huangzhong,1
|
||||
zhugeliang,1
|
||||
zhoucang,1
|
||||
guanping,1
|
||||
xushu,1
|
||||
taishici,1
|
||||
machao,1
|
||||
weiyan,1
|
||||
pangtong,1
|
||||
jiangwei,1
|
||||
liufeng,1
|
||||
jianyong,1
|
||||
sunqian,1
|
||||
mizhu,1
|
||||
caocao,1
|
||||
xiahoudun,1
|
||||
xuchu,1
|
||||
zhangliao,1
|
||||
xuhuang,1
|
||||
zhanghe,1
|
||||
caoren,1
|
||||
yujin,1
|
||||
simayi,1
|
||||
xiahouyuan,1
|
||||
guojia,1
|
||||
jiaxu,1
|
||||
dianwei,1
|
||||
xunyu,1
|
||||
xiahoumao,1
|
||||
wanglang,1
|
||||
xiahouen,1
|
||||
sunjian,1
|
||||
zhouyu,1
|
||||
huanggai,1
|
||||
ganning,1
|
||||
chengpu,1
|
||||
sunce,1
|
||||
sunquan,1
|
||||
lusu,1
|
||||
lvmeng,1
|
||||
luxun,1
|
||||
sunshangxiang,1
|
||||
xiaoqiao,1
|
||||
lingtong,1
|
||||
zhoutai,1
|
||||
zhugejin,1
|
||||
dongxi,1
|
||||
lvbu,1
|
||||
zhangjiao,1
|
||||
huaxiong,1
|
||||
yanliang,1
|
||||
wenchou,1
|
||||
gongsunzan,1
|
||||
dongzhuo,1
|
||||
lijue,1
|
||||
yuanshao,1
|
||||
yuanshu,1
|
||||
diaochan,1
|
||||
zhurong,1
|
||||
menghuo,1
|
||||
gaoshun,1
|
||||
zhanglu,1
|
||||
yanbaihu,1
|
||||
hanxuan,1
|
||||
jinxuan,1
|
||||
zhaofan,1
|
||||
zhangren,1
|
||||
chunyuqiong,1
|
||||
soldier_infantry_1,1
|
||||
soldier_infantry_2,1
|
||||
soldier_infantry_3,1
|
||||
soldier_spear_1,1
|
||||
soldier_spear_2,1
|
||||
soldier_spear_3,1
|
||||
soldier_cavalry_1,1
|
||||
soldier_cavalry_2,1
|
||||
soldier_cavalry_3,1
|
||||
soldier_archer_1,1
|
||||
soldier_archer_2,1
|
||||
soldier_archer_3,1
|
||||
soldier_strategist_1,1
|
||||
soldier_strategist_2,1
|
||||
soldier_strategist_3,1
|
||||
|
|
|
|||
|
|
|
@ -4,6 +4,7 @@ theme,UI_SkillIcon,/skill_icon/,2048,장수 스킬 아이콘 144 + 사각 틀
|
|||
theme,UI_Lobby,/Lobby/anim/,1024,로비 배경 연출(깃발·불빛·새·꽃잎 9장 · PD 2026-11-11) — Frame 층 한 장 = 드로우콜 1
|
||||
theme,UI_Domestic,/Domestic/anim/|/Domestic/icon/|/Domestic/dev/,1024,내정 연병장 풀·꽃 덤불 4 · 훈련 카드 아이콘 3 · 개발 노드 아이콘 27(PD 2026-11-12) — 128² 이하 · 내정 화면 한 장
|
||||
theme,UI_Gacha,/Samguk/gacha/,2048,뽑기 연출(삼고초려 · PD 2026-11-13) 문짝 2 · 햇살 2 · 빛줄기 2 · 반짝이 · 먼지 구름 · 고리 · 느낌표 — 장면 Frame 층 한 장 = 드로우콜 1
|
||||
theme,UI_Recruit,/Samguk/recruit/|/Samguk/flag_rank/,1024,명장 등용 배너(PD 2026-11-13) 지도 두루마리 1 + 진영 깃발 4 — 배너 층 한 장 · 배너 그림 5(1016×624)는 single_area 초과라 단독
|
||||
theme,UI_Common,*,4096,모든 화면 공통 크롬(틀·버튼·바·탭·재화·병과·작은 아이콘) — 위 줄에 안 걸린 테마 스프라이트 전부
|
||||
folder,WorldMap_UI,Assets/Resources/ChapterMap/UI,2048,월드맵 부품(깃발·띠·판·별·커서) — 폴더 전체
|
||||
option,single_area,,262144,이 넓이(px) 넘는 큰 그림(로비 바탕·키아트·로고·패턴)은 아틀라스에 넣지 않음 — 한 장 = 드로우콜 1 · 아틀라스 공간 낭비 방지
|
||||
|
|
|
|||
|
|
|
@ -28,6 +28,8 @@ MonoBehaviour:
|
|||
value: 80
|
||||
- key: pickup_share
|
||||
value: 0.5
|
||||
- key: pickup_exclusive
|
||||
value: 1
|
||||
- key: free_first_ten
|
||||
value: 1
|
||||
- key: daily_free
|
||||
|
|
@ -39,262 +41,262 @@ MonoBehaviour:
|
|||
pool:
|
||||
- heroId: liubei
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: guanyu
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: zhangfei
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: zhaoyun
|
||||
inPool: 1
|
||||
pickup: 1
|
||||
- heroId: huangzhong
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: zhugeliang
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: zhoucang
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: guanping
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: xushu
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: taishici
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: machao
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: weiyan
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: pangtong
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: jiangwei
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: liufeng
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: jianyong
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: sunqian
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: mizhu
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: caocao
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: xiahoudun
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: xuchu
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: zhangliao
|
||||
inPool: 1
|
||||
pickup: 1
|
||||
- heroId: xuhuang
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: zhanghe
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: caoren
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: yujin
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: simayi
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: xiahouyuan
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: guojia
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: jiaxu
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: dianwei
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: xunyu
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: xiahoumao
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: wanglang
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: xiahouen
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: sunjian
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: zhouyu
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: huanggai
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: ganning
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: chengpu
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: sunce
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: sunquan
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: lusu
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: lvmeng
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: luxun
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: sunshangxiang
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: xiaoqiao
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: lingtong
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: zhoutai
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: zhugejin
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: dongxi
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: lvbu
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: zhangjiao
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: huaxiong
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: yanliang
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: wenchou
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: gongsunzan
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: dongzhuo
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: lijue
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: yuanshao
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: yuanshu
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: diaochan
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: zhurong
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: menghuo
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: gaoshun
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: zhanglu
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: yanbaihu
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: hanxuan
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: jinxuan
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: zhaofan
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: zhangren
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: chunyuqiong
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: soldier_infantry_1
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: soldier_infantry_2
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: soldier_infantry_3
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: soldier_spear_1
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: soldier_spear_2
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: soldier_spear_3
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: soldier_cavalry_1
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: soldier_cavalry_2
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: soldier_cavalry_3
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: soldier_archer_1
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: soldier_archer_2
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: soldier_archer_3
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: soldier_strategist_1
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: soldier_strategist_2
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
- heroId: soldier_strategist_3
|
||||
inPool: 1
|
||||
pickup: 0
|
||||
banners:
|
||||
- id: pickup_lvbu
|
||||
type: pickup
|
||||
faction: other
|
||||
nameKo: "\uC5EC\uD3EC \uB4F1\uC6A9"
|
||||
descKo: "\uD53D\uC5C5 \uC601\uC6C5\uC758 \uB4F1\uC7A5 \uD655\uB960 \uC0C1\uC2B9"
|
||||
pool:
|
||||
- all
|
||||
soldiers: 1
|
||||
featured:
|
||||
- lvbu
|
||||
pityGroup: pickup
|
||||
start: 2026-11-13
|
||||
days: 14
|
||||
art: recruit_pickup_lvbu
|
||||
- id: wei
|
||||
type: standing
|
||||
faction: wei
|
||||
nameKo: "\uC704 \uBA85\uC7A5 \uB4F1\uC6A9"
|
||||
descKo: "\uC704\uB098\uB77C\uC758 \uBA85\uC7A5\uC744 \uB4F1\uC6A9\uD569\uB2C8\uB2E4"
|
||||
pool:
|
||||
- wei
|
||||
soldiers: 1
|
||||
featured:
|
||||
- caocao
|
||||
- xiahoudun
|
||||
- zhangliao
|
||||
- simayi
|
||||
- xuchu
|
||||
pityGroup: standing
|
||||
start:
|
||||
days: 0
|
||||
art: recruit_bg_wei
|
||||
- id: shu
|
||||
type: standing
|
||||
faction: shu
|
||||
nameKo: "\uCD09 \uBA85\uC7A5 \uB4F1\uC6A9"
|
||||
descKo: "\uCD09\uB098\uB77C\uC758 \uBA85\uC7A5\uC744 \uB4F1\uC6A9\uD569\uB2C8\uB2E4"
|
||||
pool:
|
||||
- shu
|
||||
soldiers: 1
|
||||
featured:
|
||||
- liubei
|
||||
- guanyu
|
||||
- zhangfei
|
||||
- zhaoyun
|
||||
- zhugeliang
|
||||
pityGroup: standing
|
||||
start:
|
||||
days: 0
|
||||
art: recruit_bg_shu
|
||||
- id: wu
|
||||
type: standing
|
||||
faction: wu
|
||||
nameKo: "\uC624 \uBA85\uC7A5 \uB4F1\uC6A9"
|
||||
descKo: "\uC624\uB098\uB77C\uC758 \uBA85\uC7A5\uC744 \uB4F1\uC6A9\uD569\uB2C8\uB2E4"
|
||||
pool:
|
||||
- wu
|
||||
soldiers: 1
|
||||
featured:
|
||||
- sunquan
|
||||
- zhouyu
|
||||
- sunce
|
||||
- luxun
|
||||
- sunshangxiang
|
||||
pityGroup: standing
|
||||
start:
|
||||
days: 0
|
||||
art: recruit_bg_wu
|
||||
- id: other
|
||||
type: standing
|
||||
faction: other
|
||||
nameKo: "\uAD70\uC6C5 \uBA85\uC7A5 \uB4F1\uC6A9"
|
||||
descKo: "\uCC9C\uD558\uB97C \uB2E4\uD230 \uAD70\uC6C5\uC744 \uB4F1\uC6A9\uD569\uB2C8\uB2E4"
|
||||
pool:
|
||||
- other
|
||||
soldiers: 1
|
||||
featured:
|
||||
- dongzhuo
|
||||
- yuanshao
|
||||
- diaochan
|
||||
- menghuo
|
||||
- zhurong
|
||||
pityGroup: standing
|
||||
start:
|
||||
days: 0
|
||||
art: recruit_bg_other
|
||||
|
|
|
|||
|
|
@ -2232,6 +2232,30 @@ MonoBehaviour:
|
|||
pack: {fileID: 0}
|
||||
samguk: {fileID: 21300000, guid: c7d5c5fdb0f26c34db40248bc372ddfb, type: 3}
|
||||
tint: {r: 1, g: 1, b: 1, a: 1}
|
||||
- id: recruit_bg_other
|
||||
pack: {fileID: 0}
|
||||
samguk: {fileID: 21300000, guid: b2b554a86e7fbd44092b455ec9603057, type: 3}
|
||||
tint: {r: 1, g: 1, b: 1, a: 1}
|
||||
- id: recruit_bg_shu
|
||||
pack: {fileID: 0}
|
||||
samguk: {fileID: 21300000, guid: 7be45317ea022cb4eb13eb935a6f4975, type: 3}
|
||||
tint: {r: 1, g: 1, b: 1, a: 1}
|
||||
- id: recruit_bg_wei
|
||||
pack: {fileID: 0}
|
||||
samguk: {fileID: 21300000, guid: f0f9add628d14e74eb70edfdb0089368, type: 3}
|
||||
tint: {r: 1, g: 1, b: 1, a: 1}
|
||||
- id: recruit_bg_wu
|
||||
pack: {fileID: 0}
|
||||
samguk: {fileID: 21300000, guid: 0bfef021aaab1244d87e8b5666d3d9ae, type: 3}
|
||||
tint: {r: 1, g: 1, b: 1, a: 1}
|
||||
- id: recruit_map
|
||||
pack: {fileID: 0}
|
||||
samguk: {fileID: 21300000, guid: 277f35bc5cb3e6d48b867fc870307944, type: 3}
|
||||
tint: {r: 1, g: 1, b: 1, a: 1}
|
||||
- id: recruit_pickup_lvbu
|
||||
pack: {fileID: 0}
|
||||
samguk: {fileID: 21300000, guid: 5279d758233ccdd488a3e2df62689fc9, type: 3}
|
||||
tint: {r: 1, g: 1, b: 1, a: 1}
|
||||
- id: skill_caocao_a
|
||||
pack: {fileID: 0}
|
||||
samguk: {fileID: 21300000, guid: 6cdc5ccaa86cab244912eeeb49450c82, type: 3}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
# 명장 등용 배너 배치 표(PD 2026-11-13 「뽑기 배너 영역은 예시와 같이 · 위/촉/오/군 국가별 상시 배너에 주요 영웅 5명 · 상시 배너 왼쪽 = 중국 대륙 형상 위에 진영 깃발을 마름모꼴로 = 버튼 · ↖위 ↙촉 ↗군 ↘오」)
|
||||
# 형식 = 로비 장면 표(LobbyScene.Load) · 좌표 = 배너 그림 px(왼쪽 위 0,0 · 아래로 +) · 배너 그림을 바꾸면 이 표만 고친다(C45) · 누가 나오는지·이름·기간 = Assets/Data/OutGame/gacha_banners.csv
|
||||
# view f_w,f_h = 좌표 기준 크기(배너 그림 크기)
|
||||
# map 지도 두루마리: s_sprite 그림 · f_x,f_y 왼쪽 위 · f_w,f_h 크기
|
||||
# flag_<진영> 진영 깃발 버튼(진영 = gacha_banners.csv faction): s_sprite 깃발 그림 · f_x,f_y = 지도 두루마리 왼쪽 위에서 깃발 가운데까지 · f_w 크기 · f_min 고르지 않은 깃발 배율 · f_max 고른 깃발 배율 · f_shade 고르지 않은 깃발 밝기
|
||||
# flag_glow 고른 깃발 밑 빛: s_sprite · f_w 지름 · s_color 색 · f_min~f_max 알파 맥동 · f_period 주기(초)
|
||||
# hero 주요 영웅 자리(i_n = 인원 · i_slot = featured 순서 0 가운데 앞 · 1 왼쪽 앞 · 2 오른쪽 앞 · 3 왼쪽 뒤 · 4 오른쪽 뒤): f_x,f_y 발 · f_h 표준 키(UnitStand · 로비 영웅과 같은 기준) · f_max 말 탄 영웅 머리 끝 상한(키의 배수)
|
||||
# tag 세로 이름표(i_slot = 영웅 자리): s_sprite 목패(등급 색 목패 재사용 · 세워서 씀) · f_x = 가운데 · f_y = 윗변(i_count 1 이면 아랫변 = 이름 길이와 상관없이 머리 바로 위에 붙임) · f_w 폭 · f_h 글자 1자 높이 · f_scale 글자 크기 · f_min 위아래 여백 · s_color 판 색
|
||||
# hit 영웅 누름 칸(상세 정보 팝업): f_w,f_h = 발 위로 잡는 칸 크기
|
||||
# shadow 영웅 발밑 그림자: s_sprite · f_w,f_h = 표준 키의 배수(폭 · 높이) · s_color
|
||||
s_kind,i_n,i_slot,i_count,s_sprite,f_x,f_y,f_w,f_h,f_scale,f_min,f_max,f_shade,f_period,s_color,s_note
|
||||
view,,,,,,,1016,624,,,,,,,배너 그림 1016×624(배너 틀 1032×640 안쪽 8)
|
||||
map,,,,recruit_map,58,106,380,384,,,,,,,중국 대륙 지도 두루마리(Krea 2 seed 374 · 흰 바탕 → 투명)
|
||||
flag_wei,,,,flag_wei,150,150,92,,,0.86,1.14,0.78,,,↖ 위(북쪽 가운데)
|
||||
flag_other,,,,flag_other,262,124,92,,,0.86,1.14,0.78,,,↗ 군(북동쪽)
|
||||
flag_shu,,,,flag_shu,126,252,92,,,0.86,1.14,0.78,,,↙ 촉(남서쪽)
|
||||
flag_wu,,,,flag_wu,246,264,92,,,0.86,1.14,0.78,,,↘ 오(남동쪽)
|
||||
flag_glow,,,,glow_circle,,,178,,,0.6,1.0,,0.9,#FFE27A,고른 깃발 밑 금빛(맥동)
|
||||
hero,5,3,,,622,414,,186,,,1.25,,,,왼쪽 뒤
|
||||
hero,5,4,,,846,414,,186,,,1.25,,,,오른쪽 뒤
|
||||
hero,5,1,,,566,602,,186,,,1.25,,,,왼쪽 앞
|
||||
hero,5,2,,,896,602,,186,,,1.25,,,,오른쪽 앞
|
||||
hero,5,0,,,731,606,,186,,,1.25,,,,가운데 앞(주인공)
|
||||
tag,5,3,,tre_tag,528,196,46,30,26,14,,,,#C7372A,왼쪽 뒤 영웅의 왼쪽 위
|
||||
tag,5,4,,tre_tag,938,150,46,30,26,14,,,,#C7372A,오른쪽 뒤 영웅의 오른쪽 위(화살표 위쪽)
|
||||
tag,5,0,1,tre_tag,733,418,46,30,26,14,,,,#D99A1E,가운데 영웅 머리 바로 위(아랫변 기준 · 뒤 두 사람 사이 빈틈 · 주인공 = 금빛 판으로 구분)
|
||||
tag,5,1,,tre_tag,468,398,46,30,26,14,,,,#C7372A,왼쪽 앞 영웅의 왼쪽
|
||||
tag,5,2,,tre_tag,986,398,46,30,26,14,,,,#C7372A,오른쪽 앞 영웅의 오른쪽
|
||||
hit,,,,,,,150,190,,,,,,,영웅 누름 칸
|
||||
shadow,,,,oval_shadow,,,0.66,0.15,,,,,,#00000070,발밑 그림자(f_w,f_h = 표준 키의 배수)
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4ad4f3108beb9724cbf2125fb958d455
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -4,24 +4,65 @@ using UnityEngine;
|
|||
|
||||
namespace TKS.OutGame
|
||||
{
|
||||
/// <summary>영웅·유닛 뽑기(gacha.csv key,value + gacha_pool.csv · 기획서 §4). 병사 유닛 = ★ 풀(§4).</summary>
|
||||
/// <summary>
|
||||
/// 명장 등용(뽑기 · gacha.csv key,value + gacha_pool.csv + gacha_banners.csv · 기획서 §4 · PD 2026-11-13 배너 5 = 픽업 1 + 국가별 상시 4).
|
||||
/// 병사 유닛 = ★ 풀(§4) · 픽업 영웅 = 배너 표 featured(픽업 배너에서 ★★★ 의 pickup_share).
|
||||
/// </summary>
|
||||
public class GachaConfig : ScriptableObject
|
||||
{
|
||||
public const string TypePickup = "pickup";
|
||||
public const string TypeStanding = "standing";
|
||||
public const string PoolAll = "all";
|
||||
|
||||
[Serializable]
|
||||
public class PoolEntry
|
||||
{
|
||||
public string heroId;
|
||||
public bool inPool = true;
|
||||
public bool pickup;
|
||||
}
|
||||
|
||||
/// <summary>등용 배너 1줄(gacha_banners.csv · 줄 순서 = 화면 순서).</summary>
|
||||
[Serializable]
|
||||
public class Banner
|
||||
{
|
||||
public string id;
|
||||
[Tooltip("pickup = 기간 한정 · 주요 영웅 확률 상승 / standing = 상시(국가별)")]
|
||||
public string type;
|
||||
[Tooltip("상시 배너의 진영(wei · shu · wu · other · 깃발 버튼)")]
|
||||
public string faction;
|
||||
public string nameKo;
|
||||
public string descKo;
|
||||
[Tooltip("나오는 진영(heroes.csv faction · all = 전 진영)")]
|
||||
public List<string> pool = new List<string>();
|
||||
[Tooltip("병사 유닛(★ 풀)이 나오는가")]
|
||||
public bool soldiers;
|
||||
[Tooltip("배너에 세우는 주요 영웅(픽업 = 확률 상승 대상)")]
|
||||
public List<string> featured = new List<string>();
|
||||
[Tooltip("천장을 같이 쓰는 묶음 이름")]
|
||||
public string pityGroup;
|
||||
[Tooltip("시작일 yyyy-MM-dd(UTC) · 빈 칸 = 기간 없음")]
|
||||
public string start;
|
||||
[Tooltip("기간(일) · 0 = 기간 없음")]
|
||||
public int days;
|
||||
[Tooltip("배너 그림(테마 아이콘 id)")]
|
||||
public string art;
|
||||
|
||||
public bool IsPickup
|
||||
{
|
||||
get { return type == TypePickup; }
|
||||
}
|
||||
}
|
||||
|
||||
public KeyValueList values = new KeyValueList();
|
||||
public List<PoolEntry> pool = new List<PoolEntry>();
|
||||
public List<Banner> banners = new List<Banner>();
|
||||
|
||||
public int CostSingle { get { return values.I("cost_single"); } }
|
||||
public int CostTen { get { return values.I("cost_ten"); } }
|
||||
public int Pity { get { return values.I("pity"); } }
|
||||
public float PickupShare { get { return values.F("pickup_share"); } }
|
||||
/// <summary>픽업 영웅은 픽업 배너에서만 나온다(상시 배너 풀에서 뺌).</summary>
|
||||
public bool PickupExclusive { get { return values.I("pickup_exclusive") > 0; } }
|
||||
public bool FreeFirstTen { get { return values.I("free_first_ten") > 0; } }
|
||||
public int TenCount { get { return values.I("ten_count"); } }
|
||||
public int TenGuaranteeRarity { get { return values.I("ten_guarantee_rarity"); } }
|
||||
|
|
@ -32,9 +73,16 @@ namespace TKS.OutGame
|
|||
return values.F("p_r" + rarity);
|
||||
}
|
||||
|
||||
public Banner Get(string id)
|
||||
{
|
||||
foreach (var b in banners)
|
||||
if (b.id == id) return b;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static readonly string[] RequiredKeys =
|
||||
{
|
||||
"cost_single", "cost_ten", "p_r3", "p_r2", "p_r1", "pity", "pickup_share", "free_first_ten", "ten_count", "ten_guarantee_rarity",
|
||||
"cost_single", "cost_ten", "p_r3", "p_r2", "p_r1", "pity", "pickup_share", "pickup_exclusive", "free_first_ten", "ten_count", "ten_guarantee_rarity",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TKS.OutGame
|
||||
|
|
@ -17,8 +19,10 @@ namespace TKS.OutGame
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 영웅·유닛 뽑기(기획서 §4 · gacha.csv · gacha_pool.csv): 등급 확률 p_r3/p_r2/p_r1 · 천장 = pity 번째 ★★★ 확정(획득 시 0) ·
|
||||
/// ★★★ 중 pickup_share 는 픽업 영웅 · 10연 = ten_guarantee_rarity 이상 1 보장 · 병사 유닛 = ★ 풀 · 중복 = 조각(shard_dup_r*) / 병사 = 골드(soldier_dup_gold) · 첫 10연 무료.
|
||||
/// 명장 등용(뽑기 · 기획서 §4 · gacha.csv · gacha_pool.csv · gacha_banners.csv): 등급 확률 p_r3/p_r2/p_r1 · 천장 = pity 번째 ★★★ 확정(획득 시 0 · 배너의 pity_group 끼리 같이 씀) ·
|
||||
/// 10연 = ten_guarantee_rarity 이상 1 보장 · 병사 유닛 = ★ 풀 · 중복 = 조각(shard_dup_r*) / 병사 = 골드(soldier_dup_gold) · 첫 10연 무료(배너 공통 1번).
|
||||
/// 배너(PD 2026-11-13): 상시(standing) = 그 배너 pool 진영의 영웅만(+ soldiers) · 픽업(pickup) = ★★★ 중 pickup_share 는 featured 영웅 ·
|
||||
/// pickup_exclusive = 픽업 영웅은 상시 배너에서 안 나옴 · 기간(start + days · UTC) 밖의 배너는 등용 불가.
|
||||
/// </summary>
|
||||
public static class GachaRules
|
||||
{
|
||||
|
|
@ -35,33 +39,114 @@ namespace TKS.OutGame
|
|||
return g.CostSingle * count;
|
||||
}
|
||||
|
||||
public static string CanDraw(OutGameData db, SaveData d, int count)
|
||||
// ---------- 배너 ----------
|
||||
|
||||
/// <summary>배너 시작 시각(UTC · start 가 비면 null = 기간 없음).</summary>
|
||||
public static DateTime? StartOf(GachaConfig.Banner b)
|
||||
{
|
||||
int cost = Cost(db, d, count);
|
||||
if (d.gem < cost) return "보옥 부족 " + d.gem + " / " + cost;
|
||||
if (Candidates(db, 1).Count == 0) return "뽑기 풀 비었음";
|
||||
return null;
|
||||
DateTime day;
|
||||
if (b == null || string.IsNullOrEmpty(b.start)) return null;
|
||||
if (!DateTime.TryParseExact(b.start, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out day)) return null;
|
||||
return day;
|
||||
}
|
||||
|
||||
/// <summary>풀에서 등급 tier 에 나오는 유닛(영웅 = 희귀도 = tier · 병사 = ★ 풀).</summary>
|
||||
public static List<GachaConfig.PoolEntry> Candidates(OutGameData db, int tier)
|
||||
/// <summary>배너 끝 시각(UTC · 기간 없음 = null).</summary>
|
||||
public static DateTime? EndOf(GachaConfig.Banner b)
|
||||
{
|
||||
var s = StartOf(b);
|
||||
if (s == null || b.days <= 0) return null;
|
||||
return s.Value.AddDays(b.days);
|
||||
}
|
||||
|
||||
/// <summary>지금 등용할 수 있는 배너인가(기간 없음 = 항상 · 기간 있음 = 시작 ≤ 지금 < 끝).</summary>
|
||||
public static bool IsOpen(GachaConfig.Banner b, DateTime nowUtc)
|
||||
{
|
||||
if (b == null) return false;
|
||||
var s = StartOf(b);
|
||||
if (s != null && nowUtc < s.Value) return false;
|
||||
var e = EndOf(b);
|
||||
return e == null || nowUtc < e.Value;
|
||||
}
|
||||
|
||||
/// <summary>남은 기간(기간 없음·끝남 = 0).</summary>
|
||||
public static TimeSpan Remaining(GachaConfig.Banner b, DateTime nowUtc)
|
||||
{
|
||||
var e = EndOf(b);
|
||||
if (e == null || nowUtc >= e.Value) return TimeSpan.Zero;
|
||||
return e.Value - nowUtc;
|
||||
}
|
||||
|
||||
/// <summary>화면을 열 때 고르는 배너 = 표 순서에서 처음 열려 있는 것(없으면 첫 줄).</summary>
|
||||
public static GachaConfig.Banner Default(OutGameData db, DateTime nowUtc)
|
||||
{
|
||||
foreach (var b in db.Gacha.banners)
|
||||
if (IsOpen(b, nowUtc)) return b;
|
||||
return db.Gacha.banners.Count > 0 ? db.Gacha.banners[0] : null;
|
||||
}
|
||||
|
||||
/// <summary>어느 픽업 배너의 주요 영웅인가(pickup_exclusive 판정).</summary>
|
||||
public static bool IsPickupHero(GachaConfig g, string heroId)
|
||||
{
|
||||
foreach (var b in g.banners)
|
||||
if (b.IsPickup && b.featured.Contains(heroId)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>그 유닛이 이 배너에서 나오는가(풀 등록 여부 in_pool 은 Candidates 가 본다).</summary>
|
||||
public static bool InBanner(GachaConfig g, HeroDb heroes, GachaConfig.Banner b, HeroDb.Hero h)
|
||||
{
|
||||
if (b == null || h == null) return false;
|
||||
if (h.isSoldier) return b.soldiers;
|
||||
if (!b.pool.Contains(GachaConfig.PoolAll) && !b.pool.Contains(h.faction)) return false;
|
||||
if (!b.IsPickup && g.PickupExclusive && IsPickupHero(g, h.id)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>배너에서 등급 tier 에 나오는 유닛(영웅 = 희귀도 = tier · 병사 = ★ 풀).</summary>
|
||||
public static List<GachaConfig.PoolEntry> Candidates(GachaConfig g, HeroDb heroes, GachaConfig.Banner b, int tier)
|
||||
{
|
||||
var res = new List<GachaConfig.PoolEntry>();
|
||||
foreach (var p in db.Gacha.pool)
|
||||
foreach (var p in g.pool)
|
||||
{
|
||||
if (!p.inPool) continue;
|
||||
var h = db.Heroes.Get(p.heroId);
|
||||
if (h == null) continue;
|
||||
var h = heroes.Get(p.heroId);
|
||||
if (h == null || !InBanner(g, heroes, b, h)) continue;
|
||||
int t = h.isSoldier ? 1 : h.rarity;
|
||||
if (t == tier) res.Add(p);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public static List<GachaPull> Draw(OutGameData db, SaveData d, int count, System.Random rng)
|
||||
public static List<GachaConfig.PoolEntry> Candidates(OutGameData db, GachaConfig.Banner b, int tier)
|
||||
{
|
||||
return Candidates(db.Gacha, db.Heroes, b, tier);
|
||||
}
|
||||
|
||||
// ---------- 천장 ----------
|
||||
|
||||
public static int Pity(SaveData d, GachaConfig.Banner b)
|
||||
{
|
||||
return b != null ? d.gacha.PityOf(b.pityGroup) : 0;
|
||||
}
|
||||
|
||||
// ---------- 등용 ----------
|
||||
|
||||
public static string CanDraw(OutGameData db, SaveData d, GachaConfig.Banner b, int count, DateTime nowUtc)
|
||||
{
|
||||
if (b == null) return "등용 배너 없음";
|
||||
if (!IsOpen(b, nowUtc)) return "등용 기간이 아닙니다";
|
||||
int cost = Cost(db, d, count);
|
||||
if (d.gem < cost) return "보옥 부족 " + d.gem + " / " + cost;
|
||||
bool any = false;
|
||||
for (int t = 1; t <= 3 && !any; t++) any = Candidates(db, b, t).Count > 0;
|
||||
if (!any) return "등용 풀 비었음";
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<GachaPull> Draw(OutGameData db, SaveData d, GachaConfig.Banner b, int count, System.Random rng, DateTime nowUtc)
|
||||
{
|
||||
var res = new List<GachaPull>();
|
||||
if (CanDraw(db, d, count) != null) return res;
|
||||
if (CanDraw(db, d, b, count, nowUtc) != null) return res;
|
||||
var g = db.Gacha;
|
||||
if (count == g.TenCount && FirstTenFree(db, d)) d.gacha.firstTenUsed = true;
|
||||
else d.gem -= Cost(db, d, count);
|
||||
|
|
@ -70,7 +155,7 @@ namespace TKS.OutGame
|
|||
bool guaranteed = false;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int n = d.gacha.pity + 1;
|
||||
int n = d.gacha.PityOf(b.pityGroup) + 1;
|
||||
int tier;
|
||||
bool pity = n >= g.Pity;
|
||||
if (pity) tier = 3;
|
||||
|
|
@ -81,9 +166,9 @@ namespace TKS.OutGame
|
|||
}
|
||||
if (tier >= g.TenGuaranteeRarity) guaranteed = true;
|
||||
if (count == g.TenCount && i == count - 1 && !guaranteed) tier = g.TenGuaranteeRarity;
|
||||
d.gacha.pity = tier == 3 ? 0 : n;
|
||||
d.gacha.SetPity(b.pityGroup, tier == 3 ? 0 : n);
|
||||
d.gacha.total++;
|
||||
var pull = Pick(db, tier, rng);
|
||||
var pull = Pick(db, b, tier, rng);
|
||||
if (pull == null) continue;
|
||||
pull.pity = pity;
|
||||
var h = db.Heroes.Get(pull.heroId);
|
||||
|
|
@ -103,21 +188,28 @@ namespace TKS.OutGame
|
|||
return res;
|
||||
}
|
||||
|
||||
static GachaPull Pick(OutGameData db, int tier, System.Random rng)
|
||||
static GachaPull Pick(OutGameData db, GachaConfig.Banner b, int tier, System.Random rng)
|
||||
{
|
||||
var list = Candidates(db, tier);
|
||||
for (int t = tier; list.Count == 0 && t > 1; t--) list = Candidates(db, t - 1); // 그 등급 풀이 비면 아래 등급
|
||||
// 그 등급 풀이 비면 아래 등급 · 아래도 없으면 위 등급(진영 풀은 등급이 빌 수 있음)
|
||||
var list = Candidates(db, b, tier);
|
||||
for (int t = tier - 1; list.Count == 0 && t >= 1; t--) list = Candidates(db, b, t);
|
||||
for (int t = tier + 1; list.Count == 0 && t <= 3; t++) list = Candidates(db, b, t);
|
||||
if (list.Count == 0) return null;
|
||||
if (tier == 3)
|
||||
bool pickup = false;
|
||||
if (tier == 3 && b.IsPickup)
|
||||
{
|
||||
var pick = new List<GachaConfig.PoolEntry>();
|
||||
var rest = new List<GachaConfig.PoolEntry>();
|
||||
foreach (var p in list) (p.pickup ? pick : rest).Add(p);
|
||||
if (pick.Count > 0 && (rest.Count == 0 || rng.NextDouble() < db.Gacha.PickupShare)) list = pick;
|
||||
foreach (var p in list) (b.featured.Contains(p.heroId) ? pick : rest).Add(p);
|
||||
if (pick.Count > 0 && (rest.Count == 0 || rng.NextDouble() < db.Gacha.PickupShare))
|
||||
{
|
||||
list = pick;
|
||||
pickup = true;
|
||||
}
|
||||
else if (rest.Count > 0) list = rest;
|
||||
}
|
||||
var e = list[rng.Next(list.Count)];
|
||||
return new GachaPull { heroId = e.heroId, tier = tier, pickup = e.pickup && tier == 3 };
|
||||
return new GachaPull { heroId = e.heroId, tier = tier, pickup = pickup };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,6 +173,18 @@ namespace TKS.OutGame
|
|||
if (d.inv == null) d.inv = new Inventory();
|
||||
if (d.adsUsed == null) d.adsUsed = new AdsUsed();
|
||||
if (d.gacha == null) d.gacha = new GachaState();
|
||||
if (d.gacha.pities == null) d.gacha.pities = new List<CountEntry>();
|
||||
if (d.gacha.pity > 0)
|
||||
{
|
||||
// 구 세이브(배너 하나 시절)의 천장 → 상시 배너 묶음으로 옮김(PD 2026-11-13 배너 5 · 진행 보존)
|
||||
foreach (var b in db.Gacha.banners)
|
||||
if (!b.IsPickup)
|
||||
{
|
||||
d.gacha.SetPity(b.pityGroup, Mathf.Max(d.gacha.PityOf(b.pityGroup), d.gacha.pity));
|
||||
break;
|
||||
}
|
||||
d.gacha.pity = 0;
|
||||
}
|
||||
if (d.shop == null) d.shop = new ShopState();
|
||||
if (d.stages == null) d.stages = new List<StageState>();
|
||||
// C 단계(PD #1093 C) — 구버전 세이브 = 비어 있음
|
||||
|
|
@ -304,6 +316,24 @@ namespace TKS.OutGame
|
|||
return p != null ? p.order : 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 풀 성장 상태(명장 등용 배너 「상세 정보」 · PD 2026-11-13) = 최고 레벨 · 최고 등급 · 최고 ★ · 승급 장비 전부 장착(누적) · 스킬 = 그 상태의 상한.
|
||||
/// 병사 유닛 = 최고 레벨만(승급·장비·진화 없음). 값은 전부 표(hero_levels · promotion · evolve · hero_gear · skills)에서 읽는다.
|
||||
/// </summary>
|
||||
public static HeroState FullGrowth(OutGameData db, HeroDb.Hero h)
|
||||
{
|
||||
var s = new HeroState { id = h.id, lv = db.Levels.MaxLevel, grade = db.Promotion.FirstGrade, star = Mathf.Clamp(h.rarity, 1, db.Evolve.MaxStar) };
|
||||
if (h.isSoldier) return s;
|
||||
PromotionTable.Row top = null;
|
||||
foreach (var r in db.Promotion.rows)
|
||||
if (top == null || r.order > top.order) top = r;
|
||||
if (top != null) s.grade = top.grade;
|
||||
s.star = db.Evolve.MaxStar;
|
||||
foreach (var set in h.gear) s.gearTotal += set.slots.Count;
|
||||
s.skillLv = new[] { SkillRules.Cap(db, h, s, 0), SkillRules.Cap(db, h, s, 1) };
|
||||
return s;
|
||||
}
|
||||
|
||||
// ---------- 진화 ----------
|
||||
|
||||
/// <summary>다음 ★ 에 필요한 조각(최고 ★ = 0).</summary>
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ namespace TKS.OutGame
|
|||
var names = new List<string>();
|
||||
if (pulls != null)
|
||||
foreach (var p in pulls) names.Add(p.heroId + "★" + p.tier + (p.isNew ? "N" : p.shards > 0 ? "+" + p.shards : "+g" + p.gold));
|
||||
Log("gacha 10 = " + string.Join(" ", names.ToArray()) + " · pity=" + s.Save.gacha.pity + " gem=" + s.Save.gem + " firstTenUsed=" + s.Save.gacha.firstTenUsed);
|
||||
Log("gacha 10 = " + string.Join(" ", names.ToArray()) + " · pity=" + GachaRules.Pity(s.Save, root.Trade.Banner) + " gem=" + s.Save.gem + " firstTenUsed=" + s.Save.gacha.firstTenUsed);
|
||||
yield return Shot(cam, "gacha_result");
|
||||
root.Trade.CloseResult();
|
||||
root.Trade.SetPage(2);
|
||||
|
|
|
|||
|
|
@ -295,9 +295,9 @@ namespace TKS.OutGame
|
|||
|
||||
// ---------- 거래 ----------
|
||||
|
||||
public List<GachaPull> Draw(int count)
|
||||
public List<GachaPull> Draw(GachaConfig.Banner banner, int count)
|
||||
{
|
||||
var res = GachaRules.Draw(Db, Save, count, Rng);
|
||||
var res = GachaRules.Draw(Db, Save, banner, count, Rng, Now);
|
||||
if (res.Count > 0) Commit();
|
||||
return res;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ namespace TKS.OutGame
|
|||
"heroes.csv", "skills.csv", "hero_levels.csv", "promotion.csv", "evolve.csv", "gear_items.csv",
|
||||
"hero_gear.csv", "skill_levels.csv", "gacha.csv", "gacha_pool.csv", "stage_rewards.csv", "economy.csv",
|
||||
"skill_effects.csv", // 스킬 효과 1줄 표(2026-11-10 #1112)
|
||||
"gacha_banners.csv", // 명장 등용 배너(픽업 1 + 국가별 상시 4 · PD 2026-11-13)
|
||||
// C 단계(PD #1093 C)
|
||||
"training.csv", "training_ranks.csv", "patrol.csv", "dev_tree.csv", "treasures.csv", "treasure_sets.csv", "chests.csv", "tower.csv", "dungeons.csv", "arena.csv",
|
||||
};
|
||||
|
|
@ -131,7 +132,7 @@ namespace TKS.OutGame
|
|||
d.Gear = ParseGear(rows["gear_items.csv"], e);
|
||||
d.Skills = ParseSkills(rows["skills.csv"], rows["skill_levels.csv"], rows["skill_effects.csv"], e);
|
||||
d.Heroes = ParseHeroes(rows["heroes.csv"], rows["hero_gear.csv"], d, e);
|
||||
d.Gacha = ParseGacha(rows["gacha.csv"], rows["gacha_pool.csv"], d, e);
|
||||
d.Gacha = ParseGacha(rows["gacha.csv"], rows["gacha_pool.csv"], rows["gacha_banners.csv"], d, e);
|
||||
d.Rewards = ParseRewards(rows["stage_rewards.csv"], d, e);
|
||||
d.Economy = ParseEconomy(rows["economy.csv"], d, e);
|
||||
CheckSkillOwners(d, e);
|
||||
|
|
@ -410,7 +411,7 @@ namespace TKS.OutGame
|
|||
return t;
|
||||
}
|
||||
|
||||
static GachaConfig ParseGacha(List<Dictionary<string, string>> rows, List<Dictionary<string, string>> poolRows, OutGameData d, List<string> e)
|
||||
static GachaConfig ParseGacha(List<Dictionary<string, string>> rows, List<Dictionary<string, string>> poolRows, List<Dictionary<string, string>> bannerRows, OutGameData d, List<string> e)
|
||||
{
|
||||
var t = ScriptableObject.CreateInstance<GachaConfig>();
|
||||
KeyValues(rows, "gacha.csv", GachaConfig.RequiredKeys, t.values, e);
|
||||
|
|
@ -420,18 +421,66 @@ namespace TKS.OutGame
|
|||
if (Mathf.Abs(sum - 1f) > 1e-4f) e.Add("gacha.csv: p_r1 + p_r2 + p_r3 = " + sum.ToString("0.####", CultureInfo.InvariantCulture) + " ≠ 1");
|
||||
}
|
||||
const string f = "gacha_pool.csv";
|
||||
if (!Cols(poolRows, f, e, "hero_id", "in_pool", "pickup")) return t;
|
||||
if (!Cols(poolRows, f, e, "hero_id", "in_pool")) return t;
|
||||
var seen = new HashSet<string>();
|
||||
foreach (var r in poolRows)
|
||||
{
|
||||
var p = new GachaConfig.PoolEntry { heroId = Get(r, "hero_id"), inPool = Get(r, "in_pool") == "1", pickup = Get(r, "pickup") == "1" };
|
||||
var p = new GachaConfig.PoolEntry { heroId = Get(r, "hero_id"), inPool = Get(r, "in_pool") == "1" };
|
||||
if (!Unique(seen, p.heroId, f, r, e)) continue;
|
||||
if (d.Heroes == null || d.Heroes.Get(p.heroId) == null) e.Add(f + " 행 " + Get(r, "#") + ": hero_id 「" + p.heroId + "」 가 heroes.csv 에 없음");
|
||||
t.pool.Add(p);
|
||||
}
|
||||
ParseBanners(t, bannerRows, d, e);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// gacha_banners.csv(PD 2026-11-13 명장 등용) — 배너 1줄 = id · type(pickup|standing) · faction · 이름·설명 · pool(진영 목록 · all) · soldiers(0|1) ·
|
||||
/// featured(주요 영웅 · 픽업 = 확률 상승 대상) · pity_group(천장 묶음) · start(yyyy-MM-dd UTC · 빈 칸 = 기간 없음) · days · art(배너 그림 테마 id).
|
||||
/// 검증 = id 유일 · type · 주요 영웅이 heroes.csv 에 있고 그 배너 풀에서 나오는지 · 배너마다 등급 1~3 중 나올 유닛이 있는지 · 날짜 형식.
|
||||
/// </summary>
|
||||
static void ParseBanners(GachaConfig t, List<Dictionary<string, string>> rows, OutGameData d, List<string> e)
|
||||
{
|
||||
const string f = "gacha_banners.csv";
|
||||
if (!Cols(rows, f, e, "banner_id", "type", "faction", "name_ko", "desc_ko", "pool", "soldiers", "featured", "pity_group", "start", "days", "art")) return;
|
||||
var seen = new HashSet<string>();
|
||||
var sep = new[] { ';' };
|
||||
foreach (var r in rows)
|
||||
{
|
||||
var b = new GachaConfig.Banner
|
||||
{
|
||||
id = Get(r, "banner_id"), type = Get(r, "type"), faction = Get(r, "faction"), nameKo = Get(r, "name_ko"), descKo = Get(r, "desc_ko"),
|
||||
soldiers = Get(r, "soldiers") == "1", pityGroup = Get(r, "pity_group"), start = Get(r, "start"), days = Int(r, "days", f, e), art = Get(r, "art"),
|
||||
};
|
||||
b.pool.AddRange(Get(r, "pool").Split(sep, StringSplitOptions.RemoveEmptyEntries));
|
||||
b.featured.AddRange(Get(r, "featured").Split(sep, StringSplitOptions.RemoveEmptyEntries));
|
||||
if (!Unique(seen, b.id, f, r, e)) continue;
|
||||
string at = f + " 행 " + Get(r, "#") + " 「" + b.id + "」: ";
|
||||
if (b.type != GachaConfig.TypePickup && b.type != GachaConfig.TypeStanding) e.Add(at + "type 「" + b.type + "」 = pickup | standing 만");
|
||||
if (b.pool.Count == 0) e.Add(at + "pool 비었음(진영 목록 또는 all)");
|
||||
if (b.pityGroup.Length == 0) e.Add(at + "pity_group 비었음");
|
||||
if (b.featured.Count == 0) e.Add(at + "featured 비었음");
|
||||
DateTime day;
|
||||
if (b.start.Length > 0 && !DateTime.TryParseExact(b.start, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out day)) e.Add(at + "start 「" + b.start + "」 = yyyy-MM-dd");
|
||||
if (b.days < 0) e.Add(at + "days < 0");
|
||||
t.banners.Add(b);
|
||||
}
|
||||
if (t.banners.Count == 0) e.Add(f + ": 배너 없음");
|
||||
if (d.Heroes == null) return;
|
||||
foreach (var b in t.banners)
|
||||
{
|
||||
foreach (var id in b.featured)
|
||||
{
|
||||
var h = d.Heroes.Get(id);
|
||||
if (h == null) e.Add(f + " 「" + b.id + "」: featured 「" + id + "」 가 heroes.csv 에 없음");
|
||||
else if (!GachaRules.InBanner(t, d.Heroes, b, h)) e.Add(f + " 「" + b.id + "」: featured 「" + id + "」 가 이 배너 풀에서 나오지 않음(pool · gacha_pool in_pool · pickup_exclusive)");
|
||||
}
|
||||
bool any = false;
|
||||
for (int tier = 1; tier <= 3 && !any; tier++) any = GachaRules.Candidates(t, d.Heroes, b, tier).Count > 0;
|
||||
if (!any) e.Add(f + " 「" + b.id + "」: 나올 유닛이 없음");
|
||||
}
|
||||
}
|
||||
|
||||
static RewardTable ParseRewards(List<Dictionary<string, string>> rows, OutGameData d, List<string> e)
|
||||
{
|
||||
var t = ScriptableObject.CreateInstance<RewardTable>();
|
||||
|
|
|
|||
|
|
@ -121,10 +121,23 @@ namespace TKS.OutGame
|
|||
[Serializable]
|
||||
public class GachaState
|
||||
{
|
||||
[Tooltip("마지막 ★★★ 이후 뽑은 횟수(천장 카운터)")]
|
||||
[Tooltip("구 세이브(배너 하나 시절)의 천장 카운터 — 읽을 때 상시 배너 묶음으로 옮긴다(HeroRules.Sanitize)")]
|
||||
public int pity;
|
||||
public int total;
|
||||
public bool firstTenUsed;
|
||||
[Tooltip("천장 묶음(gacha_banners.csv pity_group)별 마지막 ★★★ 이후 등용 횟수")]
|
||||
public List<CountEntry> pities = new List<CountEntry>();
|
||||
|
||||
public int PityOf(string group)
|
||||
{
|
||||
return Counts.Get(pities, group);
|
||||
}
|
||||
|
||||
public void SetPity(string group, int n)
|
||||
{
|
||||
if (pities == null) pities = new List<CountEntry>();
|
||||
Counts.Set(pities, group, n);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
|
|
|
|||
|
|
@ -1052,7 +1052,7 @@ namespace TKS.OutGame
|
|||
{
|
||||
var ribbon = UiKit.Rect("Title", ui, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -170f), new Vector2(620f, 130f));
|
||||
if (UISkin.Has("ribbon_red")) UiKit.Skin(ribbon, "ribbon_red", UiKit.White);
|
||||
UiKit.Title(UiKit.Label(ribbon, "뽑기 결과", 58, UiKit.White, TextAnchor.MiddleCenter, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 12f), new Vector2(520f, 90f)), true).raycastTarget = false;
|
||||
UiKit.Title(UiKit.Label(ribbon, "등용 결과", 58, UiKit.White, TextAnchor.MiddleCenter, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 12f), new Vector2(520f, 90f)), true).raycastTarget = false;
|
||||
|
||||
// 카드마다 그 뽑기 직후의 조각 수(같은 영웅이 여러 번 나오면 차례로 쌓임 · 세이브에는 이미 다 더해져 있음)
|
||||
var total = new Dictionary<string, int>();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace TKS.OutGame
|
||||
{
|
||||
/// <summary>
|
||||
/// 영웅 상세 정보 팝업(PD 2026-11-13 명장 등용 「상세 정보를 누르면 해당 영웅의 풀 성장 스펙 및 스킬 정보를 볼 수 있는 팝업」) —
|
||||
/// 머리(초상 · 이름 · 진영 · 병종 · ★(등급만 켜짐) · 전투력) → 풀 성장 능력치(HP · 공격력 · 방어력 = HeroRules.Compute(FullGrowth) · 비교 = 처음 얻었을 때) →
|
||||
/// 스킬 2행(액티브 · 패시브 = 아이콘 · 이름 · 한자 · 쿨 · 최고 레벨 · 설명). 부품·색 = 영웅 상세 화면(HeroDetailScreen) 것 그대로 · 보유하지 않은 영웅도 열린다.
|
||||
/// 값 = 표(hero_levels · promotion · evolve · hero_gear · skills) — 훈련·보물 효과는 유저마다 달라 넣지 않는다.
|
||||
/// </summary>
|
||||
public static class HeroInfoPopup
|
||||
{
|
||||
const float W = 860f; // 팝업 내용 칸 폭(ModalCustom 940 − 좌우 40)
|
||||
const float RowIcon = 126f;
|
||||
|
||||
/// <summary>마지막으로 연 팝업의 풀 성장 수치(테스트·캡처 로그).</summary>
|
||||
public static HeroRules.Stats LastStats { get; private set; }
|
||||
public static string LastHeroId { get; private set; }
|
||||
|
||||
public static bool Show(OutGameRoot root, string heroId)
|
||||
{
|
||||
var s = root.Session;
|
||||
var db = s.Db;
|
||||
var h = s.Def(heroId);
|
||||
if (h == null) return false;
|
||||
var full = HeroRules.FullGrowth(db, h);
|
||||
var st = HeroRules.Compute(db, s.Cfg, h, full);
|
||||
var first = HeroRules.Compute(db, s.Cfg, h, HeroRules.NewHero(db, h, 0));
|
||||
LastStats = st;
|
||||
LastHeroId = heroId;
|
||||
|
||||
var c = root.ModalCustom("상세 정보", 1200f, new OutGameRoot.ModalButton("닫기", UiKit.Dark, null));
|
||||
string g = h.isSoldier ? "white" : full.grade;
|
||||
|
||||
// ① 머리 = 초상(최고 등급 색 틀) · 이름 · 한자 · 진영 배지 · 병종 · ★ · 전투력
|
||||
UiKit.Portrait("Face", c, h, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(0f, -4f), new Vector2(204f, 204f), false, 1f, UiKit.Col("grade_" + g + "_card_bg", UiKit.Coin));
|
||||
var name = HeroDetailScreen.Txt(c, h.nameKo, 228f, 6f, 60, "white");
|
||||
if (!string.IsNullOrEmpty(h.hanja)) HeroDetailScreen.Txt(c, h.hanja, 228f + name.preferredWidth + 14f, 32f, 30, "text_soft", TextAnchor.MiddleLeft, 0f, false, 0f, false);
|
||||
UiKit.DropShadow(UiKit.FactionBadge(c, h.faction, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(228f, -88f), 64f), 3f);
|
||||
HeroDetailScreen.Ico(c, UiKit.TypeSymbolId(h.type), 304f, 90f, 60f, 60f);
|
||||
HeroDetailScreen.Txt(c, UnitTypeInfo.Korean(h.type), 372f, 104f, 30, "text_type", TextAnchor.MiddleLeft, 0f, false, 0f, false);
|
||||
// 머리 별 = 그 영웅의 등급(배너·카드와 같은 수) · 풀 성장 ★ 까지 남은 칸 = 꺼진 별(QA r1 M6 「배너 ★3 ↔ 팝업 ★5」)
|
||||
int stars = h.isSoldier ? h.rarity : full.star;
|
||||
for (int i = 0; i < stars; i++) HeroDetailScreen.Ico(c, i < h.rarity ? "star_l" : "star_off", 228f + i * 50f, 160f, 48f, 50f, i < h.rarity ? 3f : 0f);
|
||||
var pw = HeroDetailScreen.Spr(c, "Power", "chip", W - 316f, 150f, 316f, 66f, "chip_dark").transform;
|
||||
HeroDetailScreen.Ico(pw, "sword", 14f, 4f, 58f, 58f);
|
||||
HeroDetailScreen.Txt(pw, "전투력", 78f, 21f, 26, "text_soft", TextAnchor.MiddleLeft, 0f, true, 0f, false);
|
||||
HeroDetailScreen.Txt(pw, st.power.ToString("N0"), 164f, 12f, 44, "white");
|
||||
|
||||
// ② 풀 성장 능력치 = 기준 줄 + 칸 3(큰 값 = 풀 성장 · 아래 = 처음 얻었을 때)
|
||||
float y = 236f;
|
||||
UiKit.SubTitle(c, "풀 성장 능력치", 0f, y, W, 34);
|
||||
string basis = h.isSoldier
|
||||
? "Lv " + full.lv + " 기준"
|
||||
: "Lv " + full.lv + " · " + UiKit.GradeKo(full.grade) + " 등급 · ★" + full.star + " · 승급 장비 " + full.gearTotal + "개 기준";
|
||||
HeroDetailScreen.Txt(c, basis, 0f, y + 52f, 26, "text_soft", TextAnchor.MiddleCenter, W, false, 0f, false);
|
||||
y += 100f;
|
||||
string[] names = { "HP", "공격력", "방어력" };
|
||||
string[] icons = { "dom_stat_hp", "dom_stat_atk", "dom_stat_def" };
|
||||
int[] now = { st.Hp, st.Atk, st.Def };
|
||||
int[] was = { first.Hp, first.Atk, first.Def };
|
||||
const float tw = 276f, th = 150f;
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
var tile = HeroDetailScreen.Spr(c, "Stat" + i, "panel12", i * (tw + 16f), y, tw, th, "grade_" + g + "_row").transform;
|
||||
HeroDetailScreen.Ico(tile, icons[i], 14f, 14f, 52f, 52f, 3f);
|
||||
HeroDetailScreen.Txt(tile, names[i], 76f, 22f, 30, "white", TextAnchor.MiddleLeft, 0f, true);
|
||||
HeroDetailScreen.Txt(tile, now[i].ToString("N0"), 0f, 62f, 48, "white", TextAnchor.MiddleCenter, tw);
|
||||
HeroDetailScreen.Txt(tile, "처음 " + was[i].ToString("N0"), 0f, 116f, 22, "text_soft", TextAnchor.MiddleCenter, tw, false, 0f, false);
|
||||
}
|
||||
y += th + 14f;
|
||||
HeroDetailScreen.Txt(c, "내정 훈련 · 보물 효과는 포함하지 않은 값", 0f, y, 22, "text_soft", TextAnchor.MiddleCenter, W, false, 0f, false);
|
||||
y += 46f;
|
||||
|
||||
// ③ 스킬 2행(액티브 · 패시브) — 행 높이 = 설명 줄 수에 맞춤
|
||||
UiKit.SubTitle(c, "스킬", 0f, y, W, 34);
|
||||
y += 58f;
|
||||
if (h.isSoldier)
|
||||
{
|
||||
HeroDetailScreen.Txt(c, "병사 유닛은 스킬이 없습니다", 0f, y + 10f, 28, "text_soft", TextAnchor.MiddleCenter, W, false, 0f, false);
|
||||
y += 70f;
|
||||
}
|
||||
else
|
||||
{
|
||||
string tk = UiKit.TypeKey(h.type);
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
var sk = SkillRules.Skill(db, h, i);
|
||||
if (sk == null) continue;
|
||||
var row = HeroDetailScreen.Spr(c, "Skill" + i, "panel12", 0f, y, W, 160f, "grade_" + g + "_row");
|
||||
var rt = row.transform;
|
||||
string iconId = "skill_" + sk.id;
|
||||
bool hasIcon = UISkin.Theme != null && UISkin.Theme.IconEntryOf(iconId) != null;
|
||||
// 아이콘 = 영웅 상세와 같은 규칙(256² 사각 아이콘 + 병종 얇은 틀 · 아이콘이 없으면 두꺼운 틀 + 병종 기호)
|
||||
if (hasIcon)
|
||||
{
|
||||
HeroDetailScreen.Ico(rt, iconId, 14f + 10.34f, 16f + 10.34f, 105.33f, 105.33f);
|
||||
if (tk != null) HeroDetailScreen.Ico(rt, "skill_ring_" + tk, 14f, 16f, RowIcon, RowIcon, 4f);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tk != null) HeroDetailScreen.Ico(rt, "skill_" + tk + "_a", 14f, 16f, RowIcon, RowIcon, 4f);
|
||||
HeroDetailScreen.Ico(rt, UiKit.TypeSymbolId(h.type), 36f, 38f, 82f, 82f);
|
||||
}
|
||||
float x = 158f;
|
||||
var kind = HeroDetailScreen.Spr(rt, "Kind", "chip", x, 14f, 116f, 44f, i == 0 ? "chip_cool" : "chip_dark");
|
||||
HeroDetailScreen.Txt(kind.transform, i == 0 ? "액티브" : "패시브", 0f, 9f, 26, "white", TextAnchor.MiddleCenter, 116f, true, 3f);
|
||||
if (i == 0 && sk.cooldown > 0)
|
||||
{
|
||||
var cool = HeroDetailScreen.Spr(rt, "Cool", "chip", x + 126f, 14f, 124f, 44f, "chip_dark");
|
||||
HeroDetailScreen.Txt(cool.transform, "쿨 " + sk.cooldown + "턴", 0f, 9f, 26, "white", TextAnchor.MiddleCenter, 124f, false, 3f);
|
||||
}
|
||||
var lv = HeroDetailScreen.Spr(rt, "Lv", "chip", W - 176f, 14f, 160f, 44f, "chip_dark");
|
||||
HeroDetailScreen.Txt(lv.transform, "최고 Lv " + SkillRules.Level(full, i), 0f, 9f, 26, "white", TextAnchor.MiddleCenter, 160f, true, 3f);
|
||||
var nm = HeroDetailScreen.Txt(rt, sk.nameKo, x, 66f, 38, "white");
|
||||
if (!string.IsNullOrEmpty(sk.hanja)) HeroDetailScreen.Txt(rt, sk.hanja, x + nm.preferredWidth + 10f, 76f, 26, "grade_" + g + "_label", TextAnchor.MiddleLeft, 0f, false, 0f, false);
|
||||
var desc = HeroDetailScreen.Desc(rt, x, 118f, W - x - 20f);
|
||||
desc.fontSize = 24;
|
||||
desc.text = SkillInfoDb.DisplayDesc(sk);
|
||||
desc.verticalOverflow = VerticalWrapMode.Overflow;
|
||||
float dh = Mathf.Ceil(desc.preferredHeight);
|
||||
desc.rectTransform.sizeDelta = new Vector2(desc.rectTransform.sizeDelta.x, dh);
|
||||
float rh = Mathf.Max(160f, 118f + dh + 18f);
|
||||
row.rectTransform.sizeDelta = new Vector2(W, rh);
|
||||
y += rh + 14f;
|
||||
}
|
||||
}
|
||||
|
||||
// 팝업 높이 = 내용에 맞춤(제목 띠 130 + 아래 버튼 줄 160)
|
||||
var panel = (RectTransform)c.parent;
|
||||
panel.sizeDelta = new Vector2(panel.sizeDelta.x, Mathf.Clamp(y + 300f, 600f, 1620f));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f2d86e42625cf4f4cbb08170129582ee
|
||||
|
|
@ -210,7 +210,7 @@ namespace TKS.OutGame
|
|||
public static string InfoText(HeroDb.Hero h, bool own)
|
||||
{
|
||||
return h.nameKo + (string.IsNullOrEmpty(h.hanja) ? "" : "(" + h.hanja + ")") + " · " + UnitTypeInfo.Korean(h.type) + " · ★" + h.rarity +
|
||||
(h.isSoldier ? " · 병사 유닛" : "") + (own ? " · 보유" : " · 미보유 — 거래 > 뽑기");
|
||||
(h.isSoldier ? " · 병사 유닛" : "") + (own ? " · 보유" : " · 미보유 — 거래 > 명장 등용");
|
||||
}
|
||||
|
||||
public static Image EmptyIn(Transform holder, float k)
|
||||
|
|
@ -453,19 +453,19 @@ namespace TKS.OutGame
|
|||
foreach (var o in S.Owned(h.isSoldier)) order.Add(o.id);
|
||||
}
|
||||
|
||||
// ---------- 목업 좌표 도우미(부모 왼쪽 위 기준 · y 아래로) ----------
|
||||
// ---------- 목업 좌표 도우미(부모 왼쪽 위 기준 · y 아래로) · internal = 영웅 정보 팝업(HeroInfoPopup)이 같은 부품·색으로 그림 ----------
|
||||
|
||||
static Color Col(string colorId)
|
||||
internal static Color Col(string colorId)
|
||||
{
|
||||
return UISkin.Col(colorId, Color.white);
|
||||
}
|
||||
|
||||
static RectTransform At(Transform p, string name, float x, float y, float w, float h)
|
||||
internal static RectTransform At(Transform p, string name, float x, float y, float w, float h)
|
||||
{
|
||||
return UiKit.Rect(name, p, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(x, -y), new Vector2(w, h));
|
||||
}
|
||||
|
||||
static Image Spr(Transform p, string name, string skin, float x, float y, float w, float h, string color, bool raycast = false)
|
||||
internal static Image Spr(Transform p, string name, string skin, float x, float y, float w, float h, string color, bool raycast = false)
|
||||
{
|
||||
return UiKit.Skin(At(p, name, x, y, w, h), skin, Col(color), raycast);
|
||||
}
|
||||
|
|
@ -477,7 +477,7 @@ namespace TKS.OutGame
|
|||
return img;
|
||||
}
|
||||
|
||||
static Image Ico(Transform p, string icon, float x, float y, float w, float h, float shadow = 0f)
|
||||
internal static Image Ico(Transform p, string icon, float x, float y, float w, float h, float shadow = 0f)
|
||||
{
|
||||
var img = At(p, "Icon", x, y, w, h).gameObject.AddComponent<Image>();
|
||||
img.preserveAspect = true;
|
||||
|
|
@ -488,7 +488,7 @@ namespace TKS.OutGame
|
|||
}
|
||||
|
||||
/// <summary>테마 아이콘(팩 임시 = 표 tint · 신규 제작 = 원색) · alpha = 곱.</summary>
|
||||
static void SetIcon(Image img, string icon, float alpha)
|
||||
internal static void SetIcon(Image img, string icon, float alpha)
|
||||
{
|
||||
var e = UISkin.Theme != null && icon != null ? UISkin.Theme.IconEntryOf(icon) : null;
|
||||
img.sprite = e != null ? e.Sprite : null;
|
||||
|
|
@ -506,7 +506,7 @@ namespace TKS.OutGame
|
|||
}
|
||||
|
||||
/// <summary>목업 text() — y = 글자 윗변 · title = Sen(+ 한글 폴백) · false = 맑은 고딕 · stroke < 0 = 기본(크기 × 0.06) · 0 = 없음 · 그 밖 = 목업 px(절반).</summary>
|
||||
static Text Txt(Transform p, string s, float x, float y, int size, string color, TextAnchor align = TextAnchor.MiddleLeft, float w = 0f, bool title = true, float stroke = -1f, bool shadow = true)
|
||||
internal static Text Txt(Transform p, string s, float x, float y, int size, string color, TextAnchor align = TextAnchor.MiddleLeft, float w = 0f, bool title = true, float stroke = -1f, bool shadow = true)
|
||||
{
|
||||
float bw = w > 0f ? w : 900f;
|
||||
float px = align == TextAnchor.MiddleCenter ? 0.5f : align == TextAnchor.MiddleRight ? 1f : 0f;
|
||||
|
|
@ -532,7 +532,7 @@ namespace TKS.OutGame
|
|||
return t;
|
||||
}
|
||||
|
||||
static Text Desc(Transform p, float x, float y, float w)
|
||||
internal static Text Desc(Transform p, float x, float y, float w)
|
||||
{
|
||||
var t = Txt(p, "", x, y, 26, "text_desc", TextAnchor.UpperLeft, w, false, 0f, false);
|
||||
var rt = t.rectTransform;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
|
@ -6,10 +7,54 @@ using UnityEngine.UI;
|
|||
namespace TKS.OutGame
|
||||
{
|
||||
/// <summary>
|
||||
/// 거래(img11 · 기획 S15) — ① 영웅·유닛 뽑기(픽업 · 천장 n/80 · 확률 공개 · 1회/10연 · 첫 10연 무료 · 결과 = 카드 순차 공개)
|
||||
/// 거래(img11 · 기획 S15) — ① 명장 등용(뽑기 · PD 2026-11-13: 배너 2쪽 = 픽업(영웅 1명 강조 일러 · 기간 · 상세 정보) / 국가별 상시(지도 위 진영 깃발 4 = 위·촉·오·군 · 주요 영웅 5) ·
|
||||
/// 좌우 화살표로 쪽 넘김 · 천장 n/80 · 확률 공개 · 등용 1회/10회 · 첫 10회 무료 · 결과 = 삼고초려 연출 → 카드)
|
||||
/// ② 보물 상자(PD #1093 C) = 상자 5종 · 보유 수 · 구매(chests.csv buy_gold/buy_gem/buy_points · 거래 할인 개발) · 열기는 보물 > 상자 탭
|
||||
/// ③ 상점·패스 = 골드 승급 장비 상점(흰/녹 · 일일 · 새로고침 보옥 · 시장 개설 무료) · 성장 패스·월정액·챕터 팩·첫 결제 팩 = 「준비 중」 스텁(과금 없음).
|
||||
/// </summary>
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
|
||||
public class TradeScreen : OutGameScreen
|
||||
{
|
||||
int page;
|
||||
|
|
@ -24,6 +69,41 @@ namespace TKS.OutGame
|
|||
Button tenBtn;
|
||||
RectTransform pickupArt;
|
||||
|
||||
// 명장 등용 배너(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; }
|
||||
|
||||
RectTransform shopGrid;
|
||||
ScrollRect shopScroll;
|
||||
Text shopTimer;
|
||||
|
|
@ -75,9 +155,9 @@ namespace TKS.OutGame
|
|||
{
|
||||
Tab = 4;
|
||||
if (UISkin.On) UiKit.Paint(UiKit.Fill("ListBg", Rt), UiKit.Col("list_bg", UiKit.Bg), false); // 목록형 바탕(Fable §5-3)
|
||||
Header("거래", "뽑기 · 상자 · 상점 · 패스", true);
|
||||
Header("거래", "등용 · 상자 · 상점 · 패스", true);
|
||||
var body = Body();
|
||||
string[] names = { "영웅·유닛 뽑기", "보물 상자", "상점·패스" };
|
||||
string[] names = { "명장 등용", "보물 상자", "상점·패스" };
|
||||
for (int i = 0; i < names.Length; i++)
|
||||
{
|
||||
int k = i;
|
||||
|
|
@ -86,57 +166,78 @@ namespace TKS.OutGame
|
|||
: UiKit.Btn("Tab" + i, body, names[i], 32, UiKit.Paper, UiKit.Ink, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f + i * 348f, -18f), new Vector2(332f, 84f), () => SetPage(k)));
|
||||
pages.Add(UiKit.Fill("Page" + i, body, 0f, 0f, 118f, 0f));
|
||||
}
|
||||
BuildBannerPages();
|
||||
banner = GachaRules.Default(S.Db, S.Now);
|
||||
if (UISkin.Has("panel_field") && UISkin.Has("btn_l_yellow")) BuildGachaKit(pages[0]);
|
||||
else BuildGacha(pages[0]);
|
||||
BuildShop(pages[2]);
|
||||
}
|
||||
|
||||
// ---------- 뽑기(Fable 제안서 §4-4 · #1104) ----------
|
||||
// ---------- 명장 등용(뽑기 · Fable 제안서 §4-4 · #1104 → PD 2026-11-13 배너 개편) ----------
|
||||
|
||||
const float GaugeW = 820f;
|
||||
const float BannerW = 1032f, BannerH = 640f, BannerInset = 8f;
|
||||
Text pityBadge;
|
||||
Text tenCost;
|
||||
Text singleCost;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 뽑기 = 배너(틀 유지 · 아래 문양 띠 흰 8 % · 오른쪽 위 「픽업」 뱃지) → 천장 섹션(레벨형 게이지 + 노랑 배지) →
|
||||
/// 확률 섹션(제목 띠 · 행 3 = 별 아이콘 ×n + 바 + % Sen) → 1회(Secondary 하늘 + 보옥 비용) · 10연(Primary 노랑 + 「첫 무료」 꼬리 칩) · 각주 = ⓘ 팝업.
|
||||
/// 명장 등용 = 배너(틀 + 안쪽 그림 · 좌우 화살표 = 쪽 넘김 · 아래 쪽 점) → 천장 섹션(레벨형 게이지 + 노랑 배지 · 배너 묶음) →
|
||||
/// 확률 섹션(제목 띠 · 행 3 = 별 아이콘 ×n + 바 + % + 배너별 메모) → 등용 1회(Secondary 하늘 + 보옥 비용) · 10회(Primary 노랑 + 「첫 무료」 꼬리 칩) · 각주 = ⓘ 팝업.
|
||||
/// 배너 안쪽 내용은 고를 때마다 다시 그린다(DrawBanner · 좌표 = 표 recruit_banner.csv).
|
||||
/// </summary>
|
||||
void BuildGachaKit(RectTransform p)
|
||||
{
|
||||
var g = S.Db.Gacha;
|
||||
// 배너
|
||||
var banner = UiKit.At(p, "Banner", 24f, 12f, 1032f, 600f);
|
||||
UiKit.Skin(banner, "banner", Color.white);
|
||||
if (UISkin.Has("banner_pattern"))
|
||||
// 배너 = 틀 + 안쪽(둥근 마스크 · 그림 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)
|
||||
{
|
||||
var pat = UiKit.Skin(UiKit.At(banner, "Pattern", 18f, 600f - 112f, 1032f - 36f, 90f), "banner_pattern", UiKit.Col("banner_pattern", Color.clear));
|
||||
pat.type = Image.Type.Tiled;
|
||||
}
|
||||
UiKit.InfoChip(banner, "Period", 30f, 26f, 180f, 52f, null, "기간 2주", 28); // 「픽업」 = 오른쪽 위 뱃지 하나만(검수 결함 3)
|
||||
UiKit.Title(UiKit.Label(banner, "영웅·유닛 뽑기", 60, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(36f, -100f), new Vector2(600f, 90f)), true);
|
||||
UiKit.Label(banner, "모든 영웅·병사 유닛\n= 뽑기로 획득", 32, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.UpperLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(36f, -200f), new Vector2(520f, 110f), false);
|
||||
var free = UiKit.Skin(UiKit.At(banner, "Free", 36f, 600f - 104f, 300f, 64f), "tail", UiKit.Col("tail_red", UiKit.Red));
|
||||
UiKit.Title(UiKit.FillLabel(free.transform, "첫 10연 무료", 30, UiKit.White), true);
|
||||
freeChip = free.gameObject;
|
||||
pickupArt = UiKit.Rect("PickupArt", banner, new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-20f, 20f), new Vector2(560f, 560f));
|
||||
var hot = UiKit.Icon(banner, "badge_hot", new Vector2(1f, 1f), new Vector2(0.5f, 0.5f), new Vector2(-52f, -44f), 88f); // 배너 안쪽(검수 결함 3)
|
||||
if (hot != null)
|
||||
{
|
||||
UiKit.DropShadow(hot, 4f);
|
||||
UiKit.Title(UiKit.FillLabel(hot.transform, "픽업", 24, UiKit.White), true);
|
||||
BannerArrow(frame, "Prev", "arrow_prev", false);
|
||||
BannerArrow(frame, "Next", "arrow_next", true);
|
||||
}
|
||||
bannerDots = UiKit.At(p, "Dots", 24f, 12f + BannerH + 3f, BannerW, 18f);
|
||||
|
||||
// 천장 게이지
|
||||
RectTransform pr;
|
||||
var pf = UiKit.SectionPanel(p, "Pity", 24f, 632f, 1032f, 176f, false, false, out pr);
|
||||
UiKit.Title(UiKit.Label(pf, "천장", 34, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(26f, -12f), new Vector2(200f, 50f)), true);
|
||||
pityText = UiKit.Label(pf, "", 28, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.MiddleRight, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-26f, -12f), new Vector2(620f, 50f), false);
|
||||
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);
|
||||
pityBar = UiKit.Gauge(pf, "Gauge", 20f, 62f, GaugeW + 60f, "lv_badge_y", "lv_fill", out pityBadge);
|
||||
|
||||
// 확률 공개
|
||||
RectTransform probRoot;
|
||||
var prob = UiKit.SectionPanel(p, "Prob", 24f, 828f, 1032f, 400f, false, false, out probRoot);
|
||||
var prob = UiKit.SectionPanel(p, "Prob", 24f, 864f, 1032f, 384f, false, false, out probRoot);
|
||||
UiKit.SectionTitle(probRoot, "확률 공개");
|
||||
var info = UiKit.Rect("Info", probRoot, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-16f, -12f), new Vector2(64f, 64f));
|
||||
UiKit.Skin(info, "badge_circle", UiKit.Col("chip_dark", UiKit.Dark), true);
|
||||
|
|
@ -145,38 +246,385 @@ namespace TKS.OutGame
|
|||
info.gameObject.AddComponent<Button>().onClick.AddListener(ShowGachaRules);
|
||||
for (int t = 3; t >= 1; t--)
|
||||
{
|
||||
float y = 88f + (3 - t) * 98f;
|
||||
float y = 84f + (3 - t) * 94f;
|
||||
var row = UiKit.Skin(UiKit.At(prob, "Row" + t, 20f, y, 1032f - 12f - 40f, 80f), "panel12", UiKit.Col("row_on_field", UiKit.Paper)).transform;
|
||||
for (int s = 0; s < t; s++)
|
||||
{
|
||||
var st = UiKit.Icon(row, "star_on", new Vector2(0f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(40f + s * 40f, 0f), 40f);
|
||||
if (st != null) UiKit.DropShadow(st, 2f);
|
||||
}
|
||||
var fill = UiKit.Bar("P" + t, row, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(180f, 0f), new Vector2(500f, 30f), UiKit.Slot, t == 3 ? UiKit.Col("grade_orange_card_bg", UiKit.Coin) : t == 2 ? UiKit.Col("grade_purple_card_bg", UiKit.Purple) : UiKit.Col("grade_blue_card_bg", UiKit.Jade));
|
||||
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));
|
||||
UiKit.SetBar(fill, g.P(t));
|
||||
var pt = UiKit.Title(UiKit.Label(row, (g.P(t) * 100f).ToString("0.#") + "%", 32, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(700f, 0f), new Vector2(140f, 50f)), true);
|
||||
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);
|
||||
pt.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||
if (t == 1) UiKit.Label(row, "병사 포함", 24, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.MiddleRight, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-20f, 0f), new Vector2(140f, 40f), false);
|
||||
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;
|
||||
}
|
||||
|
||||
// 1회 · 10연
|
||||
singleBtn = UiKit.SkinBtn(p, "Single", "btn_sky", UiKit.White, "1회", 42, UiKit.White, 24f, 1288f, 420f, 116f, () => DoDraw(1)); // 버튼 행 = 탭 바 위 24 고정(검수 결함 4)
|
||||
singleCost = UiKit.BtnCost(singleBtn, "cur_gem", "", 44f, 36);
|
||||
tenBtn = UiKit.SkinBtn(p, "Ten", "btn_l_yellow", UiKit.White, g.TenCount + "연", 46, UiKit.Col("text_on_yellow", UiKit.Ink), 1056f - 580f, 1282f, 580f, 122f, () => DoDraw(g.TenCount));
|
||||
// 등용 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));
|
||||
tenCost = UiKit.BtnCost(tenBtn, "cur_gem", "", 44f, 36);
|
||||
var f2 = UiKit.Skin(UiKit.Rect("Free2", tenBtn.transform, new Vector2(1f, 1f), new Vector2(1f, 0.5f), new Vector2(-14f, 2f), new Vector2(200f, 50f)), "tail", UiKit.Col("tail_red", UiKit.Red));
|
||||
UiKit.Title(UiKit.FillLabel(f2.transform, "첫 무료", 26, UiKit.White), true);
|
||||
freeChip2 = f2.gameObject;
|
||||
}
|
||||
|
||||
/// <summary>뽑기 규칙(구 각주 → ⓘ 팝업 · Fable §4-4).</summary>
|
||||
/// <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>
|
||||
void ShowGachaRules()
|
||||
{
|
||||
var e = S.Db.Economy;
|
||||
var g = S.Db.Gacha;
|
||||
Root.Modal("뽑기 규칙",
|
||||
"중복 = 영웅 조각\n★★★ " + e.ShardDup(3) + " · ★★ " + e.ShardDup(2) + " · ★ " + e.ShardDup(1) + "\n병사 중복 = 골드 " + e.SoldierDupGold +
|
||||
"\n\n" + g.TenCount + "연 = ★★ 이상 1 보장\n천장 " + g.Pity + "회 = ★★★ 확정\n확률·천장 전부 공개",
|
||||
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확률·천장 전부 공개",
|
||||
new OutGameRoot.ModalButton("확인", UiKit.Dark, null));
|
||||
}
|
||||
|
||||
|
|
@ -197,22 +645,22 @@ namespace TKS.OutGame
|
|||
S.EnsureShop();
|
||||
}
|
||||
|
||||
// ---------- 뽑기 ----------
|
||||
// ---------- 명장 등용(테마가 없을 때의 예전 화면) ----------
|
||||
|
||||
void BuildGacha(RectTransform p)
|
||||
{
|
||||
var g = S.Db.Gacha;
|
||||
var banner = UiKit.TopBand("Banner", p, 12f, 440f, 24f, 24f);
|
||||
if (UISkin.Has("banner")) UiKit.Skin(banner, "banner", Color.white); // 팩 배너 틀(PD #1100)
|
||||
else UiKit.Paint(banner, new Color(0.27f, 0.14f, 0.12f));
|
||||
var pick = UiKit.Box("Pickup", banner, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(30f, -24f), new Vector2(230f, 62f), UiKit.Red);
|
||||
UiKit.FillLabel(pick.transform, "픽업 (2주)", 30, UiKit.White);
|
||||
UiKit.Title(UiKit.Label(banner, "영웅·유닛 뽑기", 60, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(36f, -110f), new Vector2(600f, 90f)), true);
|
||||
UiKit.Label(banner, "모든 영웅·병사 유닛\n= 뽑기로 획득", 32, new Color(0.9f, 0.82f, 0.78f), TextAnchor.UpperLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(36f, -210f), new Vector2(520f, 110f), false);
|
||||
var free = UiKit.Box("Free", banner, new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(36f, 28f), new Vector2(300f, 78f), new Color(0.93f, 0.75f, 0.24f));
|
||||
UiKit.FillLabel(free.transform, "첫 10연 무료", 34, UiKit.Ink);
|
||||
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);
|
||||
freeChip = free.gameObject;
|
||||
pickupArt = UiKit.Rect("PickupArt", banner, new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-20f, 10f), new Vector2(440f, 400f));
|
||||
pickupArt = UiKit.Rect("PickupArt", bn, new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-20f, 10f), new Vector2(440f, 400f));
|
||||
|
||||
var pity = UiKit.TopBand("Pity", p, 468f, 130f, 24f, 24f);
|
||||
var pimg = UiKit.Paint(pity, UiKit.Paper);
|
||||
|
|
@ -239,67 +687,70 @@ namespace TKS.OutGame
|
|||
singleBtn = UiKit.Btn("Single", btns, "", 40, UiKit.Dark, UiKit.White, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), Vector2.zero, new Vector2(500f, 160f), () => DoDraw(1));
|
||||
tenBtn = UiKit.Btn("Ten", btns, "", 40, UiKit.Red, UiKit.White, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), Vector2.zero, new Vector2(500f, 160f), () => DoDraw(g.TenCount));
|
||||
var f2 = UiKit.Box("Free2", tenBtn.transform, new Vector2(1f, 1f), new Vector2(1f, 0.5f), new Vector2(-10f, 0f), new Vector2(240f, 56f), new Color(0.93f, 0.75f, 0.24f));
|
||||
UiKit.FillLabel(f2.transform, "첫 10연 무료", 26, UiKit.Ink);
|
||||
UiKit.FillLabel(f2.transform, "첫 10회 무료", 26, UiKit.Ink);
|
||||
freeChip2 = f2.gameObject;
|
||||
|
||||
var e = S.Db.Economy;
|
||||
UiKit.Label(p, "중복 = 영웅 조각(★★★ " + e.ShardDup(3) + " · ★★ " + e.ShardDup(2) + " · ★ " + e.ShardDup(1) + ") · 병사 중복 = 골드 " + e.SoldierDupGold +
|
||||
" · 10연 = ★★ 이상 1 보장", 26, UiKit.InkSoft, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -1090f), new Vector2(1020f, 80f), false);
|
||||
" · 10회 등용 = ★★ 이상 1 보장", 26, UiKit.InkSoft, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -1090f), new Vector2(1020f, 80f), false);
|
||||
}
|
||||
|
||||
void RefreshGacha()
|
||||
{
|
||||
var g = S.Db.Gacha;
|
||||
var d = S.Save;
|
||||
if (banner == null) return;
|
||||
bool free = GachaRules.FirstTenFree(S.Db, d);
|
||||
freeChip.SetActive(free);
|
||||
bool open = GachaRules.IsOpen(banner, S.Now);
|
||||
int pity = GachaRules.Pity(d, banner);
|
||||
if (freeChip != null) freeChip.SetActive(free);
|
||||
freeChip2.SetActive(free);
|
||||
singleBtn.interactable = tenBtn.interactable = open;
|
||||
if (pityBadge != null)
|
||||
{
|
||||
pityText.text = d.gacha.pity + " / " + g.Pity + " · " + g.Pity + "회째 ★★★ 확정";
|
||||
pityBadge.text = d.gacha.pity.ToString();
|
||||
UiKit.SetGauge(pityBar, d.gacha.pity / (float)Mathf.Max(1, g.Pity), GaugeW + 60f);
|
||||
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);
|
||||
singleCost.text = g.CostSingle.ToString("N0");
|
||||
tenCost.text = free ? "무료" : g.CostTen.ToString("N0");
|
||||
var ic = tenCost.transform.parent.GetComponentInChildren<Image>(true);
|
||||
if (ic != null) ic.gameObject.SetActive(!free);
|
||||
}
|
||||
else
|
||||
{
|
||||
pityText.text = d.gacha.pity + " / " + g.Pity + " · " + g.Pity + "회 ★★★ 확정";
|
||||
UiKit.SetBar(pityBar, d.gacha.pity / (float)Mathf.Max(1, g.Pity));
|
||||
UiKit.LabelOf(singleBtn).text = "1회\n<size=32>보옥 " + g.CostSingle.ToString("N0") + "</size>";
|
||||
UiKit.LabelOf(tenBtn).text = g.TenCount + "연\n<size=32>" + (free ? "무료" : "보옥 " + g.CostTen.ToString("N0")) + "</size>";
|
||||
}
|
||||
UiKit.Clear(pickupArt);
|
||||
var picks = new List<HeroDb.Hero>();
|
||||
foreach (var pe in g.pool)
|
||||
if (pe.pickup && pe.inPool && S.Def(pe.heroId) != null) picks.Add(S.Def(pe.heroId));
|
||||
if (pityBadge != null)
|
||||
{
|
||||
var arts = picks.FindAll(h => UiKit.ArtSprite(h.art) != null);
|
||||
for (int i = 0; i < arts.Count && i < 2; i++)
|
||||
UiKit.Portrait("Pick" + i, pickupArt, arts[i], new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-10f - i * 250f, 70f), new Vector2(300f, 440f), false, 1f, new Color(1f, 1f, 1f, 0f));
|
||||
for (int i = 0; i < picks.Count && i < 2; i++)
|
||||
UiKit.InfoChip(pickupArt, "Name" + i, 560f - (i + 1) * 250f, 498f, 230f, 50f, null, picks[i].nameKo + " ★" + picks[i].rarity, 26);
|
||||
// 확률 행 메모 = 배너별(★★★ = 픽업 영웅 몫 · ★ = 병사 포함)
|
||||
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();
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < picks.Count && i < 2; i++)
|
||||
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++)
|
||||
{
|
||||
UiKit.Portrait("Pick" + i, pickupArt, picks[i], new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-i * 210f, 50f), new Vector2(230f, 330f), false, 1f, new Color(1f, 1f, 1f, 0f));
|
||||
UiKit.Label(pickupArt, picks[i].nameKo + " ★" + picks[i].rarity, 26, UiKit.White, TextAnchor.MiddleCenter, new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-i * 210f, 6f), new Vector2(230f, 44f));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
public void DoDraw(int count)
|
||||
{
|
||||
var why = GachaRules.CanDraw(S.Db, S.Save, count);
|
||||
var why = GachaRules.CanDraw(S.Db, S.Save, banner, count, S.Now);
|
||||
if (why != null)
|
||||
{
|
||||
Root.Toast(why);
|
||||
return;
|
||||
}
|
||||
LastPulls = S.Draw(count);
|
||||
LastPulls = S.Draw(banner, count);
|
||||
ShowResult(LastPulls);
|
||||
}
|
||||
|
||||
|
|
@ -323,7 +774,7 @@ namespace TKS.OutGame
|
|||
skip.transition = Selectable.Transition.None;
|
||||
skip.onClick.AddListener(RevealAll);
|
||||
resultRoot = dim.gameObject;
|
||||
UiKit.Label(dim, "뽑기 결과", 64, UiKit.White, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -200f), new Vector2(900f, 100f));
|
||||
UiKit.Label(dim, "등용 결과", 64, UiKit.White, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -200f), new Vector2(900f, 100f));
|
||||
int cols = Mathf.Min(5, Mathf.Max(1, pulls.Count));
|
||||
int rows = (pulls.Count + cols - 1) / cols;
|
||||
float cw = 196f, chh = 330f;
|
||||
|
|
@ -390,7 +841,7 @@ namespace TKS.OutGame
|
|||
if (resultRoot != null)
|
||||
{
|
||||
resultRoot.SetActive(false);
|
||||
Object.Destroy(resultRoot);
|
||||
UnityEngine.Object.Destroy(resultRoot);
|
||||
resultRoot = null;
|
||||
}
|
||||
Refresh();
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ namespace TKS.OutGame
|
|||
root.Show(root.Trade);
|
||||
root.Trade.SetPage(0);
|
||||
yield return Shot(cam, "trade_gacha", W, H);
|
||||
yield return RecruitShots(cam, root); // 명장 등용 배너(PD 2026-11-13 · 픽업 · 국가별 상시 4 · 상세 정보 팝업)
|
||||
yield return GachaShots(cam, root); // 뽑기 연출(삼고초려 장면 → 카드 획득 · PD 2026-11-13 · -uiOnly gacha_cut1|2|3 · gacha_cards 일 때만)
|
||||
root.Show(root.Treasures);
|
||||
root.Treasures.SetPage(2);
|
||||
|
|
@ -414,9 +415,66 @@ namespace TKS.OutGame
|
|||
/// <summary>캡처용 뽑기 1장 = 등급 tier 풀의 k 번째 유닛 · 세이브에도 실제 뽑기처럼(새 유닛 = 획득 · 중복 = 조각 / 병사 = 골드 · GachaRules.Draw 와 같은 처리).</summary>
|
||||
const string GachaShowcase = "zhugeliang";
|
||||
|
||||
/// <summary>
|
||||
/// 명장 등용 배너 캡처(PD 2026-11-13): trade_recruit_pickup(픽업 배너) · trade_recruit_<배너 id>(국가별 상시 = 진영 깃발 고름) ·
|
||||
/// trade_recruit_info(픽업 영웅 상세 정보 팝업) · trade_recruit_info_hero(상시 배너 가운데 영웅 상세 정보). 기간이 지난 뒤에도 찍히게 시계를 픽업 기간 안으로 둔다(캡처 동안만).
|
||||
/// </summary>
|
||||
IEnumerator RecruitShots(Camera cam, OutGameRoot root)
|
||||
{
|
||||
var s = root.Session;
|
||||
var clock = s.Clock;
|
||||
GachaConfig.Banner pick = null;
|
||||
foreach (var b in s.Db.Gacha.banners)
|
||||
if (b.IsPickup && pick == null) pick = b;
|
||||
if (pick != null && !GachaRules.IsOpen(pick, s.Now) && GachaRules.StartOf(pick) != null)
|
||||
{
|
||||
var inside = GachaRules.StartOf(pick).Value.AddHours(22.5);
|
||||
s.Clock = () => inside;
|
||||
}
|
||||
if (pick != null)
|
||||
{
|
||||
root.Trade.SelectBanner(pick.id);
|
||||
yield return Shot(cam, "trade_recruit_pickup", W, H);
|
||||
yield return Shot(cam, "trade_recruit_pickup_1080x2400", 1080, 2400); // 좁고 긴 화면(9:20)
|
||||
yield return Shot(cam, "trade_recruit_pickup_1536x2048", W2, H2); // 넓은 화면(3:4)
|
||||
Log("recruit pickup banner=" + root.Trade.Banner.id + " page=" + root.Trade.BannerPage + "/" + root.Trade.BannerPageCount + " open=" + GachaRules.IsOpen(pick, s.Now));
|
||||
}
|
||||
GachaConfig.Banner first = null;
|
||||
foreach (var b in s.Db.Gacha.banners)
|
||||
{
|
||||
if (b.IsPickup) continue;
|
||||
if (first == null) first = b;
|
||||
root.Trade.SelectBanner(b.id);
|
||||
yield return Shot(cam, "trade_recruit_" + b.id, W, H);
|
||||
Log("recruit " + b.id + " featured=" + root.Trade.FeaturedShown + " flags=" + root.Trade.FlagCount);
|
||||
}
|
||||
if (pick != null)
|
||||
{
|
||||
root.Trade.SelectBanner(pick.id);
|
||||
root.Trade.ShowBannerInfo();
|
||||
yield return Shot(cam, "trade_recruit_info", W, H);
|
||||
var st = HeroInfoPopup.LastStats;
|
||||
Log("recruit info " + HeroInfoPopup.LastHeroId + " hp=" + st.Hp + " atk=" + st.Atk + " def=" + st.Def + " power=" + st.power);
|
||||
root.CloseModal();
|
||||
}
|
||||
if (first != null && first.featured.Count > 0)
|
||||
{
|
||||
root.Trade.SelectBanner(first.id);
|
||||
root.Trade.ShowHeroInfo(first.featured[0]);
|
||||
yield return Shot(cam, "trade_recruit_info_hero", W, H);
|
||||
root.CloseModal();
|
||||
}
|
||||
s.Clock = clock;
|
||||
root.Trade.SelectBanner(GachaRules.Default(s.Db, s.Now).id);
|
||||
}
|
||||
|
||||
static GachaPull CapturePull(OutGameSession s, int tier, int k)
|
||||
{
|
||||
var list = GachaRules.Candidates(s.Db, tier);
|
||||
// 전 진영이 나오는 배너의 풀(없으면 지금 열린 첫 배너)
|
||||
GachaConfig.Banner all = null;
|
||||
foreach (var b in s.Db.Gacha.banners)
|
||||
if (all == null && b.pool.Contains(GachaConfig.PoolAll)) all = b;
|
||||
var list = GachaRules.Candidates(s.Db, all ?? GachaRules.Default(s.Db, s.Now), tier);
|
||||
var h = s.Def(list[k % list.Count].heroId);
|
||||
// ★★★ 첫 장 = 제갈량(삼고초려 장면 확인용 · 풀에 있을 때만 · 캡처 전용 선택 — 게임 확률과 무관)
|
||||
if (tier == 3 && k == 0)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ namespace TKS.Tests
|
|||
GachaPull Pull(int tier)
|
||||
{
|
||||
var s = og.Session;
|
||||
var list = GachaRules.Candidates(s.Db, tier);
|
||||
var list = GachaRules.Candidates(s.Db, og.Trade.Banner, tier);
|
||||
Assert.Greater(list.Count, 0, "등급 " + tier + " 풀");
|
||||
var h = s.Def(list[0].heroId);
|
||||
var p = new GachaPull { heroId = h.id, tier = tier };
|
||||
|
|
|
|||
|
|
@ -290,27 +290,33 @@ namespace TKS.Tests
|
|||
{
|
||||
var g = Db.Gacha;
|
||||
Assert.AreEqual(1f, g.P(1) + g.P(2) + g.P(3), 1e-4f, "확률 합 = 1");
|
||||
// 전 진영이 나오는 배너(등급 풀 = 영웅 + 병사 전부 · 배너별 규칙은 RecruitTests) · 시각 = 그 배너 기간 안
|
||||
GachaConfig.Banner b = null;
|
||||
foreach (var x in g.banners)
|
||||
if (b == null && x.pool.Contains(GachaConfig.PoolAll)) b = x;
|
||||
Assert.IsNotNull(b, "pool = all 배너");
|
||||
var at = GachaRules.StartOf(b) ?? T0;
|
||||
var rng = new System.Random(1093);
|
||||
var d = NewSave();
|
||||
int gem = d.gem;
|
||||
Assert.IsTrue(GachaRules.FirstTenFree(Db, d));
|
||||
var ten = GachaRules.Draw(Db, d, g.TenCount, rng);
|
||||
var ten = GachaRules.Draw(Db, d, b, g.TenCount, rng, at);
|
||||
Assert.AreEqual(g.TenCount, ten.Count);
|
||||
Assert.AreEqual(gem, d.gem, "첫 10연 무료");
|
||||
Assert.IsTrue(d.gacha.firstTenUsed);
|
||||
Assert.AreEqual(g.CostTen, GachaRules.Cost(Db, d, g.TenCount));
|
||||
|
||||
d.gem = 100000000;
|
||||
d.gacha.pity = g.Pity - 1;
|
||||
var p = GachaRules.Draw(Db, d, 1, rng);
|
||||
d.gacha.SetPity(b.pityGroup, g.Pity - 1);
|
||||
var p = GachaRules.Draw(Db, d, b, 1, rng, at);
|
||||
Assert.AreEqual(3, p[0].tier, "천장 = ★★★ 확정");
|
||||
Assert.IsTrue(p[0].pity);
|
||||
Assert.AreEqual(0, d.gacha.pity, "획득 시 리셋");
|
||||
Assert.AreEqual(0, GachaRules.Pity(d, b), "획득 시 리셋");
|
||||
Assert.AreEqual(3, Db.Heroes.Get(p[0].heroId).rarity);
|
||||
|
||||
for (int k = 0; k < 200; k++)
|
||||
{
|
||||
var batch = GachaRules.Draw(Db, d, g.TenCount, rng);
|
||||
var batch = GachaRules.Draw(Db, d, b, g.TenCount, rng, at);
|
||||
bool ok = false;
|
||||
foreach (var x in batch) ok |= x.tier >= g.TenGuaranteeRarity;
|
||||
Assert.IsTrue(ok, "10연 = ★★ 이상 1 보장");
|
||||
|
|
@ -320,8 +326,8 @@ namespace TKS.Tests
|
|||
int n = 20000, shards = 0, gold = 0;
|
||||
for (int k = 0; k < n; k++)
|
||||
{
|
||||
d.gacha.pity = 0;
|
||||
var x = GachaRules.Draw(Db, d, 1, rng)[0];
|
||||
d.gacha.SetPity(b.pityGroup, 0);
|
||||
var x = GachaRules.Draw(Db, d, b, 1, rng, at)[0];
|
||||
counts[x.tier]++;
|
||||
shards += x.shards;
|
||||
gold += x.gold;
|
||||
|
|
@ -401,7 +407,7 @@ namespace TKS.Tests
|
|||
d.inv.AddGear(Db.Gear.items[0].id, 3);
|
||||
d.Stage(1, 2, true).stars = 2;
|
||||
d.presets[1].slots[0] = "";
|
||||
d.gacha.pity = 7;
|
||||
d.gacha.SetPity(Db.Gacha.banners[0].pityGroup, 7);
|
||||
SaveStore.Write(d, T0);
|
||||
var l = SaveStore.Load();
|
||||
Assert.IsNotNull(l);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,385 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using TKS.OutGame;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.TestTools;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace TKS.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// 명장 등용(뽑기 메뉴 개편 · PD 2026-11-13) — ① 배너 표(gacha_banners.csv): 픽업 + 진영마다 상시 배너 · 주요 영웅이 그 배너 풀에서 나옴 · 배치 표(recruit_banner.csv)에 자리·깃발이 있음
|
||||
/// ② 상시 배너 = 그 진영 영웅(+ 병사)만 · 픽업 영웅은 상시 배너에서 안 나옴(pickup_exclusive) ③ 픽업 배너 = ★★★ 의 pickup_share 가 주요 영웅
|
||||
/// ④ 천장 = pity_group 끼리 같이 셈 · 다른 묶음은 따로 ⑤ 기간(start + days) 밖 = 등용 불가(보옥 그대로) ⑥ 구 세이브 천장 → 상시 묶음으로 옮김
|
||||
/// ⑦ 풀 성장 = 최고 레벨·등급·★·장비 전부(표 값) ⑧ 화면: 탭 이름 「명장 등용」 · 「뽑기」 글자 없음 · 좌우 쪽 넘김 · 깃발 = 배너 고르기 · 주요 영웅 5 · 상세 정보 팝업 · 고른 배너 풀로 등용.
|
||||
/// 기대 값은 전부 표에서 읽는다(C45).
|
||||
/// </summary>
|
||||
public class RecruitTests
|
||||
{
|
||||
static readonly DateTime T0 = new DateTime(2026, 11, 5, 3, 0, 0, DateTimeKind.Utc);
|
||||
OutGameRoot og;
|
||||
|
||||
static OutGameData Db
|
||||
{
|
||||
get { return OutGameData.Default; }
|
||||
}
|
||||
|
||||
static BalanceConfig Cfg
|
||||
{
|
||||
get { return Resources.Load<BalanceConfig>("BalanceConfig"); }
|
||||
}
|
||||
|
||||
static SaveData RichSave()
|
||||
{
|
||||
var d = HeroRules.NewSave(Db, Cfg.playerSpawnPoints.Length, T0);
|
||||
d.gem = 100000000;
|
||||
d.gacha.firstTenUsed = true;
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>그 배너가 열려 있는 시각(기간 있음 = 시작 + 1시간 · 없음 = T0).</summary>
|
||||
static DateTime Inside(GachaConfig.Banner b)
|
||||
{
|
||||
var s = GachaRules.StartOf(b);
|
||||
return s != null ? s.Value.AddHours(1) : T0;
|
||||
}
|
||||
|
||||
static List<GachaConfig.Banner> Banners(bool pickup)
|
||||
{
|
||||
var l = new List<GachaConfig.Banner>();
|
||||
foreach (var b in Db.Gacha.banners)
|
||||
if (b.IsPickup == pickup) l.Add(b);
|
||||
return l;
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
GachaCutscene.Manual = false;
|
||||
SaveStore.UseMemory(false);
|
||||
}
|
||||
|
||||
// ---------- 표 ----------
|
||||
|
||||
[Test]
|
||||
public void Banners_Table_PickupAndFactionStanding_FeaturedInPool_LayoutRows()
|
||||
{
|
||||
var g = Db.Gacha;
|
||||
Assert.Greater(Banners(true).Count, 0, "픽업 배너");
|
||||
// 진영마다 상시 배너 1(영웅이 있는 진영 전부)
|
||||
var factions = new HashSet<string>();
|
||||
foreach (var h in Db.Heroes.heroes)
|
||||
if (!h.isSoldier) factions.Add(h.faction);
|
||||
foreach (var f in factions)
|
||||
{
|
||||
int n = 0;
|
||||
foreach (var b in Banners(false))
|
||||
if (b.faction == f && b.pool.Contains(f)) n++;
|
||||
Assert.AreEqual(1, n, "진영 " + f + " 상시 배너");
|
||||
Assert.IsNotNull(RecruitBanner.First("flag_" + f), "배치 표 깃발 flag_" + f);
|
||||
Assert.IsNotNull(UISkin.Icon(RecruitBanner.First("flag_" + f).sprite), "깃발 그림 " + f);
|
||||
}
|
||||
Assert.IsNotNull(RecruitBanner.First("map"), "배치 표 지도");
|
||||
Assert.IsNotNull(UISkin.Icon(RecruitBanner.First("map").sprite), "지도 그림");
|
||||
foreach (var b in g.banners)
|
||||
{
|
||||
Assert.IsNotNull(UISkin.Icon(b.art), b.id + " 배너 그림 " + b.art);
|
||||
Assert.Greater(b.featured.Count, 0, b.id);
|
||||
foreach (var id in b.featured)
|
||||
{
|
||||
var h = Db.Heroes.Get(id);
|
||||
Assert.IsNotNull(h, b.id + " featured " + id);
|
||||
Assert.IsTrue(GachaRules.InBanner(g, Db.Heroes, b, h), b.id + " 주요 영웅 " + id + " 가 풀에서 나옴");
|
||||
Assert.IsNotNull(UiKit.ArtSprite(h.art), id + " 인게임 그림");
|
||||
}
|
||||
if (b.IsPickup) continue;
|
||||
Assert.AreEqual(b.featured.Count, RecruitBanner.Slots("hero", b.featured.Count).Count, b.id + " 영웅 자리 수");
|
||||
Assert.AreEqual(b.featured.Count, RecruitBanner.Slots("tag", b.featured.Count).Count, b.id + " 이름표 수");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 규칙 ----------
|
||||
|
||||
[Test]
|
||||
public void Standing_OnlyThatFaction_PickupHeroExcluded()
|
||||
{
|
||||
var g = Db.Gacha;
|
||||
var rng = new System.Random(1113);
|
||||
foreach (var b in Banners(false))
|
||||
{
|
||||
var d = RichSave();
|
||||
var seen = new HashSet<string>();
|
||||
for (int k = 0; k < 300; k++)
|
||||
foreach (var p in GachaRules.Draw(Db, d, b, g.TenCount, rng, Inside(b)))
|
||||
{
|
||||
var h = Db.Heroes.Get(p.heroId);
|
||||
seen.Add(h.id);
|
||||
if (h.isSoldier) Assert.IsTrue(b.soldiers, b.id + " 병사 " + h.id);
|
||||
else Assert.IsTrue(b.pool.Contains(GachaConfig.PoolAll) || b.pool.Contains(h.faction), b.id + " 에서 다른 진영 " + h.id + "(" + h.faction + ")");
|
||||
if (g.PickupExclusive) Assert.IsFalse(GachaRules.IsPickupHero(g, h.id), b.id + " 에서 픽업 영웅 " + h.id);
|
||||
Assert.IsFalse(p.pickup, "상시 배너 = 픽업 표시 없음");
|
||||
}
|
||||
foreach (var id in b.featured) Assert.IsTrue(seen.Contains(id), b.id + " 주요 영웅 " + id + " 가 3000회 안에 나옴");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pickup_FeaturedShare_OfTopTier()
|
||||
{
|
||||
var g = Db.Gacha;
|
||||
var rng = new System.Random(1114);
|
||||
foreach (var b in Banners(true))
|
||||
{
|
||||
var d = RichSave();
|
||||
int n = 4000, hit = 0;
|
||||
for (int k = 0; k < n; k++)
|
||||
{
|
||||
d.gacha.SetPity(b.pityGroup, g.Pity - 1); // 천장 = ★★★ 확정
|
||||
var p = GachaRules.Draw(Db, d, b, 1, rng, Inside(b))[0];
|
||||
Assert.AreEqual(3, p.tier);
|
||||
Assert.AreEqual(b.featured.Contains(p.heroId), p.pickup, p.heroId + " 픽업 표시");
|
||||
if (p.pickup) hit++;
|
||||
}
|
||||
Assert.AreEqual(g.PickupShare, hit / (float)n, 0.03f, b.id + " ★★★ 중 주요 영웅 비율 ≈ pickup_share");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pity_SharedInsideGroup_SeparateAcrossGroups()
|
||||
{
|
||||
var g = Db.Gacha;
|
||||
var rng = new System.Random(1115);
|
||||
foreach (var a in g.banners)
|
||||
{
|
||||
var d = RichSave();
|
||||
var before = new Dictionary<string, int>();
|
||||
foreach (var o in g.banners) before[o.id] = GachaRules.Pity(d, o);
|
||||
var pulls = GachaRules.Draw(Db, d, a, 1, rng, Inside(a));
|
||||
Assert.AreEqual(1, pulls.Count);
|
||||
int expect = pulls[0].tier == 3 ? 0 : 1;
|
||||
foreach (var o in g.banners)
|
||||
{
|
||||
if (o.pityGroup == a.pityGroup) Assert.AreEqual(expect, GachaRules.Pity(d, o), a.id + " → " + o.id + " 같은 묶음 = 같이 셈");
|
||||
else Assert.AreEqual(before[o.id], GachaRules.Pity(d, o), a.id + " → " + o.id + " 다른 묶음 = 그대로");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Period_OutsideIsClosed_NoGemSpent()
|
||||
{
|
||||
var g = Db.Gacha;
|
||||
var rng = new System.Random(1116);
|
||||
foreach (var b in g.banners)
|
||||
{
|
||||
var start = GachaRules.StartOf(b);
|
||||
var end = GachaRules.EndOf(b);
|
||||
if (start == null)
|
||||
{
|
||||
Assert.IsTrue(GachaRules.IsOpen(b, T0) && GachaRules.IsOpen(b, T0.AddYears(5)), b.id + " 기간 없음 = 항상 열림");
|
||||
continue;
|
||||
}
|
||||
Assert.IsTrue(GachaRules.IsOpen(b, start.Value), b.id + " 시작 시각 = 열림");
|
||||
Assert.IsFalse(GachaRules.IsOpen(b, start.Value.AddSeconds(-1)), b.id + " 시작 전");
|
||||
if (end == null) continue;
|
||||
Assert.AreEqual(b.days, (end.Value - start.Value).TotalDays, 1e-6, b.id + " 기간 = days");
|
||||
Assert.IsTrue(GachaRules.IsOpen(b, end.Value.AddSeconds(-1)), b.id + " 끝 직전");
|
||||
Assert.IsFalse(GachaRules.IsOpen(b, end.Value), b.id + " 끝 시각 = 닫힘");
|
||||
Assert.AreEqual(TimeSpan.Zero, GachaRules.Remaining(b, end.Value));
|
||||
var d = RichSave();
|
||||
int gem = d.gem;
|
||||
Assert.IsNotNull(GachaRules.CanDraw(Db, d, b, 1, end.Value), b.id + " 기간 뒤 = 불가 사유");
|
||||
Assert.AreEqual(0, GachaRules.Draw(Db, d, b, 1, rng, end.Value).Count);
|
||||
Assert.AreEqual(gem, d.gem, "닫힌 배너 = 보옥 그대로");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Sanitize_LegacyPity_MovesToStandingGroup()
|
||||
{
|
||||
var standing = Banners(false);
|
||||
if (standing.Count == 0) Assert.Ignore("상시 배너 없음");
|
||||
var d = HeroRules.NewSave(Db, Cfg.playerSpawnPoints.Length, T0);
|
||||
d.gacha.pity = 5; // 구 세이브(배너 하나 시절)
|
||||
HeroRules.Sanitize(Db, d, Cfg.playerSpawnPoints.Length);
|
||||
Assert.AreEqual(0, d.gacha.pity);
|
||||
Assert.AreEqual(5, d.gacha.PityOf(standing[0].pityGroup), "구 천장 → 상시 묶음");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FullGrowth_IsTableMaximum()
|
||||
{
|
||||
foreach (var b in Db.Gacha.banners)
|
||||
foreach (var id in b.featured)
|
||||
{
|
||||
var h = Db.Heroes.Get(id);
|
||||
var full = HeroRules.FullGrowth(Db, h);
|
||||
Assert.AreEqual(Db.Levels.MaxLevel, full.lv, id);
|
||||
Assert.AreEqual(Db.Evolve.MaxStar, full.star, id);
|
||||
Assert.AreEqual("", Db.Promotion.Get(full.grade).nextGrade, id + " 최고 등급 = 다음 등급 없음");
|
||||
int gear = 0;
|
||||
foreach (var set in h.gear) gear += set.slots.Count;
|
||||
Assert.AreEqual(gear, full.gearTotal, id + " 승급 장비 전부");
|
||||
for (int i = 0; i < 2; i++) Assert.AreEqual(SkillRules.Cap(Db, h, full, i), SkillRules.Level(full, i), id + " 스킬 " + i + " = 상한");
|
||||
var top = HeroRules.Compute(Db, Cfg, h, full);
|
||||
var first = HeroRules.Compute(Db, Cfg, h, HeroRules.NewHero(Db, h, 0));
|
||||
Assert.Greater(top.Hp, first.Hp, id);
|
||||
Assert.Greater(top.Atk, first.Atk, id);
|
||||
Assert.Greater(top.power, first.power, id);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 화면 ----------
|
||||
|
||||
IEnumerator Boot()
|
||||
{
|
||||
WorldProgress.Reset();
|
||||
SaveStore.UseMemory(true);
|
||||
SceneManager.LoadScene("Battle");
|
||||
yield return null;
|
||||
yield return null;
|
||||
og = OutGameRoot.Instance;
|
||||
Assert.IsNotNull(og);
|
||||
}
|
||||
|
||||
static List<string> Texts(Transform root)
|
||||
{
|
||||
var l = new List<string>();
|
||||
foreach (var t in root.GetComponentsInChildren<Text>(true)) l.Add(t.text ?? "");
|
||||
return l;
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator Screen_Names_Pages_Flags_Featured_Info_Draw()
|
||||
{
|
||||
yield return Boot();
|
||||
var s = og.Session;
|
||||
var g = s.Db.Gacha;
|
||||
var picks = Banners(true);
|
||||
var standing = Banners(false);
|
||||
if (picks.Count > 0)
|
||||
{
|
||||
var inside = Inside(picks[0]);
|
||||
s.Clock = () => inside; // 픽업 기간 안(날짜가 지나도 같은 화면)
|
||||
}
|
||||
og.OnTab(4);
|
||||
og.Trade.SetPage(0);
|
||||
og.Trade.SelectBanner(GachaRules.Default(s.Db, s.Now).id);
|
||||
yield return null;
|
||||
|
||||
// 이름 = 「명장 등용」 · 「등용」 버튼 · 「뽑기」 글자 없음
|
||||
var texts = Texts(og.Trade.Rt);
|
||||
Assert.IsTrue(texts.Contains("명장 등용"), "거래 탭 이름");
|
||||
Assert.IsTrue(texts.Contains("1회 등용"), "1회 등용 버튼");
|
||||
Assert.IsTrue(texts.Contains(g.TenCount + "회 등용"), "10회 등용 버튼");
|
||||
foreach (var t in texts) Assert.IsFalse(t.Contains("뽑기"), "「뽑기」 글자 남음: " + t);
|
||||
StringAssert.Contains("명장 등용", CollectionCards.InfoText(s.Db.Heroes.Get(g.banners[0].featured[0]), false));
|
||||
|
||||
// 쪽 = 픽업 배너 수 + 상시 묶음 1 · 화살표 = 넘김(끝에서 돈다)
|
||||
Assert.AreEqual(picks.Count + (standing.Count > 0 ? 1 : 0), og.Trade.BannerPageCount);
|
||||
Assert.AreEqual(GachaRules.Default(s.Db, s.Now).id, og.Trade.Banner.id, "처음 = 표 순서의 첫 열린 배너");
|
||||
int page0 = og.Trade.BannerPage;
|
||||
for (int i = 0; i < og.Trade.BannerPageCount; i++) og.Trade.StepBanner(1);
|
||||
Assert.AreEqual(page0, og.Trade.BannerPage, "한 바퀴 = 제자리");
|
||||
|
||||
// 상시 = 깃발마다 그 진영 배너 · 주요 영웅 전부 섬 · 깃발 수 = 상시 배너 수
|
||||
foreach (var b in standing)
|
||||
{
|
||||
og.Trade.SelectBanner(b.id);
|
||||
yield return null;
|
||||
Assert.AreEqual(b.id, og.Trade.Banner.id);
|
||||
Assert.AreEqual(b.featured.Count, og.Trade.FeaturedShown, b.id + " 주요 영웅");
|
||||
Assert.AreEqual(standing.Count, og.Trade.FlagCount, b.id + " 깃발");
|
||||
var names = Texts(og.Trade.Rt);
|
||||
Assert.IsTrue(names.Contains(b.nameKo), b.id + " 제목");
|
||||
}
|
||||
if (standing.Count > 1 && picks.Count > 0)
|
||||
{
|
||||
// 쪽을 넘겼다 돌아오면 고른 진영 그대로
|
||||
string last = standing[standing.Count - 1].id;
|
||||
og.Trade.SelectBanner(last);
|
||||
og.Trade.SelectBanner(picks[0].id);
|
||||
Assert.IsTrue(og.Trade.Banner.IsPickup);
|
||||
for (int i = 0; i < og.Trade.BannerPageCount && og.Trade.Banner.IsPickup; i++) og.Trade.StepBanner(1);
|
||||
Assert.AreEqual(last, og.Trade.Banner.id, "상시 쪽 = 마지막에 고른 진영");
|
||||
}
|
||||
|
||||
// 깃발 버튼을 누르면 그 배너(실제 Button)
|
||||
if (standing.Count > 1)
|
||||
{
|
||||
og.Trade.SelectBanner(standing[0].id);
|
||||
yield return null;
|
||||
Button other = null;
|
||||
foreach (var btn in og.Trade.Rt.GetComponentsInChildren<Button>(false))
|
||||
if (btn.name == "Flag_" + standing[1].id) other = btn;
|
||||
Assert.IsNotNull(other, "깃발 버튼 Flag_" + standing[1].id);
|
||||
other.onClick.Invoke();
|
||||
Assert.AreEqual(standing[1].id, og.Trade.Banner.id);
|
||||
}
|
||||
|
||||
// 픽업 = 상세 정보 팝업(풀 성장 수치 = 표 계산값)
|
||||
if (picks.Count > 0)
|
||||
{
|
||||
var b = picks[0];
|
||||
og.Trade.SelectBanner(b.id);
|
||||
yield return null;
|
||||
Button info = null;
|
||||
foreach (var btn in og.Trade.Rt.GetComponentsInChildren<Button>(false))
|
||||
if (btn.name == "BannerInfo") info = btn;
|
||||
Assert.IsNotNull(info, "상세 정보 버튼");
|
||||
info.onClick.Invoke();
|
||||
Assert.IsTrue(og.ModalVisible, "상세 정보 팝업");
|
||||
Assert.AreEqual(b.featured[0], HeroInfoPopup.LastHeroId);
|
||||
var h = s.Def(b.featured[0]);
|
||||
var want = HeroRules.Compute(s.Db, s.Cfg, h, HeroRules.FullGrowth(s.Db, h));
|
||||
Assert.AreEqual(want.power, HeroInfoPopup.LastStats.power);
|
||||
Assert.AreEqual(want.Hp, HeroInfoPopup.LastStats.Hp);
|
||||
og.CloseModal();
|
||||
}
|
||||
|
||||
// 등용 = 고른 배너의 풀(상시 = 그 진영) · 연출로 이어짐
|
||||
if (standing.Count > 0)
|
||||
{
|
||||
var b = standing[standing.Count - 1];
|
||||
og.Trade.SelectBanner(b.id);
|
||||
s.Save.gem = GachaRules.Cost(s.Db, s.Save, 1) + 1;
|
||||
GachaCutscene.Manual = true;
|
||||
og.Trade.DoDraw(1);
|
||||
Assert.IsNotNull(og.Trade.LastPulls);
|
||||
Assert.AreEqual(1, og.Trade.LastPulls.Count);
|
||||
var got = s.Def(og.Trade.LastPulls[0].heroId);
|
||||
Assert.IsTrue(got.isSoldier || b.pool.Contains(got.faction), b.id + " 에서 " + got.id);
|
||||
Assert.IsTrue(og.Trade.ResultVisible, "등용 결과(연출)");
|
||||
og.Trade.RevealAll();
|
||||
og.Trade.CloseResult();
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator Screen_ClosedPickup_ButtonsOff_Toast()
|
||||
{
|
||||
yield return Boot();
|
||||
var s = og.Session;
|
||||
GachaConfig.Banner b = null;
|
||||
foreach (var x in Banners(true))
|
||||
if (b == null && GachaRules.EndOf(x) != null) b = x;
|
||||
if (b == null) Assert.Ignore("기간 있는 픽업 배너 없음");
|
||||
var after = GachaRules.EndOf(b).Value.AddHours(1);
|
||||
s.Clock = () => after;
|
||||
og.OnTab(4);
|
||||
og.Trade.SetPage(0);
|
||||
og.Trade.SelectBanner(b.id);
|
||||
yield return null;
|
||||
Assert.IsTrue(Texts(og.Trade.Rt).Contains("등용 기간 종료"));
|
||||
int gem = s.Save.gem;
|
||||
og.Trade.DoDraw(1);
|
||||
Assert.IsFalse(og.Trade.ResultVisible);
|
||||
Assert.AreEqual(gem, s.Save.gem);
|
||||
Assert.IsNotEmpty(og.LastToast);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c09e82730962fb5419e7a354c7329c72
|
||||
|
|
@ -190,10 +190,6 @@ SpriteAtlasAsset:
|
|||
- {fileID: 2800000, guid: 32f85565e8b7a1344aabba239537fc17, type: 3}
|
||||
- {fileID: 2800000, guid: 61c36c09211a0b34ca06fdbadaf1f964, type: 3}
|
||||
- {fileID: 2800000, guid: 3bb26b9e5fda01a4f82f328bc0093baa, type: 3}
|
||||
- {fileID: 2800000, guid: eb396bdf579952f49be3d6144ebf7a16, type: 3}
|
||||
- {fileID: 2800000, guid: 13f7d098c72b722409320e5e6631bfe2, type: 3}
|
||||
- {fileID: 2800000, guid: 7ea66fd4d34772748875511032d773e3, type: 3}
|
||||
- {fileID: 2800000, guid: 2ff8381ec78b5ae4b9f9158111fe6fdc, type: 3}
|
||||
- {fileID: 2800000, guid: 7703f9c4343c53843b43fca9b982bfaf, type: 3}
|
||||
- {fileID: 2800000, guid: aea4d32da9d6a6b43b631243c524840c, type: 3}
|
||||
- {fileID: 2800000, guid: def91387a43563446bec42d3bb438eec, type: 3}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,6 @@ SpriteAtlasImporter:
|
|||
secondaryTextureSettings: {}
|
||||
variantMultiplier: 1
|
||||
bindAsDefault: 1
|
||||
userData: 22ab0136a2fbbc1bacc47eea76205049
|
||||
userData: df4c304bcd8447e6d5ed08270766d9b8
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!612988286 &1
|
||||
SpriteAtlasAsset:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name:
|
||||
serializedVersion: 2
|
||||
m_MasterAtlas: {fileID: 0}
|
||||
m_ImporterData:
|
||||
packables:
|
||||
- {fileID: 2800000, guid: eb396bdf579952f49be3d6144ebf7a16, type: 3}
|
||||
- {fileID: 2800000, guid: 13f7d098c72b722409320e5e6631bfe2, type: 3}
|
||||
- {fileID: 2800000, guid: 7ea66fd4d34772748875511032d773e3, type: 3}
|
||||
- {fileID: 2800000, guid: 2ff8381ec78b5ae4b9f9158111fe6fdc, type: 3}
|
||||
- {fileID: 2800000, guid: 277f35bc5cb3e6d48b867fc870307944, type: 3}
|
||||
m_IsVariant: 0
|
||||
m_ScriptablePacker: {fileID: 0}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0168b607c00ba3f40a691a4c8b43f88e
|
||||
SpriteAtlasImporter:
|
||||
externalObjects: {}
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
anisoLevel: 0
|
||||
compressionQuality: 0
|
||||
maxTextureSize: 0
|
||||
textureCompression: 0
|
||||
filterMode: 2
|
||||
generateMipMaps: 1
|
||||
readable: 0
|
||||
crunchedCompression: 0
|
||||
sRGB: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 1024
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
packingSettings:
|
||||
serializedVersion: 2
|
||||
padding: 4
|
||||
blockOffset: 1
|
||||
allowAlphaSplitting: 0
|
||||
enableRotation: 0
|
||||
enableTightPacking: 0
|
||||
enableAlphaDilation: 1
|
||||
secondaryTextureSettings: {}
|
||||
variantMultiplier: 1
|
||||
bindAsDefault: 1
|
||||
userData: 91492ed3673be3d4558551c77e064221
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fa1e1baa32ffaa546907e420b585b9d3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 689 KiB |
|
|
@ -0,0 +1,168 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b2b554a86e7fbd44092b455ec9603057
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: 8473151847371029095
|
||||
second: recruit_bg_other_0
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 1024
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: recruit_bg_other_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1016
|
||||
height: 624
|
||||
alignment: 0
|
||||
pivot: {x: 0, y: 0}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: -1
|
||||
bones: []
|
||||
spriteID: 76abe7666bea69570800000000000000
|
||||
internalID: 8473151847371029095
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 742 KiB |
|
|
@ -0,0 +1,168 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7be45317ea022cb4eb13eb935a6f4975
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: 5707632669473362343
|
||||
second: recruit_bg_shu_0
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 1024
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: recruit_bg_shu_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1016
|
||||
height: 624
|
||||
alignment: 0
|
||||
pivot: {x: 0, y: 0}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: -1
|
||||
bones: []
|
||||
spriteID: 7a1df5c2ca5953f40800000000000000
|
||||
internalID: 5707632669473362343
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 778 KiB |
|
|
@ -0,0 +1,168 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f0f9add628d14e74eb70edfdb0089368
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: -4364464521769003903
|
||||
second: recruit_bg_wei_0
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 1024
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: recruit_bg_wei_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1016
|
||||
height: 624
|
||||
alignment: 0
|
||||
pivot: {x: 0, y: 0}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: -1
|
||||
bones: []
|
||||
spriteID: 184d92134ae4e63c0800000000000000
|
||||
internalID: -4364464521769003903
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 844 KiB |
|
|
@ -0,0 +1,168 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0bfef021aaab1244d87e8b5666d3d9ae
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: -6950898521693107379
|
||||
second: recruit_bg_wu_0
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 1024
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: recruit_bg_wu_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1016
|
||||
height: 624
|
||||
alignment: 0
|
||||
pivot: {x: 0, y: 0}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: -1
|
||||
bones: []
|
||||
spriteID: d4bc1b83f92798f90800000000000000
|
||||
internalID: -6950898521693107379
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 176 KiB |
|
|
@ -0,0 +1,168 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 277f35bc5cb3e6d48b867fc870307944
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: -5351037008746549838
|
||||
second: recruit_map_0
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 1024
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: recruit_map_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 380
|
||||
height: 384
|
||||
alignment: 0
|
||||
pivot: {x: 0, y: 0}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: -1
|
||||
bones: []
|
||||
spriteID: 2bd61b9013c4db5b0800000000000000
|
||||
internalID: -5351037008746549838
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 669 KiB |
|
|
@ -0,0 +1,168 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5279d758233ccdd488a3e2df62689fc9
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: 9126107431752631624
|
||||
second: recruit_pickup_lvbu_0
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 1024
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: recruit_pickup_lvbu_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1016
|
||||
height: 624
|
||||
alignment: 0
|
||||
pivot: {x: 0, y: 0}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: -1
|
||||
bones: []
|
||||
spriteID: 8450ba36b5276ae70800000000000000
|
||||
internalID: 9126107431752631624
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Reference in New Issue