diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index a943f0a..cb3754a 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -36,6 +36,29 @@ if (!ENEMIES.enemies[ENEMIES.activeEnemy]) { throw new Error(`[gen-slaydeck] activeEnemy가 enemies에 없음: ${ENEMIES.activeEnemy}`); } +// 카드 프레임 (사용자 제작 이미지 — 로컬 임포트 .sprite RUID, 직업 3종 × 등급 3종) +const CARDFRAMES = JSON.parse(readFileSync('data/cardframes.json', 'utf8')); +const RARITIES = ['normal', 'unique', 'legend']; +for (const [fid, fr] of Object.entries(CARDFRAMES.frames)) { + for (const r of RARITIES) { + if (!fr[r]) throw new Error(`[gen-slaydeck] cardframes.frames.${fid}.${r} RUID 없음`); + } +} +for (const [id, c] of Object.entries(CARDS.cards)) { + if (!RARITIES.includes(c.rarity)) throw new Error(`[gen-slaydeck] 카드 ${id} rarity 누락/오류: ${c.rarity}`); + const fc = CARDFRAMES.classToFrame[c.class]; + if (!fc || !CARDFRAMES.frames[fc]) throw new Error(`[gen-slaydeck] 카드 ${id} class ${c.class} → 프레임 매핑 없음`); +} +function frameRuid(card) { + return CARDFRAMES.frames[CARDFRAMES.classToFrame[card.class]][card.rarity]; +} +function luaFramesTable() { + const frames = Object.entries(CARDFRAMES.frames).map(([fid, fr]) => + `\t${fid} = { normal = ${luaStr(fr.normal)}, unique = ${luaStr(fr.unique)}, legend = ${luaStr(fr.legend)} },`).join('\n'); + const cls = Object.entries(CARDFRAMES.classToFrame).map(([c, f]) => `\t${c} = ${luaStr(f)},`).join('\n'); + return `self.CardFrames = {\n${frames}\n}\nself.ClassToFrame = {\n${cls}\n}`; +} + // 맵은 런타임 절차 생성(GenerateMap Lua ↔ tools/map/rogue-map.mjs 미러). 정적 data/map.json 제거됨. const MAP_ROWS = 7; // 걷는 행 1..7, 보스 row 8 const MAP_COLS = 4; @@ -100,6 +123,7 @@ function luaCardsTable(cards) { if (c.value != null) fields.push(`value = ${c.value}`); if (!c.class) throw new Error(`[gen-slaydeck] 카드 ${id}에 class 누락`); fields.push(`class = ${luaStr(c.class)}`); + fields.push(`rarity = ${luaStr(c.rarity)}`); if (c.hits != null) fields.push(`hits = ${c.hits}`); if (c.pierce === true) fields.push('pierce = true'); if (c.selfVuln != null) fields.push(`selfVuln = ${c.selfVuln}`); @@ -162,6 +186,21 @@ const MAX_MONSTERS = 4; const HEAD_OFFSET_Y = 1.4; // 몬스터 월드 원점 위로 띄울 높이(머리 위) — world→screen 변환 전 가산 const HP_BAR_W = 140; +const WHITE = { r: 1, g: 1, b: 1, a: 1 }; +const INK = { r: 0.13, g: 0.11, b: 0.09, a: 1 }; // 밝은 배너·설명 박스 위 먹색 글자 +// 카드 프레임(263×366 원본, 0.72 비율) 슬롯 레이아웃 — 180×250 기준값을 폭 비례 스케일 +function cardFaceLayout(W) { + const s = W / 180; + const r = (v) => Math.round(v * s); + return { + texts: [ + ['Cost', { size: { x: r(44), y: r(44) }, pos: { x: r(-68), y: r(103) }, fontSize: r(26), bold: true, color: WHITE }], + ['Name', { size: { x: r(150), y: r(26) }, pos: { x: r(4), y: r(97) }, fontSize: r(18), bold: true, color: INK }], + ['Desc', { size: { x: r(152), y: r(64) }, pos: { x: 0, y: r(-85) }, fontSize: r(16), bold: false, color: INK }], + ], + art: { size: { x: r(110), y: r(110) }, pos: { x: 0, y: r(16) } }, + }; +} const CARD_W = 180; const CARD_H = 250; const CARD_SPACING = 200; @@ -417,7 +456,9 @@ function appendUiSection(ui, section, entities) { function upsertUi() { const ui = JSON.parse(readFileSync(UI_FILE, 'utf8')); const E = ui.ContentProto.Entities; - ui.ContentProto.Entities = E.filter((e) => !isGeneratedUiEntity(e)); + // CardHand는 스톡 섹션이라 과거 생성된 단색판(NamePlate/CostPlate)이 잔존 → 프레임 이미지 도입으로 제거 + const obsoletePlate = /^\/ui\/DefaultGroup\/CardHand\/Card\d+\/(NamePlate|CostPlate)$/; + ui.ContentProto.Entities = E.filter((e) => !isGeneratedUiEntity(e) && !obsoletePlate.test(e.path)); const byPath = new Map(ui.ContentProto.Entities.map((e) => [e.path, e])); const uiSections = new Map(); @@ -444,7 +485,7 @@ function upsertUi() { const previewIds = Object.keys(CARDS.cards); const cards = Array.from({ length: 5 }, (_, i) => { const c = CARDS.cards[previewIds[i % previewIds.length]]; - return { name: c.name, cost: String(c.cost), desc: c.desc, tint: c.kind === 'Attack' ? ATTACK : DEFEND }; + return { name: c.name, cost: String(c.cost), desc: c.desc, frame: frameRuid(c) }; }); for (let i = 1; i <= 5; i++) { @@ -456,9 +497,9 @@ function upsertUi() { tr.anchoredPosition = { x: CARD_XS[i - 1], y: 0 }; tr.OffsetMin = { x: CARD_XS[i - 1] - CARD_W / 2, y: -CARD_H / 2 }; tr.OffsetMax = { x: CARD_XS[i - 1] + CARD_W / 2, y: CARD_H / 2 }; - sp.ImageRUID = { DataId: '' }; - sp.Type = 1; - sp.Color = cards[i - 1].tint; + sp.ImageRUID = { DataId: cards[i - 1].frame }; + sp.Type = 0; + sp.Color = WHITE; sp.RaycastTarget = true; const comps = card.jsonString['@components']; if (!comps.some((c) => c['@type'] === 'MOD.Core.ButtonComponent')) { @@ -476,11 +517,9 @@ function upsertUi() { card.jsonString.enable = true; card.jsonString.visible = true; - const children = [ - ['Cost', { size: { x: 44, y: 44 }, pos: { x: -68, y: 103 }, value: cards[i - 1].cost, fontSize: 26, bold: true }], - ['Name', { size: { x: 168, y: 30 }, pos: { x: 0, y: -8 }, value: cards[i - 1].name, fontSize: 20, bold: true }], - ['Desc', { size: { x: 164, y: 70 }, pos: { x: 0, y: -62 }, value: cards[i - 1].desc, fontSize: 18, bold: false }], - ]; + const handLayout = cardFaceLayout(CARD_W); + const previewValues = { Cost: cards[i - 1].cost, Name: cards[i - 1].name, Desc: cards[i - 1].desc }; + const children = handLayout.texts.map(([suffix, cfg]) => [suffix, { ...cfg, value: previewValues[suffix] }]); for (const [suffix, cfg] of children) { const path = `/ui/DefaultGroup/CardHand/Card${i}/${suffix}`; const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8; @@ -496,7 +535,7 @@ function upsertUi() { 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 }), + text({ value: cfg.value, fontSize: cfg.fontSize, bold: cfg.bold, color: cfg.color }), ], }); ui.ContentProto.Entities.push(child); @@ -517,18 +556,19 @@ function upsertUi() { child.jsonString['@components'][2].Text = cfg.value; child.jsonString['@components'][2].FontSize = cfg.fontSize; child.jsonString['@components'][2].MaxSize = cfg.fontSize; + child.jsonString['@components'][2].FontColor = cfg.color; } } + // 프레임 이미지가 이름판·코스트판을 내장하므로 Art만 유지 (잔존 NamePlate/CostPlate는 upsertUi 초입에서 제거) const frameKids = [ - ['NamePlate', 3, { size: { x: 168, y: 34 }, pos: { x: 0, y: -8 } }, { r: 0.07, g: 0.08, b: 0.1, a: 0.92 }, 1], - ['CostPlate', 4, { size: { x: 44, y: 44 }, pos: { x: -68, y: 103 } }, { r: 0.07, g: 0.08, b: 0.1, a: 0.95 }, 1], - ['Art', 5, { size: { x: 96, y: 96 }, pos: { x: 0, y: 52 } }, { r: 1, g: 1, b: 1, a: 1 }, 0], + ['Art', 5, handLayout.art, WHITE, 0], ]; for (const [suffix, dOrder, cfg, color, spriteType] of frameKids) { const fPath = `/ui/DefaultGroup/CardHand/Card${i}/${suffix}`; - if (!byPath.get(fPath)) { - const fe = entity({ + let fe = byPath.get(fPath); + if (!fe) { + fe = entity({ id: guid('dck', 200 + i * 10 + dOrder), path: fPath, modelId: 'uisprite', @@ -542,6 +582,14 @@ function upsertUi() { }); ui.ContentProto.Entities.push(fe); byPath.set(fPath, fe); + } else { + const ftr = fe.jsonString['@components'].find((c) => c['@type'] === 'MOD.Core.UITransformComponent'); + if (ftr) { + ftr.RectSize = cfg.size; + ftr.anchoredPosition = cfg.pos; + ftr.OffsetMin = { x: cfg.pos.x - cfg.size.x / 2, y: cfg.pos.y - cfg.size.y / 2 }; + ftr.OffsetMax = { x: cfg.pos.x + cfg.size.x / 2, y: cfg.pos.y + cfg.size.y / 2 }; + } } } } @@ -758,16 +806,13 @@ function upsertUi() { 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({ color: ATTACK, type: 1 }), + sprite({ dataId: CARDFRAMES.frames.warrior.normal, color: WHITE, type: 0 }), ], }); card.jsonString.enable = false; inspect.push(card); - for (const [suffix, cfg] of [ - ['Cost', { size: { x: 38, y: 38 }, pos: { x: -58, y: 86 }, value: '1', fontSize: 22, bold: true }], - ['Name', { size: { x: 148, y: 26 }, pos: { x: 0, y: -8 }, value: '', fontSize: 17, bold: true }], - ['Desc', { size: { x: 144, y: 60 }, pos: { x: 0, y: -54 }, value: '', fontSize: 15, bold: false }], - ]) { + const inspectLayout = cardFaceLayout(INSPECT_CARD_W); + for (const [suffix, cfg] of inspectLayout.texts.map(([sfx, c]) => [sfx, { ...c, value: sfx === 'Cost' ? '1' : '' }])) { const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8; inspect.push(entity({ id: guid('ins', insN++), @@ -779,28 +824,22 @@ function upsertUi() { 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 }), - ], - })); - } - for (const [suffix, dOrder, cfg, color, spriteType] of [ - ['NamePlate', 3, { size: { x: 148, y: 30 }, pos: { x: 0, y: -8 } }, { r: 0.07, g: 0.08, b: 0.1, a: 0.92 }, 1], - ['CostPlate', 4, { size: { x: 38, y: 38 }, pos: { x: -58, y: 86 } }, { r: 0.07, g: 0.08, b: 0.1, a: 0.95 }, 1], - ['Art', 5, { size: { x: 84, y: 84 }, pos: { x: 0, y: 44 } }, { r: 1, g: 1, b: 1, a: 1 }, 0], - ]) { - inspect.push(entity({ - id: guid('ins', insN++), - path: `${cardPath}/${suffix}`, - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - 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, type: spriteType, raycast: false }), + text({ value: cfg.value, fontSize: cfg.fontSize, bold: cfg.bold, color: cfg.color }), ], })); } + inspect.push(entity({ + id: guid('ins', insN++), + 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); @@ -899,16 +938,13 @@ function upsertUi() { 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({ color: ATTACK, type: 1 }), + sprite({ dataId: CARDFRAMES.frames.warrior.normal, color: WHITE, type: 0 }), ], }); card.jsonString.enable = false; allDeck.push(card); - for (const [suffix, cfg] of [ - ['Cost', { size: { x: 38, y: 38 }, pos: { x: -58, y: 86 }, value: '1', fontSize: 22, bold: true }], - ['Name', { size: { x: 148, y: 26 }, pos: { x: 0, y: -8 }, value: '', fontSize: 17, bold: true }], - ['Desc', { size: { x: 144, y: 60 }, pos: { x: 0, y: -54 }, value: '', fontSize: 15, bold: false }], - ]) { + const allDeckLayout = cardFaceLayout(ALL_DECK_CARD_W); + for (const [suffix, cfg] of allDeckLayout.texts.map(([sfx, c]) => [sfx, { ...c, value: sfx === 'Cost' ? '1' : '' }])) { const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8; allDeck.push(entity({ id: guid('all', allN++), @@ -920,28 +956,22 @@ function upsertUi() { 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 }), - ], - })); - } - for (const [suffix, dOrder, cfg, color, spriteType] of [ - ['NamePlate', 3, { size: { x: 148, y: 30 }, pos: { x: 0, y: -8 } }, { r: 0.07, g: 0.08, b: 0.1, a: 0.92 }, 1], - ['CostPlate', 4, { size: { x: 38, y: 38 }, pos: { x: -58, y: 86 } }, { r: 0.07, g: 0.08, b: 0.1, a: 0.95 }, 1], - ['Art', 5, { size: { x: 84, y: 84 }, pos: { x: 0, y: 44 } }, { r: 1, g: 1, b: 1, a: 1 }, 0], - ]) { - allDeck.push(entity({ - id: guid('all', allN++), - path: `${cardPath}/${suffix}`, - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - 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, type: spriteType, raycast: false }), + text({ value: cfg.value, fontSize: cfg.fontSize, bold: cfg.bold, color: cfg.color }), ], })); } + allDeck.push(entity({ + id: guid('all', allN++), + 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); @@ -1415,15 +1445,12 @@ function upsertUi() { 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({ color: ATTACK, type: 1, raycast: true }), + sprite({ dataId: CARDFRAMES.frames.warrior.normal, color: WHITE, type: 0, raycast: true }), button(), ], })); - for (const [suffix, cfg] of [ - ['Cost', { size: { x: 44, y: 44 }, pos: { x: -68, y: 103 }, value: '1', fontSize: 26, bold: true }], - ['Name', { size: { x: 168, y: 30 }, pos: { x: 0, y: -8 }, value: '카드', fontSize: 20, bold: true }], - ['Desc', { size: { x: 164, y: 70 }, pos: { x: 0, y: -62 }, value: '', fontSize: 18, bold: false }], - ]) { + const rewardLayout = cardFaceLayout(CARD_W); + for (const [suffix, cfg] of rewardLayout.texts.map(([sfx, c]) => [sfx, { ...c, value: sfx === 'Cost' ? '1' : sfx === 'Name' ? '카드' : '' }])) { const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8; reward.push(entity({ id: guid('rwd', rwdN++), @@ -1435,28 +1462,22 @@ function upsertUi() { 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 }), - ], - })); - } - for (const [suffix, dOrder, cfg, color, spriteType] of [ - ['NamePlate', 3, { size: { x: 168, y: 34 }, pos: { x: 0, y: -8 } }, { r: 0.07, g: 0.08, b: 0.1, a: 0.92 }, 1], - ['CostPlate', 4, { size: { x: 44, y: 44 }, pos: { x: -68, y: 103 } }, { r: 0.07, g: 0.08, b: 0.1, a: 0.95 }, 1], - ['Art', 5, { size: { x: 96, y: 96 }, pos: { x: 0, y: 52 } }, { r: 1, g: 1, b: 1, a: 1 }, 0], - ]) { - reward.push(entity({ - id: guid('rwd', rwdN++), - path: `${cardPath}/${suffix}`, - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: dOrder, - components: [ - transform({ parentW: CARD_W, parentH: CARD_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: cfg.size, pos: cfg.pos }), - sprite({ color, type: spriteType, raycast: false }), + text({ value: cfg.value, fontSize: cfg.fontSize, bold: cfg.bold, color: cfg.color }), ], })); } + reward.push(entity({ + id: guid('rwd', rwdN++), + 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 }), + ], + })); } reward.push(entity({ id: guid('rwd', rwdN++), @@ -1631,16 +1652,14 @@ function upsertUi() { 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({ color: ATTACK, type: 1, raycast: true }), + sprite({ dataId: CARDFRAMES.frames.warrior.normal, color: WHITE, type: 0, raycast: true }), button(), ], })); - for (const [suffix, cfg] of [ - ['Cost', { size: { x: 44, y: 44 }, pos: { x: -68, y: 103 }, value: '1', fontSize: 26, bold: true, color: { r: 1, g: 1, b: 1, a: 1 } }], - ['Name', { size: { x: 168, y: 30 }, pos: { x: 0, y: -8 }, value: '카드', fontSize: 20, bold: true, color: { r: 1, g: 1, b: 1, a: 1 } }], - ['Desc', { size: { x: 164, y: 56 }, pos: { x: 0, y: -58 }, value: '', fontSize: 18, bold: false, color: { r: 1, g: 1, b: 1, a: 1 } }], - ['Price', { size: { x: 160, y: 40 }, pos: { x: 0, y: -105 }, value: '30 메소', fontSize: 22, bold: true, color: { r: 0.98, g: 0.85, b: 0.4, a: 1 } }], - ]) { + 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 [suffix, cfg] of shopTexts) { const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : suffix === 'Desc' ? 8 : 9; shop.push(entity({ id: guid('shp', shpN++), @@ -1656,24 +1675,18 @@ function upsertUi() { ], })); } - for (const [suffix, dOrder, cfg, color, spriteType] of [ - ['NamePlate', 3, { size: { x: 168, y: 34 }, pos: { x: 0, y: -8 } }, { r: 0.07, g: 0.08, b: 0.1, a: 0.92 }, 1], - ['CostPlate', 4, { size: { x: 44, y: 44 }, pos: { x: -68, y: 103 } }, { r: 0.07, g: 0.08, b: 0.1, a: 0.95 }, 1], - ['Art', 5, { size: { x: 96, y: 96 }, pos: { x: 0, y: 52 } }, { r: 1, g: 1, b: 1, a: 1 }, 0], - ]) { - shop.push(entity({ - id: guid('shp', shpN++), - path: `${cardPath}/${suffix}`, - modelId: 'uisprite', - entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', - displayOrder: dOrder, - components: [ - transform({ parentW: CARD_W, parentH: CARD_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: cfg.size, pos: cfg.pos }), - sprite({ color, type: spriteType, raycast: false }), - ], - })); - } + shop.push(entity({ + id: guid('shp', shpN++), + 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 }), + ], + })); } shop.push(entity({ id: guid('shp', shpN++), @@ -2418,6 +2431,8 @@ function writeCodeblocks() { prop('string', 'DeckInspectKind', '""'), prop('boolean', 'DeckAllOpen', 'false'), prop('any', 'Cards'), + prop('any', 'CardFrames'), + prop('any', 'ClassToFrame'), prop('number', 'PlayerHp', '0'), prop('number', 'PlayerMaxHp', '80'), prop('number', 'PlayerBlock', '0'), @@ -2667,6 +2682,7 @@ self.CurrentNodeId = "" self.CurrentEnemyId = "" self.PlayerJob = "" ${luaJobsTable(JOBS)} +${luaFramesTable()} self:GenerateMap() self:BindButtons() self:AddRelic("${RELICS.startingRelic}") @@ -3148,16 +3164,18 @@ end self:RenderPiles()`, [{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'animate' }]), method('ApplyCardFace', `local c = self.Cards[cardId] if c == nil then - c = { name = cardId, cost = 0, desc = "", kind = "Skill" } + c = { name = cardId, cost = 0, desc = "", kind = "Skill", class = "warrior", rarity = "normal" } end local e = _EntityService:GetEntityByPath(base) if e ~= nil and e.SpriteGUIRendererComponent ~= nil then - if c.kind == "Attack" then - e.SpriteGUIRendererComponent.Color = Color(0.86, 0.42, 0.38, 1) - elseif c.kind == "Power" then - e.SpriteGUIRendererComponent.Color = Color(0.46, 0.68, 0.52, 1) - else - e.SpriteGUIRendererComponent.Color = Color(0.42, 0.55, 0.85, 1) + local frames = self.CardFrames[self.ClassToFrame[c.class] or "warrior"] + local ruid = nil + if frames ~= nil then + ruid = frames[c.rarity or "normal"] + end + if ruid ~= nil then + e.SpriteGUIRendererComponent.ImageRUID = ruid + e.SpriteGUIRendererComponent.Color = Color(1, 1, 1, 1) end end self:SetText(base .. "/Cost", string.format("%d", c.cost))