1 Commits

65 changed files with 249671 additions and 251841 deletions

4
.gitignore vendored
View File

@@ -7,8 +7,6 @@
# Claude Code 로컬 설정 — 단, 팀 공유 하네스 설정(settings.json)은 커밋 (RULES.md 참조)
.claude/*
!.claude/settings.json
# 개인 스킬(superpowers) 브레인스토밍/계획 산출물 — 로컬 전용, 협업 공유 X (프로젝트 설계 문서 docs/*.md 는 추적 유지)
docs/superpowers/
# === OS / 에디터 잡파일 ===
Thumbs.db
@@ -25,5 +23,3 @@ AGENTS.md
Environment/
McpScreenshots/
*.log
# 메이커가 재편(reorg) 중 부모를 잃은 엔티티를 모아두는 임시 폴더 (잡파일)
Mislocated/

View File

@@ -1,142 +0,0 @@
{
"Id": "",
"GameId": "",
"EntryKey": "model://uibutton",
"ContentType": "x-mod/model",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.5.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"Version": 1,
"Name": "UIButton",
"BaseModelId": null,
"Id": "uibutton",
"Components": [
"MOD.Core.UITransformComponent",
"MOD.Core.SpriteGUIRendererComponent"
],
"Properties": [
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "RectSize",
"DisplayName": "RectSize",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.UITransformComponent, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "RectSize"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODDataRef, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "ImageRUID",
"DisplayName": "ImageRUID",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteGUIRendererComponent, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "ImageRUID"
}
},
{
"Type": {
"$type": "MODNativeType",
"type": "MOD.Core.MODColor, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Name": "Color",
"DisplayName": "Color",
"ShowInInspector": true,
"Link": {
"Target": {
"$type": "MODNativeType",
"type": "MOD.Core.SpriteGUIRendererComponent, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Property": "Color"
}
}
],
"Values": [
{
"TargetType": "MOD.Core.UITransformComponent",
"Name": "anchoredPosition",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 0.0,
"y": 0.0
}
},
{
"TargetType": "MOD.Core.UITransformComponent",
"Name": "RectSize",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODVector2, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODVector2, MOD.Core",
"x": 200.0,
"y": 75.0
}
},
{
"TargetType": "MOD.Core.UITransformComponent",
"Name": "AlignmentOption",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.AlignmentType, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": 0
},
{
"TargetType": "MOD.Core.SpriteGUIRendererComponent",
"Name": "ImageRUID",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODDataRef, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODDataRef, MOD.Core",
"DataId": "cc3457b8e97b3e14f9d5c39ccdd640bf"
}
},
{
"TargetType": "MOD.Core.SpriteGUIRendererComponent",
"Name": "Color",
"ValueType": {
"$type": "MODNativeType",
"type": "MOD.Core.MODColor, MOD.Core, Version=26.5.0.0, Culture=neutral, PublicKeyToken=null"
},
"Value": {
"$type": "MOD.Core.MODColor, MOD.Core",
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 1.0
}
}
],
"EventLinks": [],
"Children": []
}
}
}

View File

@@ -32,10 +32,10 @@
{
"@type": "script.SlayDeckController",
"Enable": true,
"Energy": 0,
"MaxEnergy": 3,
"Turn": 0,
"TweenEventId": 0
"Energy": 0.0,
"MaxEnergy": 3.0,
"Turn": 0.0,
"TweenEventId": 0.0
}
],
"@version": 1

View File

@@ -77,9 +77,9 @@ slaymaple/
│ ├── player/ # gen-player-lock.mjs(전투맵 입력 잠금) · freeze-turn-player.mjs(모델 이동 정지) · gen-lobby-npc.mjs(LobbyNpc·LobbyMobility codeblock)
│ ├── monster/ # gen-combat-monster.mjs(EnemyId 마커) · freeze-turn-monsters.mjs(필드 AI 정지)
│ ├── balance/ # sim-balance.mjs(전투 밸런스 몬테카를로 시뮬) · sim-balance.test.mjs
│ ├── verify/ # count.mjs·uimap.mjs·cbgap.mjs(산출물 카운트/UIGroup 매핑/재연결 GAP 검증 — 경로 내장)
│ ├── verify/ # count.mjs(산출물 카운트 검증 헬퍼 — 경로 내장)
│ └── git/ # gitea-pr.mjs(UTF-8 안전 PR 생성/수정/머지 — RULES.md 참조)
├── ui/ # UIGroup 7종 — 메이커 저작(Default/Select/Lobby/Run/Deck/Popup/Toast)
├── ui/ # UI 그룹 (DefaultGroup 8.3MB 산출물 / PopupGroup / ToastGroup)
├── docs/
│ ├── slaymaple_basic_framework.md # 전투 프레임워크 설계 문서
│ ├── ui-generation-structure.md # UI 생성 구조 문서
@@ -89,19 +89,11 @@ slaymaple/
└── README.md
```
> ⚠️ **`map/*.map` · `SlayDeckController.codeblock` · `Global/common.gamelogic`는 생성 산출물**입니다 — 직접 편집하면 재생성 때 사라집니다. 게임 로직 변경은 `data/*.json`·`tools/`의 생성기를 고 재생성하세요. **`ui/*.ui`는 메이커 저작**(생성기 미생성)이라 메이커에서만 편집합니다(자세한 규칙은 [`RULES.md`](RULES.md)).
> ⚠️ **`map/*.map` · `ui/DefaultGroup.ui` · `*.codeblock` · `Global/*.gamelogic`는 생성 산출물**입니다 — 직접 편집하면 다음 재생성 때 사라집니다. 게임 변경은 `data/*.json` 또는 `tools/`의 생성기를 고친 뒤 재생성하세요(자세한 규칙은 [`RULES.md`](RULES.md)).
> `.mcp.json`, `.codex/` 는 **Authorization 토큰이 포함**되어 있어 git에서 제외됩니다(`.gitignore`). 각자 로컬에서 직접 구성하세요.
---
## 직업 컨셉
3직업 모두 Slay the Spire 2 차용 + 메이플 IP 재해석. 카드 덱 상세 설계는 [`docs/deck-concept.md`](docs/deck-concept.md) 참조.
- **⚔️ 전사 (탱커, Ironclad 차용)** — **파이터**: 공격을 *연속*으로 내면 콤보가 쌓이고(방어·파워 등 비공격 카드를 쓰면 콤보 리셋) 콤보로 데미지 증가 버프 = 브루저. **페이지**: 위협 디버프로 버티며 방어도 축적 → **바디 슬램(방어 비례 피해)** 카운터. **스피어맨**: 하이퍼바디·아이언월 유지/리치형.
- **🗡️ 도적 (단검·독, Silent 차용)** — 표창 난사 / 독 / 교활·버림. **어쌔신**(표창·크리·흡혈) / **시프**(단검 난타·독). *형 구현 완료(Silent 88장)*.
- **🔮 법사 (약체·게이지, Defect 차용)** — **위자드(불/독)**: 독을 묻히고 *독 걸린 적에 불 카드 → 추가 데미지*(독뎀 시너지). **위자드(썬/콜)**: 오브로 썬더(다중 공격)·콜드(빙결=취약+피해), 오브 획득·다중 소모 운용. **클레릭**: 오브 없이 회복·버프 + 언데드엔 힐로 공격하는 보조 힐러.
## 게임 프레임워크 현황
**StS2풍 덱빌더 로그라이크가 end-to-end로 완성**됐고, 이제 **로비 마을을 기점으로 반복 런**이 돕니다:
@@ -112,7 +104,7 @@ slaymaple/
→ 런 클리어(승천 해금) → 로비 복귀(영혼 정산) → 다음 런 …
```
게임 전체는 `/common` 엔티티에 부착된 **`SlayDeckController` 단일 컴포넌트**로 동작합니다. **UI는 메이커 저작**(7개 UIGroup: Default/Select/Lobby/Run/Deck/Popup/Toast)이고, 컨트롤러가 엔티티 경로(`/ui/<UIGroup>/<Hud>/...`)로 내용을 런타임 주입합니다. 생성기 `tools/deck/gen-slaydeck.mjs`**`SlayDeckController.codeblock` + `common.gamelogic`만 생성**(`.ui` 미접근, 결정적 출력`RULES.md` 참조). 게임 데이터는 **`data/*.json`**, 맵 구조는 **런타임 절차 생성**(`GenerateMap` Lua ↔ `tools/map/rogue-map.mjs` JS 미러).
게임 전체는 `/common` 엔티티에 부착된 **`SlayDeckController` 단일 컴포넌트**로 동작하며, 모든 산출물(`ui/DefaultGroup.ui` · `SlayDeckController.codeblock` · `common.gamelogic`)은 **`tools/deck/gen-slaydeck.mjs` 단일 소스에서 생성**됩니다(결정적 출력, 직접 편집 금지`RULES.md` 참조). 게임 데이터는 **`data/*.json`** 가 단일 소스, 맵 구조는 **런타임 절차 생성**(`GenerateMap` Lua ↔ `tools/map/rogue-map.mjs` JS 미러).
### 구현된 기능 (배포 퀄리티 P1~P15, PR #34~#57)
@@ -133,7 +125,7 @@ slaymaple/
| **밸런스 시뮬** | `tools/balance/sim-balance.mjs` — 전투 규칙 JS 미러(몬테카를로) + `tools/map/rogue-map.mjs`(맵 생성 미러) + node 단위테스트 |
> ⚠️ 수치(적 스탯·경제·승천 배율)는 1차 조정 상태입니다. 정밀 밸런싱은 `sim-balance.mjs`로 검증하며 진행합니다.
> 도적(Silent) 카드 88장은 STS Silent 완역 포트 + **공식 스킬 아이콘 적용 완료**(PR #73). 남은 작업은 카드명 메이플 재서사(어쌔신/시프)·멀티플레이어 전제 카드 싱글 정리 — [`docs/deck-concept.md`](docs/deck-concept.md) 참조.
> 도적(Silent) 카드 88장은 효과·프레임은 적용됐으나 **카드 아이콘(image/fx) 미할당** 상태입니다(전사·마법사 카드는 실 스킬 아이콘 적용 완료).
### 유용한 스크립트 호출
`/common` 엔티티(또는 Play Test 컨텍스트)에서:
@@ -159,7 +151,7 @@ c:AdjustAscension(1) -- 메뉴에서 승천 단계 +1
### 산출물 재생성
```bash
node tools/deck/gen-slaydeck.mjs # 컨트롤러+common (UI는 메이커 저작 — 미생성)
node tools/deck/gen-slaydeck.mjs # 게임 전체(UI·컨트롤러·common·맵 인카운터)
node tools/map/gen-maps.mjs # map01~05 배경/타일
node tools/map/gen-lobby-map.mjs # 로비 맵 + NPC 배치
node tools/player/gen-lobby-npc.mjs # 로비 codeblock(LobbyNpc·LobbyMobility)
@@ -173,7 +165,7 @@ node tools/monster/gen-combat-monster.mjs # 몬스터 EnemyId 마커
## 아키텍처 메모
현재 게임 전체 로직이 `SlayDeckController` 단일 codeblock에 모여 있습니다. 초기 설계의 3분할(`SlayCardCatalog`/`SlayRunState`/`SlayCombatManager`)은 **기능적으로 모두 구현**됐으나 아직 한 컴포넌트 안에 있습니다. 맵 NPC·카메라·입력 잠금 등 **맵 단위 동작은 별도 codeblock**(LobbyNpc/LobbyMobility/MapCamera/PlayerLock/CombatMonster)으로 분리해 각 맵 루트/엔티티에 부착합니다. 카드/적/맵/유물/프레임/카메라 데이터는 `data/*.json`로 외부화돼 있습니다. **2026-06-17**: UI를 단일 `DefaultGroup`에서 7개 UIGroup(Select/Lobby/Run/Deck 등)으로 분리해 **메이커 저작으로 전환** — 생성기는 더 이상 `.ui`를 만들지 않고, 컨트롤러가 새 UIGroup 경로로 재연결됨(옛 UI emit `hud/*`·`gen-cardhand``tools/deck/legacy/` 휴면). 재연결 무결성은 `tools/verify/cbgap.mjs`(GAP 0)로 검증.
현재 게임 전체 로직이 `SlayDeckController` 단일 codeblock에 모여 있습니다. 초기 설계의 3분할(`SlayCardCatalog`/`SlayRunState`/`SlayCombatManager`)은 **기능적으로 모두 구현**됐으나 아직 한 컴포넌트 안에 있습니다. 맵 NPC·카메라·입력 잠금 등 **맵 단위 동작은 별도 codeblock**(LobbyNpc/LobbyMobility/MapCamera/PlayerLock/CombatMonster)으로 분리해 각 맵 루트/엔티티에 부착합니다. 카드/적/맵/유물/프레임/카메라 데이터는 `data/*.json`로 외부화돼 있습니다.
> ⚠️ **전투 규칙과 맵 생성은 Lua(gen-slaydeck 내장)와 JS 미러(sim-balance/rogue-map)로 이중 구현**입니다. 한쪽을 고치면 반드시 다른 쪽도 동기화하고 테스트하세요(`RULES.md` §6).
@@ -181,7 +173,6 @@ node tools/monster/gen-combat-monster.mjs # 몬스터 EnemyId 마커
## 향후 개선 계획 (후속 후보)
- [x] 전투 루프 · 런 루프 · 절차 생성 맵 · 상점/휴식/유물 방 · 유물 19종 · 물약 · 버프/디버프 · Power · 전직(전사/법사/도적 2차) · 승천+개인 저장 · 전투 모션 · 커스텀 프레임 · **반복 런·로비 맵·NPC·영혼·메소·카메라 추종 (P1~P15 완료)**
- [x] **UI 메이커-저작 전환** — 단일 DefaultGroup → 7개 UIGroup 분리, 생성기 UI 저작 폐기(`tools/deck/legacy/`), 컨트롤러 경로 재연결(cbgap GAP 0), MainMenu→로비→run NPC→charselect 부트 흐름 (2026-06-17)
- [ ] **도적 카드 아이콘** — Silent 88장에 실 스킬 아이콘(image/fx) 할당, 2차 전직 설명 한글화
- [ ] **런 이어하기** — 진행 중 런 직렬화 저장(UserDataStorage 확장, 메뉴 "이어하기" 활성화)
- [ ] **카드 제거/업그레이드** — 상점 카드 제거 슬롯, 휴식 노드에서 카드 강화

View File

@@ -11,8 +11,8 @@ Claude Code는 `CLAUDE.md`가 이 파일을 임포트하므로 자동 적용된
| 산출물 (절대 Read/Edit 금지) | 크기 | 단일 소스 (여기만 편집) | 재생성 명령 |
|---|---|---|---|
| `ui/*.ui` (Default·Select·Lobby·Run·Deck·Popup·Toast UIGroup 7종) | 9KB~4.5MB | **메이커 저작 (생성기 미생성, 2026-06-17~)** — 메이커에서 시각 편집 | (없음) |
| `RootDesk/MyDesk/SlayDeckController.codeblock` | ~270KB | `data/*.json` + `tools/deck/`(`gen-slaydeck.mjs`+`lib/`+`cb/`) | `node tools/deck/gen-slaydeck.mjs` |
| `ui/DefaultGroup.ui` | **~7.1MB** | `data/*.json` + `tools/deck/`(`gen-slaydeck.mjs`+`lib/`+`hud/`) | `node tools/deck/gen-slaydeck.mjs` |
| `RootDesk/MyDesk/SlayDeckController.codeblock` | ~270KB | 〃 | 〃 |
| `Global/common.gamelogic` | ~1KB | 〃 | 〃 |
| `map/map01.map`~`map05.map`, `map/lobby.map` | 각 ~210KB | `tools/map/`·`tools/monster/`·`tools/camera/`·`tools/player/` (↓ 보조 생성기) | 해당 생성기 |
| `RootDesk/MyDesk/CombatMonster.codeblock` | ~2KB | `tools/monster/gen-combat-monster.mjs` | `node tools/monster/gen-combat-monster.mjs` |
@@ -21,11 +21,10 @@ Claude Code는 `CLAUDE.md`가 이 파일을 임포트하므로 자동 적용된
| `RootDesk/MyDesk/LobbyNpc.codeblock`·`LobbyMobility.codeblock` | 각 ~2-3KB | `tools/player/gen-lobby-npc.mjs` | `node tools/player/gen-lobby-npc.mjs` |
| `Global/SectorConfig.config` | ~1KB | `tools/map/gen-maps.mjs`·`gen-lobby-map.mjs` (패치) | 해당 생성기 |
- `.claude/settings.json`의 permissions.deny가 위 파일의 Read/Edit/Write 도구 사용을 차단한다 (이 저장소를 열면 자동 적용). deny는 **glob**`ui/*.ui`·`map/*.map`·`RootDesk/MyDesk/*.codeblock`·`Global/common.gamelogic`·`Global/SectorConfig.config`. 따라서 **메이커 저작 codeblock/UI**(`Monster`·`MonsterAttack`·`PlayerAttack`·`PlayerHit`·`UIPopup`·`UIToast`.codeblock, **그리고 모든 `ui/*.ui`** — UI는 6개 UIGroup으로 메이커 저작)**도** Read/Edit 금지 — 이들은 생성기가 없으니 **메이커에서** 편집한다(텍스트 도구로 X). codeblock은 한 줄짜리 JSON이라 Read 시 토큰 폭발.
- **게임 로직 수정** = `tools/deck/gen-slaydeck.mjs`(오케스트레이터) + `tools/deck/cb/*.mjs`(codeblock Lua) 또는 `data/*.json`(데이터) 수정 → 재생성(`SlayDeckController.codeblock`+`common.gamelogic`만, **`.ui` 미접근**) → 통째로 커밋. **UI 수정 = 메이커에서**(생성기는 UI를 안 만든다).
- **codeblock 메서드(Lua)는 기능별 모듈** `tools/deck/cb/*.mjs`(boot·state·combat·hand·deckview·items·map·shop 등 17종). **공유분**: 상수·데이터·lua 테이블 = `tools/deck/lib/{ui-helpers,data,codeblock}.mjs`(cb가 import — `MAX_MONSTERS`=4 등). prop 103개는 오케스트레이터 `writeCodeblocks`에 유지. 특정 메서드 수정은 `cb/<name>.mjs`만(의존: orchestrator→cb→lib 단방향). **cb 모듈은 원본 메서드 순서 보존이 바이트동일 조건**. **UI emit(옛 `hud/*.mjs` 15종·`gen-cardhand.mjs`)은 `tools/deck/legacy/`로 이관 — 휴면(생성기 미사용)**: UI가 메이커 저작이라 생성기가 안 만든다. (롤백용 `legacy/upsert-ui.mjs`는 직접 실행 시에만 옛 `DefaultGroup.ui`를 재생성.)
- `.claude/settings.json`의 permissions.deny가 위 파일의 Read/Edit/Write 도구 사용을 차단한다 (이 저장소를 열면 자동 적용). deny는 **glob**`ui/*.ui`·`map/*.map`·`RootDesk/MyDesk/*.codeblock`·`Global/common.gamelogic`·`Global/SectorConfig.config`. 따라서 **메이커 저작 codeblock/UI**(`Monster`·`MonsterAttack`·`PlayerAttack`·`PlayerHit`·`UIPopup`·`UIToast`.codeblock, `ui/PopupGroup.ui`·`ui/ToastGroup.ui`)**도** Read/Edit 금지 — 이들은 생성기가 없으니 **메이커에서** 편집한다(텍스트 도구로 X). codeblock은 한 줄짜리 JSON이라 Read 시 토큰 폭발.
- 게임 로직·UI 수정 = **`tools/deck/gen-slaydeck.mjs`(오케스트레이터 + codeblock Lua) 또는 `data/*.json`(데이터) 수정** → 재생성 → 산출물은 통째로 커밋.
- **UI emit은 HUD별 모듈** `tools/deck/hud/*.mjs`(charselect·shop·combat·map·deckall·soulshop 등 16종), **codeblock 메서드(Lua)는 기능별 모듈** `tools/deck/cb/*.mjs`(boot·state·combat·hand·deckview·items·map·shop 등 17종, 메서드 161개를 연속런으로). **공유분**: UI 헬퍼·상수·데이터·lua 테이블 = `tools/deck/lib/ui-helpers.mjs`·`tools/deck/lib/data.mjs`, method/prop/codeblock 헬퍼·writeCodeblocks 상수 = `tools/deck/lib/codeblock.mjs`. 특정 화면 UI 수정은 `hud/<name>.mjs`, 특정 메서드 수정은 `cb/<name>.mjs`만(의존: orchestrator→{hud,cb}→lib 단방향). prop 103개는 오케스트레이터 writeCodeblocks에 유지. **cb 모듈은 원본 메서드 순서 보존이 바이트동일 조건** — 새 메서드는 해당 기능 모듈의 알맞은 위치에 추가하고 writeCodeblocks의 spread 순서 유지.
- 리팩터 시 **출력 바이트-동일 검증**: `node tools/deck/gen-slaydeck.mjs``node tools/verify/diffcheck.mjs [ref]`(워킹트리 vs ref(기본 HEAD) 줄바꿈 정규화 비교 — 산출물 경로를 명령줄에 노출 안 해 deny 회피). 산출물 ` M`은 보통 autocrlf churn이니 `git checkout --`로 복원.
- **UI 전면 메이커 저작 (2026-06-17~)**: 단일 `DefaultGroup`을 7개 UIGroup으로 분리 — `DefaultGroup`(MainMenu+월드조작), `SelectUIGroup`(charselect/job), `LobbyUIGroup`(lobby/board/soulshop), `RunUIGroup`(combat/map/shop/rest/treasure/reward/cardhand/deck), `DeckUIGroup`(덱 도감), `PopupGroup`·`ToastGroup`. 컨트롤러(`cb/*.mjs`)는 엔티티 **경로**(`/ui/<UIGroup>/<Hud>/...`)로 텍스트·이미지·표시숨김·상태기반 위치/크기/색을 **런타임 주입**(레이아웃=메이커, 내용=컨트롤러 — 메이커가 이 경로 유지 필수). 몬스터 슬롯 = `RunUIGroup/CombatHud/MonsterStatus{1..4}`(자식 Name·Hp·Intent·HpBarFill·Buffs·BlockBadge·TargetMarker; TargetFrame 없음). **부트 흐름**: `OnBeginPlay`→MainMenu→(`MainMenu/NewGameButton`)→로비→run NPC(`OnLobbyNpcInteract` id=="run")→charselect→런. **재연결 검증**: `node tools/verify/cbgap.mjs`(cb 참조 경로↔.ui GAP 0이어야) + 재생성 후 `git status -- ui/` 변경 0(생성기 .ui 미접근 증명). 섹션→UIGroup 일괄 remap 마이그레이션은 `tools/deck/reconnect-ui-paths.mjs`(멱등). UIGroup별 .ui 분포 확인은 `tools/verify/uimap.mjs`.
- **머지 충돌(gen-slaydeck.mjs)**: 다른 브랜치가 단일체를 수정해 충돌나면, 그쪽 버전(`git checkout --theirs tools/deck/gen-slaydeck.mjs`)을 취해 **콘텐츠 마커 기반으로 재모듈화**(라인인덱스 X — 줄 추가에 안전·export 이름 자동 파생·`const x=[]` 직전 전문 상수 walk-back 포함) 후 `node tools/verify/diffcheck.mjs origin/main`으로 ui·codeblock 바이트-동일 확인(손실 0 증명). codeblock 메서드·patchCommon은 오케스트레이터 잔류라 그쪽 변경은 자동 보존됨.
- **보조 생성기**(각자 자기 산출물의 단일 소스 — 위 표의 메인 `gen-slaydeck.mjs` 외):
- `tools/camera/gen-camera.mjs``MapCamera.codeblock` + map01~05 카메라 부착 (값 `data/camera.json`)
@@ -37,7 +36,7 @@ Claude Code는 `CLAUDE.md`가 이 파일을 임포트하므로 자동 적용된
- `tools/player/gen-player-lock.mjs``PlayerLock.codeblock` + map01~05 부착
- `tools/player/gen-lobby-npc.mjs``LobbyNpc.codeblock`·`LobbyMobility.codeblock`
- `tools/player/freeze-turn-player.mjs``Global/DefaultPlayer.model` 이동 0 고정
- (옛 `tools/deck/gen-cardhand.mjs`·`hud/*.mjs``tools/deck/legacy/`로 이관 — 휴면, UI 메이커 저작 전환)
- `tools/deck/gen-cardhand.mjs``DefaultGroup.ui` 카드핸드 보조 패처
## 2. 산출물 검증은 카운트로, 내용 출력 금지

View File

@@ -1,23 +0,0 @@
{
"Id": "",
"GameId": "",
"EntryKey": "sprite://7629b520ced54d508b040681d06741fb",
"ContentType": "x-mod/sprite",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.5.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"upload_keyname": "30/54/30542a379cb74d2d807104635740a8ea/sprite/7629b520ced54d508b040681d06741fb/639172208899179951",
"upload_hash": "C84DCE36101CF3F05E74F93F9B416E7D08D8B78B699E22CF8A6784994115DDAE",
"name": "Character select bg",
"resource_guid": "7629b520ced54d508b040681d06741fb",
"resource_version": "6a316d1a3d5de2eb0c7d345b"
}
}
}

View File

@@ -1,36 +0,0 @@
{
"Id": "",
"GameId": "",
"EntryKey": "codeblock://4a220aa8-e014-4c7b-8234-fff8c5c66686",
"ContentType": "x-mod/codeblock",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.5.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"CoreVersion": {
"Major": 0,
"Minor": 2
},
"ScriptVersion": {
"Major": 1,
"Minor": 1
},
"Description": "",
"Id": "4a220aa8-e014-4c7b-8234-fff8c5c66686",
"Language": 1,
"Name": "MapleTree",
"Type": 1,
"Source": 0,
"Target": null,
"Properties": [],
"Methods": [],
"EntityEventHandlers": []
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,23 +0,0 @@
{
"Id": "",
"GameId": "",
"EntryKey": "sprite://41ad73da083d41b0ae30bf7b86794376",
"ContentType": "x-mod/sprite",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.5.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"upload_keyname": "30/54/30542a379cb74d2d807104635740a8ea/sprite/41ad73da083d41b0ae30bf7b86794376/639172145413258274",
"upload_hash": "CFC620F96E1621FEE5594456FC8A4157BC6EF0D3E7661C5543293200FD364A85",
"name": "Thumnail",
"resource_guid": "41ad73da083d41b0ae30bf7b86794376",
"resource_version": "6a31544d335c959bb11f45eb"
}
}
}

View File

@@ -1,23 +0,0 @@
{
"Id": "",
"GameId": "",
"EntryKey": "sprite://477679b832b44e099a30e4905078dbcb",
"ContentType": "x-mod/sprite",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.5.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"upload_keyname": "30/54/30542a379cb74d2d807104635740a8ea/sprite/477679b832b44e099a30e4905078dbcb/639172226341792721",
"upload_hash": "3E30B07C24C4BC4E373CDEA653035146D2F50ACC6484F6E9DA34E6179BB38F15",
"name": "restBgImage",
"resource_guid": "477679b832b44e099a30e4905078dbcb",
"resource_version": "6a3173ea002bbe95706406b6"
}
}
}

View File

@@ -1,23 +0,0 @@
{
"Id": "",
"GameId": "",
"EntryKey": "sprite://28f3b10ac0334fbfbf29677bf963c57a",
"ContentType": "x-mod/sprite",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.5.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"upload_keyname": "30/54/30542a379cb74d2d807104635740a8ea/sprite/28f3b10ac0334fbfbf29677bf963c57a/639172222414073214",
"upload_hash": "01BE0B58F480BA86DA1D18BFE25C01E1B27219A14FE2DCD73456A7A48553CF15",
"name": "shopBgImage",
"resource_guid": "28f3b10ac0334fbfbf29677bf963c57a",
"resource_version": "6a3172612c6a274be88a130e"
}
}
}

View File

@@ -1,23 +0,0 @@
{
"Id": "",
"GameId": "",
"EntryKey": "sprite://dd6193fd37da4b12bcdbcdcf2fbe8e40",
"ContentType": "x-mod/sprite",
"Content": "",
"Usage": 0,
"UsePublish": 1,
"UseService": 0,
"CoreVersion": "26.5.0.0",
"StudioVersion": "0.1.0.0",
"DynamicLoading": 0,
"ContentProto": {
"Use": "Json",
"Json": {
"upload_keyname": "30/54/30542a379cb74d2d807104635740a8ea/sprite/dd6193fd37da4b12bcdbcdcf2fbe8e40/639172228832890845",
"upload_hash": "3EDD046B291806637ADD12A77BF94CF00BDD9F4F9912C132B14323D9DE5F297C",
"name": "treasureBgImage",
"resource_guid": "dd6193fd37da4b12bcdbcdcf2fbe8e40",
"resource_version": "6a3174e32a2802c06419f288"
}
}
}

View File

@@ -378,8 +378,7 @@
"rarity": "normal",
"desc": "피해를 3 줍니다. 약화를 1 부여합니다.",
"weak": 1,
"damage": 3,
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
"damage": 3
},
"SilentStrike": {
"name": "타격",
@@ -388,8 +387,7 @@
"class": "bandit",
"rarity": "normal",
"desc": "피해를 6 줍니다.",
"damage": 6,
"image": "92a5020c978c46bdabab910598118b86"
"damage": 6
},
"Survivor": {
"name": "생존자",
@@ -399,8 +397,7 @@
"rarity": "normal",
"desc": "방어도를 8 얻습니다. 카드를 1장 버립니다.",
"block": 8,
"discard": 1,
"image": "49c8f279bfa64bf3954037f17da0052d"
"discard": 1
},
"SilentDefend": {
"name": "수비",
@@ -409,8 +406,7 @@
"class": "bandit",
"rarity": "normal",
"desc": "방어도를 5 얻습니다.",
"block": 5,
"image": "0946f69d84464df29b24b94c744c868d"
"block": 5
},
"Slice": {
"name": "칼질",
@@ -419,8 +415,7 @@
"class": "bandit",
"rarity": "normal",
"desc": "피해를 6 줍니다.",
"damage": 6,
"image": "92a5020c978c46bdabab910598118b86"
"damage": 6
},
"Shiv": {
"name": "표창",
@@ -431,8 +426,7 @@
"desc": "피해를 4 줍니다. 소멸.",
"damage": 4,
"exhaust": true,
"token": true,
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
"token": true
},
"DaggerSpray": {
"name": "단검 분사",
@@ -443,8 +437,7 @@
"desc": "모든 적에게 피해를 4만큼 2번 줍니다.",
"aoe": true,
"damage": 4,
"hits": 2,
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
"hits": 2
},
"DaggerThrow": {
"name": "단검 투척",
@@ -455,8 +448,7 @@
"desc": "피해를 9 줍니다. 카드를 1장 뽑습니다. 카드를 1장 버립니다.",
"draw": 1,
"damage": 9,
"discard": 1,
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
"discard": 1
},
"PoisonedStab": {
"name": "독 찌르기",
@@ -466,8 +458,7 @@
"rarity": "normal",
"desc": "피해를 6 줍니다. 중독을 3 부여합니다.",
"poison": 3,
"damage": 6,
"image": "19361e72087946b1888684185b40d935"
"damage": 6
},
"SuckerPunch": {
"name": "불의의 일격",
@@ -477,8 +468,7 @@
"rarity": "normal",
"desc": "피해를 8 줍니다. 약화를 1 부여합니다.",
"weak": 1,
"damage": 8,
"image": "92a5020c978c46bdabab910598118b86"
"damage": 8
},
"LeadingStrike": {
"name": "선제 타격",
@@ -488,8 +478,7 @@
"rarity": "normal",
"desc": "피해를 3 줍니다. 표창을 2장 손으로 가져옵니다.",
"damage": 3,
"addShiv": 2,
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
"addShiv": 2
},
"FollowThrough": {
"name": "완수",
@@ -498,8 +487,7 @@
"class": "bandit",
"rarity": "normal",
"desc": "피해를 7 줍니다. 손에 다른 카드가 5장 이상 있다면, 1번 추가로 적중합니다.",
"damage": 7,
"image": "92a5020c978c46bdabab910598118b86"
"damage": 7
},
"FlickFlack": {
"name": "재주넘기",
@@ -510,8 +498,7 @@
"desc": "교활. 모든 적에게 피해를 6 줍니다.",
"aoe": true,
"damage": 6,
"sly": true,
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
"sly": true
},
"Ricochet": {
"name": "도탄",
@@ -522,8 +509,7 @@
"desc": "교활. 무작위 적에게 피해를 3만큼 4번 줍니다.",
"damage": 3,
"hits": 4,
"sly": true,
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
"sly": true
},
"Prepared": {
"name": "예비",
@@ -533,8 +519,7 @@
"rarity": "normal",
"desc": "카드를 1장 뽑습니다. 카드를 1장 버립니다.",
"draw": 1,
"discard": 1,
"image": "c1e19219745e44c39ae6ac2f77e347d9"
"discard": 1
},
"Anticipate": {
"name": "예측",
@@ -543,8 +528,7 @@
"class": "bandit",
"rarity": "normal",
"desc": "이번 턴 동안 민첩을 2 얻습니다.",
"dex": 2,
"image": "49c8f279bfa64bf3954037f17da0052d"
"dex": 2
},
"Deflect": {
"name": "튕겨내기",
@@ -553,8 +537,7 @@
"class": "bandit",
"rarity": "normal",
"desc": "방어도를 4 얻습니다.",
"block": 4,
"image": "0946f69d84464df29b24b94c744c868d"
"block": 4
},
"BladeDance": {
"name": "검무",
@@ -564,8 +547,7 @@
"rarity": "normal",
"desc": "표창을 3장 손으로 가져옵니다. 소멸.",
"addShiv": 3,
"exhaust": true,
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
"exhaust": true
},
"Backflip": {
"name": "공중제비",
@@ -575,8 +557,7 @@
"rarity": "normal",
"desc": "방어도를 5 얻습니다. 카드를 2장 뽑습니다.",
"block": 5,
"draw": 2,
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
"draw": 2
},
"DodgeAndRoll": {
"name": "구르기",
@@ -585,8 +566,7 @@
"class": "bandit",
"rarity": "normal",
"desc": "방어도를 4 얻습니다. 다음 턴에, 방어도를 4 얻습니다",
"block": 4,
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
"block": 4
},
"PiercingWail": {
"name": "귀를 찢는 비명",
@@ -595,8 +575,7 @@
"class": "bandit",
"rarity": "normal",
"desc": "이번 턴 동안 모든 적이 힘을 6 잃습니다. 소멸.",
"draw": 1,
"image": "0946f69d84464df29b24b94c744c868d"
"draw": 1
},
"CloakAndDagger": {
"name": "망토와 단검",
@@ -606,8 +585,7 @@
"rarity": "normal",
"desc": "방어도를 6 얻습니다. 표창을 1장 손으로 가져옵니다.",
"block": 6,
"addShiv": 1,
"image": "0946f69d84464df29b24b94c744c868d"
"addShiv": 1
},
"DeadlyPoison": {
"name": "맹독",
@@ -616,8 +594,7 @@
"class": "bandit",
"rarity": "normal",
"desc": "중독을 5 부여합니다.",
"poison": 5,
"image": "19361e72087946b1888684185b40d935"
"poison": 5
},
"Snakebite": {
"name": "뱀 물기",
@@ -627,8 +604,7 @@
"rarity": "normal",
"desc": "보존. 중독을 7 부여합니다.",
"poison": 7,
"retain": true,
"image": "19361e72087946b1888684185b40d935"
"retain": true
},
"Untouchable": {
"name": "범접 불가",
@@ -638,8 +614,7 @@
"rarity": "normal",
"desc": "교활. 방어도를 6 얻습니다.",
"block": 6,
"sly": true,
"image": "0946f69d84464df29b24b94c744c868d"
"sly": true
},
"Skewer": {
"name": "꼬챙이",
@@ -648,8 +623,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "피해를 8만큼 X번 줍니다.",
"draw": 1,
"image": "92a5020c978c46bdabab910598118b86"
"draw": 1
},
"Backstab": {
"name": "배신",
@@ -658,8 +632,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "선천성. 피해를 11 줍니다. 소멸.",
"damage": 11,
"image": "b1360ed0c4b942309d240634b8f36872"
"damage": 11
},
"PreciseCut": {
"name": "정밀한 베기",
@@ -668,8 +641,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "피해를 13 줍니다. 손에 있는 다른 카드 1장당 피해량이 2 감소합니다.",
"damage": 13,
"image": "92a5020c978c46bdabab910598118b86"
"damage": 13
},
"Finisher": {
"name": "마무리",
@@ -678,8 +650,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "이번 턴에 사용한 공격 카드 1장당 피해를 6 줍니다.",
"damage": 6,
"image": "b1360ed0c4b942309d240634b8f36872"
"damage": 6
},
"MementoMori": {
"name": "메멘토 모리",
@@ -688,8 +659,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "피해를 9 줍니다. 이번 턴에 버린 카드 1장당 피해량이 4 증가합니다.",
"damage": 9,
"image": "0946f69d84464df29b24b94c744c868d"
"damage": 9
},
"Strangle": {
"name": "목 조르기",
@@ -698,8 +668,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "피해를 8 줍니다. 이번 턴에 카드를 사용할 때마다, 대상 적이 체력을 2 잃습니다.",
"damage": 8,
"image": "92a5020c978c46bdabab910598118b86"
"damage": 8
},
"Flechettes": {
"name": "프레췌",
@@ -708,8 +677,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "손에 있는 스킬 카드 1장당 피해를 5 줍니다.",
"damage": 5,
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
"damage": 5
},
"Pounce": {
"name": "덮치기",
@@ -718,8 +686,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "피해를 12 줍니다. 다음에 사용하는 스킬 카드의 비용이 0 이 됩니다.",
"damage": 12,
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
"damage": 12
},
"Dash": {
"name": "돌진",
@@ -729,8 +696,7 @@
"rarity": "unique",
"desc": "방어도를 10 얻습니다. 피해를 10 줍니다.",
"block": 10,
"damage": 10,
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
"damage": 10
},
"Predator": {
"name": "천적",
@@ -740,8 +706,7 @@
"rarity": "unique",
"desc": "피해를 15 줍니다. 다음 턴에, 카드를 2장 뽑습니다.",
"draw": 2,
"damage": 15,
"image": "b1360ed0c4b942309d240634b8f36872"
"damage": 15
},
"Pinpoint": {
"name": "정밀 사격",
@@ -750,8 +715,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "피해를 15 줍니다. 이번 턴에 스킬을 사용할 때마다 비용이 1 감소합니다.",
"damage": 15,
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
"damage": 15
},
"CalculatedGamble": {
"name": "계산된 도박",
@@ -760,8 +724,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "손에 있는 모든 카드를 버린 뒤, 버린 카드의 수만큼 카드를 뽑습니다. 소멸.",
"draw": 1,
"image": "c1e19219745e44c39ae6ac2f77e347d9"
"draw": 1
},
"Expose": {
"name": "들춰내기",
@@ -770,8 +733,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "대상 적의 모든 인공물과 방어도를 제거합니다. 취약을 2 부여합니다. 소멸.",
"vuln": 2,
"image": "0946f69d84464df29b24b94c744c868d"
"vuln": 2
},
"HiddenDaggers": {
"name": "숨겨진 단검",
@@ -781,8 +743,7 @@
"rarity": "unique",
"desc": "카드를 2장 버립니다. 표창을 2장 손으로 가져옵니다.",
"discard": 2,
"addShiv": 2,
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
"addShiv": 2
},
"EscapePlan": {
"name": "탈출구",
@@ -792,8 +753,7 @@
"rarity": "unique",
"desc": "카드를 1장 뽑습니다. 뽑은 카드가 스킬 카드라면, 방어도를 3 얻습니다.",
"block": 3,
"draw": 1,
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
"draw": 1
},
"Acrobatics": {
"name": "곡예",
@@ -803,8 +763,7 @@
"rarity": "unique",
"desc": "카드를 3장 뽑습니다. 카드를 1장 버립니다.",
"draw": 3,
"discard": 1,
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
"discard": 1
},
"HandTrick": {
"name": "손기술",
@@ -813,8 +772,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "방어도를 7 얻습니다. 이번 턴 동안 손에 있는 스킬 카드 1장에 교활을 추가합니다.",
"block": 7,
"image": "c1e19219745e44c39ae6ac2f77e347d9"
"block": 7
},
"Mirage": {
"name": "신기루",
@@ -823,8 +781,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "모든 적에게 부여된 중독과 동일한 만큼의 방어도를 얻습니다. 소멸.",
"draw": 1,
"image": "0946f69d84464df29b24b94c744c868d"
"draw": 1
},
"Expertise": {
"name": "전문성",
@@ -833,8 +790,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "손에 있는 카드가 6장이 될 때까지 카드를 뽑습니다.",
"draw": 1,
"image": "c1e19219745e44c39ae6ac2f77e347d9"
"draw": 1
},
"BubbleBubble": {
"name": "차오르는 독",
@@ -843,8 +799,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "적이 중독을 보유하고 있다면, 중독을 9 부여합니다.",
"poison": 9,
"image": "19361e72087946b1888684185b40d935"
"poison": 9
},
"Blur": {
"name": "흐릿함",
@@ -853,8 +808,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "방어도를 5 얻습니다. 다음 턴 시작 시 방어도가 사라지지 않습니다.",
"block": 5,
"image": "0946f69d84464df29b24b94c744c868d"
"block": 5
},
"LegSweep": {
"name": "다리 걸기",
@@ -864,8 +818,7 @@
"rarity": "unique",
"desc": "약화를 2 부여합니다. 방어도를 11 얻습니다.",
"block": 11,
"weak": 2,
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
"weak": 2
},
"UpMySleeve": {
"name": "비책",
@@ -874,8 +827,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "표창을 3장 손으로 가져옵니다. 이 카드의 비용이 1 감소합니다.",
"addShiv": 3,
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
"addShiv": 3
},
"BouncingFlask": {
"name": "탄성 플라스크",
@@ -884,8 +836,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "무작위 적에게 중독을 3만큼 3번 부여합니다.",
"poison": 9,
"image": "19361e72087946b1888684185b40d935"
"poison": 9
},
"Reflex": {
"name": "반사신경",
@@ -895,8 +846,7 @@
"rarity": "unique",
"desc": "교활. 카드를 2장 뽑습니다.",
"draw": 2,
"sly": true,
"image": "49c8f279bfa64bf3954037f17da0052d"
"sly": true
},
"Haze": {
"name": "아지랑이",
@@ -906,8 +856,7 @@
"rarity": "unique",
"desc": "교활. 모든 적에게 중독을 4 부여합니다.",
"poison": 4,
"sly": true,
"image": "19361e72087946b1888684185b40d935"
"sly": true
},
"Tactician": {
"name": "전략가",
@@ -918,8 +867,7 @@
"desc": "교활. 을 얻습니다.",
"powerEffect": "energyPerTurn",
"value": 1,
"sly": true,
"image": "c1e19219745e44c39ae6ac2f77e347d9"
"sly": true
},
"WellLaidPlans": {
"name": "괜찮은 전략",
@@ -929,8 +877,7 @@
"rarity": "unique",
"desc": "내 턴 종료 시, 카드를 최대 1장까지 보존합니다.",
"powerEffect": "blockPerTurn",
"value": 2,
"image": "c1e19219745e44c39ae6ac2f77e347d9"
"value": 2
},
"InfiniteBlades": {
"name": "무한의 검날",
@@ -939,8 +886,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "내 턴 시작 시, 표창을 1장 손으로 가져옵니다.",
"turnStartShiv": 1,
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
"turnStartShiv": 1
},
"Footwork": {
"name": "발놀림",
@@ -949,8 +895,7 @@
"class": "bandit",
"rarity": "unique",
"desc": "민첩을 2 얻습니다.",
"dex": 2,
"image": "49c8f279bfa64bf3954037f17da0052d"
"dex": 2
},
"Outbreak": {
"name": "발병",
@@ -962,8 +907,7 @@
"aoe": true,
"powerEffect": "strengthPerTurn",
"value": 1,
"damage": 11,
"image": "19361e72087946b1888684185b40d935"
"damage": 11
},
"NoxiousFumes": {
"name": "유독 가스",
@@ -974,8 +918,7 @@
"desc": "내 턴 시작 시, 모든 적에게 중독을 2 부여합니다.",
"poison": 2,
"powerEffect": "strengthPerTurn",
"value": 1,
"image": "19361e72087946b1888684185b40d935"
"value": 1
},
"Accuracy": {
"name": "정밀",
@@ -985,8 +928,7 @@
"rarity": "unique",
"desc": "표창의 피해량이 4 증가합니다.",
"powerEffect": "strengthPerTurn",
"value": 1,
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
"value": 1
},
"PhantomBlades": {
"name": "환영검",
@@ -996,8 +938,7 @@
"rarity": "unique",
"desc": "표창이 보존을 얻습니다. 매 턴마다 처음으로 사용하는 표창의 피해량이 9 증가합니다.",
"powerEffect": "strengthPerTurn",
"value": 1,
"image": "0946f69d84464df29b24b94c744c868d"
"value": 1
},
"Speedster": {
"name": "스피드스터",
@@ -1009,8 +950,7 @@
"aoe": true,
"powerEffect": "strengthPerTurn",
"value": 1,
"damage": 2,
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
"damage": 2
},
"GrandFinale": {
"name": "대단원의 막",
@@ -1020,8 +960,7 @@
"rarity": "legend",
"desc": "뽑을 카드 더미에 카드가 없을 때만 사용할 수 있습니다. 모든 적에게 피해를 60 줍니다.",
"aoe": true,
"damage": 60,
"image": "dbdbb1b56ae54672ae68ac6882fff6a2"
"damage": 60
},
"Assassinate": {
"name": "암살",
@@ -1031,8 +970,7 @@
"rarity": "legend",
"desc": "선천성. 피해를 10 줍니다. 취약을 1 부여합니다. 소멸.",
"vuln": 1,
"damage": 10,
"image": "b1360ed0c4b942309d240634b8f36872"
"damage": 10
},
"EchoingSlash": {
"name": "메아리 참격",
@@ -1042,8 +980,7 @@
"rarity": "legend",
"desc": "모든 적에게 피해를 10 줍니다. 적을 처치할 때마다 이 효과를 반복합니다.",
"aoe": true,
"damage": 10,
"image": "dbdbb1b56ae54672ae68ac6882fff6a2"
"damage": 10
},
"TheHunt": {
"name": "사냥",
@@ -1052,8 +989,7 @@
"class": "bandit",
"rarity": "legend",
"desc": "피해를 10 줍니다. 치명타라면, 카드 보상을 추가로 얻습니다. 소멸.",
"damage": 10,
"image": "b1360ed0c4b942309d240634b8f36872"
"damage": 10
},
"Murder": {
"name": "살해",
@@ -1062,8 +998,7 @@
"class": "bandit",
"rarity": "legend",
"desc": "피해를 1 줍니다. 이번 전투 동안 뽑은 카드 1장당 피해량이 1 증가합니다.",
"damage": 1,
"image": "b1360ed0c4b942309d240634b8f36872"
"damage": 1
},
"Malaise": {
"name": "불쾌",
@@ -1072,8 +1007,7 @@
"class": "bandit",
"rarity": "legend",
"desc": "적이 힘을 X 잃습니다. 약화를 X 부여합니다. 소멸.",
"weak": 3,
"image": "0946f69d84464df29b24b94c744c868d"
"weak": 3
},
"Adrenaline": {
"name": "아드레날린",
@@ -1084,8 +1018,7 @@
"desc": "를 얻습니다. 카드를 2장 뽑습니다. 소멸.",
"draw": 2,
"powerEffect": "energyPerTurn",
"value": 1,
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
"value": 1
},
"StormOfSteel": {
"name": "강철의 폭풍",
@@ -1095,8 +1028,7 @@
"rarity": "legend",
"desc": "손에 있는 모든 카드를 버립니다. 버린 카드의 수만큼 표창을 손으로 가져옵니다.",
"discardAll": true,
"addShivPerDiscard": true,
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
"addShivPerDiscard": true
},
"ShadowStep": {
"name": "그림자 걸음",
@@ -1106,8 +1038,7 @@
"rarity": "legend",
"desc": "손에 있는 모든 카드를 버립니다. 다음 턴에, 공격 카드의 피해량이 2배가 됩니다.",
"draw": 1,
"discardAll": true,
"image": "0946f69d84464df29b24b94c744c868d"
"discardAll": true
},
"Shadowmeld": {
"name": "그림자 은신",
@@ -1116,8 +1047,7 @@
"class": "bandit",
"rarity": "legend",
"desc": "이번 턴 동안 얻는 방어도가 2배가 됩니다.",
"draw": 1,
"image": "0946f69d84464df29b24b94c744c868d"
"draw": 1
},
"CorrosiveWave": {
"name": "부식성 파도",
@@ -1126,8 +1056,7 @@
"class": "bandit",
"rarity": "legend",
"desc": "이번 턴에 카드를 뽑을 때마다, 모든 적에게 중독을 2 부여합니다.",
"poison": 2,
"image": "19361e72087946b1888684185b40d935"
"poison": 2
},
"BladeOfInk": {
"name": "잉크 칼날",
@@ -1136,8 +1065,7 @@
"class": "bandit",
"rarity": "legend",
"desc": "잉크투성이 표창을 2장 손으로 가져옵니다.",
"addShiv": 2,
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
"addShiv": 2
},
"Burst": {
"name": "폭주",
@@ -1146,8 +1074,7 @@
"class": "bandit",
"rarity": "legend",
"desc": "이번 턴에 다음에 사용하는 스킬 카드가 1번 추가로 사용됩니다.",
"draw": 1,
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
"draw": 1
},
"KnifeTrap": {
"name": "칼날 함정",
@@ -1156,8 +1083,7 @@
"class": "bandit",
"rarity": "legend",
"desc": "대상 적에게 소멸된 카드 더미에 있는 모든 표창을 사용합니다.",
"draw": 1,
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
"draw": 1
},
"BulletTime": {
"name": "불릿 타임",
@@ -1167,8 +1093,7 @@
"rarity": "legend",
"desc": "이번 턴 동안 더 이상 카드를 뽑을 수 없습니다. 이번 턴 동안 손에 있는 모든 카드를 비용 없이 사용할 수 있습니다.",
"powerEffect": "energyPerTurn",
"value": 1,
"image": "91a2d1c16cb041549adbf1a0d7b1f37f"
"value": 1
},
"Nightmare": {
"name": "악몽",
@@ -1177,8 +1102,7 @@
"class": "bandit",
"rarity": "legend",
"desc": "카드를 1장 선택합니다. 다음 턴에, 그 카드의 복사본을 3장 손으로 가져옵니다. 소멸.",
"draw": 1,
"image": "0946f69d84464df29b24b94c744c868d"
"draw": 1
},
"ToolsOfTheTrade": {
"name": "작업 도구",
@@ -1190,8 +1114,7 @@
"draw": 1,
"powerEffect": "energyPerTurn",
"value": 1,
"discard": 1,
"image": "c1e19219745e44c39ae6ac2f77e347d9"
"discard": 1
},
"Afterimage": {
"name": "잔상",
@@ -1202,8 +1125,7 @@
"desc": "카드를 사용할 때마다, 방어도를 1 얻습니다.",
"block": 1,
"powerEffect": "blockPerTurn",
"value": 2,
"image": "0946f69d84464df29b24b94c744c868d"
"value": 2
},
"Accelerant": {
"name": "촉진제",
@@ -1213,8 +1135,7 @@
"rarity": "legend",
"desc": "중독이 1번 추가로 발동합니다.",
"powerEffect": "strengthPerTurn",
"value": 1,
"image": "19361e72087946b1888684185b40d935"
"value": 1
},
"Envenom": {
"name": "독 바르기",
@@ -1225,8 +1146,7 @@
"desc": "공격 카드가 막히지 않은 피해를 줄 때마다, 중독을 1 부여합니다.",
"poison": 1,
"powerEffect": "strengthPerTurn",
"value": 1,
"image": "19361e72087946b1888684185b40d935"
"value": 1
},
"MasterPlanner": {
"name": "설계의 대가",
@@ -1236,8 +1156,7 @@
"rarity": "legend",
"desc": "스킬 카드를 사용 시, 그 카드가 교활을 얻습니다.",
"powerEffect": "strengthPerTurn",
"value": 1,
"image": "c1e19219745e44c39ae6ac2f77e347d9"
"value": 1
},
"Tracking": {
"name": "추적",
@@ -1247,8 +1166,7 @@
"rarity": "legend",
"desc": "약화 상태의 적이 공격 카드로 받는 피해가 2배가 됩니다.",
"powerEffect": "strengthPerTurn",
"value": 1,
"image": "b1360ed0c4b942309d240634b8f36872"
"value": 1
},
"FanOfKnives": {
"name": "칼날 부채",
@@ -1259,8 +1177,7 @@
"desc": "표창이 이제 모든 적을 대상으로 합니다. 표창을 4장 손으로 가져옵니다.",
"powerEffect": "strengthPerTurn",
"value": 1,
"addShiv": 4,
"image": "1b0f2dc8abd0434990eee1befefcbe0d"
"addShiv": 4
},
"SerpentForm": {
"name": "구렁이의 형상",
@@ -1271,8 +1188,7 @@
"desc": "카드를 사용할 때마다, 무작위 적에게 피해를 4 줍니다.",
"powerEffect": "strengthPerTurn",
"value": 1,
"damage": 4,
"image": "19361e72087946b1888684185b40d935"
"damage": 4
},
"Abrasive": {
"name": "연마",
@@ -1283,8 +1199,7 @@
"desc": "교활. 민첩을 1 얻습니다. 가시를 4 얻습니다.",
"dex": 1,
"thorns": 4,
"sly": true,
"image": "49c8f279bfa64bf3954037f17da0052d"
"sly": true
},
"Suppress": {
"name": "진압",
@@ -1294,8 +1209,7 @@
"rarity": "legend",
"desc": "선천성. 피해를 11 줍니다. 약화를 3 부여합니다.",
"weak": 3,
"damage": 11,
"image": "b1360ed0c4b942309d240634b8f36872"
"damage": 11
},
"WraithForm": {
"name": "유령의 형상",
@@ -1305,8 +1219,7 @@
"rarity": "legend",
"desc": "불가침을 2 얻습니다. 내 턴 종료 시 민첩을 1 잃습니다.",
"powerEffect": "blockPerTurn",
"value": 8,
"image": "0946f69d84464df29b24b94c744c868d"
"value": 8
},
"Flanking": {
"name": "측면 공격",
@@ -1315,8 +1228,7 @@
"class": "bandit",
"rarity": "legend",
"desc": "이번 턴에 대상 적이 다른 플레이어에게 받는 피해량이 2배가 됩니다.",
"draw": 1,
"image": "b1360ed0c4b942309d240634b8f36872"
"draw": 1
},
"Sneaky": {
"name": "비열함",
@@ -1326,8 +1238,7 @@
"rarity": "legend",
"desc": "교활. 다른 플레이어가 적을 공격할 때마다, 방어도를 1 얻습니다.",
"block": 1,
"sly": true,
"image": "0946f69d84464df29b24b94c744c868d"
"sly": true
}
},
"starterDecks": {

View File

@@ -1,86 +0,0 @@
# SlayMaple 덱 컨셉 & 직업 스킬셋
> SlayMaple 카드 덱의 **직업별 컨셉 · 메이플 스킬셋 · Slay the Spire 2 차용 매칭** 설계 문서.
> 원칙: 카드 한 장 = **STS2 메커니즘(뼈대) + 메이플 스킬(외형)**. STS 고유 *표현*(카드명·아트·UI)은 모방 금지, *메커니즘*만 차용(IP 해석 심사 대비).
> 수치(데미지·코스트·등급)는 `tools/balance/sim-balance.mjs`로 검증. 본 문서는 *어떤 스킬을 어떤 카드로* 만들지의 설계도.
기준: 메이플 = **클래식(빅뱅 이전)** 스킬 외형, STS = **Slay the Spire 2**.
---
## 직업 ↔ STS2 매칭 요약
| 직업 | 컨셉 | STS2 차용 |
|---|---|---|
| ⚔️ 전사 | 단단한 탱커/브루저 | The Ironclad (힘·방어·소멸) |
| 🗡️ 도적 | 단검 난사 / 독 | The Silent (표창·독·교활) |
| 🔮 법사 | 약체 + 게이지 운용 | The Defect (오브·집중) |
---
## ⚔️ 전사 (Warrior) — HP80 · 탱커
방어를 쌓고 버티다 역공하는 브루저. Ironclad의 두 축을 2차 전직으로 분화.
### 파이터 — 콤보 브루저형 탱커
- **콤보 규칙**: 공격 카드를 **연속으로** 사용하면 콤보가 쌓인다. **방어·파워 등 비공격(Skill/Power) 카드를 사용하면 콤보가 리셋(0)** 된다.
- 콤보가 쌓일수록 **데미지 증가 버프(힘 계열)** 를 받는다 → 방어를 포기하고 공격을 몰아칠수록 강해지는 리스크/리워드.
- 차용: Ironclad 힘 스택/Demon Form + 콤보. 메이플 외형: 콤보 어택·분노·브랜디시.
### 페이지 — 방어 축적 → 바디 슬램 카운터
- **위협**(전체 적 약화+취약 디버프)로 버티며 **방어도를 축적**(아이언 월 등 + 방어 유지 retain).
- **바디 슬램**: 현재 방어도에 비례한 피해로 카운터. 파워 가드(반사) 보조.
- 차용: Ironclad 방어 빌드(Barricade+Entrench→Body Slam). 메이플 외형: 위협·아이언 월·파워 가드.
### 스피어맨 — 유지/리치형
- 하이퍼 바디(최대 HP↑)·아이언 월(방어 유지)·창 리치 광역. 공격 스케일(파이터)·방어 카운터(페이지)와 구분되는 지속 탱.
---
## 🗡️ 도적 (Thief) — HP70 · 단검/독
Slay the Spire *Silent* 차용. **형(codex)이 Silent 88장 완역 포트 + 스킬 아이콘 적용 완료.** 3대 아키타입:
- **표창(Shiv) 난사**: 0코스트 표창 토큰 대량 생성 → 공격마다 연계. (정밀=표창 피해↑, 칼날 부채=표창 전체화)
- **독(Poison)**: 중독 중첩 → 매턴 틱뎀. (유독 가스·발병·촉진제·독 바르기)
- **교활(Sly)·버림(discard)**: 버려질 때 무료 발동, 얇은 덱 빠른 순환.
### 2차 갈래
- **어쌔신** — 표창 난사 + 크리 / 흡혈(드레인) 중심.
- **시프** — 단검 난타(새비지 블로우 = 다단히트) + 독 / 버림 중심.
> 남은 작업: 카드명이 STS 직역(무력화·배신·아드레날린 등) → **어쌔신/시프 메이플 스킬명으로 재서사** + 멀티플레이어 전제 카드(측면 공격·비열함·추적) 싱글 출품용 정리.
---
## 🔮 법사 (Magician) — HP70 · 약체/게이지
몸은 약하나 **게이지(오브) 운용**으로 다중 공격·화력 집중. **오브 메커니즘은 위자드(불/독·썬/콜)에만 적용**, 클레릭은 별도 보조 컨셉.
### 위자드(불/독) — 독 + 불 시너지
- **독을 묻히는 스킬**(포이즌 브레스 등)으로 대상에 독을 부여(독뎀 = DoT).
- **독이 묻은 적에게 불 카드(파이어 애로우 등)를 쓰면 추가 데미지** — *독뎀 상수* 보너스(독 스택/상수 비례).
- 즉 "독 깔기 → 불로 폭발"의 2단 콤보. 불·독 오브로 운용.
### 위자드(썬/콜) — 오브 운용(썬더 다중 / 콜드 빙결)
- **오브로 썬더·콜드를 보유**. **썬더 = 다중 공격 특화**(AoE·다단). **콜드 = 빙결 부여**(빙결 = *취약과 동일 효과* 를 데미지와 함께).
- **오브를 사용하는 만큼 오브를 획득하거나 다중 소모**하는 방식 — 오브 수급/소비 운용이 핵심.
### 클레릭 — 보조(회복·버프) · 오브 없음
- **회복 스킬**(힐)과 **각종 버프**(블레스 등) 중심의 서포트.
- **언데드 계열 몬스터에게는 힐로 공격** 가능 — 보조 힐러 정체성.
> ⚠️ 위자드 오브/게이지·전사 콤보 스택·바디 슬램·독뎀 시너지는 **신규 메커니즘** — `tools/deck/gen-slaydeck.mjs`(전투 규칙) + `tools/balance/sim-balance.mjs`(JS 미러) 양쪽 구현·동기화 필요.
---
## 차용 경계 (IP 심사 대비)
- 차용 OK = **메커니즘**(콤보 스택·방어→피해 전환·독+불 시너지·오브 게이지·빙결=취약 등 시스템).
- 모방 금지 = STS 고유 **표현**(카드명·아트·UI 직접 사용).
- 만점 루트 = STS2 메커니즘을 **메이플 스킬·외형으로 완전 재서사화**.
## 참고
- 카드 데이터 단일 소스: `data/cards.json` (현 122장: 전사 18·마법사 14·도적 88 + Shiv·저주)
- 메이플 스킬 외형 매핑·STS2 캐릭터 상세는 박재오 개인 위키 `프로젝트-메이플-덱빌딩-스킬구성` / `프로젝트-메이플-STS2-차용-덱컨셉` 참조.

View File

@@ -1,106 +0,0 @@
# Phase 2 — 캐릭터 선택 메이커 저작 파일럿 구현 계획
> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:executing-plans 로 태스크 단위 구현.
**Goal:** charselect를 생성중단→stock화(메이커 편집)하고, 캐릭터 이미지를 컨트롤러가 런타임 경로 주입(ClassPortraits)하도록 바꿔 패턴 (b)를 검증한다.
**Architecture:** ① 이미지 런타임 주입 추가(ClassPortraits + luaCharsTable + RenderCharacterSelect) → ② charselect 생성 중단(GENERATED_UI_SECTIONS/emit 제거 → 기존 엔티티 stock 보존). 컨트롤러는 경로 구동 유지.
**Tech Stack:** Node ESM 생성기, MSW Lua. 검증 = **count(동작 검증)** + 메이커 플레이테스트(바이트동일 아님 — codeblock·ui 의도적 변경).
**의존:** Phase 1b(#71) 위 스택(`feature/charselect-maker-pilot`). #70·#71 머지 후 main 리타겟.
---
## 검증 메모
Phase 2는 codeblock·ui를 **의도적으로 변경**(diffcheck-IDENTICAL 아님). 게이트:
- `node tools/deck/gen-slaydeck.mjs` 성공(throw 없음).
- `node tools/verify/count.mjs cb ClassPortraits 'ImageRUID = self.ClassPortraits'` → 주입 코드 존재.
- `node tools/verify/count.mjs ui CharacterSelectHud/WarriorButton/Art` → charselect 엔티티 ui 잔류(stock).
- 미러 테스트 무영향(회귀 확인차 실행).
- **최종**: 사용자 메이커 플레이테스트.
---
### Task 1: `luaCharsTable()` 신설 (lib/data.mjs)
**Files:** Modify `tools/deck/lib/data.mjs`
- [ ] **Step 1:** `luaNodeIconsTable`(:78-81) 바로 뒤에 추가:
```js
function luaCharsTable() {
const rows = Object.entries(CHARS.portraits).map(([c, ruid]) => `\t${c} = ${luaStr(ruid)},`).join('\n');
return `self.ClassPortraits = {\n${rows}\n}`;
}
```
- [ ] **Step 2:** `export { ... }``luaCharsTable` 추가.
- [ ] **Step 3:** 커밋(아직 미사용 — import 시 검증).
---
### Task 2: ClassPortraits 시드 + prop
**Files:** Modify `tools/deck/cb/boot.mjs`, `tools/deck/cb/run.mjs`, `tools/deck/gen-slaydeck.mjs`
- [ ] **Step 1:** `cb/boot.mjs`·`cb/run.mjs`의 import에 `luaCharsTable` 추가(`luaNodeIconsTable` 옆, `from '../lib/data.mjs'`).
- [ ] **Step 2:** `cb/boot.mjs:8`(`${luaNodeIconsTable()}`) 다음 줄에 `${luaCharsTable()}` 추가. `cb/run.mjs:34` 동일.
- [ ] **Step 3:** `gen-slaydeck.mjs:311`(`prop('any', 'NodeIcons'),`) 다음 줄에 `prop('any', 'ClassPortraits'),` 추가.
- [ ] **Step 4:** `node tools/deck/gen-slaydeck.mjs` 성공 + `node tools/verify/count.mjs cb ClassPortraits` → ≥2(시드 2회).
- [ ] **Step 5:** 산출물 churn 복원(`git checkout --`) — codeblock은 이 시점 변경됨(ClassPortraits 추가)이므로 **복원 안 함**, ui/common만 churn이면 복원. 커밋(소스 + 재생성 codeblock 분리 또는 함께 "산출물 재생성" 명시).
---
### Task 3: RenderCharacterSelect 이미지 런타임 주입
**Files:** Modify `tools/deck/cb/charselect.mjs:13`(RenderCharacterSelect)
- [ ] **Step 1:** RenderCharacterSelect 본문 **맨 앞**에 3 Art 주입 추가(Python 치환 — 실탭). classId: Warrior→warrior, Mage→magician, Thief→bandit:
```lua
local warriorArt = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/WarriorButton/Art")
if warriorArt ~= nil and warriorArt.SpriteGUIRendererComponent ~= nil and self.ClassPortraits ~= nil and self.ClassPortraits["warrior"] ~= nil then
warriorArt.SpriteGUIRendererComponent.ImageRUID = self.ClassPortraits["warrior"]
end
local mageArt = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/MageButton/Art")
if mageArt ~= nil and mageArt.SpriteGUIRendererComponent ~= nil and self.ClassPortraits ~= nil and self.ClassPortraits["magician"] ~= nil then
mageArt.SpriteGUIRendererComponent.ImageRUID = self.ClassPortraits["magician"]
end
local thiefArt = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/ThiefButton/Art")
if thiefArt ~= nil and thiefArt.SpriteGUIRendererComponent ~= nil and self.ClassPortraits ~= nil and self.ClassPortraits["bandit"] ~= nil then
thiefArt.SpriteGUIRendererComponent.ImageRUID = self.ClassPortraits["bandit"]
end
```
(기존 border/status 로직 앞에 prepend. RenderCharacterSelect는 ShowCharacterSelect/SelectClass에서 호출 → 열림·선택 시 멱등 주입.)
- [ ] **Step 2:** `node tools/deck/gen-slaydeck.mjs` + `node tools/verify/count.mjs cb 'ImageRUID = self.ClassPortraits'` → 3.
- [ ] **Step 3:** 커밋(소스 + 재생성 codeblock).
---
### Task 4: charselect 생성 중단 → stock
**Files:** Modify `tools/deck/lib/ui-helpers.mjs`, `tools/deck/gen-slaydeck.mjs`; Delete `tools/deck/hud/charselect.mjs`
- [ ] **Step 1:** `lib/ui-helpers.mjs``GENERATED_UI_SECTIONS`·`UI_APPEND_ORDER` 두 배열에서 `'CharacterSelectHud',` 줄 제거(2곳).
- [ ] **Step 2:** `gen-slaydeck.mjs`에서 `import { buildCharSelect } from './hud/charselect.mjs';`(:38)와 `emit('CharacterSelectHud', buildCharSelect());`(:229) 제거.
- [ ] **Step 3:** `git rm tools/deck/hud/charselect.mjs` (부트스트랩 완료, git 이력에 레퍼런스 잔존).
- [ ] **Step 4:** `node tools/deck/gen-slaydeck.mjs` 성공 + `node tools/verify/count.mjs ui CharacterSelectHud/WarriorButton/Art`**>0**(charselect 엔티티가 stock으로 ui에 잔류). `git status`로 ui 변경 확인(charselect가 생성→stock 전환, 위치 이동 가능 — 정상).
- [ ] **Step 5:** 커밋(소스 + 재생성 산출물, 메시지에 "charselect 생성 중단·stock화" 명시).
---
### Task 5: 마무리 — RULES·경로계약·회귀·PR
**Files:** Modify `RULES.md`
- [ ] **Step 1:** RULES §1에 한 줄: charselect는 **메이커 저작(stock)**이라 생성 안 함 — 컨트롤러가 `ClassPortraits`로 이미지 런타임 주입, 메이커 편집 시 §스펙 경로 유지. (다른 화면은 여전히 hud/cb 생성.)
- [ ] **Step 2:** 회귀: `node --test tools/balance/sim-balance.test.mjs` · `node --test tools/map/rogue-map.test.mjs` (exit 0).
- [ ] **Step 3:** push → PR(`node tools/git/gitea-pr.mjs create <spec.json>`, base=`feature/cb-modularization`, 한국어).
- [ ] **Step 4:** **사용자 메이커 플레이테스트**(워크스페이스 reload 후): 로비→직업선택→3 이미지 컨트롤러 주입 표시→클릭 금색테두리·Status→시작 그 직업→**메이커에서 카드 위치 이동·저장 후 `node gen-slaydeck` 재생성해도 charselect 유지**(stock 비파괴) 확인. 이미지 비표시 시 ClassPortraits 시드/주입 경로 점검.
---
## Self-Review
- **스펙 커버리지**: ①stock화(T4) ②런타임주입(T1-3: luaCharsTable·시드·prop·RenderCharacterSelect) ③경로구동 유지(무변경) ④경로계약(T5·스펙). 누락 없음.
- **플레이스홀더**: luaCharsTable·주입 Lua·제거 라인 구체. 검증=count+playtest(바이트동일 아님 명시).
- **타입 일관성**: `self.ClassPortraits`(prop)↔`luaCharsTable`(self.ClassPortraits=)↔RenderCharacterSelect 참조 일치. classId Warrior→warrior/Mage→magician/Thief→bandit 일관.
- **순서**: 추가(주입 T1-3) 먼저 → 중단(stock T4). 중단 전엔 생성+주입 공존(무해), 중단 후 stock+주입.
- **리스크**: 메이커 경로 변경 시 계약 깨짐(isvalid 가드로 크래시 방지·해당부 미동작). stock 전환 시 ui 위치 이동(렌더 무관).

View File

@@ -1,73 +0,0 @@
# Phase 2 — 캐릭터 선택 메이커 저작 파일럿 설계
작성일: 2026-06-16
브랜치: `feature/charselect-maker-pilot` (Phase 1b `feature/cb-modularization`/PR #71 위에 스택)
## 목표
하이브리드 UI 로드맵의 **패턴 (b)**(메이커 시각 편집) 검증 파일럿. **캐릭터 선택 화면**을 "생성기 소유 → 메이커 소유"로 이관한다:
- **레이아웃**(패널·카드 위치·버튼)은 메이커에서 시각 편집(생성기가 안 덮음).
- **동적 내용**(캐릭터 이미지·선택 테두리·상태 텍스트)은 `SlayDeckController`가 런타임에 **경로로 주입** = 컨트롤러 내용주입.
성공 시 Phase 3에서 상점·전체덱 등으로 확장.
## 현재 구조 (조사 결과)
- charselect는 **생성 섹션**: `lib/ui-helpers.mjs``GENERATED_UI_SECTIONS`(:17)·`UI_APPEND_ORDER`(:35)에 `'CharacterSelectHud'` 포함. `hud/charselect.mjs``buildCharSelect()`가 엔티티 emit, `upsertUi``emit('CharacterSelectHud', buildCharSelect())`.
- **이미지 = 생성 시 주입**: `hud/charselect.mjs:86` `sprite({ dataId: CHARS.portraits[cls.classId], … })`. 런타임 주입 아님.
- **컨트롤러는 경로 구동**: `cb/charselect.mjs``RenderCharacterSelect`(각 `{Warrior,Mage,Thief}Button``SpriteGUIRendererComponent.Color`로 선택 테두리 + Status 텍스트), `SelectClass`, `StartNewGame`. 바인딩은 `cb/state.mjs``BindMenuButtons`(경로로 WarriorButton·BackButton·StartButton 등). 표시 토글은 `ShowState`(경로). **이미지 주입은 없음.**
- **런타임 시드 모델**: `self.CardFrames``${luaFramesTable()}`로 OnBeginPlay(cb/boot)·StartRun(cb/run)에서 주입 → `ClassPortraits`의 모델.
- `upsertUi` 동작: 기존 `.ui` 로드 → 생성 섹션 엔티티 필터아웃 → emit 섹션 재추가. **생성 섹션에서 빠지면 `isGeneratedUiEntity=false`라 필터 안 됨 → 기존 엔티티 보존(stock)**.
## 상세 설계
### ① 생성 중단 → stock화 (generate-once-then-stop)
- `lib/ui-helpers.mjs` `GENERATED_UI_SECTIONS`·`UI_APPEND_ORDER`에서 `'CharacterSelectHud'` 제거.
- `gen-slaydeck.mjs`(upsertUi)에서 `emit('CharacterSelectHud', buildCharSelect())` + 관련 import 제거. `hud/charselect.mjs`**삭제**(부트스트랩 완료 — git 이력에 레퍼런스 남음).
- 효과: 현재 `DefaultGroup.ui`의 charselect 엔티티가 그대로 **stock**으로 보존 → 메이커 시각 편집 가능, 재생성에 안 덮임.
### ② 이미지 런타임 주입 (컨트롤러 내용주입 = 패턴 b 핵심)
- `lib/data.mjs``luaCharsTable()` 신설(`data/characters.json``portraits` 시드, `luaFramesTable`/`luaNodeIconsTable` 패턴; `self.ClassPortraits = { warrior="…", magician="…", bandit="…" }`).
- 주입 지점: `cb/boot.mjs` OnBeginPlay·`cb/run.mjs` StartRun에 `${luaCharsTable()}`(CardFrames 시드 옆) + prop `ClassPortraits`(any) 선언.
- `cb/charselect.mjs` `RenderCharacterSelect`에 이미지 주입 추가: 각 `{key}Button/Art` 엔티티의 `SpriteGUIRendererComponent.ImageRUID``self.ClassPortraits[classId]`로 설정(경로별 isvalid 가드). → 메이커 레이아웃(빈/임의 Art)이어도 컨트롤러가 올바른 이미지 채움. **characters.json 데이터 구동 유지.**
### ③ 경로 구동 유지 (무변경)
- 선택 테두리·Status·버튼 바인딩(`RenderCharacterSelect` 색/텍스트·`SelectClass`·`BindMenuButtons`·`StartNewGame`·`ShowState`)은 이미 경로 기반 → 변경 없음.
### ④ 엔티티 경로 계약 (docs 명시)
메이커 편집 시 아래 경로 유지 필수(컨트롤러가 이 경로로 구동; 누락 시 isvalid 가드로 무시되되 그 부분 동작 안 함):
```
/ui/DefaultGroup/CharacterSelectHud (루트, ShowState 토글)
/OpaqueBackdrop /Title /Status
/WarriorButton (+ /Art ← 이미지 주입, /NameBanner, /Name)
/ThiefButton (+ /Art, /NameBanner, /Name)
/MageButton (+ /Art, /NameBanner, /Name)
/StartButton /BackButton
```
(#67로 DeckButton 제거됨.) classId 매핑: Warrior→warrior, Thief→bandit, Mage→magician.
## 검증 (동작 — 바이트동일 아님)
- 생성기: charselect 제거 후 `node tools/deck/gen-slaydeck.mjs`**charselect 외 산출물 무영향**(`diffcheck`로 codeblock·common 확인; ui는 charselect 섹션만 stock으로 잔류·다른 섹션 동일). charselect 엔티티가 ui에 존재(`count.mjs`).
- 메이커 플레이테스트: 로비→직업선택→**3 이미지가 컨트롤러 주입으로 표시**→클릭 시 금색테두리·Status→시작 시 그 직업으로 런→**메이커에서 카드 위치 이동 후 재생성해도 유지** 확인.
## 범위 밖
- 상점·전체덱 등 다른 화면(Phase 3).
- 새 UIGroup(.ui) 분리(경로·ShowState 재작업 큼) — DefaultGroup 내 stock으로 충분.
- 게임 규칙·다른 화면 변경.
## 리스크
- stock 전환 시 charselect 엔티티의 `.ui` 내 직렬화 위치 이동 가능 → 렌더는 경로/displayOrder 기반이라 무관하나 플레이테스트로 확인.
- 메이커가 경로를 바꾸면 계약 깨짐 → 경로 표로 가드. isvalid 가드로 크래시는 방지.
- 의존: Phase 1b(cb/charselect·boot·run) 위 스택. #70·#71 머지 후 main 리타겟.
## 변경 파일 요약
| 파일 | 변경 |
|---|---|
| `tools/deck/lib/ui-helpers.mjs` | `GENERATED_UI_SECTIONS`·`UI_APPEND_ORDER`에서 CharacterSelectHud 제거 |
| `tools/deck/gen-slaydeck.mjs` | upsertUi에서 charselect emit·import 제거 |
| `tools/deck/hud/charselect.mjs` | **삭제** |
| `tools/deck/lib/data.mjs` | `luaCharsTable()` 신설 |
| `tools/deck/cb/boot.mjs`·`cb/run.mjs` | `${luaCharsTable()}` 시드 + ClassPortraits prop |
| `tools/deck/cb/charselect.mjs` | `RenderCharacterSelect`에 Art ImageRUID 주입 |
| `docs/...charselect 경로 계약` | 경로 표(이 스펙 §④) |
| `ui/DefaultGroup.ui`·codeblock | 재생성(charselect는 stock 잔류) |

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +1,15 @@
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaCharsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
export const bootMethods = [
method('OnBeginPlay', `${luaCardsTable(CARDS.cards)}
${luaFramesTable()}
${luaNodeIconsTable()}
${luaCharsTable()}
${luaSoulShopTable(SOUL_UNLOCKS)}
self.SoulUnlocks = {}
self.SoulPoints = self.SoulPoints or 0
local uiTries = 0
local uiInit = 0
uiInit = _TimerService:SetTimerRepeat(function()
uiTries = uiTries + 1
if _EntityService:GetEntityByPath("/ui/DeckUIGroup") ~= nil then
self:ActivateUIGroups()
self:ShowMainMenu()
_TimerService:ClearTimer(uiInit)
elseif uiTries > 80 then
_TimerService:ClearTimer(uiInit)
end
end, 0.1)
self:ShowLobby()
local lp = _UserService.LocalPlayer
if lp ~= nil then
self:ReqLoadAscension(lp.PlayerComponent.UserId)
@@ -29,24 +17,10 @@ if lp ~= nil then
end
_InputService:ConnectEvent(KeyDownEvent, function(e)
if e.key == KeyboardKey.LeftControl then
self.DebugCtrlDown = true
local lp2 = _UserService.LocalPlayer
if lp2 ~= nil and lp2.CurrentMapName == "${LOBBY_MAP}" and self.RunActive ~= true then
self:PlayerAttackMotion()
end
elseif e.key == KeyboardKey.LeftShift or e.key == KeyboardKey.RightShift then
self.DebugShiftDown = true
elseif e.key == KeyboardKey.C then
if self.DebugCtrlDown == true and self.DebugShiftDown == true then
self:OpenDebugCardPicker()
end
end
end)
_InputService:ConnectEvent(KeyUpEvent, function(e)
if e.key == KeyboardKey.LeftControl then
self.DebugCtrlDown = false
elseif e.key == KeyboardKey.LeftShift or e.key == KeyboardKey.RightShift then
self.DebugShiftDown = false
end
end)`),
method('ReqLoadAscension', `local ds = _DataStorageService:GetUserDataStorage(userId)
@@ -75,7 +49,7 @@ if v > self.AscensionUnlocked then v = self.AscensionUnlocked end
self.AscensionLevel = v
self:RenderAscension()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'delta' }]),
method('RenderAscension', `self:SetText("/ui/DefaultGroup/MainMenu/AscLabel", "승천 " .. string.format("%d", self.AscensionLevel) .. " / 해금 " .. string.format("%d", self.AscensionUnlocked))
self:SetText("/ui/LobbyUIGroup/LobbyHud/AscLabel", "승천 " .. string.format("%d", self.AscensionLevel) .. " / 해금 " .. string.format("%d", self.AscensionUnlocked))`),
self:SetText("/ui/DefaultGroup/LobbyHud/AscLabel", "승천 " .. string.format("%d", self.AscensionLevel) .. " / 해금 " .. string.format("%d", self.AscensionUnlocked))`),
method('AscHpMult', `local m = 1
if self.AscensionLevel >= 1 then m = m + 0.1 end
if self.AscensionLevel >= 6 then m = m + 0.1 end

View File

@@ -10,19 +10,7 @@ self:RenderCharacterSelect()`),
self:RenderCharacterSelect()`, [
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'className' },
]),
method('RenderCharacterSelect', `local warriorArt = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/WarriorButton/Art")
if warriorArt ~= nil and warriorArt.SpriteGUIRendererComponent ~= nil and self.ClassPortraits ~= nil and self.ClassPortraits["warrior"] ~= nil then
warriorArt.SpriteGUIRendererComponent.ImageRUID = self.ClassPortraits["warrior"]
end
local mageArt = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/MageButton/Art")
if mageArt ~= nil and mageArt.SpriteGUIRendererComponent ~= nil and self.ClassPortraits ~= nil and self.ClassPortraits["magician"] ~= nil then
mageArt.SpriteGUIRendererComponent.ImageRUID = self.ClassPortraits["magician"]
end
local thiefArt = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/ThiefButton/Art")
if thiefArt ~= nil and thiefArt.SpriteGUIRendererComponent ~= nil and self.ClassPortraits ~= nil and self.ClassPortraits["bandit"] ~= nil then
thiefArt.SpriteGUIRendererComponent.ImageRUID = self.ClassPortraits["bandit"]
end
local warrior = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/WarriorButton")
method('RenderCharacterSelect', `local warrior = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/WarriorButton")
if warrior ~= nil and warrior.SpriteGUIRendererComponent ~= nil then
if self.SelectedClass == "warrior" then
warrior.SpriteGUIRendererComponent.Color = Color(1, 0.82, 0.3, 1)
@@ -30,7 +18,7 @@ if warrior ~= nil and warrior.SpriteGUIRendererComponent ~= nil then
warrior.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)
end
end
local mage = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/MageButton")
local mage = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/MageButton")
if mage ~= nil and mage.SpriteGUIRendererComponent ~= nil then
if self.SelectedClass == "magician" then
mage.SpriteGUIRendererComponent.Color = Color(1, 0.82, 0.3, 1)
@@ -38,7 +26,7 @@ if mage ~= nil and mage.SpriteGUIRendererComponent ~= nil then
mage.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)
end
end
local thief = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/ThiefButton")
local thief = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/ThiefButton")
if thief ~= nil and thief.SpriteGUIRendererComponent ~= nil then
if self.SelectedClass == "bandit" then
thief.SpriteGUIRendererComponent.Color = Color(1, 0.82, 0.3, 1)
@@ -47,16 +35,16 @@ if thief ~= nil and thief.SpriteGUIRendererComponent ~= nil then
end
end
if self.SelectedClass == "warrior" then
self:SetText("/ui/SelectUIGroup/CharacterSelectHud/Status", "전사 선택됨")
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "전사 선택됨")
elseif self.SelectedClass == "bandit" then
self:SetText("/ui/SelectUIGroup/CharacterSelectHud/Status", "도적 선택됨")
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "도적 선택됨")
elseif self.SelectedClass == "magician" then
self:SetText("/ui/SelectUIGroup/CharacterSelectHud/Status", "마법사 선택됨")
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "마법사 선택됨")
else
self:SetText("/ui/SelectUIGroup/CharacterSelectHud/Status", "직업을 선택하고 시작하세요")
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "직업을 선택하고 시작하세요")
end`),
method('StartNewGame', `if self.SelectedClass ~= "warrior" and self.SelectedClass ~= "bandit" and self.SelectedClass ~= "magician" then
self:SetText("/ui/SelectUIGroup/CharacterSelectHud/Status", "직업을 먼저 선택하세요")
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "직업을 먼저 선택하세요")
return
end
self:StartRun()`),
@@ -66,5 +54,5 @@ if e ~= nil then
end`, [
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'path' },
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'enabled' },
], 2),
]),
];

View File

@@ -75,8 +75,9 @@ for i = 1, #self.Monsters do
local m = self.Monsters[i]
local active = false
if m ~= nil and m.alive == true and i == shownTarget then active = true end
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/MonsterStatus" .. tostring(i) .. "/TargetMarker", active and dragActive)
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/MonsterStatus" .. tostring(i) .. "/TargetMarker/Label", active and dragActive)
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(i) .. "/TargetFrame", active)
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(i) .. "/TargetMarker", active and dragActive)
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(i) .. "/TargetMarker/Label", active and dragActive)
end`),
method('OnCardDragBegin', `if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then
return
@@ -89,7 +90,7 @@ if self.CardHoverTweenId ~= nil and self.CardHoverTweenId ~= 0 then
self.CardHoverTweenId = 0
end
for i = 1, 10 do
local e = _EntityService:GetEntityByPath("/ui/RunUIGroup/CardHand/Card" .. tostring(i))
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(i))
if e ~= nil and e.UITransformComponent ~= nil then
e.UITransformComponent.UIScale = Vector3(1, 1, 1)
e.UITransformComponent.anchoredPosition = Vector2(self:GetHandSlotX(i), 0)
@@ -101,7 +102,7 @@ self:RenderTargetFrames()`, [{ Type: 'number', DefaultValue: null, SyncDirection
method('OnCardDrag', `if self.DragSlot ~= slot then
return
end
local e = _EntityService:GetEntityByPath("/ui/RunUIGroup/CardHand/Card" .. tostring(slot))
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(slot))
if e ~= nil and e.UITransformComponent ~= nil then
local ui = _UILogic:ScreenToUIPosition(touchPoint)
e.UITransformComponent.anchoredPosition = Vector2(ui.x, ui.y + 360)
@@ -123,7 +124,7 @@ end`, [
return
end
self.DragSlot = 0
local e = _EntityService:GetEntityByPath("/ui/RunUIGroup/CardHand/Card" .. tostring(slot))
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(slot))
if e ~= nil and e.UITransformComponent ~= nil then
e.UITransformComponent.anchoredPosition = Vector2(self:GetHandSlotX(slot), 0)
e.UITransformComponent.UIScale = Vector3(1, 1, 1)
@@ -206,7 +207,7 @@ if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then
return
end
self.FxBusy = true
local fx = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/SkillFx")
local fx = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/SkillFx")
if fx ~= nil then
if fx.SpriteGUIRendererComponent ~= nil and image ~= nil and image ~= "" then
fx.SpriteGUIRendererComponent.ImageRUID = image
@@ -237,7 +238,7 @@ end, 0.35)`, [
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'pierce' },
]),
method('PlayAoeFx', `self.FxBusy = true
local fx = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/SkillFx")
local fx = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/SkillFx")
if fx ~= nil then
if fx.SpriteGUIRendererComponent ~= nil and image ~= nil and image ~= "" then
fx.SpriteGUIRendererComponent.ImageRUID = image
@@ -286,7 +287,7 @@ if m.entity ~= nil and isvalid(m.entity) then
local ent = m.entity
_TimerService:SetTimerOnce(function() if isvalid(ent) then ent:SetVisible(false) end end, 0.4)
end
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/MonsterStatus" .. tostring(slot), false)
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(slot), false)
for i = 1, #self.Monsters do
if self.Monsters[i].alive == true then self.TargetIndex = i; break end
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
@@ -340,7 +341,7 @@ if idx == 0 or self.PlayerHp <= 0 then
return
end
local m = self.Monsters[idx]
local base = "/ui/RunUIGroup/CombatHud/MonsterStatus" .. tostring(idx)
local base = "/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(idx)
self:SetEntityEnabled(base .. "/ActFrame", true)
_TimerService:SetTimerOnce(function()
if m.poison ~= nil and m.poison > 0 then

View File

@@ -10,56 +10,56 @@ for i = #list, 2, -1 do
\tlocal j = math.random(1, i)
\tlist[i], list[j] = list[j], list[i]
end`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'list' }]),
method('BindButtons', `local endTurn = _EntityService:GetEntityByPath("/ui/RunUIGroup/DeckHud/EndTurnButton")
if endTurn ~= nil and (endTurn.ButtonComponent ~= nil or endTurn:AddComponent("ButtonComponent") ~= nil) then
method('BindButtons', `local endTurn = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckHud/EndTurnButton")
if endTurn ~= nil and endTurn.ButtonComponent ~= nil then
if self.EndTurnHandler ~= nil then
endTurn:DisconnectEvent(ButtonClickEvent, self.EndTurnHandler)
self.EndTurnHandler = nil
end
self.EndTurnHandler = endTurn:ConnectEvent(ButtonClickEvent, function() self:EndPlayerTurn() end)
end
local drawPile = _EntityService:GetEntityByPath("/ui/RunUIGroup/DeckHud/DrawPile")
if drawPile ~= nil and (drawPile.ButtonComponent ~= nil or drawPile:AddComponent("ButtonComponent") ~= nil) then
local drawPile = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckHud/DrawPile")
if drawPile ~= nil and drawPile.ButtonComponent ~= nil then
if self.DrawPileHandler ~= nil then
drawPile:DisconnectEvent(ButtonClickEvent, self.DrawPileHandler)
self.DrawPileHandler = nil
end
self.DrawPileHandler = drawPile:ConnectEvent(ButtonClickEvent, function() self:OpenDeckInspect("draw") end)
end
local discardPile = _EntityService:GetEntityByPath("/ui/RunUIGroup/DeckHud/DiscardPile")
if discardPile ~= nil and (discardPile.ButtonComponent ~= nil or discardPile:AddComponent("ButtonComponent") ~= nil) then
local discardPile = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckHud/DiscardPile")
if discardPile ~= nil and discardPile.ButtonComponent ~= nil then
if self.DiscardPileHandler ~= nil then
discardPile:DisconnectEvent(ButtonClickEvent, self.DiscardPileHandler)
self.DiscardPileHandler = nil
end
self.DiscardPileHandler = discardPile:ConnectEvent(ButtonClickEvent, function() self:OpenDeckInspect("discard") end)
end
local exhaustPile = _EntityService:GetEntityByPath("/ui/RunUIGroup/DeckHud/ExhaustPile")
if exhaustPile ~= nil and (exhaustPile.ButtonComponent ~= nil or exhaustPile:AddComponent("ButtonComponent") ~= nil) then
local exhaustPile = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckHud/ExhaustPile")
if exhaustPile ~= nil and exhaustPile.ButtonComponent ~= nil then
if self.ExhaustPileHandler ~= nil then
exhaustPile:DisconnectEvent(ButtonClickEvent, self.ExhaustPileHandler)
self.ExhaustPileHandler = nil
end
self.ExhaustPileHandler = exhaustPile:ConnectEvent(ButtonClickEvent, function() self:OpenDeckInspect("exhaust") end)
end
local inspectClose = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckInspectHud/Close")
if inspectClose ~= nil and (inspectClose.ButtonComponent ~= nil or inspectClose:AddComponent("ButtonComponent") ~= nil) then
local inspectClose = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud/Close")
if inspectClose ~= nil and inspectClose.ButtonComponent ~= nil then
if self.DeckInspectCloseHandler ~= nil then
inspectClose:DisconnectEvent(ButtonClickEvent, self.DeckInspectCloseHandler)
self.DeckInspectCloseHandler = nil
end
self.DeckInspectCloseHandler = inspectClose:ConnectEvent(ButtonClickEvent, function() self:CloseDeckInspect() end)
end
local allDeckButton = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/TopBar/AllDeckButton")
if allDeckButton ~= nil and (allDeckButton.ButtonComponent ~= nil or allDeckButton:AddComponent("ButtonComponent") ~= nil) then
local allDeckButton = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/TopBar/AllDeckButton")
if allDeckButton ~= nil and allDeckButton.ButtonComponent ~= nil then
if self.AllDeckHandler ~= nil then
allDeckButton:DisconnectEvent(ButtonClickEvent, self.AllDeckHandler)
self.AllDeckHandler = nil
end
self.AllDeckHandler = allDeckButton:ConnectEvent(ButtonClickEvent, function() self:OpenAllDeck() end)
end
local allDeckClose = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud/Close")
if allDeckClose ~= nil and (allDeckClose.ButtonComponent ~= nil or allDeckClose:AddComponent("ButtonComponent") ~= nil) then
local allDeckClose = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/Close")
if allDeckClose ~= nil and allDeckClose.ButtonComponent ~= nil then
if self.AllDeckCloseHandler ~= nil then
allDeckClose:DisconnectEvent(ButtonClickEvent, self.AllDeckCloseHandler)
self.AllDeckCloseHandler = nil
@@ -67,17 +67,10 @@ if allDeckClose ~= nil and (allDeckClose.ButtonComponent ~= nil or allDeckClose:
self.AllDeckCloseHandler = allDeckClose:ConnectEvent(ButtonClickEvent, function() self:CloseAllDeck() end)
end
self:BindClassDeckTabs()
for i = 1, 120 do
local allCard = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud/Grid/Card" .. tostring(i))
if allCard ~= nil and (allCard.ButtonComponent ~= nil or allCard:AddComponent("ButtonComponent") ~= nil) then
local slot = i
allCard:ConnectEvent(ButtonClickEvent, function() self:OnAllDeckCardButton(slot) end)
end
end
for i = 1, 10 do
local cardEntity = _EntityService:GetEntityByPath("/ui/RunUIGroup/CardHand/Card" .. tostring(i))
local cardEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(i))
if cardEntity ~= nil and cardEntity.UITouchReceiveComponent ~= nil then
local cardPath = "/ui/RunUIGroup/CardHand/Card" .. tostring(i)
local cardPath = "/ui/DefaultGroup/CardHand/Card" .. tostring(i)
cardEntity:ConnectEvent(UITouchEnterEvent, function() self:SetCardHover(cardPath, true) end)
cardEntity:ConnectEvent(UITouchExitEvent, function() self:SetCardHover(cardPath, false) end)
cardEntity:ConnectEvent(UITouchBeginDragEvent, function(ev) self:OnCardDragBegin(i) end)
@@ -85,24 +78,24 @@ for i = 1, 10 do
cardEntity:ConnectEvent(UITouchEndDragEvent, function(ev) self:OnCardDragEnd(i, ev.TouchPoint) end)
cardEntity:ConnectEvent(UITouchEnterEvent, function() self:HoverCard(i) end)
cardEntity:ConnectEvent(UITouchExitEvent, function() self:UnhoverCard(i) end)
if (cardEntity.ButtonComponent ~= nil or cardEntity:AddComponent("ButtonComponent") ~= nil) then
if cardEntity.ButtonComponent ~= nil then
cardEntity:ConnectEvent(ButtonClickEvent, function() self:OnCardButton(i) end)
end
end
end
for i = 1, 3 do
local rc = _EntityService:GetEntityByPath("/ui/RunUIGroup/RewardHud/Reward" .. tostring(i))
if rc ~= nil and (rc.ButtonComponent ~= nil or rc:AddComponent("ButtonComponent") ~= nil) then
local rc = _EntityService:GetEntityByPath("/ui/DefaultGroup/RewardHud/Reward" .. tostring(i))
if rc ~= nil and rc.ButtonComponent ~= nil then
rc:ConnectEvent(ButtonClickEvent, function() self:PickReward(i) end)
if rc.UITouchReceiveComponent ~= nil then
local cardPath = "/ui/RunUIGroup/RewardHud/Reward" .. tostring(i)
local cardPath = "/ui/DefaultGroup/RewardHud/Reward" .. tostring(i)
rc:ConnectEvent(UITouchEnterEvent, function() self:SetCardHover(cardPath, true) end)
rc:ConnectEvent(UITouchExitEvent, function() self:SetCardHover(cardPath, false) end)
end
end
end
local skip = _EntityService:GetEntityByPath("/ui/RunUIGroup/RewardHud/Skip")
if skip ~= nil and (skip.ButtonComponent ~= nil or skip:AddComponent("ButtonComponent") ~= nil) then
local skip = _EntityService:GetEntityByPath("/ui/DefaultGroup/RewardHud/Skip")
if skip ~= nil and skip.ButtonComponent ~= nil then
skip:ConnectEvent(ButtonClickEvent, function() self:PickReward(0) end)
end
local mapNodeIds = {}
@@ -114,42 +107,42 @@ end
table.insert(mapNodeIds, "boss")
for i = 1, #mapNodeIds do
local nid = mapNodeIds[i]
local mn = _EntityService:GetEntityByPath("/ui/RunUIGroup/MapHud/Node_" .. nid)
if mn ~= nil and (mn.ButtonComponent ~= nil or mn:AddComponent("ButtonComponent") ~= nil) then
local mn = _EntityService:GetEntityByPath("/ui/DefaultGroup/MapHud/Node_" .. nid)
if mn ~= nil and mn.ButtonComponent ~= nil then
mn:ConnectEvent(ButtonClickEvent, function() self:PickNode(nid) end)
end
end
for i = 1, 3 do
local sc = _EntityService:GetEntityByPath("/ui/RunUIGroup/ShopHud/Card" .. tostring(i))
if sc ~= nil and (sc.ButtonComponent ~= nil or sc:AddComponent("ButtonComponent") ~= nil) then
local sc = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Card" .. tostring(i))
if sc ~= nil and sc.ButtonComponent ~= nil then
sc:ConnectEvent(ButtonClickEvent, function() self:BuyCard(i) end)
if sc.UITouchReceiveComponent ~= nil then
local cardPath = "/ui/RunUIGroup/ShopHud/Card" .. tostring(i)
local cardPath = "/ui/DefaultGroup/ShopHud/Card" .. tostring(i)
sc:ConnectEvent(UITouchEnterEvent, function() self:SetCardHover(cardPath, true) end)
sc:ConnectEvent(UITouchExitEvent, function() self:SetCardHover(cardPath, false) end)
end
end
end
local shopLeave = _EntityService:GetEntityByPath("/ui/RunUIGroup/ShopHud/Leave")
if shopLeave ~= nil and (shopLeave.ButtonComponent ~= nil or shopLeave:AddComponent("ButtonComponent") ~= nil) then
local shopLeave = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Leave")
if shopLeave ~= nil and shopLeave.ButtonComponent ~= nil then
shopLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)
end
local shopRelic = _EntityService:GetEntityByPath("/ui/RunUIGroup/ShopHud/Relic")
if shopRelic ~= nil and (shopRelic.ButtonComponent ~= nil or shopRelic:AddComponent("ButtonComponent") ~= nil) then
local shopRelic = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Relic")
if shopRelic ~= nil and shopRelic.ButtonComponent ~= nil then
shopRelic:ConnectEvent(ButtonClickEvent, function() self:BuyRelic() end)
end
local restLeave = _EntityService:GetEntityByPath("/ui/RunUIGroup/RestHud/Leave")
if restLeave ~= nil and (restLeave.ButtonComponent ~= nil or restLeave:AddComponent("ButtonComponent") ~= nil) then
local restLeave = _EntityService:GetEntityByPath("/ui/DefaultGroup/RestHud/Leave")
if restLeave ~= nil and restLeave.ButtonComponent ~= nil then
restLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)
end
for i = 1, ${MAX_MONSTERS} do
local ms = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/MonsterStatus" .. tostring(i))
if ms ~= nil and (ms.ButtonComponent ~= nil or ms:AddComponent("ButtonComponent") ~= nil) then
local ms = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(i))
if ms ~= nil and ms.ButtonComponent ~= nil then
ms:ConnectEvent(ButtonClickEvent, function() self:SetTarget(i) end)
end
end
for i = 1, 10 do
local rs = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/TopBar/RelicSlot" .. tostring(i))
local rs = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/TopBar/RelicSlot" .. tostring(i))
if rs ~= nil and rs.UITouchReceiveComponent ~= nil then
local idx = i
rs:ConnectEvent(UITouchEnterEvent, function()
@@ -163,7 +156,7 @@ for i = 1, 10 do
end
end
for i = 1, 5 do
local ps = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/TopBar/PotionSlot" .. tostring(i))
local ps = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/TopBar/PotionSlot" .. tostring(i))
if ps ~= nil and ps.UITouchReceiveComponent ~= nil then
local idx = i
ps:ConnectEvent(UITouchEnterEvent, function()
@@ -177,42 +170,42 @@ for i = 1, 5 do
ps:ConnectEvent(UITouchDownEvent, function() self:OpenPotionMenu(idx) end)
end
end
local pmUse = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/PotionMenu/Use")
if pmUse ~= nil and (pmUse.ButtonComponent ~= nil or pmUse:AddComponent("ButtonComponent") ~= nil) then
local pmUse = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/PotionMenu/Use")
if pmUse ~= nil and pmUse.ButtonComponent ~= nil then
pmUse:ConnectEvent(ButtonClickEvent, function() self:UsePotion() end)
end
local pmToss = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/PotionMenu/Toss")
if pmToss ~= nil and (pmToss.ButtonComponent ~= nil or pmToss:AddComponent("ButtonComponent") ~= nil) then
local pmToss = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/PotionMenu/Toss")
if pmToss ~= nil and pmToss.ButtonComponent ~= nil then
pmToss:ConnectEvent(ButtonClickEvent, function() self:TossPotion() end)
end
local pmClose = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/PotionMenu/Close")
if pmClose ~= nil and (pmClose.ButtonComponent ~= nil or pmClose:AddComponent("ButtonComponent") ~= nil) then
local pmClose = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/PotionMenu/Close")
if pmClose ~= nil and pmClose.ButtonComponent ~= nil then
pmClose:ConnectEvent(ButtonClickEvent, function() self:ClosePotionMenu() end)
end
local shopPotion = _EntityService:GetEntityByPath("/ui/RunUIGroup/ShopHud/Potion")
if shopPotion ~= nil and (shopPotion.ButtonComponent ~= nil or shopPotion:AddComponent("ButtonComponent") ~= nil) then
local shopPotion = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Potion")
if shopPotion ~= nil and shopPotion.ButtonComponent ~= nil then
shopPotion:ConnectEvent(ButtonClickEvent, function() self:BuyPotion() end)
end
local chest = _EntityService:GetEntityByPath("/ui/RunUIGroup/TreasureHud/Chest")
if chest ~= nil and (chest.ButtonComponent ~= nil or chest:AddComponent("ButtonComponent") ~= nil) then
local chest = _EntityService:GetEntityByPath("/ui/DefaultGroup/TreasureHud/Chest")
if chest ~= nil and chest.ButtonComponent ~= nil then
chest:ConnectEvent(ButtonClickEvent, function() self:OpenChest() end)
end
local treasureLeave = _EntityService:GetEntityByPath("/ui/RunUIGroup/TreasureHud/Leave")
if treasureLeave ~= nil and (treasureLeave.ButtonComponent ~= nil or treasureLeave:AddComponent("ButtonComponent") ~= nil) then
local treasureLeave = _EntityService:GetEntityByPath("/ui/DefaultGroup/TreasureHud/Leave")
if treasureLeave ~= nil and treasureLeave.ButtonComponent ~= nil then
treasureLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)
end
local jcRelic = _EntityService:GetEntityByPath("/ui/SelectUIGroup/JobChoiceHud/RelicButton")
if jcRelic ~= nil and (jcRelic.ButtonComponent ~= nil or jcRelic:AddComponent("ButtonComponent") ~= nil) then
local jcRelic = _EntityService:GetEntityByPath("/ui/DefaultGroup/JobChoiceHud/RelicButton")
if jcRelic ~= nil and jcRelic.ButtonComponent ~= nil then
jcRelic:ConnectEvent(ButtonClickEvent, function() self:PickJobReward("relic") end)
end
local jcJob = _EntityService:GetEntityByPath("/ui/SelectUIGroup/JobChoiceHud/JobButton")
if jcJob ~= nil and (jcJob.ButtonComponent ~= nil or jcJob:AddComponent("ButtonComponent") ~= nil) then
local jcJob = _EntityService:GetEntityByPath("/ui/DefaultGroup/JobChoiceHud/JobButton")
if jcJob ~= nil and jcJob.ButtonComponent ~= nil then
jcJob:ConnectEvent(ButtonClickEvent, function() self:PickJobReward("job") end)
end
for i = 1, 3 do
local slotIdx = i
local jb = _EntityService:GetEntityByPath("/ui/SelectUIGroup/JobSelectHud/Job_slot" .. tostring(i))
if jb ~= nil and (jb.ButtonComponent ~= nil or jb:AddComponent("ButtonComponent") ~= nil) then
local jb = _EntityService:GetEntityByPath("/ui/DefaultGroup/JobSelectHud/Job_slot" .. tostring(i))
if jb ~= nil and jb.ButtonComponent ~= nil then
jb:ConnectEvent(ButtonClickEvent, function()
if self.JobOpts ~= nil and self.JobOpts[slotIdx] ~= nil then
self:SetJob(self.JobOpts[slotIdx].id)
@@ -340,11 +333,11 @@ for i = 1, #self.DiscardPile do
end
self.DiscardPile = {}
self:Shuffle(self.DrawPile)`),
method('RenderPiles', `self:SetText("/ui/RunUIGroup/DeckHud/DrawPile/Count", self:FormatNumber(#self.DrawPile))
self:SetText("/ui/RunUIGroup/DeckHud/DiscardPile/Count", self:FormatNumber(#self.DiscardPile))
self:SetText("/ui/RunUIGroup/DeckHud/ExhaustPile/Count", self:FormatNumber(#(self.ExhaustPile or {})))
self:SetText("/ui/RunUIGroup/DeckHud/EnergyOrb/Value", string.format("%d", self.Energy) .. "/" .. string.format("%d", self.MaxEnergy))
local inspect = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckInspectHud")
method('RenderPiles', `self:SetText("/ui/DefaultGroup/DeckHud/DrawPile/Count", self:FormatNumber(#self.DrawPile))
self:SetText("/ui/DefaultGroup/DeckHud/DiscardPile/Count", self:FormatNumber(#self.DiscardPile))
self:SetText("/ui/DefaultGroup/DeckHud/ExhaustPile/Count", self:FormatNumber(#(self.ExhaustPile or {})))
self:SetText("/ui/DefaultGroup/DeckHud/EnergyOrb/Value", string.format("%d", self.Energy) .. "/" .. string.format("%d", self.MaxEnergy))
local inspect = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud")
if inspect ~= nil and inspect.Enable == true and self.DeckInspectKind ~= "" then
self:OpenDeckInspect(self.DeckInspectKind)
end`),

View File

@@ -6,7 +6,7 @@ export const deckViewMethods = [
method('OpenDeckInspect', `self.DeckInspectKind = kind
if self.DeckAllOpen == true then
self.DeckAllOpen = false
local allHud = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud")
local allHud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud")
if allHud ~= nil then
allHud.Enable = false
end
@@ -24,12 +24,12 @@ else
title = "뽑을 덱"
end
self:RenderDeckInspect(pile, title)
local hud = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckInspectHud")
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud")
if hud ~= nil then
hud.Enable = true
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'kind' }]),
method('CloseDeckInspect', `self.DeckInspectKind = ""
local hud = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckInspectHud")
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud")
if hud ~= nil then
hud.Enable = false
end`),
@@ -41,13 +41,13 @@ local suffix = " (" .. tostring(count) .. ")"
if count > 60 then
suffix = suffix .. " - 60장까지 표시"
end
self:SetText("/ui/DeckUIGroup/DeckInspectHud/Title", title .. suffix)
local empty = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckInspectHud/Empty")
self:SetText("/ui/DefaultGroup/DeckInspectHud/Title", title .. suffix)
local empty = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud/Empty")
if empty ~= nil then
empty.Enable = count <= 0
end
for i = 1, 60 do
local e = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckInspectHud/Grid/Card" .. tostring(i))
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud/Grid/Card" .. tostring(i))
if e ~= nil then
local cardId = nil
if pile ~= nil then
@@ -64,28 +64,28 @@ end`, [
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'pile' },
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'title' },
]),
method('ApplyInspectCardVisual', `self:ApplyCardFace("/ui/DeckUIGroup/DeckInspectHud/Grid/Card" .. tostring(slot), cardId)`, [
method('ApplyInspectCardVisual', `self:ApplyCardFace("/ui/DefaultGroup/DeckInspectHud/Grid/Card" .. tostring(slot), cardId)`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
]),
method('BindClassDeckTabs', `local warriorTab = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud/WarriorTab")
if warriorTab ~= nil and (warriorTab.ButtonComponent ~= nil or warriorTab:AddComponent("ButtonComponent") ~= nil) then
method('BindClassDeckTabs', `local warriorTab = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/WarriorTab")
if warriorTab ~= nil and warriorTab.ButtonComponent ~= nil then
if self.WarriorDeckTabHandler ~= nil then
warriorTab:DisconnectEvent(ButtonClickEvent, self.WarriorDeckTabHandler)
self.WarriorDeckTabHandler = nil
end
self.WarriorDeckTabHandler = warriorTab:ConnectEvent(ButtonClickEvent, function() self:SetClassDeckTab("warrior") end)
end
local thiefTab = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud/ThiefTab")
if thiefTab ~= nil and (thiefTab.ButtonComponent ~= nil or thiefTab:AddComponent("ButtonComponent") ~= nil) then
local thiefTab = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/ThiefTab")
if thiefTab ~= nil and thiefTab.ButtonComponent ~= nil then
if self.ThiefDeckTabHandler ~= nil then
thiefTab:DisconnectEvent(ButtonClickEvent, self.ThiefDeckTabHandler)
self.ThiefDeckTabHandler = nil
end
self.ThiefDeckTabHandler = thiefTab:ConnectEvent(ButtonClickEvent, function() self:SetClassDeckTab("bandit") end)
end
local mageTab = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud/MageTab")
if mageTab ~= nil and (mageTab.ButtonComponent ~= nil or mageTab:AddComponent("ButtonComponent") ~= nil) then
local mageTab = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/MageTab")
if mageTab ~= nil and mageTab.ButtonComponent ~= nil then
if self.MageDeckTabHandler ~= nil then
mageTab:DisconnectEvent(ButtonClickEvent, self.MageDeckTabHandler)
self.MageDeckTabHandler = nil
@@ -94,31 +94,12 @@ if mageTab ~= nil and (mageTab.ButtonComponent ~= nil or mageTab:AddComponent("B
end`),
method('OpenClassDeck', `self.CodexMode = false
self.ClassDeckMode = true
self.DebugCardPickerMode = false
self.DeckAllOpen = true
self:SetClassDeckTab(className)
local hud = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud")
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud")
if hud ~= nil then
hud.Enable = true
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'className' }]),
method('OpenDebugCardPicker', `if self.RunActive ~= true or self.CombatOver == true or self.Hand == nil then
self:Toast("전투 중에만 테스트 카드를 추가할 수 있습니다")
return
end
local className = self.SelectedClass
if className ~= "warrior" and className ~= "magician" and className ~= "bandit" then
className = "bandit"
end
self.CodexMode = false
self.ClassDeckMode = true
self.DebugCardPickerMode = true
self.DeckAllOpen = true
self:SetClassDeckTab(className)
local hud = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud")
if hud ~= nil then
hud.Enable = true
end
self:Toast("테스트 카드 추가 모드")`),
method('SetClassDeckTab', `if self.ClassDeckMode ~= true then
return
end
@@ -166,9 +147,9 @@ end)
self:RenderAllDeck()
self:RenderClassDeckTabs()`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'className' }]),
method('RenderClassDeckTabs', `local tabs = {
{ path = "/ui/DeckUIGroup/DeckAllHud/WarriorTab", cls = "warrior" },
{ path = "/ui/DeckUIGroup/DeckAllHud/ThiefTab", cls = "bandit" },
{ path = "/ui/DeckUIGroup/DeckAllHud/MageTab", cls = "magician" },
{ path = "/ui/DefaultGroup/DeckAllHud/WarriorTab", cls = "warrior" },
{ path = "/ui/DefaultGroup/DeckAllHud/ThiefTab", cls = "bandit" },
{ path = "/ui/DefaultGroup/DeckAllHud/MageTab", cls = "magician" },
}
for i = 1, #tabs do
local e = _EntityService:GetEntityByPath(tabs[i].path)
@@ -183,23 +164,22 @@ for i = 1, #tabs do
end
end
end`),
method('OpenAllDeck', `local inspectHud = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckInspectHud")
method('OpenAllDeck', `local inspectHud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud")
if inspectHud ~= nil then
inspectHud.Enable = false
end
self.DeckInspectKind = ""
self.ClassDeckMode = false
self.ClassDeckClass = ""
self.DebugCardPickerMode = false
self:RenderClassDeckTabs()
self.DeckAllOpen = true
self:RenderAllDeck()
local hud = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud")
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud")
if hud ~= nil then
hud.Enable = true
end`),
method('CloseAllDeck', `self.DeckAllOpen = false
local hud = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud")
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud")
if hud ~= nil then
hud.Enable = false
end
@@ -209,7 +189,6 @@ if self.ClassDeckMode == true then
self.ClassDeckTitle = ""
self.ClassDeckClass = ""
end
self.DebugCardPickerMode = false
self:RenderClassDeckTabs()
if self.CodexMode == true then
self.CodexMode = false
@@ -220,22 +199,19 @@ local title = "모든 덱"
if self.ClassDeckMode == true then
pile = self.ClassDeckCards or {}
title = self.ClassDeckTitle
if self.DebugCardPickerMode == true then
title = title .. " - 테스트 카드 추가"
end
elseif self.CodexMode == true then
pile = self.CodexCards or {}
title = "카드 도감"
end
local count = #pile
self:SetText("/ui/DeckUIGroup/DeckAllHud/Title", title .. " (" .. tostring(count) .. ")")
self:SetText("/ui/DefaultGroup/DeckAllHud/Title", title .. " (" .. tostring(count) .. ")")
self:RenderClassDeckTabs()
local empty = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud/Empty")
local empty = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/Empty")
if empty ~= nil then
empty.Enable = count <= 0
end
for i = 1, 120 do
local e = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud/Grid/Card" .. tostring(i))
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/Grid/Card" .. tostring(i))
if e ~= nil then
local cardId = pile[i]
if cardId == nil then
@@ -246,25 +222,8 @@ for i = 1, 120 do
end
end
end`),
method('ApplyAllDeckCardVisual', `self:ApplyCardFace("/ui/DeckUIGroup/DeckAllHud/Grid/Card" .. tostring(slot), cardId)`, [
method('ApplyAllDeckCardVisual', `self:ApplyCardFace("/ui/DefaultGroup/DeckAllHud/Grid/Card" .. tostring(slot), cardId)`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
]),
method('OnAllDeckCardButton', `if self.DebugCardPickerMode ~= true then
return
end
if self.ClassDeckCards == nil then
return
end
local cardId = self.ClassDeckCards[slot]
if cardId == nil or self.Cards == nil or self.Cards[cardId] == nil then
return
end
self:AddCardsToHand(cardId, 1)
local c = self.Cards[cardId]
local name = cardId
if c.name ~= nil then name = c.name end
self:Toast("테스트 카드 추가: " .. name)`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
]),
];

View File

@@ -20,7 +20,7 @@ if n > 8 then spacing = math.floor(1400 / n) end
local startX = -((n - 1) * spacing) / 2
local drawStart = Vector2(-590, 8)
for i = 1, 10 do
\tlocal cardEntity = _EntityService:GetEntityByPath("/ui/RunUIGroup/CardHand/Card" .. tostring(i))
\tlocal cardEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(i))
\tif cardEntity ~= nil then
\t\tlocal cardId = self.Hand[i]
\t\tif cardId == nil then
@@ -81,11 +81,11 @@ local xs = {}
local baseY = 0
local hoverIndex = 0
local push = 110
if string.find(path, "/ui/RunUIGroup/CardHand/Card") == 1 then
if string.find(path, "/ui/DefaultGroup/CardHand/Card") == 1 then
if self.DragSlot ~= nil and self.DragSlot > 0 then
return
end
prefix = "/ui/RunUIGroup/CardHand/Card"
prefix = "/ui/DefaultGroup/CardHand/Card"
count = 0
if self.Hand ~= nil then count = #self.Hand end
for i = 1, count do
@@ -93,14 +93,14 @@ if string.find(path, "/ui/RunUIGroup/CardHand/Card") == 1 then
end
baseY = 0
hoverIndex = tonumber(string.match(path, "Card(%d+)")) or 0
elseif string.find(path, "/ui/RunUIGroup/RewardHud/Reward") == 1 then
prefix = "/ui/RunUIGroup/RewardHud/Reward"
elseif string.find(path, "/ui/DefaultGroup/RewardHud/Reward") == 1 then
prefix = "/ui/DefaultGroup/RewardHud/Reward"
count = 3
xs = { -300, 0, 300 }
baseY = 0
hoverIndex = tonumber(string.match(path, "Reward(%d+)")) or 0
elseif string.find(path, "/ui/RunUIGroup/ShopHud/Card") == 1 then
prefix = "/ui/RunUIGroup/ShopHud/Card"
elseif string.find(path, "/ui/DefaultGroup/ShopHud/Card") == 1 then
prefix = "/ui/DefaultGroup/ShopHud/Card"
count = 3
xs = { -300, 0, 300 }
baseY = 20
@@ -159,7 +159,7 @@ self.CardHoverTweenId = eventId`, [
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'path' },
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'hover' },
]),
method('ApplyCardVisual', `self:ApplyCardFace("/ui/RunUIGroup/CardHand/Card" .. tostring(slot), cardId)`, [
method('ApplyCardVisual', `self:ApplyCardFace("/ui/DefaultGroup/CardHand/Card" .. tostring(slot), cardId)`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
]),
@@ -181,7 +181,7 @@ if math.abs(n - math.floor(n)) < 0.00001 then
return string.format("%d", math.floor(n))
end
return tostring(n)`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'value' }], 0, 'string'),
method('AnimateCardFrom', `local cardEntity = _EntityService:GetEntityByPath("/ui/RunUIGroup/CardHand/Card" .. tostring(slot))
method('AnimateCardFrom', `local cardEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(slot))
if cardEntity == nil or cardEntity.UITransformComponent == nil then
\treturn
end
@@ -328,13 +328,13 @@ end`, [
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'triggerSly' },
]),
method('IsDiscardSelecting', `return self.DiscardSelectRemaining ~= nil and self.DiscardSelectRemaining > 0`, [], 0, 'boolean'),
method('UpdateDiscardPrompt', `local e = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/DiscardPrompt")
method('UpdateDiscardPrompt', `local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/DiscardPrompt")
if e == nil then
return
end
if self:IsDiscardSelecting() == true then
local picked = self.DiscardSelectTotal - self.DiscardSelectRemaining
self:SetText("/ui/RunUIGroup/CombatHud/DiscardPrompt", "버릴 카드 선택 " .. self:FormatNumber(picked + 1) .. "/" .. self:FormatNumber(self.DiscardSelectTotal))
self:SetText("/ui/DefaultGroup/CombatHud/DiscardPrompt", "버릴 카드 선택 " .. self:FormatNumber(picked + 1) .. "/" .. self:FormatNumber(self.DiscardSelectTotal))
e.Enable = true
else
e.Enable = false

View File

@@ -95,7 +95,7 @@ if self:AddPotion(pid) == true then
self:Toast("물약 획득: " .. p.name)
end`),
method('RenderPotions', `for i = 1, 5 do
local base = "/ui/RunUIGroup/CombatHud/TopBar/PotionSlot" .. tostring(i)
local base = "/ui/DefaultGroup/CombatHud/TopBar/PotionSlot" .. tostring(i)
local e = _EntityService:GetEntityByPath(base)
if e ~= nil and e.SpriteGUIRendererComponent ~= nil then
local pid = nil
@@ -121,11 +121,11 @@ self.PotionMenuSlot = slot
local pid = self.RunPotions[slot]
local p = self.Potions[pid]
if p ~= nil then
self:SetText("/ui/RunUIGroup/CombatHud/PotionMenu/Title", p.name .. " — " .. p.desc)
self:SetText("/ui/DefaultGroup/CombatHud/PotionMenu/Title", p.name .. " — " .. p.desc)
end
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/PotionMenu", true)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/PotionMenu", true)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
method('ClosePotionMenu', `self.PotionMenuSlot = 0
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/PotionMenu", false)`),
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/PotionMenu", false)`),
method('UsePotion', `if self.PotionMenuSlot <= 0 then
return
end
@@ -133,8 +133,8 @@ if self.CombatOver == true or self.TurnBusy == true or self.FxBusy == true then
self:Toast("지금은 사용할 수 없습니다")
return
end
local combat = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud")
local hand = _EntityService:GetEntityByPath("/ui/RunUIGroup/CardHand")
local combat = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud")
local hand = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand")
if combat == nil or combat.Enable ~= true or hand == nil or hand.Enable ~= true then
self:Toast("전투 중에만 사용할 수 있습니다")
return
@@ -189,7 +189,7 @@ if self.RunRelics ~= nil then
count = #self.RunRelics
end
for i = 1, 10 do
local base = "/ui/RunUIGroup/CombatHud/TopBar/RelicSlot" .. tostring(i)
local base = "/ui/DefaultGroup/CombatHud/TopBar/RelicSlot" .. tostring(i)
local e = _EntityService:GetEntityByPath(base)
if e ~= nil and e.SpriteGUIRendererComponent ~= nil then
local rid = nil
@@ -209,5 +209,5 @@ local of = ""
if count > 10 then
of = "+" .. tostring(count - 9)
end
self:SetText("/ui/RunUIGroup/CombatHud/TopBar/RelicOverflow", of)`),
self:SetText("/ui/DefaultGroup/CombatHud/TopBar/RelicOverflow", of)`),
];

View File

@@ -3,10 +3,10 @@ import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
export const jobMethods = [
method('ShowJobChoice', `self:SetEntityEnabled("/ui/RunUIGroup/CardHand", false)
self:SetEntityEnabled("/ui/RunUIGroup/DeckHud", false)
self:SetEntityEnabled("/ui/SelectUIGroup/JobChoiceHud", true)`),
method('PickJobReward', `self:SetEntityEnabled("/ui/SelectUIGroup/JobChoiceHud", false)
method('ShowJobChoice', `self:SetEntityEnabled("/ui/DefaultGroup/CardHand", false)
self:SetEntityEnabled("/ui/DefaultGroup/DeckHud", false)
self:SetEntityEnabled("/ui/DefaultGroup/JobChoiceHud", true)`),
method('PickJobReward', `self:SetEntityEnabled("/ui/DefaultGroup/JobChoiceHud", false)
if kind == "relic" then
local bid = self:PickNewRelic()
if bid ~= "" then
@@ -26,7 +26,7 @@ if opts == nil then
end
self.JobOpts = opts
for i = 1, 3 do
local base = "/ui/SelectUIGroup/JobSelectHud/Job_slot" .. tostring(i)
local base = "/ui/DefaultGroup/JobSelectHud/Job_slot" .. tostring(i)
local o = opts[i]
if o ~= nil then
self:SetEntityEnabled(base, true)
@@ -40,7 +40,7 @@ for i = 1, 3 do
self:SetEntityEnabled(base, false)
end
end
self:SetEntityEnabled("/ui/SelectUIGroup/JobSelectHud", true)`),
self:SetEntityEnabled("/ui/DefaultGroup/JobSelectHud", true)`),
method('JobLabel', `if self.PlayerJob ~= "" and self.Jobs ~= nil then
for cls, list in pairs(self.Jobs) do
for i = 1, #list do
@@ -73,7 +73,7 @@ if starter ~= "" then
self:Toast("2차 전직: " .. self:JobLabel() .. "! 신규 카드 — " .. sc.name)
end
end
self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/Name", self:JobLabel())
self:SetEntityEnabled("/ui/SelectUIGroup/JobSelectHud", false)
self:SetText("/ui/DefaultGroup/CombatHud/PlayerPanel/Name", self:JobLabel())
self:SetEntityEnabled("/ui/DefaultGroup/JobSelectHud", false)
self:ContinueAfterBoss()`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'jobId' }]),
];

View File

@@ -115,7 +115,7 @@ for i = 1, #list do
end
end
return false`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'id' }], 0, 'boolean'),
method('RenderMapNode', `local base = "/ui/RunUIGroup/MapHud/Node_" .. id
method('RenderMapNode', `local base = "/ui/DefaultGroup/MapHud/Node_" .. id
local e = _EntityService:GetEntityByPath(base)
if e == nil then
return
@@ -151,7 +151,7 @@ if e.SpriteGUIRendererComponent ~= nil then
e.SpriteGUIRendererComponent.Color = Color(0.68, 0.68, 0.72, 0.85)
end
end
if (e.ButtonComponent ~= nil or e:AddComponent("ButtonComponent") ~= nil) then
if e.ButtonComponent ~= nil then
e.ButtonComponent.Enable = reachable
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'id' }]),
method('RenderMapDots', `local node = self.MapNodes[fromId]
@@ -162,7 +162,7 @@ if node ~= nil then
end
end
for k = 1, 3 do
local d = _EntityService:GetEntityByPath("/ui/RunUIGroup/MapHud/Dot_" .. dotId .. "_" .. tostring(k))
local d = _EntityService:GetEntityByPath("/ui/DefaultGroup/MapHud/Dot_" .. dotId .. "_" .. tostring(k))
if d ~= nil then
d.Enable = has
if has == true and d.SpriteGUIRendererComponent ~= nil then
@@ -210,7 +210,7 @@ if self.VisitedNodes == nil then
self.VisitedNodes = {}
end
table.insert(self.VisitedNodes, id)
local hud = _EntityService:GetEntityByPath("/ui/RunUIGroup/MapHud")
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/MapHud")
if hud ~= nil then
hud.Enable = false
end

View File

@@ -15,7 +15,7 @@ return table.concat(parts, " ")`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'poison' },
], 0, 'string'),
method('RenderCombat', `for i = 1, ${MAX_MONSTERS} do
local base = "/ui/RunUIGroup/CombatHud/MonsterStatus" .. tostring(i)
local base = "/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(i)
local m = self.Monsters[i]
if m ~= nil and m.alive == true then
self:SetEntityEnabled(base, true)
@@ -41,6 +41,7 @@ return table.concat(parts, " ")`, [
local dragActive = self.DragTargetIndex ~= nil and self.DragTargetIndex > 0
local shownTarget = self.TargetIndex
if dragActive == true then shownTarget = self.DragTargetIndex end
self:SetEntityEnabled(base .. "/TargetFrame", i == shownTarget)
self:SetEntityEnabled(base .. "/TargetMarker", i == shownTarget and dragActive)
self:SetEntityEnabled(base .. "/TargetMarker/Label", i == shownTarget and dragActive)
local intentEntity = _EntityService:GetEntityByPath(base .. "/Intent")
@@ -63,10 +64,10 @@ return table.concat(parts, " ")`, [
self:SetEntityEnabled(base, false)
end
end
self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/HpText", string.format("%d", self.PlayerHp) .. "/" .. string.format("%d", self.PlayerMaxHp))
self:SetHpBar("/ui/RunUIGroup/CombatHud/PlayerPanel/HpBarFill", self.PlayerHp, self.PlayerMaxHp, 220)
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge", self.PlayerBlock > 0)
self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge/Value", string.format("%d", self.PlayerBlock))
self:SetText("/ui/DefaultGroup/CombatHud/PlayerPanel/HpText", string.format("%d", self.PlayerHp) .. "/" .. string.format("%d", self.PlayerMaxHp))
self:SetHpBar("/ui/DefaultGroup/CombatHud/PlayerPanel/HpBarFill", self.PlayerHp, self.PlayerMaxHp, 220)
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/PlayerPanel/BlockBadge", self.PlayerBlock > 0)
self:SetText("/ui/DefaultGroup/CombatHud/PlayerPanel/BlockBadge/Value", string.format("%d", self.PlayerBlock))
local pb = self:BuffsLabel(self.PlayerStr, self.PlayerWeak, self.PlayerVuln, 0)
if self.PlayerDex ~= nil and self.PlayerDex > 0 then
if pb ~= "" then pb = pb .. " " end
@@ -85,10 +86,10 @@ if self.PlayerPowers ~= nil and #self.PlayerPowers > 0 then
if pb ~= "" then pb = pb .. " · " end
pb = pb .. table.concat(names, " ")
end
self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/Buffs", pb)
self:SetText("/ui/DefaultGroup/CombatHud/PlayerPanel/Buffs", pb)
self:RenderRun()`),
method('ShowDmgPop', `local slotKey = string.format("%d", math.floor(slot or 0))
local base = "/ui/RunUIGroup/CombatHud/DmgPop" .. slotKey
local base = "/ui/DefaultGroup/CombatHud/DmgPop" .. slotKey
local pop = _EntityService:GetEntityByPath(base)
if pop == nil then
return
@@ -133,7 +134,7 @@ if m ~= nil and m.entity ~= nil and isvalid(m.entity) and m.entity.TransformComp
local screen = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + ${HEAD_OFFSET_Y + 0.45}))
popPos = _UILogic:ScreenToUIPosition(screen)
else
local slotEntity = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/MonsterStatus" .. slotKey)
local slotEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/MonsterSlot" .. slotKey)
if slotEntity ~= nil and slotEntity.UITransformComponent ~= nil then
local sp = slotEntity.UITransformComponent.anchoredPosition
popPos = Vector2(sp.x, sp.y + 76)
@@ -168,7 +169,7 @@ end, 0.48)`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
]),
method('ShowPlayerDmgPop', `local base = "/ui/RunUIGroup/CombatHud/PlayerPanel/DmgPop"
method('ShowPlayerDmgPop', `local base = "/ui/DefaultGroup/CombatHud/PlayerPanel/DmgPop"
if amount > 0 then
self:SetText(base, "-" .. string.format("%d", amount))
else
@@ -290,7 +291,7 @@ end
local wp = tr.WorldPosition
local screen = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + ${HEAD_OFFSET_Y}))
local uipos = _UILogic:ScreenToUIPosition(screen)
local e = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/MonsterStatus" .. tostring(slot))
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(slot))
if e ~= nil and e.UITransformComponent ~= nil then
e.UITransformComponent.anchoredPosition = uipos
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
@@ -302,6 +303,6 @@ end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], N
if self.AscensionLevel > 0 then
floorText = floorText .. " · 승천" .. string.format("%d", self.AscensionLevel)
end
self:SetText("/ui/RunUIGroup/CombatHud/TopBar/Floor", floorText)
self:SetText("/ui/RunUIGroup/CombatHud/TopBar/Gold", "메소 " .. string.format("%d", self.Gold))`),
self:SetText("/ui/DefaultGroup/CombatHud/TopBar/Floor", floorText)
self:SetText("/ui/DefaultGroup/CombatHud/TopBar/Gold", "메소 " .. string.format("%d", self.Gold))`),
];

View File

@@ -11,8 +11,8 @@ for id, c in pairs(self.Cards) do
end
table.sort(pool)
return pool`, [], 0, 'any'),
method('OfferReward', `self:SetEntityEnabled("/ui/RunUIGroup/CardHand", false)
self:SetEntityEnabled("/ui/RunUIGroup/DeckHud", false)
method('OfferReward', `self:SetEntityEnabled("/ui/DefaultGroup/CardHand", false)
self:SetEntityEnabled("/ui/DefaultGroup/DeckHud", false)
local pool = self:CardPool()
local byRarity = {}
for _, id in ipairs(pool) do
@@ -30,11 +30,11 @@ for i = 1, 3 do
self.RewardChoices[i] = bucket[math.random(1, #bucket)]
self:ApplyRewardVisual(i, self.RewardChoices[i])
end
local hud = _EntityService:GetEntityByPath("/ui/RunUIGroup/RewardHud")
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/RewardHud")
if hud ~= nil then
hud.Enable = true
end`),
method('ApplyRewardVisual', `self:ApplyCardFace("/ui/RunUIGroup/RewardHud/Reward" .. tostring(slot), cardId)`, [
method('ApplyRewardVisual', `self:ApplyCardFace("/ui/DefaultGroup/RewardHud/Reward" .. tostring(slot), cardId)`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
]),
@@ -47,7 +47,7 @@ if slot ~= 0 and self.RewardChoices ~= nil then
table.insert(self.RunDeck, id)
end
end
local hud = _EntityService:GetEntityByPath("/ui/RunUIGroup/RewardHud")
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/RewardHud")
if hud ~= nil then
hud.Enable = false
end

View File

@@ -1,5 +1,5 @@
import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaCharsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
export const runMethods = [
@@ -32,7 +32,6 @@ self.PlayerJob = ""
${luaJobsTable(JOBS)}
${luaFramesTable()}
${luaNodeIconsTable()}
${luaCharsTable()}
self:GenerateMap()
self:BindButtons()
self:AddRelic("${RELICS.startingRelic}")
@@ -59,11 +58,11 @@ _TimerService:SetTimerOnce(function()
end, 0.2)`),
method('StartCombat', `self:ShowState("combat")
self:KickCombatCamera()
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/Result", false)
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/PotionMenu", false)
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/TooltipBox", false)
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/DiscardPrompt", false)
self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/Name", self:JobLabel())
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/Result", false)
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/PotionMenu", false)
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/TooltipBox", false)
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/DiscardPrompt", false)
self:SetText("/ui/DefaultGroup/CombatHud/PlayerPanel/Name", self:JobLabel())
self.MaxEnergy = 3
self.Turn = 0
self.PlayerBlock = 0
@@ -95,19 +94,7 @@ self:BuildMonsters()
self:RenderCombat()
self:StartPlayerTurn()
self:ApplyRelics("combatStart")
self:RenderCombat()
local slotTid = 0
slotTid = _TimerService:SetTimerRepeat(function()
if self.CombatOver == true or self.Monsters == nil or #self.Monsters == 0 then
_TimerService:ClearTimer(slotTid)
return
end
for i = 1, #self.Monsters do
if self.Monsters[i] ~= nil and self.Monsters[i].alive == true then
self:PositionMonsterSlot(i)
end
end
end, 0.15)`),
self:RenderCombat()`),
method('RegisterMonster', `if self.Registered == nil then
self.Registered = {}
end

View File

@@ -16,8 +16,8 @@ if lp.CurrentMapName == target then
return
end
_TeleportService:TeleportToMapPosition(lp, Vector3(-6, 0.03, 0), target)`),
method('ShowResult', `self:SetText("/ui/RunUIGroup/CombatHud/Result", text)
local entity = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/Result")
method('ShowResult', `self:SetText("/ui/DefaultGroup/CombatHud/Result", text)
local entity = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/Result")
if entity ~= nil then
entity.Enable = true
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'text' }]),

View File

@@ -20,11 +20,11 @@ self.ShopPotion = pkeys[math.random(1, #pkeys)]
self.ShopPotionBought = false
self:RenderShop()
self:ShowState("shop")`),
method('RenderShop', `self:SetText("/ui/RunUIGroup/ShopHud/Gold", "메소 " .. string.format("%d", self.Gold))
method('RenderShop', `self:SetText("/ui/DefaultGroup/ShopHud/Gold", "메소 " .. string.format("%d", self.Gold))
for i = 1, 3 do
local cid = self.ShopChoices[i]
local c = self.Cards[cid]
local base = "/ui/RunUIGroup/ShopHud/Card" .. tostring(i)
local base = "/ui/DefaultGroup/ShopHud/Card" .. tostring(i)
if c ~= nil then
self:ApplyCardFace(base, cid)
self:SetText(base .. "/Price", string.format("%d", ${CARD_PRICE}) .. " 메소")
@@ -38,9 +38,9 @@ for i = 1, 3 do
end
local rr = self.Relics[self.ShopRelic]
if rr ~= nil then
self:SetText("/ui/RunUIGroup/ShopHud/Relic/Label", rr.name .. " — " .. rr.desc)
self:SetText("/ui/RunUIGroup/ShopHud/Relic/Price", string.format("%d", ${RELIC_PRICE}) .. " 메소")
local re = _EntityService:GetEntityByPath("/ui/RunUIGroup/ShopHud/Relic")
self:SetText("/ui/DefaultGroup/ShopHud/Relic/Label", rr.name .. " — " .. rr.desc)
self:SetText("/ui/DefaultGroup/ShopHud/Relic/Price", string.format("%d", ${RELIC_PRICE}) .. " 메소")
local re = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Relic")
if re ~= nil and re.SpriteGUIRendererComponent ~= nil then
if self.ShopRelicBought == true then
re.SpriteGUIRendererComponent.Color = Color(0.2, 0.22, 0.26, 0.6)
@@ -51,9 +51,9 @@ if rr ~= nil then
end
local pp = self.Potions[self.ShopPotion]
if pp ~= nil then
self:SetText("/ui/RunUIGroup/ShopHud/Potion/Label", pp.name .. " — " .. pp.desc)
self:SetText("/ui/RunUIGroup/ShopHud/Potion/Price", string.format("%d", ${POTIONS.shopPrice}) .. " 메소")
local pe = _EntityService:GetEntityByPath("/ui/RunUIGroup/ShopHud/Potion")
self:SetText("/ui/DefaultGroup/ShopHud/Potion/Label", pp.name .. " — " .. pp.desc)
self:SetText("/ui/DefaultGroup/ShopHud/Potion/Price", string.format("%d", ${POTIONS.shopPrice}) .. " 메소")
local pe = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Potion")
if pe ~= nil and pe.SpriteGUIRendererComponent ~= nil then
if self.ShopPotionBought == true then
pe.SpriteGUIRendererComponent.Color = Color(0.2, 0.22, 0.26, 0.6)
@@ -106,24 +106,24 @@ if self.PlayerHp > self.PlayerMaxHp then
self.PlayerHp = self.PlayerMaxHp
end
local healed = self.PlayerHp - old
self:SetText("/ui/RunUIGroup/RestHud/Info", "HP " .. string.format("%d", old) .. " → " .. string.format("%d", self.PlayerHp) .. " (+" .. string.format("%d", healed) .. ")")
self:SetText("/ui/DefaultGroup/RestHud/Info", "HP " .. string.format("%d", old) .. " → " .. string.format("%d", self.PlayerHp) .. " (+" .. string.format("%d", healed) .. ")")
self:RenderCombat()
self:ShowState("rest")`),
method('LeaveNode', `local s = _EntityService:GetEntityByPath("/ui/RunUIGroup/ShopHud")
method('LeaveNode', `local s = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud")
if s ~= nil then
s.Enable = false
end
local r = _EntityService:GetEntityByPath("/ui/RunUIGroup/RestHud")
local r = _EntityService:GetEntityByPath("/ui/DefaultGroup/RestHud")
if r ~= nil then
r.Enable = false
end
local t = _EntityService:GetEntityByPath("/ui/RunUIGroup/TreasureHud")
local t = _EntityService:GetEntityByPath("/ui/DefaultGroup/TreasureHud")
if t ~= nil then
t.Enable = false
end
self:ShowMap()`),
method('ShowTreasure', `self.ChestOpened = false
local chest = _EntityService:GetEntityByPath("/ui/RunUIGroup/TreasureHud/Chest")
local chest = _EntityService:GetEntityByPath("/ui/DefaultGroup/TreasureHud/Chest")
if chest ~= nil then
if chest.SpriteGUIRendererComponent ~= nil then
chest.SpriteGUIRendererComponent.ImageRUID = "${CHEST_CLOSED_RUID}"
@@ -132,15 +132,15 @@ if chest ~= nil then
chest.UITransformComponent.anchoredPosition = Vector2(0, 40)
end
end
self:SetEntityEnabled("/ui/RunUIGroup/TreasureHud/Reward", false)
self:SetEntityEnabled("/ui/RunUIGroup/TreasureHud/Hint", true)
self:SetEntityEnabled("/ui/DefaultGroup/TreasureHud/Reward", false)
self:SetEntityEnabled("/ui/DefaultGroup/TreasureHud/Hint", true)
self:ShowState("treasure")`),
method('OpenChest', `if self.ChestOpened == true then
return
end
self.ChestOpened = true
self:SetEntityEnabled("/ui/RunUIGroup/TreasureHud/Hint", false)
local chest = _EntityService:GetEntityByPath("/ui/RunUIGroup/TreasureHud/Chest")
self:SetEntityEnabled("/ui/DefaultGroup/TreasureHud/Hint", false)
local chest = _EntityService:GetEntityByPath("/ui/DefaultGroup/TreasureHud/Chest")
local steps = { 10, -10, 8, -8, 5, 0 }
for i = 1, #steps do
local dx = steps[i]
@@ -167,7 +167,7 @@ _TimerService:SetTimerOnce(function()
end
self.Gold = self.Gold + g
self:RenderRun()
self:SetText("/ui/RunUIGroup/TreasureHud/Reward", msg)
self:SetEntityEnabled("/ui/RunUIGroup/TreasureHud/Reward", true)
self:SetText("/ui/DefaultGroup/TreasureHud/Reward", msg)
self:SetEntityEnabled("/ui/DefaultGroup/TreasureHud/Reward", true)
end, 0.55)`),
];

View File

@@ -6,8 +6,8 @@ export const soulMethods = [
method('ShowSoulShop', `self:RenderSoulLabel()
self:RenderSoulShop()
self:BindSoulShopButtons()
self:SetEntityEnabled("/ui/LobbyUIGroup/SoulShopHud", true)`),
method('CloseSoulShop', `self:SetEntityEnabled("/ui/LobbyUIGroup/SoulShopHud", false)`),
self:SetEntityEnabled("/ui/DefaultGroup/SoulShopHud", true)`),
method('CloseSoulShop', `self:SetEntityEnabled("/ui/DefaultGroup/SoulShopHud", false)`),
method('ReqLoadSouls', `local ds = _DataStorageService:GetUserDataStorage(userId)
local e1, pts = ds:GetAndWait("soulPoints")
local e2, unl = ds:GetAndWait("soulUnlocks")
@@ -63,7 +63,7 @@ self:RenderSoulLabel()
self:RenderSoulShop()`, [{ Type: "number", DefaultValue: null, SyncDirection: 0, Attributes: [], Name: "slot" }]),
method('RenderSoulShop', `local defs = self.SoulShopDef or {}
for i = 1, 4 do
local base = "/ui/LobbyUIGroup/SoulShopHud/Item" .. tostring(i)
local base = "/ui/DefaultGroup/SoulShopHud/Item" .. tostring(i)
local d = defs[i]
if d == nil then
self:SetEntityEnabled(base, false)
@@ -87,8 +87,8 @@ end
self.SoulShopBound = true
for i = 1, 4 do
local idx = i
local e = _EntityService:GetEntityByPath("/ui/LobbyUIGroup/SoulShopHud/Item" .. tostring(i))
if e ~= nil and (e.ButtonComponent ~= nil or e:AddComponent("ButtonComponent") ~= nil) then
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/SoulShopHud/Item" .. tostring(i))
if e ~= nil and e.ButtonComponent ~= nil then
e:ConnectEvent(ButtonClickEvent, function() self:BuySoulUnlock(idx) end)
end
end`),

View File

@@ -6,45 +6,37 @@ export const stateMethods = [
method('HideGameHud', `self:SetEntityEnabled("/ui/DefaultGroup/Button_Attack", false)
self:SetEntityEnabled("/ui/DefaultGroup/Button_Jump", false)
self:SetEntityEnabled("/ui/DefaultGroup/UIJoystick", false)
self:SetEntityEnabled("/ui/RunUIGroup/DeckHud", false)
self:SetEntityEnabled("/ui/RunUIGroup/CardHand", false)
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud", false)
self:SetEntityEnabled("/ui/RunUIGroup/RewardHud", false)
self:SetEntityEnabled("/ui/RunUIGroup/MapHud", false)
self:SetEntityEnabled("/ui/RunUIGroup/ShopHud", false)
self:SetEntityEnabled("/ui/RunUIGroup/RestHud", false)
self:SetEntityEnabled("/ui/RunUIGroup/TreasureHud", false)
self:SetEntityEnabled("/ui/SelectUIGroup/JobChoiceHud", false)
self:SetEntityEnabled("/ui/SelectUIGroup/JobSelectHud", false)
self:SetEntityEnabled("/ui/DeckUIGroup/DeckInspectHud", false)
self:SetEntityEnabled("/ui/DeckUIGroup/DeckAllHud", false)
self:SetEntityEnabled("/ui/LobbyUIGroup/LobbyHud", false)
self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", false)
self:SetEntityEnabled("/ui/LobbyUIGroup/SoulShopHud", false)`),
method('ActivateUIGroups', `local function grp(n)
local g = _EntityService:GetEntityByPath("/ui/" .. n)
if g ~= nil then g:SetEnable(true) end
end
grp("SelectUIGroup")
grp("LobbyUIGroup")
grp("RunUIGroup")
grp("DeckUIGroup")`, [], 2),
self:SetEntityEnabled("/ui/DefaultGroup/DeckHud", false)
self:SetEntityEnabled("/ui/DefaultGroup/CardHand", false)
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud", false)
self:SetEntityEnabled("/ui/DefaultGroup/RewardHud", false)
self:SetEntityEnabled("/ui/DefaultGroup/MapHud", false)
self:SetEntityEnabled("/ui/DefaultGroup/ShopHud", false)
self:SetEntityEnabled("/ui/DefaultGroup/RestHud", false)
self:SetEntityEnabled("/ui/DefaultGroup/TreasureHud", false)
self:SetEntityEnabled("/ui/DefaultGroup/JobChoiceHud", false)
self:SetEntityEnabled("/ui/DefaultGroup/JobSelectHud", false)
self:SetEntityEnabled("/ui/DefaultGroup/DeckInspectHud", false)
self:SetEntityEnabled("/ui/DefaultGroup/DeckAllHud", false)
self:SetEntityEnabled("/ui/DefaultGroup/LobbyHud", false)
self:SetEntityEnabled("/ui/DefaultGroup/BoardHud", false)
self:SetEntityEnabled("/ui/DefaultGroup/SoulShopHud", false)`),
method('ShowState', `self:HideGameHud()
self:SetEntityEnabled("/ui/DefaultGroup/MainMenu", state == "menu")
self:SetEntityEnabled("/ui/SelectUIGroup/CharacterSelectHud", state == "charselect")
self:SetEntityEnabled("/ui/LobbyUIGroup/LobbyHud", state == "lobby")
self:SetEntityEnabled("/ui/DefaultGroup/CharacterSelectHud", state == "charselect")
self:SetEntityEnabled("/ui/DefaultGroup/LobbyHud", state == "lobby")
if state == "map" then
self:SetEntityEnabled("/ui/RunUIGroup/MapHud", true)
self:SetEntityEnabled("/ui/DefaultGroup/MapHud", true)
elseif state == "combat" then
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud", true)
self:SetEntityEnabled("/ui/RunUIGroup/DeckHud", true)
self:SetEntityEnabled("/ui/RunUIGroup/CardHand", true)
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud", true)
self:SetEntityEnabled("/ui/DefaultGroup/DeckHud", true)
self:SetEntityEnabled("/ui/DefaultGroup/CardHand", true)
elseif state == "shop" then
self:SetEntityEnabled("/ui/RunUIGroup/ShopHud", true)
self:SetEntityEnabled("/ui/DefaultGroup/ShopHud", true)
elseif state == "rest" then
self:SetEntityEnabled("/ui/RunUIGroup/RestHud", true)
self:SetEntityEnabled("/ui/DefaultGroup/RestHud", true)
elseif state == "treasure" then
self:SetEntityEnabled("/ui/RunUIGroup/TreasureHud", true)
self:SetEntityEnabled("/ui/DefaultGroup/TreasureHud", true)
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'state' }]),
method('ShowMainMenu', `self.SelectedClass = ""
self:RenderAscension()
@@ -54,39 +46,39 @@ self:SetText("/ui/DefaultGroup/MainMenu/Subtitle", "캐릭터를 고르고 덱
self:SetText("/ui/DefaultGroup/MainMenu/NewGameButton", "새 게임")
self:BindMenuButtons()`),
method('BindMenuButtons', `local buttonEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/NewGameButton")
if buttonEntity ~= nil and (buttonEntity.ButtonComponent ~= nil or buttonEntity:AddComponent("ButtonComponent") ~= nil) then
if buttonEntity ~= nil and buttonEntity.ButtonComponent ~= nil then
if self.NewGameHandler ~= nil then
buttonEntity:DisconnectEvent(ButtonClickEvent, self.NewGameHandler)
self.NewGameHandler = nil
end
self.NewGameHandler = buttonEntity:ConnectEvent(ButtonClickEvent, function() self:ShowLobby() end)
self.NewGameHandler = buttonEntity:ConnectEvent(ButtonClickEvent, function() self:ShowCharacterSelect() end)
end
local warrior = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/WarriorButton")
if warrior ~= nil and (warrior.ButtonComponent ~= nil or warrior:AddComponent("ButtonComponent") ~= nil) then
local warrior = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/WarriorButton")
if warrior ~= nil and warrior.ButtonComponent ~= nil then
if self.WarriorSelectHandler ~= nil then
warrior:DisconnectEvent(ButtonClickEvent, self.WarriorSelectHandler)
self.WarriorSelectHandler = nil
end
self.WarriorSelectHandler = warrior:ConnectEvent(ButtonClickEvent, function() self:SelectClass("warrior") end)
end
local thief = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/ThiefButton")
if thief ~= nil and (thief.ButtonComponent ~= nil or thief:AddComponent("ButtonComponent") ~= nil) then
local thief = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/ThiefButton")
if thief ~= nil and thief.ButtonComponent ~= nil then
if self.ThiefSelectHandler ~= nil then
thief:DisconnectEvent(ButtonClickEvent, self.ThiefSelectHandler)
self.ThiefSelectHandler = nil
end
self.ThiefSelectHandler = thief:ConnectEvent(ButtonClickEvent, function() self:SelectClass("bandit") end)
end
local mage = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/MageButton")
if mage ~= nil and (mage.ButtonComponent ~= nil or mage:AddComponent("ButtonComponent") ~= nil) then
local mage = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/MageButton")
if mage ~= nil and mage.ButtonComponent ~= nil then
if self.MageSelectHandler ~= nil then
mage:DisconnectEvent(ButtonClickEvent, self.MageSelectHandler)
self.MageSelectHandler = nil
end
self.MageSelectHandler = mage:ConnectEvent(ButtonClickEvent, function() self:SelectClass("magician") end)
end
local allDeckClose = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud/Close")
if allDeckClose ~= nil and (allDeckClose.ButtonComponent ~= nil or allDeckClose:AddComponent("ButtonComponent") ~= nil) then
local allDeckClose = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/Close")
if allDeckClose ~= nil and allDeckClose.ButtonComponent ~= nil then
if self.AllDeckCloseHandler ~= nil then
allDeckClose:DisconnectEvent(ButtonClickEvent, self.AllDeckCloseHandler)
self.AllDeckCloseHandler = nil
@@ -94,16 +86,16 @@ if allDeckClose ~= nil and (allDeckClose.ButtonComponent ~= nil or allDeckClose:
self.AllDeckCloseHandler = allDeckClose:ConnectEvent(ButtonClickEvent, function() self:CloseAllDeck() end)
end
self:BindClassDeckTabs()
local start = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/StartButton")
if start ~= nil and (start.ButtonComponent ~= nil or start:AddComponent("ButtonComponent") ~= nil) then
local start = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/StartButton")
if start ~= nil and start.ButtonComponent ~= nil then
if self.StartGameHandler ~= nil then
start:DisconnectEvent(ButtonClickEvent, self.StartGameHandler)
self.StartGameHandler = nil
end
self.StartGameHandler = start:ConnectEvent(ButtonClickEvent, function() self:StartNewGame() end)
end
local charBack = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/BackButton")
if charBack ~= nil and (charBack.ButtonComponent ~= nil or charBack:AddComponent("ButtonComponent") ~= nil) then
local charBack = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/BackButton")
if charBack ~= nil and charBack.ButtonComponent ~= nil then
if self.CharBackHandler ~= nil then
charBack:DisconnectEvent(ButtonClickEvent, self.CharBackHandler)
self.CharBackHandler = nil
@@ -111,7 +103,7 @@ if charBack ~= nil and (charBack.ButtonComponent ~= nil or charBack:AddComponent
self.CharBackHandler = charBack:ConnectEvent(ButtonClickEvent, function() self:ShowLobby() end)
end
local ascMinus = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/AscMinus")
if ascMinus ~= nil and (ascMinus.ButtonComponent ~= nil or ascMinus:AddComponent("ButtonComponent") ~= nil) then
if ascMinus ~= nil and ascMinus.ButtonComponent ~= nil then
if self.AscMinusHandler ~= nil then
ascMinus:DisconnectEvent(ButtonClickEvent, self.AscMinusHandler)
self.AscMinusHandler = nil
@@ -119,7 +111,7 @@ if ascMinus ~= nil and (ascMinus.ButtonComponent ~= nil or ascMinus:AddComponent
self.AscMinusHandler = ascMinus:ConnectEvent(ButtonClickEvent, function() self:AdjustAscension(-1) end)
end
local ascPlus = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/AscPlus")
if ascPlus ~= nil and (ascPlus.ButtonComponent ~= nil or ascPlus:AddComponent("ButtonComponent") ~= nil) then
if ascPlus ~= nil and ascPlus.ButtonComponent ~= nil then
if self.AscPlusHandler ~= nil then
ascPlus:DisconnectEvent(ButtonClickEvent, self.AscPlusHandler)
self.AscPlusHandler = nil
@@ -130,8 +122,8 @@ end`),
self:RenderAscension()
self:RenderSoulLabel()
self:ShowState("lobby")
self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", false)
self:SetEntityEnabled("/ui/LobbyUIGroup/SoulShopHud", false)
self:SetEntityEnabled("/ui/DefaultGroup/BoardHud", false)
self:SetEntityEnabled("/ui/DefaultGroup/SoulShopHud", false)
self:BindLobbyButtons()
self:BindMenuButtons()
self:GoLobbyMap()`),
@@ -163,39 +155,39 @@ elseif id == "board" then
self:ShowBoard()
end`, [{ Type: 'string', DefaultValue: '""', SyncDirection: 0, Attributes: [], Name: 'id' }]),
method('RenderSoulLabel', `local s = self.SoulPoints or 0
self:SetText("/ui/LobbyUIGroup/LobbyHud/SoulLabel", "영혼 " .. string.format("%d", s))
self:SetText("/ui/LobbyUIGroup/SoulShopHud/Souls", "영혼 " .. string.format("%d", s))`),
self:SetText("/ui/DefaultGroup/LobbyHud/SoulLabel", "영혼 " .. string.format("%d", s))
self:SetText("/ui/DefaultGroup/SoulShopHud/Souls", "영혼 " .. string.format("%d", s))`),
method('BindLobbyButtons', `if self.LobbyBound == true then
return
end
self.LobbyBound = true
local function bindClick(path, fn)
local e = _EntityService:GetEntityByPath(path)
if e ~= nil and (e.ButtonComponent ~= nil or e:AddComponent("ButtonComponent") ~= nil) then
if e ~= nil and e.ButtonComponent ~= nil then
e:ConnectEvent(ButtonClickEvent, fn)
end
end
bindClick("/ui/LobbyUIGroup/LobbyHud/AscMinus", function() self:AdjustAscension(-1) end)
bindClick("/ui/LobbyUIGroup/LobbyHud/AscPlus", function() self:AdjustAscension(1) end)
bindClick("/ui/LobbyUIGroup/BoardHud/Close", function() self:CloseBoard() end)
bindClick("/ui/LobbyUIGroup/SoulShopHud/Close", function() self:CloseSoulShop() end)`),
bindClick("/ui/DefaultGroup/LobbyHud/AscMinus", function() self:AdjustAscension(-1) end)
bindClick("/ui/DefaultGroup/LobbyHud/AscPlus", function() self:AdjustAscension(1) end)
bindClick("/ui/DefaultGroup/BoardHud/Close", function() self:CloseBoard() end)
bindClick("/ui/DefaultGroup/SoulShopHud/Close", function() self:CloseSoulShop() end)`),
method('ShowCodex', `self.CodexMode = true
self.ClassDeckMode = true
local close = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud/Close")
if close ~= nil and (close.ButtonComponent ~= nil or close:AddComponent("ButtonComponent") ~= nil) then
local close = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/Close")
if close ~= nil and close.ButtonComponent ~= nil then
if self.AllDeckCloseHandler ~= nil then
close:DisconnectEvent(ButtonClickEvent, self.AllDeckCloseHandler)
end
self.AllDeckCloseHandler = close:ConnectEvent(ButtonClickEvent, function() self:CloseAllDeck() end)
end
self:BindClassDeckTabs()
self:SetEntityEnabled("/ui/LobbyUIGroup/LobbyHud", false)
self:SetEntityEnabled("/ui/DefaultGroup/LobbyHud", false)
self:SetClassDeckTab("warrior")
local hud = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud")
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud")
if hud ~= nil then
hud.Enable = true
end
self:RenderAllDeck()`),
method('ShowBoard', `self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", true)`),
method('CloseBoard', `self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", false)`),
method('ShowBoard', `self:SetEntityEnabled("/ui/DefaultGroup/BoardHud", true)`),
method('CloseBoard', `self:SetEntityEnabled("/ui/DefaultGroup/BoardHud", false)`),
];

View File

@@ -68,7 +68,7 @@ local cardId = self.Hand[slot]
if cardId == nil then
return
end
local e = _EntityService:GetEntityByPath("/ui/RunUIGroup/CardHand/Card" .. tostring(slot))
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(slot))
local tx = 0
if e ~= nil and e.UITransformComponent ~= nil then
tx = e.UITransformComponent.anchoredPosition.x
@@ -87,7 +87,7 @@ if c ~= nil then
self:HideTooltip()
end
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
method('UnhoverCard', `local e = _EntityService:GetEntityByPath("/ui/RunUIGroup/CardHand/Card" .. tostring(slot))
method('UnhoverCard', `local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(slot))
if e ~= nil and e.UITransformComponent ~= nil then
e.UITransformComponent.UIScale = Vector3(1, 1, 1)
end
@@ -97,9 +97,9 @@ self:HideTooltip()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, At
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'desc' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'x' },
]),
method('ShowTooltipAt', `self:SetText("/ui/RunUIGroup/CombatHud/TooltipBox/Name", name)
self:SetText("/ui/RunUIGroup/CombatHud/TooltipBox/Desc", desc)
local e = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/TooltipBox")
method('ShowTooltipAt', `self:SetText("/ui/DefaultGroup/CombatHud/TooltipBox/Name", name)
self:SetText("/ui/DefaultGroup/CombatHud/TooltipBox/Desc", desc)
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/TooltipBox")
if e ~= nil then
if e.UITransformComponent ~= nil then
e.UITransformComponent.anchoredPosition = Vector2(x, y)
@@ -111,5 +111,5 @@ end`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'x' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'y' },
]),
method('HideTooltip', `self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/TooltipBox", false)`),
method('HideTooltip', `self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/TooltipBox", false)`),
];

View File

@@ -1,7 +1,7 @@
import { readFileSync, writeFileSync } from 'node:fs';
import { POTIONS } from './lib/data.mjs';
import { prop, codeblock, RUN_LENGTH } from './lib/codeblock.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from './lib/data.mjs';
import { prop, method, codeblock, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from './lib/codeblock.mjs';
import { bootMethods } from './cb/boot.mjs';
import { stateMethods } from './cb/state.mjs';
import { soulMethods } from './cb/soul.mjs';
@@ -19,7 +19,243 @@ import { itemMethods } from './cb/items.mjs';
import { tooltipMethods } from './cb/tooltip.mjs';
import { mapMethods } from './cb/map.mjs';
import { shopMethods } from './cb/shop.mjs';
import { COMMON_FILE } from './lib/ui-helpers.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from './lib/ui-helpers.mjs';
import { buildDeckHud } from './hud/deckhud.mjs';
import { buildDeckInspect } from './hud/deckinspect.mjs';
import { buildDeckAll } from './hud/deckall.mjs';
import { buildCombat } from './hud/combat.mjs';
import { buildReward } from './hud/reward.mjs';
import { buildMap } from './hud/map.mjs';
import { buildShop } from './hud/shop.mjs';
import { buildRest } from './hud/rest.mjs';
import { buildTreasure } from './hud/treasure.mjs';
import { buildJobChoice } from './hud/jobchoice.mjs';
import { buildJobSelect } from './hud/jobselect.mjs';
import { buildLobby } from './hud/lobby.mjs';
import { buildBoard } from './hud/board.mjs';
import { buildSoulShop } from './hud/soulshop.mjs';
import { buildMainMenu } from './hud/mainmenu.mjs';
import { buildCharSelect } from './hud/charselect.mjs';
function upsertUi() {
const ui = JSON.parse(readFileSync(UI_FILE, 'utf8'));
const E = ui.ContentProto.Entities;
// CardHand는 스톡 섹션이라 과거 생성된 단색판(NamePlate/CostPlate)이 잔존 → 프레임 이미지 도입으로 제거
const obsoletePlate = /^\/ui\/DefaultGroup\/CardHand\/Card\d+\/(NamePlate|CostPlate)$/;
ui.ContentProto.Entities = E.filter((e) => !isGeneratedUiEntity(e) && !obsoletePlate.test(e.path));
const byPath = new Map(ui.ContentProto.Entities.map((e) => [e.path, e]));
const uiSections = new Map();
const emit = (section, entities) => {
if (uiSections.has(section)) {
throw new Error(`[gen-slaydeck] duplicate generated UI section: ${section}`);
}
uiSections.set(section, entities);
};
for (const path of DISABLED_STOCK_CONTROLS.map((name) => uiPath(name))) {
const e = byPath.get(path);
if (e != null) {
e.jsonString.enable = false;
e.jsonString.visible = false;
for (const component of e.jsonString['@components'] || []) {
component.Enable = false;
if (component.RaycastTarget != null) component.RaycastTarget = false;
}
}
}
// 카드 미리보기(초기 정적 표시 — 런타임 RenderHand가 덮어씀): 카드 종류를 순환해 다양성 표시
const previewIds = Object.keys(CARDS.cards);
const cards = Array.from({ length: 10 }, (_, i) => {
const c = CARDS.cards[previewIds[i % previewIds.length]];
return { name: c.name, cost: String(c.cost), desc: c.desc, frame: frameRuid(c) };
});
// 손패 슬롯 10개 (최대 손패 한도). Card1~5는 기존 엔티티, Card6~10은 신규 생성.
for (let i = 1; i <= 10; i++) {
const cardPath = `/ui/DefaultGroup/CardHand/Card${i}`;
let card = byPath.get(cardPath);
if (!card) {
card = entity({
id: guid('dck', 500 + i),
path: cardPath,
modelId: 'uisprite',
entryId: 'UISprite',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent',
displayOrder: 4,
components: [
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: CARD_W, y: CARD_H }, pos: { x: 0, y: 0 } }),
sprite({ color: WHITE, type: 0, raycast: true }),
button(),
],
});
ui.ContentProto.Entities.push(card);
byPath.set(cardPath, card);
}
const tr = card.jsonString['@components'].find((c) => c['@type'] === 'MOD.Core.UITransformComponent');
const sp = card.jsonString['@components'].find((c) => c['@type'] === 'MOD.Core.SpriteGUIRendererComponent');
const sx = -680 + (i - 1) * 150;
tr.RectSize = { x: CARD_W, y: CARD_H };
tr.anchoredPosition = { x: sx, y: 0 };
tr.OffsetMin = { x: sx - CARD_W / 2, y: -CARD_H / 2 };
tr.OffsetMax = { x: sx + CARD_W / 2, y: CARD_H / 2 };
sp.ImageRUID = { DataId: cards[i - 1].frame };
sp.Type = 0;
sp.Color = WHITE;
sp.RaycastTarget = true;
const comps = card.jsonString['@components'];
if (!comps.some((c) => c['@type'] === 'MOD.Core.ButtonComponent')) {
comps.push(button());
}
if (!card.componentNames.includes('MOD.Core.ButtonComponent')) {
card.componentNames += ',MOD.Core.ButtonComponent';
}
if (!comps.some((c) => c['@type'] === 'MOD.Core.UITouchReceiveComponent')) {
comps.push({ '@type': 'MOD.Core.UITouchReceiveComponent', Enable: true });
}
if (!card.componentNames.includes('MOD.Core.UITouchReceiveComponent')) {
card.componentNames += ',MOD.Core.UITouchReceiveComponent';
}
card.jsonString.enable = true;
card.jsonString.visible = true;
const handLayout = cardFaceLayout(CARD_W);
const previewValues = { Cost: cards[i - 1].cost, Name: cards[i - 1].name, Desc: cards[i - 1].desc };
const children = handLayout.texts.map(([suffix, cfg]) => [suffix, { ...cfg, value: previewValues[suffix] }]);
for (const [suffix, cfg] of children) {
const path = `/ui/DefaultGroup/CardHand/Card${i}/${suffix}`;
const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8;
let child = byPath.get(path);
if (!child) {
child = entity({
id: guid('dck', i * 10 + children.findIndex(([s]) => s === suffix)),
path,
modelId: 'uitext',
entryId: 'UIText',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
displayOrder: dOrder,
components: [
transform({ parentW: CARD_W, parentH: CARD_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: cfg.size, pos: cfg.pos }),
sprite({ color: TRANSPARENT }),
text({ value: cfg.value, fontSize: cfg.fontSize, bold: cfg.bold, color: cfg.color, dropShadow: cfg.dropShadow, outlineWidth: cfg.outlineWidth }),
],
});
ui.ContentProto.Entities.push(child);
byPath.set(path, child);
} else {
child.id = guid('dck', i * 10 + children.findIndex(([s]) => s === suffix));
child.jsonString.enable = true;
child.jsonString.visible = true;
child.jsonString.displayOrder = dOrder;
const ctr = child.jsonString['@components'].find((c) => c['@type'] === 'MOD.Core.UITransformComponent');
if (ctr) {
const pivot = { x: 0.5, y: 0.5 };
ctr.RectSize = cfg.size;
ctr.anchoredPosition = cfg.pos;
ctr.OffsetMin = { x: cfg.pos.x - pivot.x * cfg.size.x, y: cfg.pos.y - pivot.y * cfg.size.y };
ctr.OffsetMax = { x: cfg.pos.x + (1 - pivot.x) * cfg.size.x, y: cfg.pos.y + (1 - pivot.y) * cfg.size.y };
}
child.jsonString['@components'][2].Text = cfg.value;
child.jsonString['@components'][2].FontSize = cfg.fontSize;
child.jsonString['@components'][2].MaxSize = cfg.fontSize;
child.jsonString['@components'][2].FontColor = cfg.color;
child.jsonString['@components'][2].Bold = cfg.bold;
child.jsonString['@components'][2].DropShadow = cfg.dropShadow === true;
child.jsonString['@components'][2].DropShadowDistance = cfg.dropShadow === true ? 18 : 32;
child.jsonString['@components'][2].OutlineWidth = cfg.outlineWidth || 1;
}
}
// 프레임 이미지가 이름판·코스트판을 내장하므로 Art만 유지 (잔존 NamePlate/CostPlate는 upsertUi 초입에서 제거)
const frameKids = [
['Art', 5, handLayout.art, WHITE, 0],
];
for (const [suffix, dOrder, cfg, color, spriteType] of frameKids) {
const fPath = `/ui/DefaultGroup/CardHand/Card${i}/${suffix}`;
let fe = byPath.get(fPath);
if (!fe) {
fe = entity({
id: guid('dck', 200 + i * 10 + dOrder),
path: fPath,
modelId: 'uisprite',
entryId: 'UISprite',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
displayOrder: dOrder,
components: [
transform({ parentW: CARD_W, parentH: CARD_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: cfg.size, pos: cfg.pos }),
sprite({ color, type: spriteType, raycast: false }),
],
});
ui.ContentProto.Entities.push(fe);
byPath.set(fPath, fe);
} else {
const ftr = fe.jsonString['@components'].find((c) => c['@type'] === 'MOD.Core.UITransformComponent');
if (ftr) {
ftr.RectSize = cfg.size;
ftr.anchoredPosition = cfg.pos;
ftr.OffsetMin = { x: cfg.pos.x - cfg.size.x / 2, y: cfg.pos.y - cfg.size.y / 2 };
ftr.OffsetMax = { x: cfg.pos.x + cfg.size.x / 2, y: cfg.pos.y + cfg.size.y / 2 };
}
}
}
}
emit('DeckHud', buildDeckHud());
emit('DeckInspectHud', buildDeckInspect());
emit('DeckAllHud', buildDeckAll());
emit('CombatHud', buildCombat());
emit('RewardHud', buildReward());
emit('MapHud', buildMap());
emit('ShopHud', buildShop());
emit('RestHud', buildRest());
// 유물 방 — 보물 상자 (P8)
emit('TreasureHud', buildTreasure());
// 전직 선택 (P9) — 보스 보상: 유물 vs 2차 전직
emit('JobChoiceHud', buildJobChoice());
emit('JobSelectHud', buildJobSelect());
emit('MainMenu', buildMainMenu());
emit('CharacterSelectHud', buildCharSelect());
// ── LobbyHud — 반복 런의 허브. NPC 클릭으로 런시작/도감/영혼상점/게시판 ──
emit('LobbyHud', buildLobby());
// ── BoardHud — 게시판(공지/팁) ──
emit('BoardHud', buildBoard());
// ── SoulShopHud — 영혼 메타 상점 (Phase 9에서 해금 항목·구매 로직 채움) ──
emit('SoulShopHud', buildSoulShop());
for (const section of UI_APPEND_ORDER) {
const entities = uiSections.get(section);
if (entities == null) {
throw new Error(`[gen-slaydeck] missing generated UI section: ${section}`);
}
appendUiSection(ui, section, entities);
}
// 엔티티 id 유일성 검증 — 같은 id가 다른 path에 재배정되면 메이커 refresh 병합이 꼬임
const seenIds = new Map();
for (const e of ui.ContentProto.Entities) {
const prev = seenIds.get(e.id);
if (prev != null) throw new Error(`[gen-slaydeck] 엔티티 id 중복: ${e.id} (${prev}${e.path})`);
seenIds.set(e.id, e.path);
}
JSON.parse(JSON.stringify(ui));
writeFileSync(UI_FILE, JSON.stringify(ui, null, 2), 'utf8');
}
function writeCodeblocks() {
const combat = codeblock('SlayDeckController', 'SlayDeckController', [
@@ -62,9 +298,6 @@ function writeCodeblocks() {
prop('boolean', 'CodexMode', 'false'),
prop('any', 'CodexCards'),
prop('boolean', 'ClassDeckMode', 'false'),
prop('boolean', 'DebugCardPickerMode', 'false'),
prop('boolean', 'DebugCtrlDown', 'false'),
prop('boolean', 'DebugShiftDown', 'false'),
prop('any', 'ClassDeckCards'),
prop('string', 'ClassDeckTitle', '""'),
prop('string', 'ClassDeckClass', '""'),
@@ -76,7 +309,6 @@ function writeCodeblocks() {
prop('any', 'Cards'),
prop('any', 'CardFrames'),
prop('any', 'NodeIcons'),
prop('any', 'ClassPortraits'),
prop('any', 'ClassToFrame'),
prop('number', 'PlayerHp', '0'),
prop('number', 'PlayerMaxHp', '80'),
@@ -166,7 +398,8 @@ function patchCommon() {
writeFileSync(COMMON_FILE, JSON.stringify(common, null, 2), 'utf8');
}
upsertUi();
writeCodeblocks();
patchCommon();
console.log('SlayDeckController/common 생성 완료 (UI는 메이커 저작 — 생성기 미접근).');
console.log('Slay deck UI and combat codeblocks generated.');

View File

@@ -1,5 +1,5 @@
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../../lib/data.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
export function buildBoard() {
const board = [];

View File

@@ -0,0 +1,171 @@
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
export function buildCharSelect() {
const select = [];
select.push(entity({
id: guid('menu', 100),
path: '/ui/DefaultGroup/CharacterSelectHud',
modelId: 'uisprite',
entryId: 'UISprite',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
displayOrder: 21,
components: [
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1920, y: 1080 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }),
sprite({ color: { r: 0.05, g: 0.07, b: 0.11, a: 1 }, type: 1, raycast: true }),
],
}));
select.push(entity({
id: guid('menu', 190),
path: '/ui/DefaultGroup/CharacterSelectHud/OpaqueBackdrop',
modelId: 'uisprite',
entryId: 'UISprite',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
displayOrder: 0,
components: [
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1920, y: 1080 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }),
sprite({ color: TRANSPARENT, type: 1, raycast: false }),
],
}));
select.push(entity({
id: guid('menu', 101),
path: '/ui/DefaultGroup/CharacterSelectHud/Title',
modelId: 'uitext',
entryId: 'UIText',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
displayOrder: 1,
components: [
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 760, y: 72 }, pos: { x: 0, y: 355 }, align: ALIGN_CENTER }),
sprite({ color: TRANSPARENT }),
text({ value: '\uCE90\uB9AD\uD130 \uC120\uD0DD', fontSize: 42, bold: true, color: GOLD, alignment: 0 }),
],
}));
select.push(entity({
id: guid('menu', 102),
path: '/ui/DefaultGroup/CharacterSelectHud/Status',
modelId: 'uitext',
entryId: 'UIText',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
displayOrder: 2,
components: [
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 680, y: 44 }, pos: { x: 0, y: 298 }, align: ALIGN_CENTER }),
sprite({ color: TRANSPARENT }),
text({ value: '\uC804\uC0AC\uB97C \uC120\uD0DD\uD558\uACE0 \uC2DC\uC791\uD558\uC138\uC694', fontSize: 22, color: { r: 0.86, g: 0.9, b: 0.94, a: 1 }, alignment: 0 }),
],
}));
const classCards = [
{ key: 'Warrior', classId: 'warrior', label: '\uC804\uC0AC', desc: '\uAC15\uD55C \uACF5\uACA9\uACFC \uBC29\uC5B4', x: -360, enabled: true, tint: { r: 0.74, g: 0.32, b: 0.28, a: 1 } },
{ key: 'Thief', classId: 'bandit', label: '\uB3C4\uC801', desc: '\uB3C5\u00B7\uB2E8\uAC80\u00B7\uB4DC\uB85C\uC6B0', x: 0, enabled: true, tint: { r: 0.26, g: 0.5, b: 0.34, a: 1 } },
{ key: 'Mage', classId: 'magician', label: '\uB9C8\uBC95\uC0AC', desc: '\uB9C8\uBC95 \uC6D0\uAC70\uB9AC \uB51C\uB7EC', x: 360, enabled: true, tint: { r: 0.3, g: 0.4, b: 0.75, a: 1 } },
];
for (let i = 0; i < classCards.length; i++) {
const cls = classCards[i];
const base = `/ui/DefaultGroup/CharacterSelectHud/${cls.key}Button`;
select.push(entity({
id: guid('menu', 110 + i),
path: base,
modelId: 'uibutton',
entryId: 'UIButton',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent',
displayOrder: 10 + i,
components: [
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 270, y: 330 }, pos: { x: cls.x, y: 40 }, align: ALIGN_CENTER }),
sprite({ color: cls.enabled ? { r: 0.16, g: 0.2, b: 0.26, a: 1 } : { r: 0.11, g: 0.12, b: 0.14, a: 1 }, type: 1, raycast: cls.enabled }),
button({ enabled: cls.enabled }),
],
}));
select.push(entity({
id: guid('menu', 200 + i),
path: `${base}/Art`,
modelId: 'uisprite',
entryId: 'UISprite',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
displayOrder: 0,
components: [
transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 258, y: 318 }, pos: { x: 0, y: 0 } }),
sprite({ dataId: CHARS.portraits[cls.classId], color: { r: 1, g: 1, b: 1, a: 1 }, type: 0, raycast: false }),
],
}));
select.push(entity({
id: guid('menu', 210 + i),
path: `${base}/NameBanner`,
modelId: 'uisprite',
entryId: 'UISprite',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
displayOrder: 1,
components: [
transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 258, y: 60 }, pos: { x: 0, y: -137 } }),
sprite({ color: { r: 0, g: 0, b: 0, a: 0.55 }, type: 1, raycast: false }),
],
}));
select.push(entity({
id: guid('menu', 120 + i),
path: `${base}/Name`,
modelId: 'uitext',
entryId: 'UIText',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
displayOrder: 2,
components: [
transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 230, y: 54 }, pos: { x: 0, y: -137 } }),
sprite({ color: TRANSPARENT }),
text({ value: cls.label, fontSize: 34, bold: true, color: cls.enabled ? GOLD : { r: 0.55, g: 0.58, b: 0.62, a: 1 }, alignment: 4 }),
],
}));
if (!cls.enabled) {
select.push(entity({
id: guid('menu', 150 + i),
path: `${base}/LockBody`,
modelId: 'uisprite',
entryId: 'UISprite',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
displayOrder: 3,
components: [
transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 76, y: 58 }, pos: { x: 0, y: 4 } }),
sprite({ color: { r: 0.78, g: 0.69, b: 0.42, a: 1 }, type: 1 }),
],
}));
select.push(entity({
id: guid('menu', 160 + i),
path: `${base}/LockShackle`,
modelId: 'uisprite',
entryId: 'UISprite',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
displayOrder: 4,
components: [
transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 54, y: 42 }, pos: { x: 0, y: 48 } }),
sprite({ color: { r: 0.78, g: 0.69, b: 0.42, a: 1 }, type: 1 }),
],
}));
}
}
select.push(entity({
id: guid('menu', 180),
path: '/ui/DefaultGroup/CharacterSelectHud/StartButton',
modelId: 'uibutton',
entryId: 'UIButton',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent',
displayOrder: 20,
components: [
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 220, y: 68 }, pos: { x: 720, y: -360 }, align: ALIGN_CENTER }),
sprite({ color: { r: 0.15, g: 0.2, b: 0.26, a: 1 }, type: 1, raycast: true }),
button(),
text({ value: '\uC2DC\uC791', fontSize: 30, bold: true, color: GOLD, alignment: 0 }),
],
}));
select.push(entity({
id: guid('menu', 230),
path: '/ui/DefaultGroup/CharacterSelectHud/BackButton',
modelId: 'uibutton',
entryId: 'UIButton',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent',
displayOrder: 22,
components: [
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 180, y: 56 }, pos: { x: -800, y: 430 }, align: ALIGN_CENTER }),
sprite({ color: { r: 0.15, g: 0.2, b: 0.26, a: 1 }, type: 1, raycast: true }),
button(),
text({ value: '\u2190 \uB4A4\uB85C', fontSize: 26, bold: true, color: GOLD, alignment: 0 }),
],
}));
select[0].jsonString.enable = false;
return select;
}

View File

@@ -1,5 +1,5 @@
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../../lib/data.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
export function buildCombat() {
const PANEL_BG = { r: 0.08, g: 0.09, b: 0.11, a: 0.78 };

View File

@@ -1,5 +1,5 @@
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../../lib/data.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
export function buildDeckAll() {
const allDeck = [];
@@ -116,12 +116,11 @@ export function buildDeckAll() {
path: cardPath,
modelId: 'uisprite',
entryId: 'UISprite',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
displayOrder: i,
components: [
transform({ parentW: 980, parentH: 620, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: ALL_DECK_CARD_W, y: ALL_DECK_CARD_H }, pos: { x: 0, y: 0 } }),
sprite({ dataId: CARDFRAMES.frames.warrior.normal, color: WHITE, type: 0, raycast: true }),
button(),
sprite({ dataId: CARDFRAMES.frames.warrior.normal, color: WHITE, type: 0 }),
],
});
card.jsonString.enable = false;

View File

@@ -1,5 +1,5 @@
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../../lib/data.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
export function buildDeckHud() {
const hud = [];

View File

@@ -1,5 +1,5 @@
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../../lib/data.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
export function buildDeckInspect() {
const inspect = [];

View File

@@ -1,5 +1,5 @@
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../../lib/data.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
export function buildJobChoice() {
const jobChoice = [];

View File

@@ -1,5 +1,5 @@
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../../lib/data.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
export function buildJobSelect() {
const jobSelect = [];

View File

@@ -1,5 +1,5 @@
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../../lib/data.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
export function buildLobby() {
const lobby = [];

View File

@@ -1,5 +1,5 @@
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../../lib/data.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
export function buildMainMenu() {
const menu = [];

View File

@@ -1,5 +1,5 @@
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../../lib/data.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
export function buildMap() {
const TYPE_KO = { combat: '전투', elite: '엘리트', boss: '보스', shop: '상점', rest: '휴식' };

View File

@@ -1,5 +1,5 @@
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../../lib/data.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
export function buildRest() {
const rest = [];

View File

@@ -1,5 +1,5 @@
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../../lib/data.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
export function buildReward() {
const reward = [];

View File

@@ -1,5 +1,5 @@
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../../lib/data.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
export function buildShop() {
const shop = [];

View File

@@ -1,5 +1,5 @@
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../../lib/data.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
export function buildSoulShop() {
const soulShop = [];

View File

@@ -1,5 +1,5 @@
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../../lib/data.mjs';
import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs';
import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs';
export function buildTreasure() {
const treasure = [];

View File

@@ -1,246 +0,0 @@
import { readFileSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { CARDS, frameRuid } from '../lib/data.mjs';
import { UI_FILE, isGeneratedUiEntity, DISABLED_STOCK_CONTROLS, uiPath, guid, entity, transform, sprite, button, text, WHITE, TRANSPARENT, CARD_W, CARD_H, cardFaceLayout, UI_APPEND_ORDER, appendUiSection } from '../lib/ui-helpers.mjs';
import { buildDeckHud } from './hud/deckhud.mjs';
import { buildDeckInspect } from './hud/deckinspect.mjs';
import { buildDeckAll } from './hud/deckall.mjs';
import { buildCombat } from './hud/combat.mjs';
import { buildReward } from './hud/reward.mjs';
import { buildMap } from './hud/map.mjs';
import { buildShop } from './hud/shop.mjs';
import { buildRest } from './hud/rest.mjs';
import { buildTreasure } from './hud/treasure.mjs';
import { buildJobChoice } from './hud/jobchoice.mjs';
import { buildJobSelect } from './hud/jobselect.mjs';
import { buildLobby } from './hud/lobby.mjs';
import { buildBoard } from './hud/board.mjs';
import { buildSoulShop } from './hud/soulshop.mjs';
import { buildMainMenu } from './hud/mainmenu.mjs';
// ⚠️ 휴면(LEGACY): 메이커 저작 전환 후 생성기는 .ui를 안 만든다. 이 파일은 옛 DefaultGroup.ui
// 단일 저작 로직의 롤백/참조용. import는 무해(함수만 정의), 직접 실행할 때만 .ui를 옛 생성본으로 덮어쓴다.
function upsertUi() {
const ui = JSON.parse(readFileSync(UI_FILE, 'utf8'));
const E = ui.ContentProto.Entities;
// CardHand는 스톡 섹션이라 과거 생성된 단색판(NamePlate/CostPlate)이 잔존 → 프레임 이미지 도입으로 제거
const obsoletePlate = /^\/ui\/DefaultGroup\/CardHand\/Card\d+\/(NamePlate|CostPlate)$/;
ui.ContentProto.Entities = E.filter((e) => !isGeneratedUiEntity(e) && !obsoletePlate.test(e.path));
const byPath = new Map(ui.ContentProto.Entities.map((e) => [e.path, e]));
const uiSections = new Map();
const emit = (section, entities) => {
if (uiSections.has(section)) {
throw new Error(`[gen-slaydeck] duplicate generated UI section: ${section}`);
}
uiSections.set(section, entities);
};
for (const path of DISABLED_STOCK_CONTROLS.map((name) => uiPath(name))) {
const e = byPath.get(path);
if (e != null) {
e.jsonString.enable = false;
e.jsonString.visible = false;
for (const component of e.jsonString['@components'] || []) {
component.Enable = false;
if (component.RaycastTarget != null) component.RaycastTarget = false;
}
}
}
// 카드 미리보기(초기 정적 표시 — 런타임 RenderHand가 덮어씀): 카드 종류를 순환해 다양성 표시
const previewIds = Object.keys(CARDS.cards);
const cards = Array.from({ length: 10 }, (_, i) => {
const c = CARDS.cards[previewIds[i % previewIds.length]];
return { name: c.name, cost: String(c.cost), desc: c.desc, frame: frameRuid(c) };
});
// 손패 슬롯 10개 (최대 손패 한도). Card1~5는 기존 엔티티, Card6~10은 신규 생성.
for (let i = 1; i <= 10; i++) {
const cardPath = `/ui/DefaultGroup/CardHand/Card${i}`;
let card = byPath.get(cardPath);
if (!card) {
card = entity({
id: guid('dck', 500 + i),
path: cardPath,
modelId: 'uisprite',
entryId: 'UISprite',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent',
displayOrder: 4,
components: [
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: CARD_W, y: CARD_H }, pos: { x: 0, y: 0 } }),
sprite({ color: WHITE, type: 0, raycast: true }),
button(),
],
});
ui.ContentProto.Entities.push(card);
byPath.set(cardPath, card);
}
const tr = card.jsonString['@components'].find((c) => c['@type'] === 'MOD.Core.UITransformComponent');
const sp = card.jsonString['@components'].find((c) => c['@type'] === 'MOD.Core.SpriteGUIRendererComponent');
const sx = -680 + (i - 1) * 150;
tr.RectSize = { x: CARD_W, y: CARD_H };
tr.anchoredPosition = { x: sx, y: 0 };
tr.OffsetMin = { x: sx - CARD_W / 2, y: -CARD_H / 2 };
tr.OffsetMax = { x: sx + CARD_W / 2, y: CARD_H / 2 };
sp.ImageRUID = { DataId: cards[i - 1].frame };
sp.Type = 0;
sp.Color = WHITE;
sp.RaycastTarget = true;
const comps = card.jsonString['@components'];
if (!comps.some((c) => c['@type'] === 'MOD.Core.ButtonComponent')) {
comps.push(button());
}
if (!card.componentNames.includes('MOD.Core.ButtonComponent')) {
card.componentNames += ',MOD.Core.ButtonComponent';
}
if (!comps.some((c) => c['@type'] === 'MOD.Core.UITouchReceiveComponent')) {
comps.push({ '@type': 'MOD.Core.UITouchReceiveComponent', Enable: true });
}
if (!card.componentNames.includes('MOD.Core.UITouchReceiveComponent')) {
card.componentNames += ',MOD.Core.UITouchReceiveComponent';
}
card.jsonString.enable = true;
card.jsonString.visible = true;
const handLayout = cardFaceLayout(CARD_W);
const previewValues = { Cost: cards[i - 1].cost, Name: cards[i - 1].name, Desc: cards[i - 1].desc };
const children = handLayout.texts.map(([suffix, cfg]) => [suffix, { ...cfg, value: previewValues[suffix] }]);
for (const [suffix, cfg] of children) {
const path = `/ui/DefaultGroup/CardHand/Card${i}/${suffix}`;
const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8;
let child = byPath.get(path);
if (!child) {
child = entity({
id: guid('dck', i * 10 + children.findIndex(([s]) => s === suffix)),
path,
modelId: 'uitext',
entryId: 'UIText',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
displayOrder: dOrder,
components: [
transform({ parentW: CARD_W, parentH: CARD_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: cfg.size, pos: cfg.pos }),
sprite({ color: TRANSPARENT }),
text({ value: cfg.value, fontSize: cfg.fontSize, bold: cfg.bold, color: cfg.color, dropShadow: cfg.dropShadow, outlineWidth: cfg.outlineWidth }),
],
});
ui.ContentProto.Entities.push(child);
byPath.set(path, child);
} else {
child.id = guid('dck', i * 10 + children.findIndex(([s]) => s === suffix));
child.jsonString.enable = true;
child.jsonString.visible = true;
child.jsonString.displayOrder = dOrder;
const ctr = child.jsonString['@components'].find((c) => c['@type'] === 'MOD.Core.UITransformComponent');
if (ctr) {
const pivot = { x: 0.5, y: 0.5 };
ctr.RectSize = cfg.size;
ctr.anchoredPosition = cfg.pos;
ctr.OffsetMin = { x: cfg.pos.x - pivot.x * cfg.size.x, y: cfg.pos.y - pivot.y * cfg.size.y };
ctr.OffsetMax = { x: cfg.pos.x + (1 - pivot.x) * cfg.size.x, y: cfg.pos.y + (1 - pivot.y) * cfg.size.y };
}
child.jsonString['@components'][2].Text = cfg.value;
child.jsonString['@components'][2].FontSize = cfg.fontSize;
child.jsonString['@components'][2].MaxSize = cfg.fontSize;
child.jsonString['@components'][2].FontColor = cfg.color;
child.jsonString['@components'][2].Bold = cfg.bold;
child.jsonString['@components'][2].DropShadow = cfg.dropShadow === true;
child.jsonString['@components'][2].DropShadowDistance = cfg.dropShadow === true ? 18 : 32;
child.jsonString['@components'][2].OutlineWidth = cfg.outlineWidth || 1;
}
}
// 프레임 이미지가 이름판·코스트판을 내장하므로 Art만 유지 (잔존 NamePlate/CostPlate는 upsertUi 초입에서 제거)
const frameKids = [
['Art', 5, handLayout.art, WHITE, 0],
];
for (const [suffix, dOrder, cfg, color, spriteType] of frameKids) {
const fPath = `/ui/DefaultGroup/CardHand/Card${i}/${suffix}`;
let fe = byPath.get(fPath);
if (!fe) {
fe = entity({
id: guid('dck', 200 + i * 10 + dOrder),
path: fPath,
modelId: 'uisprite',
entryId: 'UISprite',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
displayOrder: dOrder,
components: [
transform({ parentW: CARD_W, parentH: CARD_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: cfg.size, pos: cfg.pos }),
sprite({ color, type: spriteType, raycast: false }),
],
});
ui.ContentProto.Entities.push(fe);
byPath.set(fPath, fe);
} else {
const ftr = fe.jsonString['@components'].find((c) => c['@type'] === 'MOD.Core.UITransformComponent');
if (ftr) {
ftr.RectSize = cfg.size;
ftr.anchoredPosition = cfg.pos;
ftr.OffsetMin = { x: cfg.pos.x - cfg.size.x / 2, y: cfg.pos.y - cfg.size.y / 2 };
ftr.OffsetMax = { x: cfg.pos.x + cfg.size.x / 2, y: cfg.pos.y + cfg.size.y / 2 };
}
}
}
}
emit('DeckHud', buildDeckHud());
emit('DeckInspectHud', buildDeckInspect());
emit('DeckAllHud', buildDeckAll());
emit('CombatHud', buildCombat());
emit('RewardHud', buildReward());
emit('MapHud', buildMap());
emit('ShopHud', buildShop());
emit('RestHud', buildRest());
// 유물 방 — 보물 상자 (P8)
emit('TreasureHud', buildTreasure());
// 전직 선택 (P9) — 보스 보상: 유물 vs 2차 전직
emit('JobChoiceHud', buildJobChoice());
emit('JobSelectHud', buildJobSelect());
emit('MainMenu', buildMainMenu());
// ── LobbyHud — 반복 런의 허브. NPC 클릭으로 런시작/도감/영혼상점/게시판 ──
emit('LobbyHud', buildLobby());
// ── BoardHud — 게시판(공지/팁) ──
emit('BoardHud', buildBoard());
// ── SoulShopHud — 영혼 메타 상점 (Phase 9에서 해금 항목·구매 로직 채움) ──
emit('SoulShopHud', buildSoulShop());
for (const section of UI_APPEND_ORDER) {
const entities = uiSections.get(section);
if (entities == null) {
throw new Error(`[gen-slaydeck] missing generated UI section: ${section}`);
}
appendUiSection(ui, section, entities);
}
// 엔티티 id 유일성 검증 — 같은 id가 다른 path에 재배정되면 메이커 refresh 병합이 꼬임
const seenIds = new Map();
for (const e of ui.ContentProto.Entities) {
const prev = seenIds.get(e.id);
if (prev != null) throw new Error(`[gen-slaydeck] 엔티티 id 중복: ${e.id} (${prev}${e.path})`);
seenIds.set(e.id, e.path);
}
JSON.parse(JSON.stringify(ui));
writeFileSync(UI_FILE, JSON.stringify(ui, null, 2), 'utf8');
}
// 롤백/참조용 직접 실행 시에만 동작 (import 시에는 실행 안 함)
if (process.argv[1] === fileURLToPath(import.meta.url)) {
upsertUi();
}

View File

@@ -79,10 +79,6 @@ function luaNodeIconsTable() {
const rows = Object.entries(NODEICONS.icons).map(([t, ruid]) => `\t${t} = ${luaStr(ruid)},`).join('\n');
return `self.NodeIcons = {\n${rows}\n}`;
}
function luaCharsTable() {
const rows = Object.entries(CHARS.portraits).map(([c, ruid]) => `\t${c} = ${luaStr(ruid)},`).join('\n');
return `self.ClassPortraits = {\n${rows}\n}`;
}
// 맵은 런타임 절차 생성(GenerateMap Lua ↔ tools/map/rogue-map.mjs 미러). 정적 data/map.json 제거됨.
const MAP_ROWS = 6; // 걷는 행 1..6, 보스 row 7 (depth 최대 7)
@@ -198,4 +194,4 @@ function luaDeckTable(deck) {
return `self.DrawPile = { ${deck.map(luaStr).join(', ')} }`;
}
export { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, luaCharsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable };
export { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, luaSoulShopTable, CARDFRAMES, RARITIES, frameRuid, luaFramesTable, luaNodeIconsTable, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, luaRelicsTable, POTIONS, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable };

View File

@@ -14,6 +14,7 @@ const GENERATED_UI_SECTIONS = [
'JobChoiceHud',
'JobSelectHud',
'MainMenu',
'CharacterSelectHud',
'LobbyHud',
'BoardHud',
'SoulShopHud',
@@ -31,6 +32,7 @@ const UI_APPEND_ORDER = [
'DeckInspectHud',
'DeckAllHud',
'MainMenu',
'CharacterSelectHud',
'LobbyHud',
'BoardHud',
'SoulShopHud',

View File

@@ -1,28 +0,0 @@
import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
// 일회성·멱등 마이그레이션: cb/*.mjs의 UI 경로 리터럴을 메이커 재편 UIGroup으로 재연결.
// 이미 이동된 경로는 매치 안 됨(멱등). MainMenu·Button_Attack/Jump·UIJoystick(=DefaultGroup 잔류)은 미변경.
// 섹션→UIGroup 매핑은 tools/verify/uimap.mjs 탐색으로 검증된 실제 .ui 분포 기준.
const MOVE = {
CharacterSelectHud: 'SelectUIGroup', JobChoiceHud: 'SelectUIGroup', JobSelectHud: 'SelectUIGroup',
LobbyHud: 'LobbyUIGroup', BoardHud: 'LobbyUIGroup', SoulShopHud: 'LobbyUIGroup',
CombatHud: 'RunUIGroup', DeckHud: 'RunUIGroup', CardHand: 'RunUIGroup', MapHud: 'RunUIGroup',
RewardHud: 'RunUIGroup', ShopHud: 'RunUIGroup', RestHud: 'RunUIGroup', TreasureHud: 'RunUIGroup',
DeckInspectHud: 'DeckUIGroup', DeckAllHud: 'DeckUIGroup',
};
const CB_DIR = 'tools/deck/cb';
let n = 0;
for (const f of readdirSync(CB_DIR).filter((x) => x.endsWith('.mjs'))) {
const p = join(CB_DIR, f);
const before = readFileSync(p, 'utf8');
let s = before;
// 1) 몬스터 슬롯: 그룹+이름 동시 (CombatHud 일반 remap보다 먼저). 슬롯 5→4는 MAX_MONSTERS(=4)가 이미 반영.
s = s.split('/ui/DefaultGroup/CombatHud/MonsterSlot').join('/ui/RunUIGroup/CombatHud/MonsterStatus');
// 2) 섹션별 그룹 접두사 remap
for (const [section, group] of Object.entries(MOVE)) {
s = s.split(`/ui/DefaultGroup/${section}`).join(`/ui/${group}/${section}`);
}
if (s !== before) { writeFileSync(p, s, 'utf8'); n++; console.log(' remapped', f); }
}
console.log(`reconnect-ui-paths: ${n} files updated`);

View File

@@ -1,69 +0,0 @@
import { readFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
// cb 컨트롤러가 참조하는 /ui/DefaultGroup/... 경로를, 사용자가 메이커에서 재편한
// 새 UIGroup(.ui) 구조에 대조해 "그대로 옮겨지면 해결(resolved)" vs "이름/구조가 바뀌어
// 못 찾음(GAP)"을 분류. GAP = 사용자에게 구→신 매핑을 받아야 할 항목.
// 출력은 경로 이름 + EXISTS/GAP boolean 뿐(엔티티 값 본문 미출력 → RULES §2 준수).
// 섹션(=DefaultGroup 다음 첫 세그먼트) → 이동된 UIGroup (uimap.mjs 탐색 결과 기반)
const SECTION_TO_GROUP = {
MainMenu: 'DefaultGroup', Button_Attack: 'DefaultGroup', Button_Jump: 'DefaultGroup', UIJoystick: 'DefaultGroup',
CharacterSelectHud: 'SelectUIGroup', JobChoiceHud: 'SelectUIGroup', JobSelectHud: 'SelectUIGroup',
LobbyHud: 'LobbyUIGroup', BoardHud: 'LobbyUIGroup', SoulShopHud: 'LobbyUIGroup',
CombatHud: 'RunUIGroup', DeckHud: 'RunUIGroup', CardHand: 'RunUIGroup', MapHud: 'RunUIGroup',
RewardHud: 'RunUIGroup', ShopHud: 'RunUIGroup', RestHud: 'RunUIGroup', TreasureHud: 'RunUIGroup',
DeckInspectHud: 'DeckUIGroup', DeckAllHud: 'DeckUIGroup',
};
// 새 .ui 로드
const UI_DIR = 'ui';
const ui = {};
for (const f of readdirSync(UI_DIR).filter((x) => x.endsWith('.ui'))) {
ui[f.replace('.ui', '')] = readFileSync(join(UI_DIR, f), 'utf8');
}
// cb 소스에서 /ui/DefaultGroup/... 경로 리터럴 추출 (템플릿 ${...} 포함)
const CB_DIR = 'tools/deck/cb';
const re = /\/ui\/DefaultGroup\/[^"'`\s),]+/g;
const paths = new Set();
for (const f of readdirSync(CB_DIR).filter((x) => x.endsWith('.mjs'))) {
const src = readFileSync(join(CB_DIR, f), 'utf8');
for (const m of src.match(re) || []) paths.add(m);
}
// 동적 세그먼트(${...}) 앞 정적 prefix만 취해 존재검사 (e.g. .../Card${i} → .../Card)
function staticPrefix(p) {
const i = p.indexOf('${');
if (i === -1) return { p, dyn: false };
// ${...} 직전까지 (마지막 세그먼트의 정적 앞부분 포함)
return { p: p.slice(0, i), dyn: true };
}
const bySection = {};
for (const p of [...paths].sort()) {
const rest = p.slice('/ui/DefaultGroup/'.length); // Section/child/...
const section = rest.split('/')[0].split('${')[0];
const group = SECTION_TO_GROUP[section] || '??';
const newPath = group === '??' ? p : p.replace('/ui/DefaultGroup/', `/ui/${group}/`);
const { p: probe } = staticPrefix(newPath);
const blob = ui[group] || '';
const exists = group !== '??' && blob.includes(probe);
(bySection[section] ||= []).push({ old: p, neu: newPath, exists, group });
}
// 보고
let totResolved = 0, totGap = 0;
for (const section of Object.keys(bySection).sort()) {
const rows = bySection[section];
const group = rows[0].group;
const gaps = rows.filter((r) => !r.exists);
const ok = rows.length - gaps.length;
totResolved += ok; totGap += gaps.length;
console.log(`\n[${section}] → ${group} 해결 ${ok} / GAP ${gaps.length} (총 ${rows.length})`);
for (const g of gaps) {
// GAP만 상세 출력 (사용자가 신규 이름 채워야 할 대상)
console.log(` GAP ${g.old.replace('/ui/DefaultGroup/', '')} →(없음) ${g.neu.replace(`/ui/${group}/`, `${group}: `)}`);
}
}
console.log(`\n=== 합계: 자동해결 ${totResolved} / GAP ${totGap} (distinct 경로 ${paths.size}) ===`);

View File

@@ -1,35 +0,0 @@
import { readFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
// UIGroup(.ui) 매핑 헬퍼 (RULES §2: 내용 출력 금지·카운트만).
// 어떤 섹션/엔티티 이름이 어느 .ui 파일에 들어있는지 카운트 매트릭스로 보고.
// 산출물 경로를 명령줄에 노출하지 않아(디렉토리 스캔) deny 회피.
//
// 사용: node tools/verify/uimap.mjs <pattern> [<pattern> ...]
// 각 pattern은 정규식. 출력은 "pattern | file=count ..." 형식(본문 미출력).
const UI_DIR = 'ui';
const files = readdirSync(UI_DIR).filter((f) => f.endsWith('.ui'));
const cache = {};
for (const f of files) {
cache[f] = readFileSync(join(UI_DIR, f), 'utf8');
}
// 파일별 크기/JSON 유효성 헤더
console.log('=== ui/*.ui ===');
for (const f of files) {
let ok = false;
try { JSON.parse(cache[f]); ok = true; } catch { ok = false; }
console.log(` ${f} bytes=${cache[f].length} jsonValid=${ok}`);
}
const pats = process.argv.slice(2);
if (pats.length === 0) process.exit(0);
console.log('=== matches (file=count, 0 생략) ===');
for (const pat of pats) {
const re = new RegExp(pat, 'g');
const hits = [];
for (const f of files) {
const m = cache[f].match(re);
const n = m ? m.length : 0;
if (n > 0) hits.push(`${f}=${n}`);
}
console.log(` /${pat}/ ${hits.length ? hits.join(' ') : '(none)'}`);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -585,7 +585,7 @@
{
"id": "94a274e4-4111-40f1-924d-c95a3a1f14d5",
"path": "/ui/PopupGroup/PopupBack/PopupPanel/PopupBtnOK",
"componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent",
"componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent",
"jsonString": {
"name": "PopupBtnOK",
"path": "/ui/PopupGroup/PopupBack/PopupPanel/PopupBtnOK",
@@ -719,6 +719,53 @@
"Type": 1,
"Enable": true
},
{
"@type": "MOD.Core.ButtonComponent",
"Colors": {
"NormalColor": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 1.0
},
"HighlightedColor": {
"r": 0.9607843,
"g": 0.9607843,
"b": 0.9607843,
"a": 1.0
},
"PressedColor": {
"r": 0.784313738,
"g": 0.784313738,
"b": 0.784313738,
"a": 1.0
},
"SelectedColor": {
"r": 0.9607843,
"g": 0.9607843,
"b": 0.9607843,
"a": 1.0
},
"DisabledColor": {
"r": 0.784313738,
"g": 0.784313738,
"b": 0.784313738,
"a": 0.5019608
},
"ColorMultiplier": 1.0,
"FadeDuration": 0.1
},
"ImageRUIDs": {
"HighlightedSprite": null,
"PressedSprite": null,
"SelectedSprite": null,
"DisabledSprite": null
},
"KeyCode": 0,
"OverrideSorting": false,
"Transition": 1,
"Enable": true
},
{
"@type": "MOD.Core.TextComponent",
"Alignment": 4,
@@ -773,7 +820,7 @@
{
"id": "0f5de49b-2adc-409a-816d-15aa43df8e0d",
"path": "/ui/PopupGroup/PopupBack/PopupPanel/PopupBtnCancel",
"componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent",
"componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent",
"jsonString": {
"name": "PopupBtnCancel",
"path": "/ui/PopupGroup/PopupBack/PopupPanel/PopupBtnCancel",
@@ -907,6 +954,53 @@
"Type": 1,
"Enable": true
},
{
"@type": "MOD.Core.ButtonComponent",
"Colors": {
"NormalColor": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 1.0
},
"HighlightedColor": {
"r": 0.9607843,
"g": 0.9607843,
"b": 0.9607843,
"a": 1.0
},
"PressedColor": {
"r": 0.784313738,
"g": 0.784313738,
"b": 0.784313738,
"a": 1.0
},
"SelectedColor": {
"r": 0.9607843,
"g": 0.9607843,
"b": 0.9607843,
"a": 1.0
},
"DisabledColor": {
"r": 0.784313738,
"g": 0.784313738,
"b": 0.784313738,
"a": 0.5019608
},
"ColorMultiplier": 1.0,
"FadeDuration": 0.1
},
"ImageRUIDs": {
"HighlightedSprite": null,
"PressedSprite": null,
"SelectedSprite": null,
"DisabledSprite": null
},
"KeyCode": 0,
"OverrideSorting": false,
"Transition": 1,
"Enable": true
},
{
"@type": "MOD.Core.TextComponent",
"Alignment": 4,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff