diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index 79b1f1f..a976782 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -15,6 +15,8 @@ import { buildJobSelect } from './hud/jobselect.mjs'; import { buildLobby } from './hud/lobby.mjs'; import { buildBoard } from './hud/board.mjs'; import { buildSoulShop } from './hud/soulshop.mjs'; +import { buildMainMenu } from './hud/mainmenu.mjs'; +import { buildCharSelect } from './hud/charselect.mjs'; 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() { @@ -205,308 +207,8 @@ function upsertUi() { emit('JobSelectHud', buildJobSelect()); - const menu = []; - menu.push(entity({ - id: guid('menu', 0), - path: '/ui/DefaultGroup/MainMenu', - 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: 1920, y: 1080 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.06, g: 0.09, b: 0.13, a: 1 }, type: 1, raycast: true }), - ], - })); - menu.push(entity({ - id: guid('menu', 50), - path: '/ui/DefaultGroup/MainMenu/OpaqueBackdrop', - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 0, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1920, y: 1080 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }), - sprite({ color: TRANSPARENT, type: 1, raycast: false }), - ], - })); - menu.push(entity({ - id: guid('menu', 1), - path: '/ui/DefaultGroup/MainMenu/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: 720, y: 100 }, pos: { x: 0, y: 180 }, align: ALIGN_CENTER }), - sprite({ color: TRANSPARENT }), - text({ value: '슬레이 메이플', fontSize: 64, bold: true, color: GOLD, alignment: 0 }), - ], - })); - menu.push(entity({ - id: guid('menu', 2), - path: '/ui/DefaultGroup/MainMenu/Subtitle', - 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: 48 }, pos: { x: 0, y: 104 }, align: ALIGN_CENTER }), - sprite({ color: TRANSPARENT }), - text({ value: '카드를 뽑고, 덱을 만들고, 첨탑을 오른다', fontSize: 24, color: { r: 0.82, g: 0.86, b: 0.9, a: 1 }, alignment: 0 }), - ], - })); - menu.push(entity({ - id: guid('menu', 3), - path: '/ui/DefaultGroup/MainMenu/NewGameButton', - 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: 260, y: 68 }, pos: { x: 0, y: -20 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.13, g: 0.15, b: 0.18, a: 1 }, type: 1, raycast: true }), - button(), - text({ value: '새 게임', fontSize: 30, bold: true, color: GOLD, alignment: 0 }), - ], - })); - // 승천 선택 (P11): [-] 라벨 [+] - menu.push(entity({ - id: guid('menu', 195), - path: '/ui/DefaultGroup/MainMenu/AscMinus', - modelId: 'uibutton', - entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', - displayOrder: 5, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 52, y: 52 }, pos: { x: -170, y: -185 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.13, g: 0.15, b: 0.18, a: 1 }, type: 1, raycast: true }), - button(), - text({ value: '-', fontSize: 30, bold: true, color: GOLD, alignment: 4 }), - ], - })); - menu.push(entity({ - id: guid('menu', 196), - path: '/ui/DefaultGroup/MainMenu/AscLabel', - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 6, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 250, y: 40 }, pos: { x: 0, y: -185 }, align: ALIGN_CENTER }), - sprite({ color: TRANSPARENT }), - text({ value: '승천 0 / 해금 0', fontSize: 22, bold: true, color: { r: 0.85, g: 0.7, b: 0.95, a: 1 }, alignment: 4 }), - ], - })); - menu.push(entity({ - id: guid('menu', 197), - path: '/ui/DefaultGroup/MainMenu/AscPlus', - modelId: 'uibutton', - entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', - displayOrder: 7, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 52, y: 52 }, pos: { x: 170, y: -185 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.13, g: 0.15, b: 0.18, a: 1 }, type: 1, raycast: true }), - button(), - text({ value: '+', fontSize: 30, bold: true, color: GOLD, alignment: 4 }), - ], - })); - menu.push(entity({ - id: guid('menu', 4), - path: '/ui/DefaultGroup/MainMenu/ContinueButton', - modelId: 'uibutton', - entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,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: 260, y: 58 }, pos: { x: 0, y: -100 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.1, g: 0.11, b: 0.13, a: 0.78 }, type: 1, raycast: false }), - button({ enabled: false }), - text({ value: '이어하기', fontSize: 24, bold: true, color: { r: 0.55, g: 0.58, b: 0.62, a: 1 }, alignment: 0 }), - ], - })); - const select = []; - select.push(entity({ - id: guid('menu', 100), - path: '/ui/DefaultGroup/CharacterSelectHud', - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 21, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1920, y: 1080 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.05, g: 0.07, b: 0.11, a: 1 }, type: 1, raycast: true }), - ], - })); - select.push(entity({ - id: guid('menu', 190), - path: '/ui/DefaultGroup/CharacterSelectHud/OpaqueBackdrop', - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 0, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1920, y: 1080 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }), - sprite({ color: TRANSPARENT, type: 1, raycast: false }), - ], - })); - select.push(entity({ - id: guid('menu', 101), - path: '/ui/DefaultGroup/CharacterSelectHud/Title', - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 1, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 760, y: 72 }, pos: { x: 0, y: 355 }, align: ALIGN_CENTER }), - sprite({ color: TRANSPARENT }), - text({ value: '\uCE90\uB9AD\uD130 \uC120\uD0DD', fontSize: 42, bold: true, color: GOLD, alignment: 0 }), - ], - })); - select.push(entity({ - id: guid('menu', 102), - path: '/ui/DefaultGroup/CharacterSelectHud/Status', - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 2, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 680, y: 44 }, pos: { x: 0, y: 298 }, align: ALIGN_CENTER }), - sprite({ color: TRANSPARENT }), - text({ value: '\uC804\uC0AC\uB97C \uC120\uD0DD\uD558\uACE0 \uC2DC\uC791\uD558\uC138\uC694', fontSize: 22, color: { r: 0.86, g: 0.9, b: 0.94, a: 1 }, alignment: 0 }), - ], - })); - const classCards = [ - { key: 'Warrior', classId: 'warrior', label: '\uC804\uC0AC', desc: '\uAC15\uD55C \uACF5\uACA9\uACFC \uBC29\uC5B4', x: -360, enabled: true, tint: { r: 0.74, g: 0.32, b: 0.28, a: 1 } }, - { key: 'Thief', classId: 'bandit', label: '\uB3C4\uC801', desc: '\uB3C5\u00B7\uB2E8\uAC80\u00B7\uB4DC\uB85C\uC6B0', x: 0, enabled: true, tint: { r: 0.26, g: 0.5, b: 0.34, a: 1 } }, - { key: 'Mage', classId: 'magician', label: '\uB9C8\uBC95\uC0AC', desc: '\uB9C8\uBC95 \uC6D0\uAC70\uB9AC \uB51C\uB7EC', x: 360, enabled: true, tint: { r: 0.3, g: 0.4, b: 0.75, a: 1 } }, - ]; - for (let i = 0; i < classCards.length; i++) { - const cls = classCards[i]; - const base = `/ui/DefaultGroup/CharacterSelectHud/${cls.key}Button`; - select.push(entity({ - id: guid('menu', 110 + i), - path: base, - modelId: 'uibutton', - entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', - displayOrder: 10 + i, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 270, y: 330 }, pos: { x: cls.x, y: 40 }, align: ALIGN_CENTER }), - sprite({ color: cls.enabled ? { r: 0.16, g: 0.2, b: 0.26, a: 1 } : { r: 0.11, g: 0.12, b: 0.14, a: 1 }, type: 1, raycast: cls.enabled }), - button({ enabled: cls.enabled }), - ], - })); - select.push(entity({ - id: guid('menu', 200 + i), - path: `${base}/Art`, - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 0, - components: [ - transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 258, y: 318 }, pos: { x: 0, y: 0 } }), - sprite({ dataId: CHARS.portraits[cls.classId], color: { r: 1, g: 1, b: 1, a: 1 }, type: 0, raycast: false }), - ], - })); - select.push(entity({ - id: guid('menu', 210 + i), - path: `${base}/NameBanner`, - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 1, - components: [ - transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 258, y: 60 }, pos: { x: 0, y: -137 } }), - sprite({ color: { r: 0, g: 0, b: 0, a: 0.55 }, type: 1, raycast: false }), - ], - })); - select.push(entity({ - id: guid('menu', 120 + i), - path: `${base}/Name`, - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', - displayOrder: 2, - components: [ - transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 230, y: 54 }, pos: { x: 0, y: -137 } }), - sprite({ color: TRANSPARENT }), - text({ value: cls.label, fontSize: 34, bold: true, color: cls.enabled ? GOLD : { r: 0.55, g: 0.58, b: 0.62, a: 1 }, alignment: 4 }), - ], - })); - if (!cls.enabled) { - select.push(entity({ - id: guid('menu', 150 + i), - path: `${base}/LockBody`, - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 3, - components: [ - transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 76, y: 58 }, pos: { x: 0, y: 4 } }), - sprite({ color: { r: 0.78, g: 0.69, b: 0.42, a: 1 }, type: 1 }), - ], - })); - select.push(entity({ - id: guid('menu', 160 + i), - path: `${base}/LockShackle`, - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: 4, - components: [ - transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 54, y: 42 }, pos: { x: 0, y: 48 } }), - sprite({ color: { r: 0.78, g: 0.69, b: 0.42, a: 1 }, type: 1 }), - ], - })); - } - select.push(entity({ - id: guid('menu', 170 + i), - path: `/ui/DefaultGroup/CharacterSelectHud/${cls.key}DeckButton`, - modelId: 'uibutton', - entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', - displayOrder: 18 + i, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 160, y: 46 }, pos: { x: cls.x, y: -160 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.11, g: 0.13, b: 0.16, a: 1 }, type: 1, raycast: true }), - button({ enabled: cls.enabled }), - text({ value: '\uB371 \uBCF4\uAE30', fontSize: 20, bold: true, color: GOLD, alignment: 0 }), - ], - })); - } - select.push(entity({ - id: guid('menu', 180), - path: '/ui/DefaultGroup/CharacterSelectHud/StartButton', - modelId: 'uibutton', - entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', - displayOrder: 20, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 220, y: 68 }, pos: { x: 720, y: -360 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.15, g: 0.2, b: 0.26, a: 1 }, type: 1, raycast: true }), - button(), - text({ value: '\uC2DC\uC791', fontSize: 30, bold: true, color: GOLD, alignment: 0 }), - ], - })); - select.push(entity({ - id: guid('menu', 230), - path: '/ui/DefaultGroup/CharacterSelectHud/BackButton', - modelId: 'uibutton', - entryId: 'UIButton', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', - displayOrder: 22, - components: [ - transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 180, y: 56 }, pos: { x: -800, y: 430 }, align: ALIGN_CENTER }), - sprite({ color: { r: 0.15, g: 0.2, b: 0.26, a: 1 }, type: 1, raycast: true }), - button(), - text({ value: '\u2190 \uB4A4\uB85C', fontSize: 26, bold: true, color: GOLD, alignment: 0 }), - ], - })); - select[0].jsonString.enable = false; - emit('MainMenu', menu); - emit('CharacterSelectHud', select); + emit('MainMenu', buildMainMenu()); + emit('CharacterSelectHud', buildCharSelect()); // ── LobbyHud — 반복 런의 허브. NPC 클릭으로 런시작/도감/영혼상점/게시판 ── emit('LobbyHud', buildLobby()); diff --git a/tools/deck/hud/charselect.mjs b/tools/deck/hud/charselect.mjs new file mode 100644 index 0000000..5601d22 --- /dev/null +++ b/tools/deck/hud/charselect.mjs @@ -0,0 +1,185 @@ +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 buildCharSelect() { + const select = []; + select.push(entity({ + id: guid('menu', 100), + path: '/ui/DefaultGroup/CharacterSelectHud', + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 21, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1920, y: 1080 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.05, g: 0.07, b: 0.11, a: 1 }, type: 1, raycast: true }), + ], + })); + select.push(entity({ + id: guid('menu', 190), + path: '/ui/DefaultGroup/CharacterSelectHud/OpaqueBackdrop', + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 0, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1920, y: 1080 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }), + sprite({ color: TRANSPARENT, type: 1, raycast: false }), + ], + })); + select.push(entity({ + id: guid('menu', 101), + path: '/ui/DefaultGroup/CharacterSelectHud/Title', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 1, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 760, y: 72 }, pos: { x: 0, y: 355 }, align: ALIGN_CENTER }), + sprite({ color: TRANSPARENT }), + text({ value: '\uCE90\uB9AD\uD130 \uC120\uD0DD', fontSize: 42, bold: true, color: GOLD, alignment: 0 }), + ], + })); + select.push(entity({ + id: guid('menu', 102), + path: '/ui/DefaultGroup/CharacterSelectHud/Status', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 2, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 680, y: 44 }, pos: { x: 0, y: 298 }, align: ALIGN_CENTER }), + sprite({ color: TRANSPARENT }), + text({ value: '\uC804\uC0AC\uB97C \uC120\uD0DD\uD558\uACE0 \uC2DC\uC791\uD558\uC138\uC694', fontSize: 22, color: { r: 0.86, g: 0.9, b: 0.94, a: 1 }, alignment: 0 }), + ], + })); + const classCards = [ + { key: 'Warrior', classId: 'warrior', label: '\uC804\uC0AC', desc: '\uAC15\uD55C \uACF5\uACA9\uACFC \uBC29\uC5B4', x: -360, enabled: true, tint: { r: 0.74, g: 0.32, b: 0.28, a: 1 } }, + { key: 'Thief', classId: 'bandit', label: '\uB3C4\uC801', desc: '\uB3C5\u00B7\uB2E8\uAC80\u00B7\uB4DC\uB85C\uC6B0', x: 0, enabled: true, tint: { r: 0.26, g: 0.5, b: 0.34, a: 1 } }, + { key: 'Mage', classId: 'magician', label: '\uB9C8\uBC95\uC0AC', desc: '\uB9C8\uBC95 \uC6D0\uAC70\uB9AC \uB51C\uB7EC', x: 360, enabled: true, tint: { r: 0.3, g: 0.4, b: 0.75, a: 1 } }, + ]; + for (let i = 0; i < classCards.length; i++) { + const cls = classCards[i]; + const base = `/ui/DefaultGroup/CharacterSelectHud/${cls.key}Button`; + select.push(entity({ + id: guid('menu', 110 + i), + path: base, + modelId: 'uibutton', + entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', + displayOrder: 10 + i, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 270, y: 330 }, pos: { x: cls.x, y: 40 }, align: ALIGN_CENTER }), + sprite({ color: cls.enabled ? { r: 0.16, g: 0.2, b: 0.26, a: 1 } : { r: 0.11, g: 0.12, b: 0.14, a: 1 }, type: 1, raycast: cls.enabled }), + button({ enabled: cls.enabled }), + ], + })); + select.push(entity({ + id: guid('menu', 200 + i), + path: `${base}/Art`, + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 0, + components: [ + transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 258, y: 318 }, pos: { x: 0, y: 0 } }), + sprite({ dataId: CHARS.portraits[cls.classId], color: { r: 1, g: 1, b: 1, a: 1 }, type: 0, raycast: false }), + ], + })); + select.push(entity({ + id: guid('menu', 210 + i), + path: `${base}/NameBanner`, + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 1, + components: [ + transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 258, y: 60 }, pos: { x: 0, y: -137 } }), + sprite({ color: { r: 0, g: 0, b: 0, a: 0.55 }, type: 1, raycast: false }), + ], + })); + select.push(entity({ + id: guid('menu', 120 + i), + path: `${base}/Name`, + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 2, + components: [ + transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 230, y: 54 }, pos: { x: 0, y: -137 } }), + sprite({ color: TRANSPARENT }), + text({ value: cls.label, fontSize: 34, bold: true, color: cls.enabled ? GOLD : { r: 0.55, g: 0.58, b: 0.62, a: 1 }, alignment: 4 }), + ], + })); + if (!cls.enabled) { + select.push(entity({ + id: guid('menu', 150 + i), + path: `${base}/LockBody`, + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 3, + components: [ + transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 76, y: 58 }, pos: { x: 0, y: 4 } }), + sprite({ color: { r: 0.78, g: 0.69, b: 0.42, a: 1 }, type: 1 }), + ], + })); + select.push(entity({ + id: guid('menu', 160 + i), + path: `${base}/LockShackle`, + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 4, + components: [ + transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 54, y: 42 }, pos: { x: 0, y: 48 } }), + sprite({ color: { r: 0.78, g: 0.69, b: 0.42, a: 1 }, type: 1 }), + ], + })); + } + select.push(entity({ + id: guid('menu', 170 + i), + path: `/ui/DefaultGroup/CharacterSelectHud/${cls.key}DeckButton`, + modelId: 'uibutton', + entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', + displayOrder: 18 + i, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 160, y: 46 }, pos: { x: cls.x, y: -160 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.11, g: 0.13, b: 0.16, a: 1 }, type: 1, raycast: true }), + button({ enabled: cls.enabled }), + text({ value: '\uB371 \uBCF4\uAE30', fontSize: 20, bold: true, color: GOLD, alignment: 0 }), + ], + })); + } + select.push(entity({ + id: guid('menu', 180), + path: '/ui/DefaultGroup/CharacterSelectHud/StartButton', + modelId: 'uibutton', + entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', + displayOrder: 20, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 220, y: 68 }, pos: { x: 720, y: -360 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.15, g: 0.2, b: 0.26, a: 1 }, type: 1, raycast: true }), + button(), + text({ value: '\uC2DC\uC791', fontSize: 30, bold: true, color: GOLD, alignment: 0 }), + ], + })); + select.push(entity({ + id: guid('menu', 230), + path: '/ui/DefaultGroup/CharacterSelectHud/BackButton', + modelId: 'uibutton', + entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', + displayOrder: 22, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 180, y: 56 }, pos: { x: -800, y: 430 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.15, g: 0.2, b: 0.26, a: 1 }, type: 1, raycast: true }), + button(), + text({ value: '\u2190 \uB4A4\uB85C', fontSize: 26, bold: true, color: GOLD, alignment: 0 }), + ], + })); + select[0].jsonString.enable = false; + return select; +} diff --git a/tools/deck/hud/mainmenu.mjs b/tools/deck/hud/mainmenu.mjs new file mode 100644 index 0000000..df87f0d --- /dev/null +++ b/tools/deck/hud/mainmenu.mjs @@ -0,0 +1,127 @@ +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 buildMainMenu() { + const menu = []; + menu.push(entity({ + id: guid('menu', 0), + path: '/ui/DefaultGroup/MainMenu', + 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: 1920, y: 1080 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.06, g: 0.09, b: 0.13, a: 1 }, type: 1, raycast: true }), + ], + })); + menu.push(entity({ + id: guid('menu', 50), + path: '/ui/DefaultGroup/MainMenu/OpaqueBackdrop', + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 0, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1920, y: 1080 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }), + sprite({ color: TRANSPARENT, type: 1, raycast: false }), + ], + })); + menu.push(entity({ + id: guid('menu', 1), + path: '/ui/DefaultGroup/MainMenu/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: 720, y: 100 }, pos: { x: 0, y: 180 }, align: ALIGN_CENTER }), + sprite({ color: TRANSPARENT }), + text({ value: '슬레이 메이플', fontSize: 64, bold: true, color: GOLD, alignment: 0 }), + ], + })); + menu.push(entity({ + id: guid('menu', 2), + path: '/ui/DefaultGroup/MainMenu/Subtitle', + 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: 48 }, pos: { x: 0, y: 104 }, align: ALIGN_CENTER }), + sprite({ color: TRANSPARENT }), + text({ value: '카드를 뽑고, 덱을 만들고, 첨탑을 오른다', fontSize: 24, color: { r: 0.82, g: 0.86, b: 0.9, a: 1 }, alignment: 0 }), + ], + })); + menu.push(entity({ + id: guid('menu', 3), + path: '/ui/DefaultGroup/MainMenu/NewGameButton', + 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: 260, y: 68 }, pos: { x: 0, y: -20 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.13, g: 0.15, b: 0.18, a: 1 }, type: 1, raycast: true }), + button(), + text({ value: '새 게임', fontSize: 30, bold: true, color: GOLD, alignment: 0 }), + ], + })); + // 승천 선택 (P11): [-] 라벨 [+] + menu.push(entity({ + id: guid('menu', 195), + path: '/ui/DefaultGroup/MainMenu/AscMinus', + modelId: 'uibutton', + entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', + displayOrder: 5, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 52, y: 52 }, pos: { x: -170, y: -185 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.13, g: 0.15, b: 0.18, a: 1 }, type: 1, raycast: true }), + button(), + text({ value: '-', fontSize: 30, bold: true, color: GOLD, alignment: 4 }), + ], + })); + menu.push(entity({ + id: guid('menu', 196), + path: '/ui/DefaultGroup/MainMenu/AscLabel', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 6, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 250, y: 40 }, pos: { x: 0, y: -185 }, align: ALIGN_CENTER }), + sprite({ color: TRANSPARENT }), + text({ value: '승천 0 / 해금 0', fontSize: 22, bold: true, color: { r: 0.85, g: 0.7, b: 0.95, a: 1 }, alignment: 4 }), + ], + })); + menu.push(entity({ + id: guid('menu', 197), + path: '/ui/DefaultGroup/MainMenu/AscPlus', + modelId: 'uibutton', + entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', + displayOrder: 7, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 52, y: 52 }, pos: { x: 170, y: -185 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.13, g: 0.15, b: 0.18, a: 1 }, type: 1, raycast: true }), + button(), + text({ value: '+', fontSize: 30, bold: true, color: GOLD, alignment: 4 }), + ], + })); + menu.push(entity({ + id: guid('menu', 4), + path: '/ui/DefaultGroup/MainMenu/ContinueButton', + modelId: 'uibutton', + entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,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: 260, y: 58 }, pos: { x: 0, y: -100 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.1, g: 0.11, b: 0.13, a: 0.78 }, type: 1, raycast: false }), + button({ enabled: false }), + text({ value: '이어하기', fontSize: 24, bold: true, color: { r: 0.55, g: 0.58, b: 0.62, a: 1 }, alignment: 0 }), + ], + })); + return menu; +}