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; }