feat(card-frames): 커스텀 카드 프레임 — 직업×등급 프레임·보상 가중 추첨 (P13) #50
@@ -791,14 +791,16 @@ function upsertUi() {
|
|||||||
scrollLayoutGroup({ cellSize: { x: 158, y: 214 }, spacing: { x: 22, y: 22 }, columns: 5 }),
|
scrollLayoutGroup({ cellSize: { x: 158, y: 214 }, spacing: { x: 22, y: 22 }, columns: 5 }),
|
||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
let insN = 6;
|
|
||||||
const INSPECT_CARD_COUNT = 60;
|
const INSPECT_CARD_COUNT = 60;
|
||||||
const INSPECT_CARD_W = 158;
|
const INSPECT_CARD_W = 158;
|
||||||
const INSPECT_CARD_H = 214;
|
const INSPECT_CARD_H = 214;
|
||||||
|
// id는 구 레이아웃(stride 7: root,Cost,Name,Desc,[NamePlate],[CostPlate],Art) 매핑 보존 —
|
||||||
|
// 같은 path가 항상 같은 id를 갖지 않으면 메이커 refresh의 id 기준 in-place 병합이 꼬임 (P13 실측)
|
||||||
for (let i = 1; i <= INSPECT_CARD_COUNT; i++) {
|
for (let i = 1; i <= INSPECT_CARD_COUNT; i++) {
|
||||||
|
const insBase = 6 + (i - 1) * 7;
|
||||||
const cardPath = `/ui/DefaultGroup/DeckInspectHud/Grid/Card${i}`;
|
const cardPath = `/ui/DefaultGroup/DeckInspectHud/Grid/Card${i}`;
|
||||||
const card = entity({
|
const card = entity({
|
||||||
id: guid('ins', insN++),
|
id: guid('ins', insBase),
|
||||||
path: cardPath,
|
path: cardPath,
|
||||||
modelId: 'uisprite',
|
modelId: 'uisprite',
|
||||||
entryId: 'UISprite',
|
entryId: 'UISprite',
|
||||||
@@ -812,10 +814,10 @@ function upsertUi() {
|
|||||||
card.jsonString.enable = false;
|
card.jsonString.enable = false;
|
||||||
inspect.push(card);
|
inspect.push(card);
|
||||||
const inspectLayout = cardFaceLayout(INSPECT_CARD_W);
|
const inspectLayout = cardFaceLayout(INSPECT_CARD_W);
|
||||||
for (const [suffix, cfg] of inspectLayout.texts.map(([sfx, c]) => [sfx, { ...c, value: sfx === 'Cost' ? '1' : '' }])) {
|
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;
|
const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8;
|
||||||
inspect.push(entity({
|
inspect.push(entity({
|
||||||
id: guid('ins', insN++),
|
id: guid('ins', insBase + 1 + tIdx),
|
||||||
path: `${cardPath}/${suffix}`,
|
path: `${cardPath}/${suffix}`,
|
||||||
modelId: 'uitext',
|
modelId: 'uitext',
|
||||||
entryId: 'UIText',
|
entryId: 'UIText',
|
||||||
@@ -829,7 +831,7 @@ function upsertUi() {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
inspect.push(entity({
|
inspect.push(entity({
|
||||||
id: guid('ins', insN++),
|
id: guid('ins', insBase + 6),
|
||||||
path: `${cardPath}/Art`,
|
path: `${cardPath}/Art`,
|
||||||
modelId: 'uisprite',
|
modelId: 'uisprite',
|
||||||
entryId: 'UISprite',
|
entryId: 'UISprite',
|
||||||
@@ -923,14 +925,15 @@ function upsertUi() {
|
|||||||
scrollLayoutGroup({ cellSize: { x: 158, y: 214 }, spacing: { x: 22, y: 22 }, columns: 5 }),
|
scrollLayoutGroup({ cellSize: { x: 158, y: 214 }, spacing: { x: 22, y: 22 }, columns: 5 }),
|
||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
let allN = 6;
|
|
||||||
const ALL_DECK_CARD_COUNT = 120;
|
const ALL_DECK_CARD_COUNT = 120;
|
||||||
const ALL_DECK_CARD_W = 158;
|
const ALL_DECK_CARD_W = 158;
|
||||||
const ALL_DECK_CARD_H = 214;
|
const ALL_DECK_CARD_H = 214;
|
||||||
|
// id 매핑 보존 (stride 7) — DeckInspectHud 주석 참조
|
||||||
for (let i = 1; i <= ALL_DECK_CARD_COUNT; i++) {
|
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 cardPath = `/ui/DefaultGroup/DeckAllHud/Grid/Card${i}`;
|
||||||
const card = entity({
|
const card = entity({
|
||||||
id: guid('all', allN++),
|
id: guid('all', allBase),
|
||||||
path: cardPath,
|
path: cardPath,
|
||||||
modelId: 'uisprite',
|
modelId: 'uisprite',
|
||||||
entryId: 'UISprite',
|
entryId: 'UISprite',
|
||||||
@@ -944,10 +947,10 @@ function upsertUi() {
|
|||||||
card.jsonString.enable = false;
|
card.jsonString.enable = false;
|
||||||
allDeck.push(card);
|
allDeck.push(card);
|
||||||
const allDeckLayout = cardFaceLayout(ALL_DECK_CARD_W);
|
const allDeckLayout = cardFaceLayout(ALL_DECK_CARD_W);
|
||||||
for (const [suffix, cfg] of allDeckLayout.texts.map(([sfx, c]) => [sfx, { ...c, value: sfx === 'Cost' ? '1' : '' }])) {
|
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;
|
const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8;
|
||||||
allDeck.push(entity({
|
allDeck.push(entity({
|
||||||
id: guid('all', allN++),
|
id: guid('all', allBase + 1 + tIdx),
|
||||||
path: `${cardPath}/${suffix}`,
|
path: `${cardPath}/${suffix}`,
|
||||||
modelId: 'uitext',
|
modelId: 'uitext',
|
||||||
entryId: 'UIText',
|
entryId: 'UIText',
|
||||||
@@ -961,7 +964,7 @@ function upsertUi() {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
allDeck.push(entity({
|
allDeck.push(entity({
|
||||||
id: guid('all', allN++),
|
id: guid('all', allBase + 6),
|
||||||
path: `${cardPath}/Art`,
|
path: `${cardPath}/Art`,
|
||||||
modelId: 'uisprite',
|
modelId: 'uisprite',
|
||||||
entryId: 'UISprite',
|
entryId: 'UISprite',
|
||||||
@@ -1432,12 +1435,13 @@ function upsertUi() {
|
|||||||
text({ value: '보상 카드 선택', fontSize: 44, bold: true, color: GOLD, alignment: 4 }),
|
text({ value: '보상 카드 선택', fontSize: 44, bold: true, color: GOLD, alignment: 4 }),
|
||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
let rwdN = 2;
|
|
||||||
const rewardXs = [-300, 0, 300];
|
const rewardXs = [-300, 0, 300];
|
||||||
|
// id 매핑 보존 (stride 7) — DeckInspectHud 주석 참조
|
||||||
for (let i = 1; i <= 3; i++) {
|
for (let i = 1; i <= 3; i++) {
|
||||||
|
const rwdBase = 2 + (i - 1) * 7;
|
||||||
const cardPath = `/ui/DefaultGroup/RewardHud/Reward${i}`;
|
const cardPath = `/ui/DefaultGroup/RewardHud/Reward${i}`;
|
||||||
reward.push(entity({
|
reward.push(entity({
|
||||||
id: guid('rwd', rwdN++),
|
id: guid('rwd', rwdBase),
|
||||||
path: cardPath,
|
path: cardPath,
|
||||||
modelId: 'uisprite',
|
modelId: 'uisprite',
|
||||||
entryId: 'UISprite',
|
entryId: 'UISprite',
|
||||||
@@ -1450,10 +1454,10 @@ function upsertUi() {
|
|||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
const rewardLayout = cardFaceLayout(CARD_W);
|
const rewardLayout = cardFaceLayout(CARD_W);
|
||||||
for (const [suffix, cfg] of rewardLayout.texts.map(([sfx, c]) => [sfx, { ...c, value: sfx === 'Cost' ? '1' : sfx === 'Name' ? '카드' : '' }])) {
|
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;
|
const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8;
|
||||||
reward.push(entity({
|
reward.push(entity({
|
||||||
id: guid('rwd', rwdN++),
|
id: guid('rwd', rwdBase + 1 + tIdx),
|
||||||
path: `${cardPath}/${suffix}`,
|
path: `${cardPath}/${suffix}`,
|
||||||
modelId: 'uitext',
|
modelId: 'uitext',
|
||||||
entryId: 'UIText',
|
entryId: 'UIText',
|
||||||
@@ -1467,7 +1471,7 @@ function upsertUi() {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
reward.push(entity({
|
reward.push(entity({
|
||||||
id: guid('rwd', rwdN++),
|
id: guid('rwd', rwdBase + 6),
|
||||||
path: `${cardPath}/Art`,
|
path: `${cardPath}/Art`,
|
||||||
modelId: 'uisprite',
|
modelId: 'uisprite',
|
||||||
entryId: 'UISprite',
|
entryId: 'UISprite',
|
||||||
@@ -1479,6 +1483,7 @@ function upsertUi() {
|
|||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
let rwdN = 2 + 3 * 7; // 구 시퀀스의 루프 종료 시점 값(23) 보존 — Skip 등 후속 id 불변
|
||||||
reward.push(entity({
|
reward.push(entity({
|
||||||
id: guid('rwd', rwdN++),
|
id: guid('rwd', rwdN++),
|
||||||
path: '/ui/DefaultGroup/RewardHud/Skip',
|
path: '/ui/DefaultGroup/RewardHud/Skip',
|
||||||
@@ -1639,12 +1644,13 @@ function upsertUi() {
|
|||||||
text({ value: '메소 0', fontSize: 28, bold: true, color: { r: 0.98, g: 0.85, b: 0.4, a: 1 }, alignment: 4 }),
|
text({ value: '메소 0', fontSize: 28, bold: true, color: { r: 0.98, g: 0.85, b: 0.4, a: 1 }, alignment: 4 }),
|
||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
let shpN = 3;
|
|
||||||
const shopXs = [-300, 0, 300];
|
const shopXs = [-300, 0, 300];
|
||||||
|
// id 매핑 보존 (stride 8: root,Cost,Name,Desc,Price,[NamePlate],[CostPlate],Art) — DeckInspectHud 주석 참조
|
||||||
for (let i = 1; i <= 3; i++) {
|
for (let i = 1; i <= 3; i++) {
|
||||||
|
const shpBase = 3 + (i - 1) * 8;
|
||||||
const cardPath = `/ui/DefaultGroup/ShopHud/Card${i}`;
|
const cardPath = `/ui/DefaultGroup/ShopHud/Card${i}`;
|
||||||
shop.push(entity({
|
shop.push(entity({
|
||||||
id: guid('shp', shpN++),
|
id: guid('shp', shpBase),
|
||||||
path: cardPath,
|
path: cardPath,
|
||||||
modelId: 'uisprite',
|
modelId: 'uisprite',
|
||||||
entryId: 'UISprite',
|
entryId: 'UISprite',
|
||||||
@@ -1659,10 +1665,10 @@ function upsertUi() {
|
|||||||
const shopLayout = cardFaceLayout(CARD_W);
|
const shopLayout = cardFaceLayout(CARD_W);
|
||||||
const shopTexts = shopLayout.texts.map(([sfx, c]) => [sfx, { ...c, value: sfx === 'Cost' ? '1' : sfx === 'Name' ? '카드' : '' }]);
|
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 } }]);
|
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) {
|
for (const [tIdx, [suffix, cfg]] of shopTexts.entries()) {
|
||||||
const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : suffix === 'Desc' ? 8 : 9;
|
const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : suffix === 'Desc' ? 8 : 9;
|
||||||
shop.push(entity({
|
shop.push(entity({
|
||||||
id: guid('shp', shpN++),
|
id: guid('shp', shpBase + 1 + tIdx),
|
||||||
path: `${cardPath}/${suffix}`,
|
path: `${cardPath}/${suffix}`,
|
||||||
modelId: 'uitext',
|
modelId: 'uitext',
|
||||||
entryId: 'UIText',
|
entryId: 'UIText',
|
||||||
@@ -1676,7 +1682,7 @@ function upsertUi() {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
shop.push(entity({
|
shop.push(entity({
|
||||||
id: guid('shp', shpN++),
|
id: guid('shp', shpBase + 7),
|
||||||
path: `${cardPath}/Art`,
|
path: `${cardPath}/Art`,
|
||||||
modelId: 'uisprite',
|
modelId: 'uisprite',
|
||||||
entryId: 'UISprite',
|
entryId: 'UISprite',
|
||||||
@@ -1688,6 +1694,7 @@ function upsertUi() {
|
|||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
let shpN = 3 + 3 * 8; // 구 시퀀스의 루프 종료 시점 값(27) 보존 — Relic 등 후속 id 불변
|
||||||
shop.push(entity({
|
shop.push(entity({
|
||||||
id: guid('shp', shpN++),
|
id: guid('shp', shpN++),
|
||||||
path: '/ui/DefaultGroup/ShopHud/Relic',
|
path: '/ui/DefaultGroup/ShopHud/Relic',
|
||||||
@@ -2342,6 +2349,14 @@ function upsertUi() {
|
|||||||
appendUiSection(ui, section, entities);
|
appendUiSection(ui, section, entities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 엔티티 id 유일성 검증 — 같은 id가 다른 path에 재배정되면 메이커 refresh 병합이 꼬임
|
||||||
|
const seenIds = new Map();
|
||||||
|
for (const e of ui.ContentProto.Entities) {
|
||||||
|
const prev = seenIds.get(e.id);
|
||||||
|
if (prev != null) throw new Error(`[gen-slaydeck] 엔티티 id 중복: ${e.id} (${prev} ↔ ${e.path})`);
|
||||||
|
seenIds.set(e.id, e.path);
|
||||||
|
}
|
||||||
|
|
||||||
JSON.parse(JSON.stringify(ui));
|
JSON.parse(JSON.stringify(ui));
|
||||||
writeFileSync(UI_FILE, JSON.stringify(ui, null, 2), 'utf8');
|
writeFileSync(UI_FILE, JSON.stringify(ui, null, 2), 'utf8');
|
||||||
}
|
}
|
||||||
|
|||||||
111424
ui/DefaultGroup.ui
111424
ui/DefaultGroup.ui
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user