diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index 8282454..79b1f1f 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -1,6 +1,20 @@ import { readFileSync, writeFileSync } from 'node:fs'; 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 { 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 { 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'; function upsertUi() { @@ -167,1628 +181,29 @@ function upsertUi() { } } - const hud = []; - const add = (e) => hud.push(e); + emit('DeckHud', buildDeckHud()); - add(entity({ - id: guid('hud', 0), - path: '/ui/DefaultGroup/DeckHud', - modelId: 'uiempty', - entryId: 'UIEmpty', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 5, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1280, y: 330 }, pos: { x: 0, y: 180 }, align: ALIGN_BOTTOM_CENTER }), - sprite({ color: TRANSPARENT }), - ], - })); + emit('DeckInspectHud', buildDeckInspect()); - for (const pile of [ - { key: 'DrawPile', x: -590, label: '뽑을 덱', count: '10', color: { r: 0.17, g: 0.20, b: 0.25, a: 1 } }, - { key: 'DiscardPile', x: 590, label: '버린 덱', count: '0', color: { r: 0.22, g: 0.18, b: 0.16, a: 1 } }, - ]) { - add(entity({ - id: guid('hud', hud.length), - path: `/ui/DefaultGroup/DeckHud/${pile.key}`, - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', - displayOrder: pile.key === 'DrawPile' ? 0 : 1, - components: [ - transform({ parentW: 1280, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 132, y: 186 }, pos: { x: pile.x, y: 8 }, align: ALIGN_CENTER }), - sprite({ color: pile.color, type: 1, raycast: true }), - button(), - ], - })); - add(entity({ - id: guid('hud', hud.length), - path: `/ui/DefaultGroup/DeckHud/${pile.key}/Label`, - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 0, - components: [ - transform({ parentW: 132, parentH: 186, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 120, y: 42 }, pos: { x: 0, y: 45 } }), - sprite({ color: TRANSPARENT }), - text({ value: pile.label, fontSize: 21, bold: true, color: GOLD }), - ], - })); - add(entity({ - id: guid('hud', hud.length), - path: `/ui/DefaultGroup/DeckHud/${pile.key}/Count`, - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 1, - components: [ - transform({ parentW: 132, parentH: 186, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 120, y: 72 }, pos: { x: 0, y: -20 } }), - sprite({ color: TRANSPARENT }), - text({ value: pile.count, fontSize: 42, bold: true }), - ], - })); - } + emit('DeckAllHud', buildDeckAll()); - add(entity({ - id: guid('hud', hud.length), - path: '/ui/DefaultGroup/DeckHud/EndTurnButton', - modelId: 'uibutton', - entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', - displayOrder: 2, - components: [ - transform({ parentW: 1280, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 200, y: 64 }, pos: { x: 560, y: 160 }, align: ALIGN_CENTER }), - sprite({ color: DARK, type: 1, raycast: true }), - button(), - text({ value: '턴 종료', fontSize: 28, bold: true, color: GOLD, alignment: 0 }), - ], - })); + emit('CombatHud', buildCombat()); - add(entity({ - id: guid('hud', hud.length), - path: '/ui/DefaultGroup/DeckHud/EnergyOrb', - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 3, - components: [ - transform({ parentW: 1280, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 96, y: 96 }, pos: { x: -560, y: 160 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.12, g: 0.2, b: 0.34, a: 0.95 }, type: 1 }), - ], - })); - add(entity({ - id: guid('hud', hud.length), - path: '/ui/DefaultGroup/DeckHud/EnergyOrb/Value', - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 0, - components: [ - transform({ parentW: 96, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 92, y: 48 }, pos: { x: 0, y: 6 } }), - sprite({ color: TRANSPARENT }), - text({ value: '3/3', fontSize: 34, bold: true, color: { r: 0.65, g: 0.92, b: 1, a: 1 }, alignment: 4 }), - ], - })); - add(entity({ - id: guid('hud', hud.length), - path: '/ui/DefaultGroup/DeckHud/EnergyOrb/Label', - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 1, - components: [ - transform({ parentW: 96, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 92, y: 24 }, pos: { x: 0, y: -28 } }), - sprite({ color: TRANSPARENT }), - text({ value: '에너지', fontSize: 14, bold: true, color: { r: 0.55, g: 0.7, b: 0.85, a: 1 }, alignment: 4 }), - ], - })); + emit('RewardHud', buildReward()); - emit('DeckHud', hud); + emit('MapHud', buildMap()); - const inspect = []; - const inspectHud = entity({ - id: guid('ins', 0), - path: '/ui/DefaultGroup/DeckInspectHud', - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 15, - 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.04, g: 0.05, b: 0.07, a: 0.78 }, type: 1, raycast: true }), - ], - }); - inspectHud.jsonString.enable = false; - inspect.push(inspectHud); - inspect.push(entity({ - id: guid('ins', 1), - path: '/ui/DefaultGroup/DeckInspectHud/Panel', - 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: 1040, y: 760 }, pos: { x: 0, y: 10 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.08, g: 0.09, b: 0.11, a: 0.96 }, type: 1 }), - ], - })); - inspect.push(entity({ - id: guid('ins', 2), - path: '/ui/DefaultGroup/DeckInspectHud/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: 720, y: 54 }, pos: { x: 0, y: 350 } }), - sprite({ color: TRANSPARENT }), - text({ value: '\uB371 \uBCF4\uAE30', fontSize: 34, bold: true, color: GOLD, alignment: 4 }), - ], - })); - inspect.push(entity({ - id: guid('ins', 3), - path: '/ui/DefaultGroup/DeckInspectHud/Close', - modelId: 'uibutton', - entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,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: 78, y: 52 }, pos: { x: 466, y: 350 } }), - sprite({ color: { r: 0.16, g: 0.18, b: 0.22, a: 1 }, type: 1, raycast: true }), - button(), - text({ value: 'X', fontSize: 26, bold: true, color: GOLD, alignment: 4 }), - ], - })); - inspect.push(entity({ - id: guid('ins', 4), - path: '/ui/DefaultGroup/DeckInspectHud/Empty', - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 3, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 600, y: 50 }, pos: { x: 0, y: 30 } }), - sprite({ color: TRANSPARENT }), - text({ value: '\uCE74\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4', fontSize: 28, bold: true, color: { r: 0.82, g: 0.86, b: 0.9, a: 1 }, alignment: 4 }), - ], - })); - inspect.push(entity({ - id: guid('ins', 5), - path: '/ui/DefaultGroup/DeckInspectHud/Grid', - modelId: 'uiempty', - entryId: 'UIEmpty', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ScrollLayoutGroupComponent', - displayOrder: 4, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 950, y: 610 }, pos: { x: 0, y: 0 } }), - sprite({ color: TRANSPARENT, type: 1, raycast: true }), - scrollLayoutGroup({ cellSize: { x: 158, y: 214 }, spacing: { x: 22, y: 22 }, columns: 5 }), - ], - })); - const INSPECT_CARD_COUNT = 60; - const INSPECT_CARD_W = 158; - const INSPECT_CARD_H = 214; - // 카드 단위 엔티티는 v2 네임스페이스(ins2/all2/rwd2/shp2) — 자식 구성이 바뀌면 id를 통째로 새로 발급해야 함. - // 구 id를 다른 path에 재사용하면 메이커 refresh의 id 기준 in-place 병합이 꼬여 자식이 소실됨 (P13 실측). - for (let i = 1; i <= INSPECT_CARD_COUNT; i++) { - const insBase = 6 + (i - 1) * 7; - const cardPath = `/ui/DefaultGroup/DeckInspectHud/Grid/Card${i}`; - const card = entity({ - id: guid('ins2', insBase), - path: cardPath, - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: i, - components: [ - transform({ parentW: 950, parentH: 610, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: INSPECT_CARD_W, y: INSPECT_CARD_H }, pos: { x: 0, y: 0 } }), - sprite({ dataId: CARDFRAMES.frames.warrior.normal, color: WHITE, type: 0 }), - ], - }); - card.jsonString.enable = false; - inspect.push(card); - const inspectLayout = cardFaceLayout(INSPECT_CARD_W); - for (const [tIdx, [suffix, cfg]] of inspectLayout.texts.map(([sfx, c]) => [sfx, { ...c, value: sfx === 'Cost' ? '1' : '' }]).entries()) { - const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8; - inspect.push(entity({ - id: guid('ins2', insBase + 1 + tIdx), - path: `${cardPath}/${suffix}`, - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: dOrder, - components: [ - transform({ parentW: INSPECT_CARD_W, parentH: INSPECT_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 }), - ], - })); - } - inspect.push(entity({ - id: guid('ins2', insBase + 6), - path: `${cardPath}/Art`, - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 5, - components: [ - transform({ parentW: INSPECT_CARD_W, parentH: INSPECT_CARD_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: inspectLayout.art.size, pos: inspectLayout.art.pos }), - sprite({ color: WHITE, type: 0, raycast: false }), - ], - })); - } - emit('DeckInspectHud', inspect); + emit('ShopHud', buildShop()); - const allDeck = []; - const allHud = entity({ - id: guid('all', 0), - path: '/ui/DefaultGroup/DeckAllHud', - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 16, - 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.04, g: 0.05, b: 0.07, a: 0.78 }, type: 1, raycast: true }), - ], - }); - allHud.jsonString.enable = false; - allDeck.push(allHud); - allDeck.push(entity({ - id: guid('all', 1), - path: '/ui/DefaultGroup/DeckAllHud/Panel', - 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: 1080, y: 800 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.08, g: 0.09, b: 0.11, a: 0.96 }, type: 1 }), - ], - })); - allDeck.push(entity({ - id: guid('all', 2), - path: '/ui/DefaultGroup/DeckAllHud/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: 54 }, pos: { x: 0, y: 380 } }), - sprite({ color: TRANSPARENT }), - text({ value: '모든 덱', fontSize: 34, bold: true, color: GOLD, alignment: 4 }), - ], - })); - allDeck.push(entity({ - id: guid('all', 3), - path: '/ui/DefaultGroup/DeckAllHud/Close', - modelId: 'uibutton', - entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,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: 78, y: 52 }, pos: { x: 486, y: 380 } }), - sprite({ color: { r: 0.16, g: 0.18, b: 0.22, a: 1 }, type: 1, raycast: true }), - button(), - text({ value: 'X', fontSize: 26, bold: true, color: GOLD, alignment: 4 }), - ], - })); - const deckTabs = [ - { key: 'Warrior', label: '전사', x: -210 }, - { key: 'Thief', label: '도적', x: 0 }, - { key: 'Mage', label: '마법사', x: 210 }, - ]; - for (let i = 0; i < deckTabs.length; i++) { - const tab = deckTabs[i]; - allDeck.push(entity({ - id: guid('all', 10 + i), - path: `/ui/DefaultGroup/DeckAllHud/${tab.key}Tab`, - modelId: 'uibutton', - entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', - displayOrder: 3 + i, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 170, y: 46 }, pos: { x: tab.x, y: 318 } }), - sprite({ color: { r: 0.11, g: 0.13, b: 0.16, a: 1 }, type: 1, raycast: true }), - button(), - text({ value: tab.label, fontSize: 22, bold: true, color: GOLD, alignment: 4 }), - ], - })); - } - allDeck.push(entity({ - id: guid('all', 4), - path: '/ui/DefaultGroup/DeckAllHud/Empty', - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 3, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 600, y: 50 }, pos: { x: 0, y: 40 } }), - sprite({ color: TRANSPARENT }), - text({ value: '덱이 없습니다', fontSize: 28, bold: true, color: { r: 0.82, g: 0.86, b: 0.9, a: 1 }, alignment: 4 }), - ], - })); - allDeck.push(entity({ - id: guid('all', 5), - path: '/ui/DefaultGroup/DeckAllHud/Grid', - modelId: 'uiempty', - entryId: 'UIEmpty', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ScrollLayoutGroupComponent', - displayOrder: 4, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 980, y: 620 }, pos: { x: 0, y: 0 } }), - sprite({ color: TRANSPARENT, type: 1, raycast: true }), - scrollLayoutGroup({ cellSize: { x: 158, y: 214 }, spacing: { x: 22, y: 22 }, columns: 5 }), - ], - })); - const ALL_DECK_CARD_COUNT = 120; - const ALL_DECK_CARD_W = 158; - const ALL_DECK_CARD_H = 214; - // 카드 단위 엔티티 v2 네임스페이스 — DeckInspectHud 주석 참조 - for (let i = 1; i <= ALL_DECK_CARD_COUNT; i++) { - const allBase = 6 + (i - 1) * 7; - const cardPath = `/ui/DefaultGroup/DeckAllHud/Grid/Card${i}`; - const card = entity({ - id: guid('all2', allBase), - path: cardPath, - modelId: 'uisprite', - entryId: 'UISprite', - 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 }), - ], - }); - card.jsonString.enable = false; - allDeck.push(card); - const allDeckLayout = cardFaceLayout(ALL_DECK_CARD_W); - for (const [tIdx, [suffix, cfg]] of allDeckLayout.texts.map(([sfx, c]) => [sfx, { ...c, value: sfx === 'Cost' ? '1' : '' }]).entries()) { - const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8; - allDeck.push(entity({ - id: guid('all2', allBase + 1 + tIdx), - path: `${cardPath}/${suffix}`, - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: dOrder, - components: [ - transform({ parentW: ALL_DECK_CARD_W, parentH: ALL_DECK_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 }), - ], - })); - } - allDeck.push(entity({ - id: guid('all2', allBase + 6), - path: `${cardPath}/Art`, - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 5, - components: [ - transform({ parentW: ALL_DECK_CARD_W, parentH: ALL_DECK_CARD_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: allDeckLayout.art.size, pos: allDeckLayout.art.pos }), - sprite({ color: WHITE, type: 0, raycast: false }), - ], - })); - } - emit('DeckAllHud', allDeck); - - const PANEL_BG = { r: 0.08, g: 0.09, b: 0.11, a: 0.78 }; - const combat = []; - combat.push(entity({ - id: guid('cmb', 0), - path: '/ui/DefaultGroup/CombatHud', - modelId: 'uiempty', - entryId: 'UIEmpty', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 4, - 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 }), - ], - })); - const SLOT_W = 140, SLOT_H = 96; - for (let i = 1; i <= MAX_MONSTERS; i++) { - const base = `/ui/DefaultGroup/CombatHud/MonsterSlot${i}`; - const slot = entity({ - id: guid('cmb', 40 + i), - path: base, - modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', - displayOrder: 20 + i, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: SLOT_W, y: SLOT_H }, pos: { x: (i - 2.5) * 320, y: 300 } }), - sprite({ color: { r: 0, g: 0, b: 0, a: 0.0001 }, type: 1, raycast: true }), - button(), - ], - }); - slot.jsonString.enable = false; - combat.push(slot); - const targetFrame = entity({ - id: guid('cmb', 220 + i), path: `${base}/TargetFrame`, modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 0, - components: [ - transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: SLOT_W + 16, y: SLOT_H + 12 }, pos: { x: 0, y: 0 } }), - sprite({ color: { r: 0.95, g: 0.78, b: 0.25, a: 0.28 }, type: 1 }), - ], - }); - targetFrame.jsonString.enable = false; - combat.push(targetFrame); - const targetMarker = entity({ - id: guid('cmb', 360 + i), path: `${base}/TargetMarker`, modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 9, - components: [ - transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 116, y: 116 }, pos: { x: 0, y: 2 } }), - sprite({ color: { r: 0.95, g: 0.08, b: 0.05, a: 0.92 }, type: 1 }), - text({ value: '+', fontSize: 72, bold: true, color: { r: 1, g: 0.94, b: 0.28, a: 1 }, alignment: 4, outlineWidth: 4 }), - ], - }); - targetMarker.jsonString.enable = false; - combat.push(targetMarker); - const targetLabel = entity({ - id: guid('cmb', 370 + i), path: `${base}/TargetMarker/Label`, modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 10, - components: [ - transform({ parentW: 116, parentH: 116, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 112, y: 28 }, pos: { x: 0, y: -52 } }), - sprite({ color: { r: 0.08, g: 0.02, b: 0.02, a: 0.86 }, type: 1 }), - text({ value: 'TARGET', fontSize: 18, bold: true, color: { r: 1, g: 0.94, b: 0.28, a: 1 }, alignment: 4, outlineWidth: 3 }), - ], - }); - targetLabel.jsonString.enable = false; - combat.push(targetLabel); - const actFrame = entity({ - id: guid('cmb', 240 + i), path: `${base}/ActFrame`, modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 0, - components: [ - transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: SLOT_W + 16, y: SLOT_H + 12 }, pos: { x: 0, y: 0 } }), - sprite({ color: { r: 0.95, g: 0.3, b: 0.25, a: 0.3 }, type: 1 }), - ], - }); - actFrame.jsonString.enable = false; - combat.push(actFrame); - combat.push(entity({ - id: guid('cmb', 60 + i), path: `${base}/Name`, modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 1, - components: [ - transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: SLOT_W, y: 30 }, pos: { x: 0, y: 34 } }), - sprite({ color: TRANSPARENT }), - text({ value: '', fontSize: 22, bold: true, color: GOLD, alignment: 4 }), - ], - })); - combat.push(entity({ - id: guid('cmb', 80 + i), path: `${base}/Hp`, modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 2, - components: [ - transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: SLOT_W, y: 26 }, pos: { x: 0, y: 6 } }), - sprite({ color: TRANSPARENT }), - text({ value: '', fontSize: 20, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), - ], - })); - combat.push(entity({ - id: guid('cmb', 100 + i), path: `${base}/HpBarBg`, modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 3, - components: [ - transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: HP_BAR_W, y: 14 }, pos: { x: 0, y: -14 } }), - sprite({ color: { r: 0.18, g: 0.05, b: 0.06, a: 1 }, type: 1 }), - ], - })); - combat.push(entity({ - id: guid('cmb', 120 + i), path: `${base}/HpBarFill`, modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 4, - components: [ - transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0, y: 0.5 }, size: { x: HP_BAR_W, y: 14 }, pos: { x: -HP_BAR_W / 2, y: -14 } }), - sprite({ color: { r: 0.86, g: 0.35, b: 0.32, a: 1 }, type: 1 }), - ], - })); - combat.push(entity({ - id: guid('cmb', 140 + i), path: `${base}/Intent`, modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 5, - components: [ - transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: SLOT_W + 40, y: 24 }, pos: { x: 0, y: -36 } }), - sprite({ color: TRANSPARENT }), - text({ value: '', fontSize: 17, bold: true, color: { r: 1, g: 0.72, b: 0.5, a: 1 }, alignment: 4 }), - ], - })); - const dmgPopBase = `/ui/DefaultGroup/CombatHud/DmgPop${i}`; - const dmgPop = entity({ - id: guid('cmb', 250 + i), path: dmgPopBase, modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 80 + i, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 192, y: 56 }, pos: { x: 0, y: 0 } }), - sprite({ color: TRANSPARENT, type: 1 }), - ], - }); - dmgPop.jsonString.enable = false; - combat.push(dmgPop); - for (let d = 0; d < DAMAGE_POP_MAX_DIGITS; d++) { - combat.push(entity({ - id: guid('cmb', 380 + i * 10 + d), path: `${dmgPopBase}/Digit${d + 1}`, modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 90 + i, - components: [ - transform({ parentW: 192, parentH: 56, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: DAMAGE_POP_DIGIT_W, y: DAMAGE_POP_DIGIT_H }, pos: { x: 0, y: 0 } }), - sprite({ dataId: DAMAGE_DIGIT_RUIDS[0], color: { r: 1, g: 1, b: 1, a: 1 }, type: 0 }), - ], - })); - } - const mBlockBadge = entity({ - id: guid('cmb', 270 + i), path: `${base}/BlockBadge`, modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 6, - components: [ - transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 40, y: 36 }, pos: { x: -HP_BAR_W / 2 - 30, y: -14 } }), - sprite({ color: { r: 0.32, g: 0.5, b: 0.85, a: 1 }, type: 1 }), - ], - }); - mBlockBadge.jsonString.enable = false; - combat.push(mBlockBadge); - combat.push(entity({ - id: guid('cmb', 280 + i), path: `${base}/BlockBadge/Value`, modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 0, - components: [ - transform({ parentW: 40, parentH: 36, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 40, y: 32 }, pos: { x: 0, y: 0 } }), - sprite({ color: TRANSPARENT }), - text({ value: '0', fontSize: 17, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), - ], - })); - combat.push(entity({ - id: guid('cmb', 290 + i), path: `${base}/Buffs`, modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 7, - components: [ - transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: SLOT_W + 60, y: 22 }, pos: { x: 0, y: -58 } }), - sprite({ color: TRANSPARENT }), - text({ value: '', fontSize: 15, bold: true, color: { r: 0.85, g: 0.65, b: 1, a: 1 }, alignment: 4 }), - ], - })); - } - const PP = '/ui/DefaultGroup/CombatHud/PlayerPanel'; - combat.push(entity({ - id: guid('cmb', 210), path: PP, modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 5, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 300, y: 96 }, pos: { x: -760, y: -494 }, align: ALIGN_CENTER }), - sprite({ color: PANEL_BG, type: 1 }), - ], - })); - combat.push(entity({ - id: guid('cmb', 211), path: `${PP}/Name`, modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 0, - components: [ - transform({ parentW: 300, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 280, y: 28 }, pos: { x: 0, y: 28 } }), - sprite({ color: TRANSPARENT }), - text({ value: '플레이어', fontSize: 18, bold: true, color: GOLD, alignment: 4 }), - ], - })); - combat.push(entity({ - id: guid('cmb', 212), path: `${PP}/HpBarBg`, modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 1, - components: [ - transform({ parentW: 300, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 220, y: 16 }, pos: { x: 16, y: -6 } }), - sprite({ color: { r: 0.18, g: 0.05, b: 0.06, a: 1 }, type: 1 }), - ], - })); - combat.push(entity({ - id: guid('cmb', 213), path: `${PP}/HpBarFill`, modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 2, - components: [ - transform({ parentW: 300, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0, y: 0.5 }, size: { x: 220, y: 14 }, pos: { x: -94, y: -6 } }), - sprite({ color: { r: 0.3, g: 0.78, b: 0.36, a: 1 }, type: 1 }), - ], - })); - combat.push(entity({ - id: guid('cmb', 214), path: `${PP}/HpText`, modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 3, - components: [ - transform({ parentW: 300, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 220, y: 24 }, pos: { x: 16, y: -30 } }), - sprite({ color: TRANSPARENT }), - text({ value: '80/80', fontSize: 16, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), - ], - })); - const blockBadge = entity({ - id: guid('cmb', 215), path: `${PP}/BlockBadge`, modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 4, - components: [ - transform({ parentW: 300, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 44, y: 40 }, pos: { x: -122, y: -12 } }), - sprite({ color: { r: 0.32, g: 0.5, b: 0.85, a: 1 }, type: 1 }), - ], - }); - blockBadge.jsonString.enable = false; - combat.push(blockBadge); - combat.push(entity({ - id: guid('cmb', 216), path: `${PP}/BlockBadge/Value`, modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 0, - components: [ - transform({ parentW: 44, parentH: 40, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 44, y: 36 }, pos: { x: 0, y: 0 } }), - sprite({ color: TRANSPARENT }), - text({ value: '0', fontSize: 18, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), - ], - })); - combat.push(entity({ - id: guid('cmb', 217), path: `${PP}/Buffs`, modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 6, - components: [ - transform({ parentW: 300, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 280, y: 22 }, pos: { x: 0, y: -44 } }), - sprite({ color: TRANSPARENT }), - text({ value: '', fontSize: 14, bold: true, color: { r: 0.85, g: 0.65, b: 1, a: 1 }, alignment: 4 }), - ], - })); - const playerDmgPop = entity({ - id: guid('cmb', 260), path: `${PP}/DmgPop`, modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 5, - components: [ - transform({ parentW: 300, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 160, y: 30 }, pos: { x: 16, y: 40 } }), - sprite({ color: TRANSPARENT }), - text({ value: '', fontSize: 22, bold: true, color: { r: 1, g: 0.4, b: 0.35, a: 1 }, alignment: 4 }), - ], - }); - playerDmgPop.jsonString.enable = false; - combat.push(playerDmgPop); - combat.push(entity({ - id: guid('cmb', 200), - path: '/ui/DefaultGroup/CombatHud/TopBar', - modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 9, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1200, y: 52 }, pos: { x: 0, y: 486 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.06, g: 0.07, b: 0.1, a: 0.82 }, type: 1 }), - ], - })); - const topTexts = [ - ['Floor', -520, 160, '막 1/3', GOLD], - ['Gold', -360, 160, '메소 0', { r: 0.98, g: 0.85, b: 0.4, a: 1 }], - ]; - topTexts.forEach(([suffix, x, w, value, color], ti) => { - combat.push(entity({ - id: guid('cmb', 201 + ti), - path: `/ui/DefaultGroup/CombatHud/TopBar/${suffix}`, - modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: ti, - components: [ - transform({ parentW: 1200, parentH: 52, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: w, y: 40 }, pos: { x: x, y: 0 } }), - sprite({ color: TRANSPARENT }), - text({ value, fontSize: 22, bold: true, color, alignment: 4 }), - ], - })); - }); - combat.push(entity({ - id: guid('cmb', 209), - path: '/ui/DefaultGroup/CombatHud/TopBar/MesoIcon', - modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 2, - components: [ - transform({ parentW: 1200, parentH: 52, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 26, y: 26 }, pos: { x: -432, y: 0 } }), - sprite({ color: { r: 1, g: 0.82, b: 0.2, a: 1 }, type: 1 }), - ], - })); - for (let i = 1; i <= 10; i++) { - combat.push(entity({ - id: guid('cmb', 300 + i), - path: `/ui/DefaultGroup/CombatHud/TopBar/RelicSlot${i}`, - modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.UITouchReceiveComponent', - displayOrder: 3 + i, - components: [ - transform({ parentW: 1200, parentH: 52, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 40, y: 40 }, pos: { x: -240 + (i - 1) * 48, y: 0 } }), - sprite({ color: { r: 0.15, g: 0.16, b: 0.2, a: 0.6 }, type: 0, raycast: true }), - { '@type': 'MOD.Core.UITouchReceiveComponent', Enable: true }, - ], - })); - } - combat.push(entity({ - id: guid('cmb', 311), - path: '/ui/DefaultGroup/CombatHud/TopBar/RelicOverflow', - modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 14, - components: [ - transform({ parentW: 1200, parentH: 52, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 60, y: 30 }, pos: { x: 192, y: 0 } }), - sprite({ color: TRANSPARENT }), - text({ value: '', fontSize: 18, bold: true, color: { r: 0.8, g: 0.7, b: 0.95, a: 1 }, alignment: 4 }), - ], - })); - for (let i = 1; i <= 5; i++) { - combat.push(entity({ - id: guid('cmb', 320 + i), - path: `/ui/DefaultGroup/CombatHud/TopBar/PotionSlot${i}`, - modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.UITouchReceiveComponent', - displayOrder: 14 + i, - components: [ - transform({ parentW: 1200, parentH: 52, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 40, y: 40 }, pos: { x: 240 + (i - 1) * 44, y: 0 } }), - sprite({ color: { r: 0.22, g: 0.25, b: 0.3, a: 0.9 }, type: 0, raycast: true }), - { '@type': 'MOD.Core.UITouchReceiveComponent', Enable: true }, - ], - })); - } - const tooltipBox = entity({ - id: guid('cmb', 330), - path: '/ui/DefaultGroup/CombatHud/TooltipBox', - modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 20, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 360, y: 150 }, pos: { x: 0, y: 400 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.04, g: 0.05, b: 0.08, a: 0.96 }, type: 1 }), - ], - }); - tooltipBox.jsonString.enable = false; - combat.push(tooltipBox); - combat.push(entity({ - id: guid('cmb', 331), - path: '/ui/DefaultGroup/CombatHud/TooltipBox/Name', - modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 0, - components: [ - transform({ parentW: 360, parentH: 150, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 332, y: 28 }, pos: { x: 0, y: 52 } }), - sprite({ color: TRANSPARENT }), - text({ value: '', fontSize: 19, bold: true, color: GOLD, alignment: 4 }), - ], - })); - combat.push(entity({ - id: guid('cmb', 332), - path: '/ui/DefaultGroup/CombatHud/TooltipBox/Desc', - modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 1, - components: [ - transform({ parentW: 360, parentH: 150, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 332, y: 102 }, pos: { x: 0, y: -18 } }), - sprite({ color: TRANSPARENT }), - text({ value: '', fontSize: 15, bold: false, color: { r: 0.92, g: 0.92, b: 0.95, a: 1 }, alignment: 0 }), - ], - })); - const discardPrompt = entity({ - id: guid('cmb', 333), - path: '/ui/DefaultGroup/CombatHud/DiscardPrompt', - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,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: 520, y: 48 }, pos: { x: 0, y: -260 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.05, g: 0.06, b: 0.09, a: 0.86 }, type: 1 }), - text({ value: '', fontSize: 22, bold: true, color: GOLD, alignment: 4 }), - ], - }); - discardPrompt.jsonString.enable = false; - combat.push(discardPrompt); - const potionMenu = entity({ - id: guid('cmb', 340), - path: '/ui/DefaultGroup/CombatHud/PotionMenu', - 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: 380, y: 180 }, pos: { x: 0, y: 120 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.07, g: 0.08, b: 0.12, a: 0.97 }, type: 1 }), - ], - }); - potionMenu.jsonString.enable = false; - combat.push(potionMenu); - combat.push(entity({ - id: guid('cmb', 341), - path: '/ui/DefaultGroup/CombatHud/PotionMenu/Title', - modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 0, - components: [ - transform({ parentW: 380, parentH: 180, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 360, y: 36 }, pos: { x: 0, y: 52 } }), - sprite({ color: TRANSPARENT }), - text({ value: '', fontSize: 19, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), - ], - })); - const pmButtons = [ - ['Use', '사용', -120, { r: 0.32, g: 0.55, b: 0.36, a: 1 }], - ['Toss', '버리기', 0, { r: 0.6, g: 0.32, b: 0.3, a: 1 }], - ['Close', '닫기', 120, { r: 0.25, g: 0.28, b: 0.35, a: 1 }], - ]; - pmButtons.forEach(([suffix, label, x, color], bi) => { - combat.push(entity({ - id: guid('cmb', 342 + bi), - path: `/ui/DefaultGroup/CombatHud/PotionMenu/${suffix}`, - modelId: 'uibutton', entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', - displayOrder: 1 + bi, - components: [ - transform({ parentW: 380, parentH: 180, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 104, y: 46 }, pos: { x, y: -40 } }), - sprite({ color, type: 1, raycast: true }), - button(), - text({ value: label, fontSize: 20, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), - ], - })); - }); - combat.push(entity({ - id: guid('cmb', 205), - path: '/ui/DefaultGroup/CombatHud/TopBar/AllDeckButton', - modelId: 'uibutton', entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', - displayOrder: 3, - components: [ - transform({ parentW: 1200, parentH: 52, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 140, y: 40 }, pos: { x: 528, y: 0 } }), - sprite({ color: DARK, type: 1, raycast: true }), - button(), - text({ value: '모든덱보기', fontSize: 18, bold: true, color: GOLD, alignment: 0 }), - ], - })); - const skillFx = entity({ - id: guid('cmb', 230), path: '/ui/DefaultGroup/CombatHud/SkillFx', - modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 30, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 110, y: 110 }, pos: { x: 0, y: 0 } }), - sprite({ color: { r: 1, g: 1, b: 1, a: 1 }, type: 0, raycast: false }), - ], - }); - skillFx.jsonString.enable = false; - combat.push(skillFx); - const result = entity({ - id: guid('cmb', 2), - path: '/ui/DefaultGroup/CombatHud/Result', - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 8, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 700, y: 140 }, pos: { x: 0, y: 120 } }), - sprite({ color: TRANSPARENT }), - text({ value: '', fontSize: 64, bold: true, color: GOLD, alignment: 4 }), - ], - }); - result.jsonString.enable = false; - combat.push(result); - emit('CombatHud', combat); - - const reward = []; - const rewardHud = entity({ - id: guid('rwd', 0), - path: '/ui/DefaultGroup/RewardHud', - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 6, - 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.04, g: 0.05, b: 0.07, a: 0.86 }, type: 1, raycast: true }), - ], - }); - rewardHud.jsonString.enable = false; - reward.push(rewardHud); - reward.push(entity({ - id: guid('rwd', 1), - path: '/ui/DefaultGroup/RewardHud/Title', - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 0, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 700, y: 64 }, pos: { x: 0, y: 300 } }), - sprite({ color: TRANSPARENT }), - text({ value: '보상 카드 선택', fontSize: 44, bold: true, color: GOLD, alignment: 4 }), - ], - })); - const rewardXs = [-300, 0, 300]; - // 카드 단위 엔티티 v2 네임스페이스 — DeckInspectHud 주석 참조 - for (let i = 1; i <= 3; i++) { - const rwdBase = 2 + (i - 1) * 7; - const cardPath = `/ui/DefaultGroup/RewardHud/Reward${i}`; - reward.push(entity({ - id: guid('rwd2', rwdBase), - path: cardPath, - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent', - displayOrder: i, - 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: rewardXs[i - 1], y: 0 } }), - sprite({ dataId: CARDFRAMES.frames.warrior.normal, color: WHITE, type: 0, raycast: true }), - button(), - { '@type': 'MOD.Core.UITouchReceiveComponent', Enable: true }, - ], - })); - const rewardLayout = cardFaceLayout(CARD_W); - for (const [tIdx, [suffix, cfg]] of rewardLayout.texts.map(([sfx, c]) => [sfx, { ...c, value: sfx === 'Cost' ? '1' : sfx === 'Name' ? '카드' : '' }]).entries()) { - const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8; - reward.push(entity({ - id: guid('rwd2', rwdBase + 1 + tIdx), - path: `${cardPath}/${suffix}`, - 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 }), - ], - })); - } - reward.push(entity({ - id: guid('rwd2', rwdBase + 6), - path: `${cardPath}/Art`, - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 5, - components: [ - transform({ parentW: CARD_W, parentH: CARD_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: rewardLayout.art.size, pos: rewardLayout.art.pos }), - sprite({ color: WHITE, type: 0, raycast: false }), - ], - })); - } - let rwdN = 2 + 3 * 7; // 구 시퀀스의 루프 종료 시점 값(23) 보존 — Skip 등 후속 id 불변 - reward.push(entity({ - id: guid('rwd', rwdN++), - path: '/ui/DefaultGroup/RewardHud/Skip', - modelId: 'uibutton', - entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', - displayOrder: 10, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 200, y: 60 }, pos: { x: 0, y: -260 } }), - sprite({ color: DARK, type: 1, raycast: true }), - button(), - text({ value: '건너뛰기', fontSize: 26, bold: true, color: GOLD, alignment: 4 }), - ], - })); - emit('RewardHud', reward); - - const TYPE_KO = { combat: '전투', elite: '엘리트', boss: '보스', shop: '상점', rest: '휴식' }; - const map = []; - const mapHud = entity({ - id: guid('map', 0), - path: '/ui/DefaultGroup/MapHud', - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 7, - 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 }), - // 불투명 다크 배경(게임 월드 가림 보장). 그 위 BgImage 자식이 배경 스프라이트를 얹는다. - sprite({ color: { r: 0.06, g: 0.07, b: 0.11, a: 1 }, type: 1, raycast: true }), - ], - }); - mapHud.jsonString.enable = false; - map.push(mapHud); - // 배경 이미지(displayOrder 0 = 도트/타이틀/노드 아래). nodeicons.json background는 SPRITE RUID여야 렌더됨 - // — 메이플 BackgroundComponent 리소스는 UI 스프라이트로 안 뜬다. 유효 스프라이트면 풀스크린 표시, 아니면 투명(다크 배경 노출). - map.push(entity({ - id: guid('map', 990), - path: '/ui/DefaultGroup/MapHud/BgImage', - 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({ dataId: NODEICONS.background, color: { r: 0.5, g: 0.52, b: 0.58, a: 1 }, type: 0, raycast: false }), - ], - })); - map.push(entity({ - id: guid('map', 1), - path: '/ui/DefaultGroup/MapHud/Title', - 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: 700, y: 60 }, pos: { x: 0, y: 510 } }), - sprite({ color: TRANSPARENT }), - text({ value: '다음 노드 선택', fontSize: 40, bold: true, color: GOLD, alignment: 4 }), - ], - })); - // 절차 생성 맵용 정적 그리드 — 가로 진행(왼→오른쪽): 행(row)=x축, 열(col)=y축 분기, 보스는 최우측 중앙. - const nodeX = (row) => -540 + (row - 1) * 150; - const nodeY = (col) => 180 - (col - 1) * 120; - const BOSS_POS = { x: nodeX(MAP_ROWS) + 150, y: 0 }; - let mapN = 2; - const pushMapNode = (id, pos, size, label) => { - const nodePath = `/ui/DefaultGroup/MapHud/Node_${id}`; - const nodeEnt = entity({ - id: guid('map', mapN++), - path: nodePath, - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', - displayOrder: 5, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size, pos }), - sprite({ color: { r: 1, g: 1, b: 1, a: 1 }, type: 0, raycast: true }), - button(), - ], - }); - nodeEnt.jsonString.enable = false; - map.push(nodeEnt); - }; - for (let r = 1; r <= MAP_ROWS; r++) { - for (let c = 1; c <= MAP_COLS; c++) { - pushMapNode(`r${r}c${c}`, { x: nodeX(r), y: nodeY(c) }, { x: 64, y: 64 }, ''); - } - } - pushMapNode('boss', BOSS_POS, { x: 88, y: 88 }, '보스'); - const pushDots = (dotId, from, to) => { - for (let k = 1; k <= 3; k++) { - const t = k / 4; - const dot = entity({ - id: guid('map', mapN++), - path: `/ui/DefaultGroup/MapHud/Dot_${dotId}_${k}`, - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 1, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 8, y: 8 }, pos: { x: from.x + (to.x - from.x) * t, y: from.y + (to.y - from.y) * t } }), - sprite({ color: { r: 0.5, g: 0.5, b: 0.55, a: 0.8 }, type: 1 }), - ], - }); - dot.jsonString.enable = false; - map.push(dot); - } - }; - for (let r = 1; r < MAP_ROWS; r++) { - for (let c = 1; c <= MAP_COLS; c++) { - for (let c2 = c - 1; c2 <= c + 1; c2++) { - if (c2 < 1 || c2 > MAP_COLS) continue; - pushDots(`r${r}c${c}_${c2}`, { x: nodeX(r), y: nodeY(c) }, { x: nodeX(r + 1), y: nodeY(c2) }); - } - } - } - for (let c = 1; c <= MAP_COLS; c++) { - pushDots(`r${MAP_ROWS}c${c}_b`, { x: nodeX(MAP_ROWS), y: nodeY(c) }, BOSS_POS); - } - // 노드 종류 범례 (우측 하단) — 각 타입 아이콘 + 이름 - const LEGEND = [['combat', '전투'], ['elite', '엘리트'], ['boss', '보스'], ['shop', '상점'], ['rest', '휴식'], ['treasure', '보물']]; - const lgW = 300, lgH = 312; - map.push(entity({ - id: guid('map', 991), - path: '/ui/DefaultGroup/MapHud/Legend', - modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 4, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: lgW, y: lgH }, pos: { x: 760, y: -334 } }), - sprite({ color: { r: 0.08, g: 0.09, b: 0.14, a: 0.86 }, type: 1, raycast: false }), - ], - })); - map.push(entity({ - id: guid('map', 992), - path: '/ui/DefaultGroup/MapHud/Legend/LegendTitle', - modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 1, - components: [ - transform({ parentW: lgW, parentH: lgH, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: lgW - 20, y: 32 }, pos: { x: 0, y: lgH / 2 - 26 } }), - sprite({ color: TRANSPARENT }), - text({ value: '노드 종류', fontSize: 22, bold: true, color: GOLD, alignment: 4 }), - ], - })); - let lgId = 993; - LEGEND.forEach(([t, ko], i) => { - const rowY = lgH / 2 - 78 - i * 38; - map.push(entity({ - id: guid('map', lgId++), - path: `/ui/DefaultGroup/MapHud/Legend/Icon_${t}`, - modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 2, - components: [ - transform({ parentW: lgW, parentH: lgH, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 36, y: 36 }, pos: { x: -lgW / 2 + 38, y: rowY } }), - sprite({ dataId: NODEICONS.icons[t], color: { r: 1, g: 1, b: 1, a: 1 }, type: 0, raycast: false }), - ], - })); - map.push(entity({ - id: guid('map', lgId++), - path: `/ui/DefaultGroup/MapHud/Legend/Label_${t}`, - modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 2, - components: [ - transform({ parentW: lgW, parentH: lgH, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: lgW - 110, y: 30 }, pos: { x: 32, y: rowY } }), - sprite({ color: TRANSPARENT }), - text({ value: ko, fontSize: 19, bold: false, color: { r: 0.9, g: 0.92, b: 0.96, a: 1 }, alignment: 4 }), - ], - })); - }); - emit('MapHud', map); - - const shop = []; - const shopHud = entity({ - id: guid('shp', 0), - path: '/ui/DefaultGroup/ShopHud', - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 8, - 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.06, b: 0.09, a: 0.92 }, type: 1, raycast: true }), - ], - }); - shopHud.jsonString.enable = false; - shop.push(shopHud); - shop.push(entity({ - id: guid('shp', 1), - path: '/ui/DefaultGroup/ShopHud/Title', - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 0, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 700, y: 60 }, pos: { x: 0, y: 400 } }), - sprite({ color: TRANSPARENT }), - text({ value: '상점', fontSize: 44, bold: true, color: GOLD, alignment: 4 }), - ], - })); - shop.push(entity({ - id: guid('shp', 2), - path: '/ui/DefaultGroup/ShopHud/Gold', - 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: 300, y: 44 }, pos: { x: 0, y: 330 } }), - sprite({ color: TRANSPARENT }), - text({ value: '메소 0', fontSize: 28, bold: true, color: { r: 0.98, g: 0.85, b: 0.4, a: 1 }, alignment: 4 }), - ], - })); - shop.push(entity({ - id: guid('shp', 3), - path: '/ui/DefaultGroup/ShopHud/MesoIcon', - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 1, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 30, y: 30 }, pos: { x: -86, y: 330 } }), - sprite({ color: { r: 1, g: 0.82, b: 0.2, a: 1 }, type: 1 }), - ], - })); - const shopXs = [-300, 0, 300]; - // 카드 단위 엔티티 v2 네임스페이스 (stride 8: Price 포함) — DeckInspectHud 주석 참조 - for (let i = 1; i <= 3; i++) { - const shpBase = 3 + (i - 1) * 8; - const cardPath = `/ui/DefaultGroup/ShopHud/Card${i}`; - shop.push(entity({ - id: guid('shp2', shpBase), - path: cardPath, - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent', - displayOrder: i, - 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: shopXs[i - 1], y: 20 } }), - sprite({ dataId: CARDFRAMES.frames.warrior.normal, color: WHITE, type: 0, raycast: true }), - button(), - { '@type': 'MOD.Core.UITouchReceiveComponent', Enable: true }, - ], - })); - const shopLayout = cardFaceLayout(CARD_W); - const shopTexts = shopLayout.texts.map(([sfx, c]) => [sfx, { ...c, value: sfx === 'Cost' ? '1' : sfx === 'Name' ? '카드' : '' }]); - shopTexts.push(['Price', { size: { x: 160, y: 40 }, pos: { x: 0, y: -135 }, value: '30 메소', fontSize: 22, bold: true, color: { r: 0.98, g: 0.85, b: 0.4, a: 1 } }]); - for (const [tIdx, [suffix, cfg]] of shopTexts.entries()) { - const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : suffix === 'Desc' ? 8 : 9; - shop.push(entity({ - id: guid('shp2', shpBase + 1 + tIdx), - path: `${cardPath}/${suffix}`, - 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 }), - ], - })); - } - shop.push(entity({ - id: guid('shp2', shpBase + 7), - path: `${cardPath}/Art`, - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 5, - components: [ - transform({ parentW: CARD_W, parentH: CARD_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: shopLayout.art.size, pos: shopLayout.art.pos }), - sprite({ color: WHITE, type: 0, raycast: false }), - ], - })); - } - let shpN = 3 + 3 * 8; // 구 시퀀스의 루프 종료 시점 값(27) 보존 — Relic 등 후속 id 불변 - shop.push(entity({ - id: guid('shp', shpN++), - path: '/ui/DefaultGroup/ShopHud/Relic', - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', - displayOrder: 9, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 560, y: 76 }, pos: { x: 0, y: -190 } }), - sprite({ color: { r: 0.7, g: 0.55, b: 0.85, a: 1 }, type: 1, raycast: true }), - button(), - ], - })); - shop.push(entity({ - id: guid('shp', shpN++), - path: '/ui/DefaultGroup/ShopHud/Relic/Label', - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 0, - components: [ - transform({ parentW: 560, parentH: 76, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 540, y: 40 }, pos: { x: 0, y: 12 } }), - sprite({ color: TRANSPARENT }), - text({ value: '유물', fontSize: 22, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), - ], - })); - shop.push(entity({ - id: guid('shp', shpN++), - path: '/ui/DefaultGroup/ShopHud/Relic/Price', - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 1, - components: [ - transform({ parentW: 560, parentH: 76, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 540, y: 30 }, pos: { x: 0, y: -22 } }), - sprite({ color: TRANSPARENT }), - text({ value: '60 메소', fontSize: 20, bold: true, color: { r: 0.98, g: 0.85, b: 0.4, a: 1 }, alignment: 4 }), - ], - })); - shop.push(entity({ - id: guid('shp', shpN++), - path: '/ui/DefaultGroup/ShopHud/Potion', - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', - displayOrder: 11, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 560, y: 76 }, pos: { x: 0, y: -278 } }), - sprite({ color: { r: 0.45, g: 0.7, b: 0.55, a: 1 }, type: 1, raycast: true }), - button(), - ], - })); - shop.push(entity({ - id: guid('shp', shpN++), - path: '/ui/DefaultGroup/ShopHud/Potion/Label', - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 0, - components: [ - transform({ parentW: 560, parentH: 76, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 540, y: 40 }, pos: { x: 0, y: 12 } }), - sprite({ color: TRANSPARENT }), - text({ value: '물약', fontSize: 22, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), - ], - })); - shop.push(entity({ - id: guid('shp', shpN++), - path: '/ui/DefaultGroup/ShopHud/Potion/Price', - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 1, - components: [ - transform({ parentW: 560, parentH: 76, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 540, y: 30 }, pos: { x: 0, y: -22 } }), - sprite({ color: TRANSPARENT }), - text({ value: '20 메소', fontSize: 20, bold: true, color: { r: 0.98, g: 0.85, b: 0.4, a: 1 }, alignment: 4 }), - ], - })); - shop.push(entity({ - id: guid('shp', shpN++), - path: '/ui/DefaultGroup/ShopHud/Leave', - modelId: 'uibutton', - entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', - displayOrder: 10, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 200, y: 60 }, pos: { x: 0, y: -380 } }), - sprite({ color: DARK, type: 1, raycast: true }), - button(), - text({ value: '나가기', fontSize: 26, bold: true, color: GOLD, alignment: 4 }), - ], - })); - emit('ShopHud', shop); - - const rest = []; - const restHud = entity({ - id: guid('rst', 0), - path: '/ui/DefaultGroup/RestHud', - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 9, - 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.08, b: 0.06, a: 0.92 }, type: 1, raycast: true }), - ], - }); - restHud.jsonString.enable = false; - rest.push(restHud); - rest.push(entity({ - id: guid('rst', 1), - path: '/ui/DefaultGroup/RestHud/Title', - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 0, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 700, y: 60 }, pos: { x: 0, y: 140 } }), - sprite({ color: TRANSPARENT }), - text({ value: '휴식', fontSize: 44, bold: true, color: GOLD, alignment: 4 }), - ], - })); - rest.push(entity({ - id: guid('rst', 2), - path: '/ui/DefaultGroup/RestHud/Info', - 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: 600, y: 50 }, pos: { x: 0, y: 30 } }), - sprite({ color: TRANSPARENT }), - text({ value: 'HP 회복', fontSize: 30, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), - ], - })); - rest.push(entity({ - id: guid('rst', 3), - path: '/ui/DefaultGroup/RestHud/Leave', - modelId: 'uibutton', - entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,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: 200, y: 60 }, pos: { x: 0, y: -120 } }), - sprite({ color: DARK, type: 1, raycast: true }), - button(), - text({ value: '나가기', fontSize: 26, bold: true, color: GOLD, alignment: 4 }), - ], - })); - emit('RestHud', rest); + emit('RestHud', buildRest()); // 유물 방 — 보물 상자 (P8) - const treasure = []; - const treasureHud = entity({ - id: guid('trs', 0), - path: '/ui/DefaultGroup/TreasureHud', - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 8, - 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.06, b: 0.09, a: 0.92 }, type: 1, raycast: true }), - ], - }); - treasureHud.jsonString.enable = false; - treasure.push(treasureHud); - treasure.push(entity({ - id: guid('trs', 1), - path: '/ui/DefaultGroup/TreasureHud/Title', - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 0, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 700, y: 60 }, pos: { x: 0, y: 320 } }), - sprite({ color: TRANSPARENT }), - text({ value: '보물 상자', fontSize: 40, bold: true, color: GOLD, alignment: 4 }), - ], - })); - treasure.push(entity({ - id: guid('trs', 2), - path: '/ui/DefaultGroup/TreasureHud/Chest', - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', - displayOrder: 1, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 180, y: 180 }, pos: { x: 0, y: 40 } }), - sprite({ dataId: CHEST_CLOSED_RUID, color: { r: 1, g: 1, b: 1, a: 1 }, type: 0, raycast: true }), - button(), - ], - })); - treasure.push(entity({ - id: guid('trs', 3), - path: '/ui/DefaultGroup/TreasureHud/Hint', - 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: 500, y: 34 }, pos: { x: 0, y: -90 } }), - sprite({ color: TRANSPARENT }), - text({ value: '상자를 클릭해 여세요', fontSize: 20, bold: false, color: { r: 0.85, g: 0.85, b: 0.9, a: 1 }, alignment: 4 }), - ], - })); - const treasureReward = entity({ - id: guid('trs', 4), - path: '/ui/DefaultGroup/TreasureHud/Reward', - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 3, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 800, y: 44 }, pos: { x: 0, y: -160 } }), - sprite({ color: TRANSPARENT }), - text({ value: '', fontSize: 28, bold: true, color: { r: 0.98, g: 0.85, b: 0.4, a: 1 }, alignment: 4 }), - ], - }); - treasureReward.jsonString.enable = false; - treasure.push(treasureReward); - treasure.push(entity({ - id: guid('trs', 5), - path: '/ui/DefaultGroup/TreasureHud/Leave', - modelId: 'uibutton', - entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', - displayOrder: 4, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 200, y: 60 }, pos: { x: 0, y: -280 } }), - sprite({ color: DARK, type: 1, raycast: true }), - button(), - text({ value: '나가기', fontSize: 26, bold: true, color: GOLD, alignment: 4 }), - ], - })); - emit('TreasureHud', treasure); + emit('TreasureHud', buildTreasure()); // 전직 선택 (P9) — 보스 보상: 유물 vs 2차 전직 - const jobChoice = []; - const jobChoiceHud = entity({ - id: guid('job', 0), - path: '/ui/DefaultGroup/JobChoiceHud', - modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 9, - 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.06, b: 0.09, a: 0.92 }, type: 1, raycast: true }), - ], - }); - jobChoiceHud.jsonString.enable = false; - jobChoice.push(jobChoiceHud); - jobChoice.push(entity({ - id: guid('job', 1), - path: '/ui/DefaultGroup/JobChoiceHud/Title', - modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 0, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 800, y: 60 }, pos: { x: 0, y: 220 } }), - sprite({ color: TRANSPARENT }), - text({ value: '보스 처치 보상을 선택하세요', fontSize: 36, bold: true, color: GOLD, alignment: 4 }), - ], - })); - const jcButtons = [ - ['RelicButton', '유물 획득', -240, { r: 0.7, g: 0.55, b: 0.85, a: 1 }], - ['JobButton', '2차 전직', 240, { r: 0.86, g: 0.6, b: 0.3, a: 1 }], - ]; - jcButtons.forEach(([suffix, label, x, color], bi) => { - jobChoice.push(entity({ - id: guid('job', 2 + bi), - path: `/ui/DefaultGroup/JobChoiceHud/${suffix}`, - modelId: 'uibutton', entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', - displayOrder: 1 + bi, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 380, y: 140 }, pos: { x, y: 0 } }), - sprite({ color, type: 1, raycast: true }), - button(), - text({ value: label, fontSize: 32, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), - ], - })); - }); - emit('JobChoiceHud', jobChoice); + emit('JobChoiceHud', buildJobChoice()); - const jobSelect = []; - const jobSelectHud = entity({ - id: guid('job', 10), - path: '/ui/DefaultGroup/JobSelectHud', - modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 10, - 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.06, b: 0.09, a: 0.94 }, type: 1, raycast: true }), - ], - }); - jobSelectHud.jsonString.enable = false; - jobSelect.push(jobSelectHud); - jobSelect.push(entity({ - id: guid('job', 11), - path: '/ui/DefaultGroup/JobSelectHud/Title', - modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 0, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 800, y: 60 }, pos: { x: 0, y: 300 } }), - sprite({ color: TRANSPARENT }), - text({ value: '2차 전직 — 직업을 선택하세요', fontSize: 36, bold: true, color: GOLD, alignment: 4 }), - ], - })); - // 범용 슬롯 3개 — ShowJobSelect(Lua)가 클래스별 JOBS로 텍스트를 채움 (P10 동적화) - const jobs = [ - ['slot1', '', '', '', -440, { r: 0.82, g: 0.4, b: 0.34, a: 1 }], - ['slot2', '', '', '', 0, { r: 0.4, g: 0.55, b: 0.85, a: 1 }], - ['slot3', '', '', '', 440, { r: 0.42, g: 0.72, b: 0.46, a: 1 }], - ]; - jobs.forEach(([jobId, name, desc, starter, x, color], ji) => { - const base = `/ui/DefaultGroup/JobSelectHud/Job_${jobId}`; - jobSelect.push(entity({ - id: guid('job', 12 + ji * 4), - path: base, - modelId: 'uibutton', entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', - displayOrder: 1 + ji, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 380, y: 420 }, pos: { x, y: -20 } }), - sprite({ color, type: 1, raycast: true }), - button(), - ], - })); - jobSelect.push(entity({ - id: guid('job', 13 + ji * 4), - path: `${base}/Name`, - modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 0, - components: [ - transform({ parentW: 380, parentH: 420, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 360, y: 50 }, pos: { x: 0, y: 150 } }), - sprite({ color: TRANSPARENT }), - text({ value: name, fontSize: 34, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), - ], - })); - jobSelect.push(entity({ - id: guid('job', 14 + ji * 4), - path: `${base}/Desc`, - modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 1, - components: [ - transform({ parentW: 380, parentH: 420, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 340, y: 160 }, pos: { x: 0, y: 0 } }), - sprite({ color: TRANSPARENT }), - text({ value: desc, fontSize: 22, bold: false, color: { r: 0.95, g: 0.95, b: 0.97, a: 1 }, alignment: 4 }), - ], - })); - jobSelect.push(entity({ - id: guid('job', 15 + ji * 4), - path: `${base}/Starter`, - modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 2, - components: [ - transform({ parentW: 380, parentH: 420, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 340, y: 32 }, pos: { x: 0, y: -160 } }), - sprite({ color: TRANSPARENT }), - text({ value: starter, fontSize: 18, bold: true, color: GOLD, alignment: 4 }), - ], - })); - }); - emit('JobSelectHud', jobSelect); + emit('JobSelectHud', buildJobSelect()); const menu = []; menu.push(entity({ @@ -2094,201 +509,13 @@ function upsertUi() { emit('CharacterSelectHud', select); // ── LobbyHud — 반복 런의 허브. NPC 클릭으로 런시작/도감/영혼상점/게시판 ── - const lobby = []; - let lobId = 0; - const lobbyRoot = entity({ - id: guid('lob', lobId++), - path: '/ui/DefaultGroup/LobbyHud', - modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 11, - 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 }), - // 로비가 물리 맵이 됨 — 투명 + 비레이캐스트로 맵을 가리지 않고 월드 NPC 클릭이 통과되게 함. - sprite({ color: TRANSPARENT, type: 1, raycast: false }), - ], - }); - lobbyRoot.jsonString.enable = false; - lobby.push(lobbyRoot); - const lobTexts = [ - ['Title', 0, 478, 760, '메이플 로비', 40, GOLD], - ['SoulLabel', 700, 478, 320, '영혼 0', 28, { r: 0.6, g: 0.85, b: 1, a: 1 }], - ['AscLabel', -560, 478, 380, '승천 0 / 해금 0', 22, { r: 0.9, g: 0.7, b: 0.5, a: 1 }], - ['Hint', 0, -478, 1500, 'NPC에게 다가가 ↑ 또는 클릭으로 대화 · ← → 이동 · Ctrl 공격', 20, { r: 0.72, g: 0.76, b: 0.82, a: 1 }], - ]; - for (const [suffix, x, y, w, value, fs, color] of lobTexts) { - lobby.push(entity({ - id: guid('lob', lobId++), - path: `/ui/DefaultGroup/LobbyHud/${suffix}`, - 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: w, y: 56 }, pos: { x, y } }), - sprite({ color: TRANSPARENT }), - text({ value, fontSize: fs, bold: true, color, alignment: 4 }), - ], - })); - } - for (const [suffix, x, label] of [['AscMinus', -780, '<'], ['AscPlus', -540, '>']]) { - lobby.push(entity({ - id: guid('lob', lobId++), - path: `/ui/DefaultGroup/LobbyHud/${suffix}`, - modelId: 'uibutton', entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,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: 46, y: 42 }, pos: { x, y: 470 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.2, g: 0.24, b: 0.3, a: 1 }, type: 1, raycast: true }), - button(), - text({ value: label, fontSize: 28, bold: true, color: WHITE, alignment: 4 }), - ], - })); - } - // NPC 4종은 로비 물리 맵의 월드 엔티티(map/lobby.map + LobbyNpc codeblock)로 이동. UI 버튼 행 제거. - emit('LobbyHud', lobby); + emit('LobbyHud', buildLobby()); // ── BoardHud — 게시판(공지/팁) ── - const board = []; - let brdId = 0; - const boardRoot = entity({ - id: guid('brd', brdId++), - path: '/ui/DefaultGroup/BoardHud', - modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 14, - 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.06, b: 0.09, a: 0.95 }, type: 1, raycast: true }), - ], - }); - boardRoot.jsonString.enable = false; - board.push(boardRoot); - board.push(entity({ - id: guid('brd', brdId++), - path: '/ui/DefaultGroup/BoardHud/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: 700, y: 60 }, pos: { x: 0, y: 400 } }), - sprite({ color: TRANSPARENT }), - text({ value: '게시판', fontSize: 44, bold: true, color: GOLD, alignment: 4 }), - ], - })); - board.push(entity({ - id: guid('brd', brdId++), - path: '/ui/DefaultGroup/BoardHud/Body', - 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: 1100, y: 520 }, pos: { x: 0, y: 20 } }), - sprite({ color: { r: 0.1, g: 0.12, b: 0.16, a: 0.9 }, type: 1 }), - text({ value: '· 카드는 직업/등급에 따라 보상 확률이 다릅니다.\n· 몬스터는 매 턴 정해진 행동 중 하나를 무작위로 합니다.\n· 일부 몬스터는 덱에 저주 카드(상처/화상)를 넣습니다.\n· 손패는 최대 10장, 초과분은 자동으로 버려집니다.\n· 2차 전직 후 보스를 잡으면 영혼이 쌓입니다.\n· 영혼은 상인 NPC에서 덱빌딩 해금에 사용합니다.', fontSize: 24, bold: false, color: { r: 0.86, g: 0.9, b: 0.94, a: 1 }, alignment: 0 }), - ], - })); - board.push(entity({ - id: guid('brd', brdId++), - path: '/ui/DefaultGroup/BoardHud/Close', - modelId: 'uibutton', entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,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: 200, y: 60 }, pos: { x: 0, y: -380 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.2, g: 0.24, b: 0.3, a: 1 }, type: 1, raycast: true }), - button(), - text({ value: '닫기', fontSize: 28, bold: true, color: WHITE, alignment: 4 }), - ], - })); - emit('BoardHud', board); + emit('BoardHud', buildBoard()); // ── SoulShopHud — 영혼 메타 상점 (Phase 9에서 해금 항목·구매 로직 채움) ── - const soulShop = []; - let soulId = 0; - const soulRoot = entity({ - id: guid('soul', soulId++), - path: '/ui/DefaultGroup/SoulShopHud', - modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 15, - 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.06, b: 0.09, a: 0.95 }, type: 1, raycast: true }), - ], - }); - soulRoot.jsonString.enable = false; - soulShop.push(soulRoot); - soulShop.push(entity({ - id: guid('soul', soulId++), - path: '/ui/DefaultGroup/SoulShopHud/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: 700, y: 60 }, pos: { x: 0, y: 410 } }), - sprite({ color: TRANSPARENT }), - text({ value: '영혼 상점', fontSize: 44, bold: true, color: { r: 0.6, g: 0.85, b: 1, a: 1 }, alignment: 4 }), - ], - })); - soulShop.push(entity({ - id: guid('soul', soulId++), - path: '/ui/DefaultGroup/SoulShopHud/Souls', - 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: 400, y: 44 }, pos: { x: 0, y: 345 } }), - sprite({ color: TRANSPARENT }), - text({ value: '영혼 0', fontSize: 28, bold: true, color: { r: 0.6, g: 0.85, b: 1, a: 1 }, alignment: 4 }), - ], - })); - soulShop.push(entity({ - id: guid('soul', soulId++), - path: '/ui/DefaultGroup/SoulShopHud/Close', - modelId: 'uibutton', entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,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: 200, y: 60 }, pos: { x: 0, y: -400 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.2, g: 0.24, b: 0.3, a: 1 }, type: 1, raycast: true }), - button(), - text({ value: '닫기', fontSize: 28, bold: true, color: WHITE, alignment: 4 }), - ], - })); - for (let i = 1; i <= 4; i++) { - const ip = `/ui/DefaultGroup/SoulShopHud/Item${i}`; - const iy = 230 - (i - 1) * 125; - soulShop.push(entity({ - id: guid('soul', soulId++), - path: ip, modelId: 'uibutton', entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', - displayOrder: 2, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 760, y: 104 }, pos: { x: 0, y: iy }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.14, g: 0.16, b: 0.22, a: 1 }, type: 1, raycast: true }), - button(), - ], - })); - for (const [suffix, x, y, w, fs, color] of [ - ['Name', -180, 22, 360, 28, GOLD], - ['Desc', -180, -24, 440, 20, { r: 0.86, g: 0.9, b: 0.94, a: 1 }], - ['Status', 270, 0, 220, 22, { r: 0.6, g: 0.85, b: 1, a: 1 }], - ]) { - soulShop.push(entity({ - id: guid('soul', soulId++), - path: `${ip}/${suffix}`, modelId: 'uitext', entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 1, - components: [ - transform({ parentW: 760, parentH: 104, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: w, y: 38 }, pos: { x, y } }), - sprite({ color: TRANSPARENT }), - text({ value: '', fontSize: fs, bold: suffix === 'Name', color, alignment: 4 }), - ], - })); - } - } - emit('SoulShopHud', soulShop); + emit('SoulShopHud', buildSoulShop()); for (const section of UI_APPEND_ORDER) { const entities = uiSections.get(section); diff --git a/tools/deck/hud/board.mjs b/tools/deck/hud/board.mjs new file mode 100644 index 0000000..299bc34 --- /dev/null +++ b/tools/deck/hud/board.mjs @@ -0,0 +1,58 @@ +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, 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'; + +export function buildBoard() { + const board = []; + let brdId = 0; + const boardRoot = entity({ + id: guid('brd', brdId++), + path: '/ui/DefaultGroup/BoardHud', + modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 14, + 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.06, b: 0.09, a: 0.95 }, type: 1, raycast: true }), + ], + }); + boardRoot.jsonString.enable = false; + board.push(boardRoot); + board.push(entity({ + id: guid('brd', brdId++), + path: '/ui/DefaultGroup/BoardHud/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: 700, y: 60 }, pos: { x: 0, y: 400 } }), + sprite({ color: TRANSPARENT }), + text({ value: '게시판', fontSize: 44, bold: true, color: GOLD, alignment: 4 }), + ], + })); + board.push(entity({ + id: guid('brd', brdId++), + path: '/ui/DefaultGroup/BoardHud/Body', + 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: 1100, y: 520 }, pos: { x: 0, y: 20 } }), + sprite({ color: { r: 0.1, g: 0.12, b: 0.16, a: 0.9 }, type: 1 }), + text({ value: '· 카드는 직업/등급에 따라 보상 확률이 다릅니다.\n· 몬스터는 매 턴 정해진 행동 중 하나를 무작위로 합니다.\n· 일부 몬스터는 덱에 저주 카드(상처/화상)를 넣습니다.\n· 손패는 최대 10장, 초과분은 자동으로 버려집니다.\n· 2차 전직 후 보스를 잡으면 영혼이 쌓입니다.\n· 영혼은 상인 NPC에서 덱빌딩 해금에 사용합니다.', fontSize: 24, bold: false, color: { r: 0.86, g: 0.9, b: 0.94, a: 1 }, alignment: 0 }), + ], + })); + board.push(entity({ + id: guid('brd', brdId++), + path: '/ui/DefaultGroup/BoardHud/Close', + modelId: 'uibutton', entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,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: 200, y: 60 }, pos: { x: 0, y: -380 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.2, g: 0.24, b: 0.3, a: 1 }, type: 1, raycast: true }), + button(), + text({ value: '닫기', fontSize: 28, bold: true, color: WHITE, alignment: 4 }), + ], + })); + return board; +} diff --git a/tools/deck/hud/combat.mjs b/tools/deck/hud/combat.mjs new file mode 100644 index 0000000..ae80a9b --- /dev/null +++ b/tools/deck/hud/combat.mjs @@ -0,0 +1,494 @@ +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, 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'; + +export function buildCombat() { + const PANEL_BG = { r: 0.08, g: 0.09, b: 0.11, a: 0.78 }; + const combat = []; + combat.push(entity({ + id: guid('cmb', 0), + path: '/ui/DefaultGroup/CombatHud', + modelId: 'uiempty', + entryId: 'UIEmpty', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 4, + 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 }), + ], + })); + const SLOT_W = 140, SLOT_H = 96; + for (let i = 1; i <= MAX_MONSTERS; i++) { + const base = `/ui/DefaultGroup/CombatHud/MonsterSlot${i}`; + const slot = entity({ + id: guid('cmb', 40 + i), + path: base, + modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', + displayOrder: 20 + i, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: SLOT_W, y: SLOT_H }, pos: { x: (i - 2.5) * 320, y: 300 } }), + sprite({ color: { r: 0, g: 0, b: 0, a: 0.0001 }, type: 1, raycast: true }), + button(), + ], + }); + slot.jsonString.enable = false; + combat.push(slot); + const targetFrame = entity({ + id: guid('cmb', 220 + i), path: `${base}/TargetFrame`, modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 0, + components: [ + transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: SLOT_W + 16, y: SLOT_H + 12 }, pos: { x: 0, y: 0 } }), + sprite({ color: { r: 0.95, g: 0.78, b: 0.25, a: 0.28 }, type: 1 }), + ], + }); + targetFrame.jsonString.enable = false; + combat.push(targetFrame); + const targetMarker = entity({ + id: guid('cmb', 360 + i), path: `${base}/TargetMarker`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 9, + components: [ + transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 116, y: 116 }, pos: { x: 0, y: 2 } }), + sprite({ color: { r: 0.95, g: 0.08, b: 0.05, a: 0.92 }, type: 1 }), + text({ value: '+', fontSize: 72, bold: true, color: { r: 1, g: 0.94, b: 0.28, a: 1 }, alignment: 4, outlineWidth: 4 }), + ], + }); + targetMarker.jsonString.enable = false; + combat.push(targetMarker); + const targetLabel = entity({ + id: guid('cmb', 370 + i), path: `${base}/TargetMarker/Label`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 10, + components: [ + transform({ parentW: 116, parentH: 116, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 112, y: 28 }, pos: { x: 0, y: -52 } }), + sprite({ color: { r: 0.08, g: 0.02, b: 0.02, a: 0.86 }, type: 1 }), + text({ value: 'TARGET', fontSize: 18, bold: true, color: { r: 1, g: 0.94, b: 0.28, a: 1 }, alignment: 4, outlineWidth: 3 }), + ], + }); + targetLabel.jsonString.enable = false; + combat.push(targetLabel); + const actFrame = entity({ + id: guid('cmb', 240 + i), path: `${base}/ActFrame`, modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 0, + components: [ + transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: SLOT_W + 16, y: SLOT_H + 12 }, pos: { x: 0, y: 0 } }), + sprite({ color: { r: 0.95, g: 0.3, b: 0.25, a: 0.3 }, type: 1 }), + ], + }); + actFrame.jsonString.enable = false; + combat.push(actFrame); + combat.push(entity({ + id: guid('cmb', 60 + i), path: `${base}/Name`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 1, + components: [ + transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: SLOT_W, y: 30 }, pos: { x: 0, y: 34 } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: 22, bold: true, color: GOLD, alignment: 4 }), + ], + })); + combat.push(entity({ + id: guid('cmb', 80 + i), path: `${base}/Hp`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 2, + components: [ + transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: SLOT_W, y: 26 }, pos: { x: 0, y: 6 } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: 20, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), + ], + })); + combat.push(entity({ + id: guid('cmb', 100 + i), path: `${base}/HpBarBg`, modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 3, + components: [ + transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: HP_BAR_W, y: 14 }, pos: { x: 0, y: -14 } }), + sprite({ color: { r: 0.18, g: 0.05, b: 0.06, a: 1 }, type: 1 }), + ], + })); + combat.push(entity({ + id: guid('cmb', 120 + i), path: `${base}/HpBarFill`, modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 4, + components: [ + transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0, y: 0.5 }, size: { x: HP_BAR_W, y: 14 }, pos: { x: -HP_BAR_W / 2, y: -14 } }), + sprite({ color: { r: 0.86, g: 0.35, b: 0.32, a: 1 }, type: 1 }), + ], + })); + combat.push(entity({ + id: guid('cmb', 140 + i), path: `${base}/Intent`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 5, + components: [ + transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: SLOT_W + 40, y: 24 }, pos: { x: 0, y: -36 } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: 17, bold: true, color: { r: 1, g: 0.72, b: 0.5, a: 1 }, alignment: 4 }), + ], + })); + const dmgPopBase = `/ui/DefaultGroup/CombatHud/DmgPop${i}`; + const dmgPop = entity({ + id: guid('cmb', 250 + i), path: dmgPopBase, modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 80 + i, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 192, y: 56 }, pos: { x: 0, y: 0 } }), + sprite({ color: TRANSPARENT, type: 1 }), + ], + }); + dmgPop.jsonString.enable = false; + combat.push(dmgPop); + for (let d = 0; d < DAMAGE_POP_MAX_DIGITS; d++) { + combat.push(entity({ + id: guid('cmb', 380 + i * 10 + d), path: `${dmgPopBase}/Digit${d + 1}`, modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 90 + i, + components: [ + transform({ parentW: 192, parentH: 56, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: DAMAGE_POP_DIGIT_W, y: DAMAGE_POP_DIGIT_H }, pos: { x: 0, y: 0 } }), + sprite({ dataId: DAMAGE_DIGIT_RUIDS[0], color: { r: 1, g: 1, b: 1, a: 1 }, type: 0 }), + ], + })); + } + const mBlockBadge = entity({ + id: guid('cmb', 270 + i), path: `${base}/BlockBadge`, modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 6, + components: [ + transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 40, y: 36 }, pos: { x: -HP_BAR_W / 2 - 30, y: -14 } }), + sprite({ color: { r: 0.32, g: 0.5, b: 0.85, a: 1 }, type: 1 }), + ], + }); + mBlockBadge.jsonString.enable = false; + combat.push(mBlockBadge); + combat.push(entity({ + id: guid('cmb', 280 + i), path: `${base}/BlockBadge/Value`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 40, parentH: 36, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 40, y: 32 }, pos: { x: 0, y: 0 } }), + sprite({ color: TRANSPARENT }), + text({ value: '0', fontSize: 17, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), + ], + })); + combat.push(entity({ + id: guid('cmb', 290 + i), path: `${base}/Buffs`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 7, + components: [ + transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: SLOT_W + 60, y: 22 }, pos: { x: 0, y: -58 } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: 15, bold: true, color: { r: 0.85, g: 0.65, b: 1, a: 1 }, alignment: 4 }), + ], + })); + } + const PP = '/ui/DefaultGroup/CombatHud/PlayerPanel'; + combat.push(entity({ + id: guid('cmb', 210), path: PP, modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 5, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 300, y: 96 }, pos: { x: -760, y: -494 }, align: ALIGN_CENTER }), + sprite({ color: PANEL_BG, type: 1 }), + ], + })); + combat.push(entity({ + id: guid('cmb', 211), path: `${PP}/Name`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 300, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 280, y: 28 }, pos: { x: 0, y: 28 } }), + sprite({ color: TRANSPARENT }), + text({ value: '플레이어', fontSize: 18, bold: true, color: GOLD, alignment: 4 }), + ], + })); + combat.push(entity({ + id: guid('cmb', 212), path: `${PP}/HpBarBg`, modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 1, + components: [ + transform({ parentW: 300, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 220, y: 16 }, pos: { x: 16, y: -6 } }), + sprite({ color: { r: 0.18, g: 0.05, b: 0.06, a: 1 }, type: 1 }), + ], + })); + combat.push(entity({ + id: guid('cmb', 213), path: `${PP}/HpBarFill`, modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 2, + components: [ + transform({ parentW: 300, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0, y: 0.5 }, size: { x: 220, y: 14 }, pos: { x: -94, y: -6 } }), + sprite({ color: { r: 0.3, g: 0.78, b: 0.36, a: 1 }, type: 1 }), + ], + })); + combat.push(entity({ + id: guid('cmb', 214), path: `${PP}/HpText`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 3, + components: [ + transform({ parentW: 300, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 220, y: 24 }, pos: { x: 16, y: -30 } }), + sprite({ color: TRANSPARENT }), + text({ value: '80/80', fontSize: 16, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), + ], + })); + const blockBadge = entity({ + id: guid('cmb', 215), path: `${PP}/BlockBadge`, modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 4, + components: [ + transform({ parentW: 300, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 44, y: 40 }, pos: { x: -122, y: -12 } }), + sprite({ color: { r: 0.32, g: 0.5, b: 0.85, a: 1 }, type: 1 }), + ], + }); + blockBadge.jsonString.enable = false; + combat.push(blockBadge); + combat.push(entity({ + id: guid('cmb', 216), path: `${PP}/BlockBadge/Value`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 44, parentH: 40, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 44, y: 36 }, pos: { x: 0, y: 0 } }), + sprite({ color: TRANSPARENT }), + text({ value: '0', fontSize: 18, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), + ], + })); + combat.push(entity({ + id: guid('cmb', 217), path: `${PP}/Buffs`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 6, + components: [ + transform({ parentW: 300, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 280, y: 22 }, pos: { x: 0, y: -44 } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: 14, bold: true, color: { r: 0.85, g: 0.65, b: 1, a: 1 }, alignment: 4 }), + ], + })); + const playerDmgPop = entity({ + id: guid('cmb', 260), path: `${PP}/DmgPop`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 5, + components: [ + transform({ parentW: 300, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 160, y: 30 }, pos: { x: 16, y: 40 } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: 22, bold: true, color: { r: 1, g: 0.4, b: 0.35, a: 1 }, alignment: 4 }), + ], + }); + playerDmgPop.jsonString.enable = false; + combat.push(playerDmgPop); + combat.push(entity({ + id: guid('cmb', 200), + path: '/ui/DefaultGroup/CombatHud/TopBar', + modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 9, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1200, y: 52 }, pos: { x: 0, y: 486 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.06, g: 0.07, b: 0.1, a: 0.82 }, type: 1 }), + ], + })); + const topTexts = [ + ['Floor', -520, 160, '막 1/3', GOLD], + ['Gold', -360, 160, '메소 0', { r: 0.98, g: 0.85, b: 0.4, a: 1 }], + ]; + topTexts.forEach(([suffix, x, w, value, color], ti) => { + combat.push(entity({ + id: guid('cmb', 201 + ti), + path: `/ui/DefaultGroup/CombatHud/TopBar/${suffix}`, + modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: ti, + components: [ + transform({ parentW: 1200, parentH: 52, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: w, y: 40 }, pos: { x: x, y: 0 } }), + sprite({ color: TRANSPARENT }), + text({ value, fontSize: 22, bold: true, color, alignment: 4 }), + ], + })); + }); + combat.push(entity({ + id: guid('cmb', 209), + path: '/ui/DefaultGroup/CombatHud/TopBar/MesoIcon', + modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 2, + components: [ + transform({ parentW: 1200, parentH: 52, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 26, y: 26 }, pos: { x: -432, y: 0 } }), + sprite({ color: { r: 1, g: 0.82, b: 0.2, a: 1 }, type: 1 }), + ], + })); + for (let i = 1; i <= 10; i++) { + combat.push(entity({ + id: guid('cmb', 300 + i), + path: `/ui/DefaultGroup/CombatHud/TopBar/RelicSlot${i}`, + modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.UITouchReceiveComponent', + displayOrder: 3 + i, + components: [ + transform({ parentW: 1200, parentH: 52, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 40, y: 40 }, pos: { x: -240 + (i - 1) * 48, y: 0 } }), + sprite({ color: { r: 0.15, g: 0.16, b: 0.2, a: 0.6 }, type: 0, raycast: true }), + { '@type': 'MOD.Core.UITouchReceiveComponent', Enable: true }, + ], + })); + } + combat.push(entity({ + id: guid('cmb', 311), + path: '/ui/DefaultGroup/CombatHud/TopBar/RelicOverflow', + modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 14, + components: [ + transform({ parentW: 1200, parentH: 52, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 60, y: 30 }, pos: { x: 192, y: 0 } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: 18, bold: true, color: { r: 0.8, g: 0.7, b: 0.95, a: 1 }, alignment: 4 }), + ], + })); + for (let i = 1; i <= 5; i++) { + combat.push(entity({ + id: guid('cmb', 320 + i), + path: `/ui/DefaultGroup/CombatHud/TopBar/PotionSlot${i}`, + modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.UITouchReceiveComponent', + displayOrder: 14 + i, + components: [ + transform({ parentW: 1200, parentH: 52, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 40, y: 40 }, pos: { x: 240 + (i - 1) * 44, y: 0 } }), + sprite({ color: { r: 0.22, g: 0.25, b: 0.3, a: 0.9 }, type: 0, raycast: true }), + { '@type': 'MOD.Core.UITouchReceiveComponent', Enable: true }, + ], + })); + } + const tooltipBox = entity({ + id: guid('cmb', 330), + path: '/ui/DefaultGroup/CombatHud/TooltipBox', + modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 20, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 360, y: 150 }, pos: { x: 0, y: 400 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.04, g: 0.05, b: 0.08, a: 0.96 }, type: 1 }), + ], + }); + tooltipBox.jsonString.enable = false; + combat.push(tooltipBox); + combat.push(entity({ + id: guid('cmb', 331), + path: '/ui/DefaultGroup/CombatHud/TooltipBox/Name', + modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 360, parentH: 150, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 332, y: 28 }, pos: { x: 0, y: 52 } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: 19, bold: true, color: GOLD, alignment: 4 }), + ], + })); + combat.push(entity({ + id: guid('cmb', 332), + path: '/ui/DefaultGroup/CombatHud/TooltipBox/Desc', + modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 1, + components: [ + transform({ parentW: 360, parentH: 150, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 332, y: 102 }, pos: { x: 0, y: -18 } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: 15, bold: false, color: { r: 0.92, g: 0.92, b: 0.95, a: 1 }, alignment: 0 }), + ], + })); + const discardPrompt = entity({ + id: guid('cmb', 333), + path: '/ui/DefaultGroup/CombatHud/DiscardPrompt', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,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: 520, y: 48 }, pos: { x: 0, y: -260 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.05, g: 0.06, b: 0.09, a: 0.86 }, type: 1 }), + text({ value: '', fontSize: 22, bold: true, color: GOLD, alignment: 4 }), + ], + }); + discardPrompt.jsonString.enable = false; + combat.push(discardPrompt); + const potionMenu = entity({ + id: guid('cmb', 340), + path: '/ui/DefaultGroup/CombatHud/PotionMenu', + 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: 380, y: 180 }, pos: { x: 0, y: 120 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.07, g: 0.08, b: 0.12, a: 0.97 }, type: 1 }), + ], + }); + potionMenu.jsonString.enable = false; + combat.push(potionMenu); + combat.push(entity({ + id: guid('cmb', 341), + path: '/ui/DefaultGroup/CombatHud/PotionMenu/Title', + modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 380, parentH: 180, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 360, y: 36 }, pos: { x: 0, y: 52 } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: 19, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), + ], + })); + const pmButtons = [ + ['Use', '사용', -120, { r: 0.32, g: 0.55, b: 0.36, a: 1 }], + ['Toss', '버리기', 0, { r: 0.6, g: 0.32, b: 0.3, a: 1 }], + ['Close', '닫기', 120, { r: 0.25, g: 0.28, b: 0.35, a: 1 }], + ]; + pmButtons.forEach(([suffix, label, x, color], bi) => { + combat.push(entity({ + id: guid('cmb', 342 + bi), + path: `/ui/DefaultGroup/CombatHud/PotionMenu/${suffix}`, + modelId: 'uibutton', entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', + displayOrder: 1 + bi, + components: [ + transform({ parentW: 380, parentH: 180, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 104, y: 46 }, pos: { x, y: -40 } }), + sprite({ color, type: 1, raycast: true }), + button(), + text({ value: label, fontSize: 20, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), + ], + })); + }); + combat.push(entity({ + id: guid('cmb', 205), + path: '/ui/DefaultGroup/CombatHud/TopBar/AllDeckButton', + modelId: 'uibutton', entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', + displayOrder: 3, + components: [ + transform({ parentW: 1200, parentH: 52, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 140, y: 40 }, pos: { x: 528, y: 0 } }), + sprite({ color: DARK, type: 1, raycast: true }), + button(), + text({ value: '모든덱보기', fontSize: 18, bold: true, color: GOLD, alignment: 0 }), + ], + })); + const skillFx = entity({ + id: guid('cmb', 230), path: '/ui/DefaultGroup/CombatHud/SkillFx', + modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 30, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 110, y: 110 }, pos: { x: 0, y: 0 } }), + sprite({ color: { r: 1, g: 1, b: 1, a: 1 }, type: 0, raycast: false }), + ], + }); + skillFx.jsonString.enable = false; + combat.push(skillFx); + const result = entity({ + id: guid('cmb', 2), + path: '/ui/DefaultGroup/CombatHud/Result', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 8, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 700, y: 140 }, pos: { x: 0, y: 120 } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: 64, bold: true, color: GOLD, alignment: 4 }), + ], + }); + result.jsonString.enable = false; + combat.push(result); + return combat; +} diff --git a/tools/deck/hud/deckall.mjs b/tools/deck/hud/deckall.mjs new file mode 100644 index 0000000..34ff6c7 --- /dev/null +++ b/tools/deck/hud/deckall.mjs @@ -0,0 +1,159 @@ +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, 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'; + +export function buildDeckAll() { + const allDeck = []; + const allHud = entity({ + id: guid('all', 0), + path: '/ui/DefaultGroup/DeckAllHud', + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 16, + 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.04, g: 0.05, b: 0.07, a: 0.78 }, type: 1, raycast: true }), + ], + }); + allHud.jsonString.enable = false; + allDeck.push(allHud); + allDeck.push(entity({ + id: guid('all', 1), + path: '/ui/DefaultGroup/DeckAllHud/Panel', + 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: 1080, y: 800 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.08, g: 0.09, b: 0.11, a: 0.96 }, type: 1 }), + ], + })); + allDeck.push(entity({ + id: guid('all', 2), + path: '/ui/DefaultGroup/DeckAllHud/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: 54 }, pos: { x: 0, y: 380 } }), + sprite({ color: TRANSPARENT }), + text({ value: '모든 덱', fontSize: 34, bold: true, color: GOLD, alignment: 4 }), + ], + })); + allDeck.push(entity({ + id: guid('all', 3), + path: '/ui/DefaultGroup/DeckAllHud/Close', + modelId: 'uibutton', + entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,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: 78, y: 52 }, pos: { x: 486, y: 380 } }), + sprite({ color: { r: 0.16, g: 0.18, b: 0.22, a: 1 }, type: 1, raycast: true }), + button(), + text({ value: 'X', fontSize: 26, bold: true, color: GOLD, alignment: 4 }), + ], + })); + const deckTabs = [ + { key: 'Warrior', label: '전사', x: -210 }, + { key: 'Thief', label: '도적', x: 0 }, + { key: 'Mage', label: '마법사', x: 210 }, + ]; + for (let i = 0; i < deckTabs.length; i++) { + const tab = deckTabs[i]; + allDeck.push(entity({ + id: guid('all', 10 + i), + path: `/ui/DefaultGroup/DeckAllHud/${tab.key}Tab`, + modelId: 'uibutton', + entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', + displayOrder: 3 + i, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 170, y: 46 }, pos: { x: tab.x, y: 318 } }), + sprite({ color: { r: 0.11, g: 0.13, b: 0.16, a: 1 }, type: 1, raycast: true }), + button(), + text({ value: tab.label, fontSize: 22, bold: true, color: GOLD, alignment: 4 }), + ], + })); + } + allDeck.push(entity({ + id: guid('all', 4), + path: '/ui/DefaultGroup/DeckAllHud/Empty', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 3, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 600, y: 50 }, pos: { x: 0, y: 40 } }), + sprite({ color: TRANSPARENT }), + text({ value: '덱이 없습니다', fontSize: 28, bold: true, color: { r: 0.82, g: 0.86, b: 0.9, a: 1 }, alignment: 4 }), + ], + })); + allDeck.push(entity({ + id: guid('all', 5), + path: '/ui/DefaultGroup/DeckAllHud/Grid', + modelId: 'uiempty', + entryId: 'UIEmpty', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ScrollLayoutGroupComponent', + displayOrder: 4, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 980, y: 620 }, pos: { x: 0, y: 0 } }), + sprite({ color: TRANSPARENT, type: 1, raycast: true }), + scrollLayoutGroup({ cellSize: { x: 158, y: 214 }, spacing: { x: 22, y: 22 }, columns: 5 }), + ], + })); + const ALL_DECK_CARD_COUNT = 120; + const ALL_DECK_CARD_W = 158; + const ALL_DECK_CARD_H = 214; + // 카드 단위 엔티티 v2 네임스페이스 — DeckInspectHud 주석 참조 + for (let i = 1; i <= ALL_DECK_CARD_COUNT; i++) { + const allBase = 6 + (i - 1) * 7; + const cardPath = `/ui/DefaultGroup/DeckAllHud/Grid/Card${i}`; + const card = entity({ + id: guid('all2', allBase), + path: cardPath, + modelId: 'uisprite', + entryId: 'UISprite', + 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 }), + ], + }); + card.jsonString.enable = false; + allDeck.push(card); + const allDeckLayout = cardFaceLayout(ALL_DECK_CARD_W); + for (const [tIdx, [suffix, cfg]] of allDeckLayout.texts.map(([sfx, c]) => [sfx, { ...c, value: sfx === 'Cost' ? '1' : '' }]).entries()) { + const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8; + allDeck.push(entity({ + id: guid('all2', allBase + 1 + tIdx), + path: `${cardPath}/${suffix}`, + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: dOrder, + components: [ + transform({ parentW: ALL_DECK_CARD_W, parentH: ALL_DECK_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 }), + ], + })); + } + allDeck.push(entity({ + id: guid('all2', allBase + 6), + path: `${cardPath}/Art`, + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 5, + components: [ + transform({ parentW: ALL_DECK_CARD_W, parentH: ALL_DECK_CARD_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: allDeckLayout.art.size, pos: allDeckLayout.art.pos }), + sprite({ color: WHITE, type: 0, raycast: false }), + ], + })); + } + return allDeck; +} diff --git a/tools/deck/hud/deckhud.mjs b/tools/deck/hud/deckhud.mjs new file mode 100644 index 0000000..ca69dae --- /dev/null +++ b/tools/deck/hud/deckhud.mjs @@ -0,0 +1,121 @@ +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, 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'; + +export function buildDeckHud() { + const hud = []; + const add = (e) => hud.push(e); + + add(entity({ + id: guid('hud', 0), + path: '/ui/DefaultGroup/DeckHud', + modelId: 'uiempty', + entryId: 'UIEmpty', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 5, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1280, y: 330 }, pos: { x: 0, y: 180 }, align: ALIGN_BOTTOM_CENTER }), + sprite({ color: TRANSPARENT }), + ], + })); + + for (const pile of [ + { key: 'DrawPile', x: -590, label: '뽑을 덱', count: '10', color: { r: 0.17, g: 0.20, b: 0.25, a: 1 } }, + { key: 'DiscardPile', x: 590, label: '버린 덱', count: '0', color: { r: 0.22, g: 0.18, b: 0.16, a: 1 } }, + ]) { + add(entity({ + id: guid('hud', hud.length), + path: `/ui/DefaultGroup/DeckHud/${pile.key}`, + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', + displayOrder: pile.key === 'DrawPile' ? 0 : 1, + components: [ + transform({ parentW: 1280, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 132, y: 186 }, pos: { x: pile.x, y: 8 }, align: ALIGN_CENTER }), + sprite({ color: pile.color, type: 1, raycast: true }), + button(), + ], + })); + add(entity({ + id: guid('hud', hud.length), + path: `/ui/DefaultGroup/DeckHud/${pile.key}/Label`, + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 132, parentH: 186, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 120, y: 42 }, pos: { x: 0, y: 45 } }), + sprite({ color: TRANSPARENT }), + text({ value: pile.label, fontSize: 21, bold: true, color: GOLD }), + ], + })); + add(entity({ + id: guid('hud', hud.length), + path: `/ui/DefaultGroup/DeckHud/${pile.key}/Count`, + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 1, + components: [ + transform({ parentW: 132, parentH: 186, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 120, y: 72 }, pos: { x: 0, y: -20 } }), + sprite({ color: TRANSPARENT }), + text({ value: pile.count, fontSize: 42, bold: true }), + ], + })); + } + + add(entity({ + id: guid('hud', hud.length), + path: '/ui/DefaultGroup/DeckHud/EndTurnButton', + modelId: 'uibutton', + entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', + displayOrder: 2, + components: [ + transform({ parentW: 1280, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 200, y: 64 }, pos: { x: 560, y: 160 }, align: ALIGN_CENTER }), + sprite({ color: DARK, type: 1, raycast: true }), + button(), + text({ value: '턴 종료', fontSize: 28, bold: true, color: GOLD, alignment: 0 }), + ], + })); + + add(entity({ + id: guid('hud', hud.length), + path: '/ui/DefaultGroup/DeckHud/EnergyOrb', + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 3, + components: [ + transform({ parentW: 1280, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 96, y: 96 }, pos: { x: -560, y: 160 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.12, g: 0.2, b: 0.34, a: 0.95 }, type: 1 }), + ], + })); + add(entity({ + id: guid('hud', hud.length), + path: '/ui/DefaultGroup/DeckHud/EnergyOrb/Value', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 96, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 92, y: 48 }, pos: { x: 0, y: 6 } }), + sprite({ color: TRANSPARENT }), + text({ value: '3/3', fontSize: 34, bold: true, color: { r: 0.65, g: 0.92, b: 1, a: 1 }, alignment: 4 }), + ], + })); + add(entity({ + id: guid('hud', hud.length), + path: '/ui/DefaultGroup/DeckHud/EnergyOrb/Label', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 1, + components: [ + transform({ parentW: 96, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 92, y: 24 }, pos: { x: 0, y: -28 } }), + sprite({ color: TRANSPARENT }), + text({ value: '에너지', fontSize: 14, bold: true, color: { r: 0.55, g: 0.7, b: 0.85, a: 1 }, alignment: 4 }), + ], + })); + + return hud; +} diff --git a/tools/deck/hud/deckinspect.mjs b/tools/deck/hud/deckinspect.mjs new file mode 100644 index 0000000..d077e90 --- /dev/null +++ b/tools/deck/hud/deckinspect.mjs @@ -0,0 +1,138 @@ +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, 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'; + +export function buildDeckInspect() { + const inspect = []; + const inspectHud = entity({ + id: guid('ins', 0), + path: '/ui/DefaultGroup/DeckInspectHud', + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 15, + 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.04, g: 0.05, b: 0.07, a: 0.78 }, type: 1, raycast: true }), + ], + }); + inspectHud.jsonString.enable = false; + inspect.push(inspectHud); + inspect.push(entity({ + id: guid('ins', 1), + path: '/ui/DefaultGroup/DeckInspectHud/Panel', + 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: 1040, y: 760 }, pos: { x: 0, y: 10 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.08, g: 0.09, b: 0.11, a: 0.96 }, type: 1 }), + ], + })); + inspect.push(entity({ + id: guid('ins', 2), + path: '/ui/DefaultGroup/DeckInspectHud/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: 720, y: 54 }, pos: { x: 0, y: 350 } }), + sprite({ color: TRANSPARENT }), + text({ value: '\uB371 \uBCF4\uAE30', fontSize: 34, bold: true, color: GOLD, alignment: 4 }), + ], + })); + inspect.push(entity({ + id: guid('ins', 3), + path: '/ui/DefaultGroup/DeckInspectHud/Close', + modelId: 'uibutton', + entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,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: 78, y: 52 }, pos: { x: 466, y: 350 } }), + sprite({ color: { r: 0.16, g: 0.18, b: 0.22, a: 1 }, type: 1, raycast: true }), + button(), + text({ value: 'X', fontSize: 26, bold: true, color: GOLD, alignment: 4 }), + ], + })); + inspect.push(entity({ + id: guid('ins', 4), + path: '/ui/DefaultGroup/DeckInspectHud/Empty', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 3, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 600, y: 50 }, pos: { x: 0, y: 30 } }), + sprite({ color: TRANSPARENT }), + text({ value: '\uCE74\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4', fontSize: 28, bold: true, color: { r: 0.82, g: 0.86, b: 0.9, a: 1 }, alignment: 4 }), + ], + })); + inspect.push(entity({ + id: guid('ins', 5), + path: '/ui/DefaultGroup/DeckInspectHud/Grid', + modelId: 'uiempty', + entryId: 'UIEmpty', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ScrollLayoutGroupComponent', + displayOrder: 4, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 950, y: 610 }, pos: { x: 0, y: 0 } }), + sprite({ color: TRANSPARENT, type: 1, raycast: true }), + scrollLayoutGroup({ cellSize: { x: 158, y: 214 }, spacing: { x: 22, y: 22 }, columns: 5 }), + ], + })); + const INSPECT_CARD_COUNT = 60; + const INSPECT_CARD_W = 158; + const INSPECT_CARD_H = 214; + // 카드 단위 엔티티는 v2 네임스페이스(ins2/all2/rwd2/shp2) — 자식 구성이 바뀌면 id를 통째로 새로 발급해야 함. + // 구 id를 다른 path에 재사용하면 메이커 refresh의 id 기준 in-place 병합이 꼬여 자식이 소실됨 (P13 실측). + for (let i = 1; i <= INSPECT_CARD_COUNT; i++) { + const insBase = 6 + (i - 1) * 7; + const cardPath = `/ui/DefaultGroup/DeckInspectHud/Grid/Card${i}`; + const card = entity({ + id: guid('ins2', insBase), + path: cardPath, + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: i, + components: [ + transform({ parentW: 950, parentH: 610, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: INSPECT_CARD_W, y: INSPECT_CARD_H }, pos: { x: 0, y: 0 } }), + sprite({ dataId: CARDFRAMES.frames.warrior.normal, color: WHITE, type: 0 }), + ], + }); + card.jsonString.enable = false; + inspect.push(card); + const inspectLayout = cardFaceLayout(INSPECT_CARD_W); + for (const [tIdx, [suffix, cfg]] of inspectLayout.texts.map(([sfx, c]) => [sfx, { ...c, value: sfx === 'Cost' ? '1' : '' }]).entries()) { + const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8; + inspect.push(entity({ + id: guid('ins2', insBase + 1 + tIdx), + path: `${cardPath}/${suffix}`, + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: dOrder, + components: [ + transform({ parentW: INSPECT_CARD_W, parentH: INSPECT_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 }), + ], + })); + } + inspect.push(entity({ + id: guid('ins2', insBase + 6), + path: `${cardPath}/Art`, + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 5, + components: [ + transform({ parentW: INSPECT_CARD_W, parentH: INSPECT_CARD_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: inspectLayout.art.size, pos: inspectLayout.art.pos }), + sprite({ color: WHITE, type: 0, raycast: false }), + ], + })); + } + return inspect; +} diff --git a/tools/deck/hud/jobchoice.mjs b/tools/deck/hud/jobchoice.mjs new file mode 100644 index 0000000..de336db --- /dev/null +++ b/tools/deck/hud/jobchoice.mjs @@ -0,0 +1,51 @@ +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, 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'; + +export function buildJobChoice() { + const jobChoice = []; + const jobChoiceHud = entity({ + id: guid('job', 0), + path: '/ui/DefaultGroup/JobChoiceHud', + modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 9, + 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.06, b: 0.09, a: 0.92 }, type: 1, raycast: true }), + ], + }); + jobChoiceHud.jsonString.enable = false; + jobChoice.push(jobChoiceHud); + jobChoice.push(entity({ + id: guid('job', 1), + path: '/ui/DefaultGroup/JobChoiceHud/Title', + modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 800, y: 60 }, pos: { x: 0, y: 220 } }), + sprite({ color: TRANSPARENT }), + text({ value: '보스 처치 보상을 선택하세요', fontSize: 36, bold: true, color: GOLD, alignment: 4 }), + ], + })); + const jcButtons = [ + ['RelicButton', '유물 획득', -240, { r: 0.7, g: 0.55, b: 0.85, a: 1 }], + ['JobButton', '2차 전직', 240, { r: 0.86, g: 0.6, b: 0.3, a: 1 }], + ]; + jcButtons.forEach(([suffix, label, x, color], bi) => { + jobChoice.push(entity({ + id: guid('job', 2 + bi), + path: `/ui/DefaultGroup/JobChoiceHud/${suffix}`, + modelId: 'uibutton', entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', + displayOrder: 1 + bi, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 380, y: 140 }, pos: { x, y: 0 } }), + sprite({ color, type: 1, raycast: true }), + button(), + text({ value: label, fontSize: 32, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), + ], + })); + }); + return jobChoice; +} diff --git a/tools/deck/hud/jobselect.mjs b/tools/deck/hud/jobselect.mjs new file mode 100644 index 0000000..494d08d --- /dev/null +++ b/tools/deck/hud/jobselect.mjs @@ -0,0 +1,89 @@ +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, 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'; + +export function buildJobSelect() { + const jobSelect = []; + const jobSelectHud = entity({ + id: guid('job', 10), + path: '/ui/DefaultGroup/JobSelectHud', + modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 10, + 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.06, b: 0.09, a: 0.94 }, type: 1, raycast: true }), + ], + }); + jobSelectHud.jsonString.enable = false; + jobSelect.push(jobSelectHud); + jobSelect.push(entity({ + id: guid('job', 11), + path: '/ui/DefaultGroup/JobSelectHud/Title', + modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 800, y: 60 }, pos: { x: 0, y: 300 } }), + sprite({ color: TRANSPARENT }), + text({ value: '2차 전직 — 직업을 선택하세요', fontSize: 36, bold: true, color: GOLD, alignment: 4 }), + ], + })); + // 범용 슬롯 3개 — ShowJobSelect(Lua)가 클래스별 JOBS로 텍스트를 채움 (P10 동적화) + const jobs = [ + ['slot1', '', '', '', -440, { r: 0.82, g: 0.4, b: 0.34, a: 1 }], + ['slot2', '', '', '', 0, { r: 0.4, g: 0.55, b: 0.85, a: 1 }], + ['slot3', '', '', '', 440, { r: 0.42, g: 0.72, b: 0.46, a: 1 }], + ]; + jobs.forEach(([jobId, name, desc, starter, x, color], ji) => { + const base = `/ui/DefaultGroup/JobSelectHud/Job_${jobId}`; + jobSelect.push(entity({ + id: guid('job', 12 + ji * 4), + path: base, + modelId: 'uibutton', entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', + displayOrder: 1 + ji, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 380, y: 420 }, pos: { x, y: -20 } }), + sprite({ color, type: 1, raycast: true }), + button(), + ], + })); + jobSelect.push(entity({ + id: guid('job', 13 + ji * 4), + path: `${base}/Name`, + modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 380, parentH: 420, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 360, y: 50 }, pos: { x: 0, y: 150 } }), + sprite({ color: TRANSPARENT }), + text({ value: name, fontSize: 34, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), + ], + })); + jobSelect.push(entity({ + id: guid('job', 14 + ji * 4), + path: `${base}/Desc`, + modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 1, + components: [ + transform({ parentW: 380, parentH: 420, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 340, y: 160 }, pos: { x: 0, y: 0 } }), + sprite({ color: TRANSPARENT }), + text({ value: desc, fontSize: 22, bold: false, color: { r: 0.95, g: 0.95, b: 0.97, a: 1 }, alignment: 4 }), + ], + })); + jobSelect.push(entity({ + id: guid('job', 15 + ji * 4), + path: `${base}/Starter`, + modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 2, + components: [ + transform({ parentW: 380, parentH: 420, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 340, y: 32 }, pos: { x: 0, y: -160 } }), + sprite({ color: TRANSPARENT }), + text({ value: starter, fontSize: 18, bold: true, color: GOLD, alignment: 4 }), + ], + })); + }); + return jobSelect; +} diff --git a/tools/deck/hud/lobby.mjs b/tools/deck/hud/lobby.mjs new file mode 100644 index 0000000..65b1ba0 --- /dev/null +++ b/tools/deck/hud/lobby.mjs @@ -0,0 +1,58 @@ +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, 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'; + +export function buildLobby() { + const lobby = []; + let lobId = 0; + const lobbyRoot = entity({ + id: guid('lob', lobId++), + path: '/ui/DefaultGroup/LobbyHud', + modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 11, + 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 }), + // 로비가 물리 맵이 됨 — 투명 + 비레이캐스트로 맵을 가리지 않고 월드 NPC 클릭이 통과되게 함. + sprite({ color: TRANSPARENT, type: 1, raycast: false }), + ], + }); + lobbyRoot.jsonString.enable = false; + lobby.push(lobbyRoot); + const lobTexts = [ + ['Title', 0, 478, 760, '메이플 로비', 40, GOLD], + ['SoulLabel', 700, 478, 320, '영혼 0', 28, { r: 0.6, g: 0.85, b: 1, a: 1 }], + ['AscLabel', -560, 478, 380, '승천 0 / 해금 0', 22, { r: 0.9, g: 0.7, b: 0.5, a: 1 }], + ['Hint', 0, -478, 1500, 'NPC에게 다가가 ↑ 또는 클릭으로 대화 · ← → 이동 · Ctrl 공격', 20, { r: 0.72, g: 0.76, b: 0.82, a: 1 }], + ]; + for (const [suffix, x, y, w, value, fs, color] of lobTexts) { + lobby.push(entity({ + id: guid('lob', lobId++), + path: `/ui/DefaultGroup/LobbyHud/${suffix}`, + 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: w, y: 56 }, pos: { x, y } }), + sprite({ color: TRANSPARENT }), + text({ value, fontSize: fs, bold: true, color, alignment: 4 }), + ], + })); + } + for (const [suffix, x, label] of [['AscMinus', -780, '<'], ['AscPlus', -540, '>']]) { + lobby.push(entity({ + id: guid('lob', lobId++), + path: `/ui/DefaultGroup/LobbyHud/${suffix}`, + modelId: 'uibutton', entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,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: 46, y: 42 }, pos: { x, y: 470 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.2, g: 0.24, b: 0.3, a: 1 }, type: 1, raycast: true }), + button(), + text({ value: label, fontSize: 28, bold: true, color: WHITE, alignment: 4 }), + ], + })); + } + // NPC 4종은 로비 물리 맵의 월드 엔티티(map/lobby.map + LobbyNpc codeblock)로 이동. UI 버튼 행 제거. + return lobby; +} diff --git a/tools/deck/hud/map.mjs b/tools/deck/hud/map.mjs new file mode 100644 index 0000000..87a229a --- /dev/null +++ b/tools/deck/hud/map.mjs @@ -0,0 +1,162 @@ +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, 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'; + +export function buildMap() { + const TYPE_KO = { combat: '전투', elite: '엘리트', boss: '보스', shop: '상점', rest: '휴식' }; + const map = []; + const mapHud = entity({ + id: guid('map', 0), + path: '/ui/DefaultGroup/MapHud', + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 7, + 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 }), + // 불투명 다크 배경(게임 월드 가림 보장). 그 위 BgImage 자식이 배경 스프라이트를 얹는다. + sprite({ color: { r: 0.06, g: 0.07, b: 0.11, a: 1 }, type: 1, raycast: true }), + ], + }); + mapHud.jsonString.enable = false; + map.push(mapHud); + // 배경 이미지(displayOrder 0 = 도트/타이틀/노드 아래). nodeicons.json background는 SPRITE RUID여야 렌더됨 + // — 메이플 BackgroundComponent 리소스는 UI 스프라이트로 안 뜬다. 유효 스프라이트면 풀스크린 표시, 아니면 투명(다크 배경 노출). + map.push(entity({ + id: guid('map', 990), + path: '/ui/DefaultGroup/MapHud/BgImage', + 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({ dataId: NODEICONS.background, color: { r: 0.5, g: 0.52, b: 0.58, a: 1 }, type: 0, raycast: false }), + ], + })); + map.push(entity({ + id: guid('map', 1), + path: '/ui/DefaultGroup/MapHud/Title', + 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: 700, y: 60 }, pos: { x: 0, y: 510 } }), + sprite({ color: TRANSPARENT }), + text({ value: '다음 노드 선택', fontSize: 40, bold: true, color: GOLD, alignment: 4 }), + ], + })); + // 절차 생성 맵용 정적 그리드 — 가로 진행(왼→오른쪽): 행(row)=x축, 열(col)=y축 분기, 보스는 최우측 중앙. + const nodeX = (row) => -540 + (row - 1) * 150; + const nodeY = (col) => 180 - (col - 1) * 120; + const BOSS_POS = { x: nodeX(MAP_ROWS) + 150, y: 0 }; + let mapN = 2; + const pushMapNode = (id, pos, size, label) => { + const nodePath = `/ui/DefaultGroup/MapHud/Node_${id}`; + const nodeEnt = entity({ + id: guid('map', mapN++), + path: nodePath, + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', + displayOrder: 5, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size, pos }), + sprite({ color: { r: 1, g: 1, b: 1, a: 1 }, type: 0, raycast: true }), + button(), + ], + }); + nodeEnt.jsonString.enable = false; + map.push(nodeEnt); + }; + for (let r = 1; r <= MAP_ROWS; r++) { + for (let c = 1; c <= MAP_COLS; c++) { + pushMapNode(`r${r}c${c}`, { x: nodeX(r), y: nodeY(c) }, { x: 64, y: 64 }, ''); + } + } + pushMapNode('boss', BOSS_POS, { x: 88, y: 88 }, '보스'); + const pushDots = (dotId, from, to) => { + for (let k = 1; k <= 3; k++) { + const t = k / 4; + const dot = entity({ + id: guid('map', mapN++), + path: `/ui/DefaultGroup/MapHud/Dot_${dotId}_${k}`, + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 1, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 8, y: 8 }, pos: { x: from.x + (to.x - from.x) * t, y: from.y + (to.y - from.y) * t } }), + sprite({ color: { r: 0.5, g: 0.5, b: 0.55, a: 0.8 }, type: 1 }), + ], + }); + dot.jsonString.enable = false; + map.push(dot); + } + }; + for (let r = 1; r < MAP_ROWS; r++) { + for (let c = 1; c <= MAP_COLS; c++) { + for (let c2 = c - 1; c2 <= c + 1; c2++) { + if (c2 < 1 || c2 > MAP_COLS) continue; + pushDots(`r${r}c${c}_${c2}`, { x: nodeX(r), y: nodeY(c) }, { x: nodeX(r + 1), y: nodeY(c2) }); + } + } + } + for (let c = 1; c <= MAP_COLS; c++) { + pushDots(`r${MAP_ROWS}c${c}_b`, { x: nodeX(MAP_ROWS), y: nodeY(c) }, BOSS_POS); + } + // 노드 종류 범례 (우측 하단) — 각 타입 아이콘 + 이름 + const LEGEND = [['combat', '전투'], ['elite', '엘리트'], ['boss', '보스'], ['shop', '상점'], ['rest', '휴식'], ['treasure', '보물']]; + const lgW = 300, lgH = 312; + map.push(entity({ + id: guid('map', 991), + path: '/ui/DefaultGroup/MapHud/Legend', + modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 4, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: lgW, y: lgH }, pos: { x: 760, y: -334 } }), + sprite({ color: { r: 0.08, g: 0.09, b: 0.14, a: 0.86 }, type: 1, raycast: false }), + ], + })); + map.push(entity({ + id: guid('map', 992), + path: '/ui/DefaultGroup/MapHud/Legend/LegendTitle', + modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 1, + components: [ + transform({ parentW: lgW, parentH: lgH, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: lgW - 20, y: 32 }, pos: { x: 0, y: lgH / 2 - 26 } }), + sprite({ color: TRANSPARENT }), + text({ value: '노드 종류', fontSize: 22, bold: true, color: GOLD, alignment: 4 }), + ], + })); + let lgId = 993; + LEGEND.forEach(([t, ko], i) => { + const rowY = lgH / 2 - 78 - i * 38; + map.push(entity({ + id: guid('map', lgId++), + path: `/ui/DefaultGroup/MapHud/Legend/Icon_${t}`, + modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 2, + components: [ + transform({ parentW: lgW, parentH: lgH, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 36, y: 36 }, pos: { x: -lgW / 2 + 38, y: rowY } }), + sprite({ dataId: NODEICONS.icons[t], color: { r: 1, g: 1, b: 1, a: 1 }, type: 0, raycast: false }), + ], + })); + map.push(entity({ + id: guid('map', lgId++), + path: `/ui/DefaultGroup/MapHud/Legend/Label_${t}`, + modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 2, + components: [ + transform({ parentW: lgW, parentH: lgH, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: lgW - 110, y: 30 }, pos: { x: 32, y: rowY } }), + sprite({ color: TRANSPARENT }), + text({ value: ko, fontSize: 19, bold: false, color: { r: 0.9, g: 0.92, b: 0.96, a: 1 }, alignment: 4 }), + ], + })); + }); + return map; +} diff --git a/tools/deck/hud/rest.mjs b/tools/deck/hud/rest.mjs new file mode 100644 index 0000000..fb9d6f3 --- /dev/null +++ b/tools/deck/hud/rest.mjs @@ -0,0 +1,61 @@ +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, 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'; + +export function buildRest() { + const rest = []; + const restHud = entity({ + id: guid('rst', 0), + path: '/ui/DefaultGroup/RestHud', + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 9, + 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.08, b: 0.06, a: 0.92 }, type: 1, raycast: true }), + ], + }); + restHud.jsonString.enable = false; + rest.push(restHud); + rest.push(entity({ + id: guid('rst', 1), + path: '/ui/DefaultGroup/RestHud/Title', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 700, y: 60 }, pos: { x: 0, y: 140 } }), + sprite({ color: TRANSPARENT }), + text({ value: '휴식', fontSize: 44, bold: true, color: GOLD, alignment: 4 }), + ], + })); + rest.push(entity({ + id: guid('rst', 2), + path: '/ui/DefaultGroup/RestHud/Info', + 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: 600, y: 50 }, pos: { x: 0, y: 30 } }), + sprite({ color: TRANSPARENT }), + text({ value: 'HP 회복', fontSize: 30, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), + ], + })); + rest.push(entity({ + id: guid('rst', 3), + path: '/ui/DefaultGroup/RestHud/Leave', + modelId: 'uibutton', + entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,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: 200, y: 60 }, pos: { x: 0, y: -120 } }), + sprite({ color: DARK, type: 1, raycast: true }), + button(), + text({ value: '나가기', fontSize: 26, bold: true, color: GOLD, alignment: 4 }), + ], + })); + return rest; +} diff --git a/tools/deck/hud/reward.mjs b/tools/deck/hud/reward.mjs new file mode 100644 index 0000000..6105513 --- /dev/null +++ b/tools/deck/hud/reward.mjs @@ -0,0 +1,98 @@ +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, 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'; + +export function buildReward() { + const reward = []; + const rewardHud = entity({ + id: guid('rwd', 0), + path: '/ui/DefaultGroup/RewardHud', + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 6, + 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.04, g: 0.05, b: 0.07, a: 0.86 }, type: 1, raycast: true }), + ], + }); + rewardHud.jsonString.enable = false; + reward.push(rewardHud); + reward.push(entity({ + id: guid('rwd', 1), + path: '/ui/DefaultGroup/RewardHud/Title', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 700, y: 64 }, pos: { x: 0, y: 300 } }), + sprite({ color: TRANSPARENT }), + text({ value: '보상 카드 선택', fontSize: 44, bold: true, color: GOLD, alignment: 4 }), + ], + })); + const rewardXs = [-300, 0, 300]; + // 카드 단위 엔티티 v2 네임스페이스 — DeckInspectHud 주석 참조 + for (let i = 1; i <= 3; i++) { + const rwdBase = 2 + (i - 1) * 7; + const cardPath = `/ui/DefaultGroup/RewardHud/Reward${i}`; + reward.push(entity({ + id: guid('rwd2', rwdBase), + path: cardPath, + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent', + displayOrder: i, + 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: rewardXs[i - 1], y: 0 } }), + sprite({ dataId: CARDFRAMES.frames.warrior.normal, color: WHITE, type: 0, raycast: true }), + button(), + { '@type': 'MOD.Core.UITouchReceiveComponent', Enable: true }, + ], + })); + const rewardLayout = cardFaceLayout(CARD_W); + for (const [tIdx, [suffix, cfg]] of rewardLayout.texts.map(([sfx, c]) => [sfx, { ...c, value: sfx === 'Cost' ? '1' : sfx === 'Name' ? '카드' : '' }]).entries()) { + const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8; + reward.push(entity({ + id: guid('rwd2', rwdBase + 1 + tIdx), + path: `${cardPath}/${suffix}`, + 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 }), + ], + })); + } + reward.push(entity({ + id: guid('rwd2', rwdBase + 6), + path: `${cardPath}/Art`, + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 5, + components: [ + transform({ parentW: CARD_W, parentH: CARD_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: rewardLayout.art.size, pos: rewardLayout.art.pos }), + sprite({ color: WHITE, type: 0, raycast: false }), + ], + })); + } + let rwdN = 2 + 3 * 7; // 구 시퀀스의 루프 종료 시점 값(23) 보존 — Skip 등 후속 id 불변 + reward.push(entity({ + id: guid('rwd', rwdN++), + path: '/ui/DefaultGroup/RewardHud/Skip', + modelId: 'uibutton', + entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', + displayOrder: 10, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 200, y: 60 }, pos: { x: 0, y: -260 } }), + sprite({ color: DARK, type: 1, raycast: true }), + button(), + text({ value: '건너뛰기', fontSize: 26, bold: true, color: GOLD, alignment: 4 }), + ], + })); + return reward; +} diff --git a/tools/deck/hud/shop.mjs b/tools/deck/hud/shop.mjs new file mode 100644 index 0000000..d0166d8 --- /dev/null +++ b/tools/deck/hud/shop.mjs @@ -0,0 +1,203 @@ +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, 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'; + +export function buildShop() { + const shop = []; + const shopHud = entity({ + id: guid('shp', 0), + path: '/ui/DefaultGroup/ShopHud', + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 8, + 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.06, b: 0.09, a: 0.92 }, type: 1, raycast: true }), + ], + }); + shopHud.jsonString.enable = false; + shop.push(shopHud); + shop.push(entity({ + id: guid('shp', 1), + path: '/ui/DefaultGroup/ShopHud/Title', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 700, y: 60 }, pos: { x: 0, y: 400 } }), + sprite({ color: TRANSPARENT }), + text({ value: '상점', fontSize: 44, bold: true, color: GOLD, alignment: 4 }), + ], + })); + shop.push(entity({ + id: guid('shp', 2), + path: '/ui/DefaultGroup/ShopHud/Gold', + 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: 300, y: 44 }, pos: { x: 0, y: 330 } }), + sprite({ color: TRANSPARENT }), + text({ value: '메소 0', fontSize: 28, bold: true, color: { r: 0.98, g: 0.85, b: 0.4, a: 1 }, alignment: 4 }), + ], + })); + shop.push(entity({ + id: guid('shp', 3), + path: '/ui/DefaultGroup/ShopHud/MesoIcon', + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 1, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 30, y: 30 }, pos: { x: -86, y: 330 } }), + sprite({ color: { r: 1, g: 0.82, b: 0.2, a: 1 }, type: 1 }), + ], + })); + const shopXs = [-300, 0, 300]; + // 카드 단위 엔티티 v2 네임스페이스 (stride 8: Price 포함) — DeckInspectHud 주석 참조 + for (let i = 1; i <= 3; i++) { + const shpBase = 3 + (i - 1) * 8; + const cardPath = `/ui/DefaultGroup/ShopHud/Card${i}`; + shop.push(entity({ + id: guid('shp2', shpBase), + path: cardPath, + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent', + displayOrder: i, + 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: shopXs[i - 1], y: 20 } }), + sprite({ dataId: CARDFRAMES.frames.warrior.normal, color: WHITE, type: 0, raycast: true }), + button(), + { '@type': 'MOD.Core.UITouchReceiveComponent', Enable: true }, + ], + })); + const shopLayout = cardFaceLayout(CARD_W); + const shopTexts = shopLayout.texts.map(([sfx, c]) => [sfx, { ...c, value: sfx === 'Cost' ? '1' : sfx === 'Name' ? '카드' : '' }]); + shopTexts.push(['Price', { size: { x: 160, y: 40 }, pos: { x: 0, y: -135 }, value: '30 메소', fontSize: 22, bold: true, color: { r: 0.98, g: 0.85, b: 0.4, a: 1 } }]); + for (const [tIdx, [suffix, cfg]] of shopTexts.entries()) { + const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : suffix === 'Desc' ? 8 : 9; + shop.push(entity({ + id: guid('shp2', shpBase + 1 + tIdx), + path: `${cardPath}/${suffix}`, + 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 }), + ], + })); + } + shop.push(entity({ + id: guid('shp2', shpBase + 7), + path: `${cardPath}/Art`, + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 5, + components: [ + transform({ parentW: CARD_W, parentH: CARD_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: shopLayout.art.size, pos: shopLayout.art.pos }), + sprite({ color: WHITE, type: 0, raycast: false }), + ], + })); + } + let shpN = 3 + 3 * 8; // 구 시퀀스의 루프 종료 시점 값(27) 보존 — Relic 등 후속 id 불변 + shop.push(entity({ + id: guid('shp', shpN++), + path: '/ui/DefaultGroup/ShopHud/Relic', + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', + displayOrder: 9, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 560, y: 76 }, pos: { x: 0, y: -190 } }), + sprite({ color: { r: 0.7, g: 0.55, b: 0.85, a: 1 }, type: 1, raycast: true }), + button(), + ], + })); + shop.push(entity({ + id: guid('shp', shpN++), + path: '/ui/DefaultGroup/ShopHud/Relic/Label', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 560, parentH: 76, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 540, y: 40 }, pos: { x: 0, y: 12 } }), + sprite({ color: TRANSPARENT }), + text({ value: '유물', fontSize: 22, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), + ], + })); + shop.push(entity({ + id: guid('shp', shpN++), + path: '/ui/DefaultGroup/ShopHud/Relic/Price', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 1, + components: [ + transform({ parentW: 560, parentH: 76, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 540, y: 30 }, pos: { x: 0, y: -22 } }), + sprite({ color: TRANSPARENT }), + text({ value: '60 메소', fontSize: 20, bold: true, color: { r: 0.98, g: 0.85, b: 0.4, a: 1 }, alignment: 4 }), + ], + })); + shop.push(entity({ + id: guid('shp', shpN++), + path: '/ui/DefaultGroup/ShopHud/Potion', + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', + displayOrder: 11, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 560, y: 76 }, pos: { x: 0, y: -278 } }), + sprite({ color: { r: 0.45, g: 0.7, b: 0.55, a: 1 }, type: 1, raycast: true }), + button(), + ], + })); + shop.push(entity({ + id: guid('shp', shpN++), + path: '/ui/DefaultGroup/ShopHud/Potion/Label', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 560, parentH: 76, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 540, y: 40 }, pos: { x: 0, y: 12 } }), + sprite({ color: TRANSPARENT }), + text({ value: '물약', fontSize: 22, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), + ], + })); + shop.push(entity({ + id: guid('shp', shpN++), + path: '/ui/DefaultGroup/ShopHud/Potion/Price', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 1, + components: [ + transform({ parentW: 560, parentH: 76, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 540, y: 30 }, pos: { x: 0, y: -22 } }), + sprite({ color: TRANSPARENT }), + text({ value: '20 메소', fontSize: 20, bold: true, color: { r: 0.98, g: 0.85, b: 0.4, a: 1 }, alignment: 4 }), + ], + })); + shop.push(entity({ + id: guid('shp', shpN++), + path: '/ui/DefaultGroup/ShopHud/Leave', + modelId: 'uibutton', + entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', + displayOrder: 10, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 200, y: 60 }, pos: { x: 0, y: -380 } }), + sprite({ color: DARK, type: 1, raycast: true }), + button(), + text({ value: '나가기', fontSize: 26, bold: true, color: GOLD, alignment: 4 }), + ], + })); + return shop; +} diff --git a/tools/deck/hud/soulshop.mjs b/tools/deck/hud/soulshop.mjs new file mode 100644 index 0000000..2e45c59 --- /dev/null +++ b/tools/deck/hud/soulshop.mjs @@ -0,0 +1,90 @@ +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, 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'; + +export function buildSoulShop() { + const soulShop = []; + let soulId = 0; + const soulRoot = entity({ + id: guid('soul', soulId++), + path: '/ui/DefaultGroup/SoulShopHud', + modelId: 'uisprite', entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 15, + 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.06, b: 0.09, a: 0.95 }, type: 1, raycast: true }), + ], + }); + soulRoot.jsonString.enable = false; + soulShop.push(soulRoot); + soulShop.push(entity({ + id: guid('soul', soulId++), + path: '/ui/DefaultGroup/SoulShopHud/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: 700, y: 60 }, pos: { x: 0, y: 410 } }), + sprite({ color: TRANSPARENT }), + text({ value: '영혼 상점', fontSize: 44, bold: true, color: { r: 0.6, g: 0.85, b: 1, a: 1 }, alignment: 4 }), + ], + })); + soulShop.push(entity({ + id: guid('soul', soulId++), + path: '/ui/DefaultGroup/SoulShopHud/Souls', + 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: 400, y: 44 }, pos: { x: 0, y: 345 } }), + sprite({ color: TRANSPARENT }), + text({ value: '영혼 0', fontSize: 28, bold: true, color: { r: 0.6, g: 0.85, b: 1, a: 1 }, alignment: 4 }), + ], + })); + soulShop.push(entity({ + id: guid('soul', soulId++), + path: '/ui/DefaultGroup/SoulShopHud/Close', + modelId: 'uibutton', entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,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: 200, y: 60 }, pos: { x: 0, y: -400 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.2, g: 0.24, b: 0.3, a: 1 }, type: 1, raycast: true }), + button(), + text({ value: '닫기', fontSize: 28, bold: true, color: WHITE, alignment: 4 }), + ], + })); + for (let i = 1; i <= 4; i++) { + const ip = `/ui/DefaultGroup/SoulShopHud/Item${i}`; + const iy = 230 - (i - 1) * 125; + soulShop.push(entity({ + id: guid('soul', soulId++), + path: ip, modelId: 'uibutton', entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', + displayOrder: 2, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 760, y: 104 }, pos: { x: 0, y: iy }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.14, g: 0.16, b: 0.22, a: 1 }, type: 1, raycast: true }), + button(), + ], + })); + for (const [suffix, x, y, w, fs, color] of [ + ['Name', -180, 22, 360, 28, GOLD], + ['Desc', -180, -24, 440, 20, { r: 0.86, g: 0.9, b: 0.94, a: 1 }], + ['Status', 270, 0, 220, 22, { r: 0.6, g: 0.85, b: 1, a: 1 }], + ]) { + soulShop.push(entity({ + id: guid('soul', soulId++), + path: `${ip}/${suffix}`, modelId: 'uitext', entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 1, + components: [ + transform({ parentW: 760, parentH: 104, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: w, y: 38 }, pos: { x, y } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: fs, bold: suffix === 'Name', color, alignment: 4 }), + ], + })); + } + } + return soulShop; +} diff --git a/tools/deck/hud/treasure.mjs b/tools/deck/hud/treasure.mjs new file mode 100644 index 0000000..8fecfaa --- /dev/null +++ b/tools/deck/hud/treasure.mjs @@ -0,0 +1,89 @@ +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, 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'; + +export function buildTreasure() { + const treasure = []; + const treasureHud = entity({ + id: guid('trs', 0), + path: '/ui/DefaultGroup/TreasureHud', + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 8, + 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.06, b: 0.09, a: 0.92 }, type: 1, raycast: true }), + ], + }); + treasureHud.jsonString.enable = false; + treasure.push(treasureHud); + treasure.push(entity({ + id: guid('trs', 1), + path: '/ui/DefaultGroup/TreasureHud/Title', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 700, y: 60 }, pos: { x: 0, y: 320 } }), + sprite({ color: TRANSPARENT }), + text({ value: '보물 상자', fontSize: 40, bold: true, color: GOLD, alignment: 4 }), + ], + })); + treasure.push(entity({ + id: guid('trs', 2), + path: '/ui/DefaultGroup/TreasureHud/Chest', + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', + displayOrder: 1, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 180, y: 180 }, pos: { x: 0, y: 40 } }), + sprite({ dataId: CHEST_CLOSED_RUID, color: { r: 1, g: 1, b: 1, a: 1 }, type: 0, raycast: true }), + button(), + ], + })); + treasure.push(entity({ + id: guid('trs', 3), + path: '/ui/DefaultGroup/TreasureHud/Hint', + 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: 500, y: 34 }, pos: { x: 0, y: -90 } }), + sprite({ color: TRANSPARENT }), + text({ value: '상자를 클릭해 여세요', fontSize: 20, bold: false, color: { r: 0.85, g: 0.85, b: 0.9, a: 1 }, alignment: 4 }), + ], + })); + const treasureReward = entity({ + id: guid('trs', 4), + path: '/ui/DefaultGroup/TreasureHud/Reward', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 3, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 800, y: 44 }, pos: { x: 0, y: -160 } }), + sprite({ color: TRANSPARENT }), + text({ value: '', fontSize: 28, bold: true, color: { r: 0.98, g: 0.85, b: 0.4, a: 1 }, alignment: 4 }), + ], + }); + treasureReward.jsonString.enable = false; + treasure.push(treasureReward); + treasure.push(entity({ + id: guid('trs', 5), + path: '/ui/DefaultGroup/TreasureHud/Leave', + modelId: 'uibutton', + entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', + displayOrder: 4, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 200, y: 60 }, pos: { x: 0, y: -280 } }), + sprite({ color: DARK, type: 1, raycast: true }), + button(), + text({ value: '나가기', fontSize: 26, bold: true, color: GOLD, alignment: 4 }), + ], + })); + return treasure; +}