From b0d3da2f39ea3e9695a1463ce8ed006cf037e049 Mon Sep 17 00:00:00 2001 From: gahusb Date: Wed, 17 Jun 2026 02:46:59 +0900 Subject: [PATCH] =?UTF-8?q?refactor(deck):=20=EC=98=A4=EC=BC=80=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=A0=88=EC=9D=B4=ED=84=B0=EB=A5=BC=20=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC+common=20=EC=A0=84=EC=9A=A9?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=8A=AC=EB=A6=BC=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - upsertUi(UI 저작) 함수·hud import 15종 제거 → legacy로 이전(Task 2·3) - data/codeblock/ui-helpers import를 writeCodeblocks·patchCommon에 필요한 최소(POTIONS / prop·codeblock·RUN_LENGTH / COMMON_FILE)로 슬림화 - 결과: 생성기가 .ui에 일절 접근 안 함(메이커 저작 UI 보존) Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/deck/gen-slaydeck.mjs | 243 +----------------------------------- 1 file changed, 4 insertions(+), 239 deletions(-) diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index 93a463b..19cc27c 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -1,7 +1,7 @@ import { readFileSync, writeFileSync } from 'node:fs'; -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 { POTIONS } from './lib/data.mjs'; +import { prop, codeblock, RUN_LENGTH } from './lib/codeblock.mjs'; import { bootMethods } from './cb/boot.mjs'; import { stateMethods } from './cb/state.mjs'; import { soulMethods } from './cb/soul.mjs'; @@ -19,241 +19,7 @@ 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 { 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'; - -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 { COMMON_FILE } from './lib/ui-helpers.mjs'; function writeCodeblocks() { const combat = codeblock('SlayDeckController', 'SlayDeckController', [ @@ -397,8 +163,7 @@ function patchCommon() { writeFileSync(COMMON_FILE, JSON.stringify(common, null, 2), 'utf8'); } -upsertUi(); writeCodeblocks(); patchCommon(); -console.log('Slay deck UI and combat codeblocks generated.'); +console.log('SlayDeckController/common 생성 완료 (UI는 메이커 저작 — 생성기 미접근).');