3001 lines
123 KiB
JavaScript
3001 lines
123 KiB
JavaScript
import { readFileSync, writeFileSync } from 'node:fs';
|
|
|
|
const CARDS = JSON.parse(readFileSync('data/cards.json', 'utf8'));
|
|
const ENEMIES = JSON.parse(readFileSync('data/enemies.json', 'utf8'));
|
|
|
|
// 검증 (fail-fast): 잘못된 데이터면 생성 중단
|
|
for (const id of CARDS.starterDeck) {
|
|
if (!CARDS.cards[id]) {
|
|
throw new Error(`[gen-slaydeck] starterDeck에 없는 카드 id 참조: ${id}`);
|
|
}
|
|
}
|
|
if (!ENEMIES.enemies[ENEMIES.activeEnemy]) {
|
|
throw new Error(`[gen-slaydeck] activeEnemy가 enemies에 없음: ${ENEMIES.activeEnemy}`);
|
|
}
|
|
|
|
const MAP = JSON.parse(readFileSync('data/map.json', 'utf8'));
|
|
for (const id of MAP.start) {
|
|
if (!MAP.nodes[id]) throw new Error(`[gen-slaydeck] map.start에 없는 노드 id: ${id}`);
|
|
}
|
|
for (const [id, n] of Object.entries(MAP.nodes)) {
|
|
if (n.enemy && !ENEMIES.enemies[n.enemy]) throw new Error(`[gen-slaydeck] 노드 ${id}의 enemy 없음: ${n.enemy}`);
|
|
for (const nx of n.next) {
|
|
if (!MAP.nodes[nx]) throw new Error(`[gen-slaydeck] 노드 ${id}.next에 없는 노드 id: ${nx}`);
|
|
}
|
|
}
|
|
const MAX_ROW = Math.max(...Object.values(MAP.nodes).map((n) => n.row));
|
|
|
|
const RELICS = JSON.parse(readFileSync('data/relics.json', 'utf8'));
|
|
if (!RELICS.relics[RELICS.startingRelic]) throw new Error(`[gen-slaydeck] startingRelic 없음: ${RELICS.startingRelic}`);
|
|
for (const id of RELICS.relicPool) {
|
|
if (!RELICS.relics[id]) throw new Error(`[gen-slaydeck] relicPool에 없는 유물 id: ${id}`);
|
|
}
|
|
function luaRelicsTable(relics) {
|
|
const lines = Object.entries(relics).map(([id, r]) =>
|
|
`\t${id} = { name = ${luaStr(r.name)}, desc = ${luaStr(r.desc)}, hook = ${luaStr(r.hook)}, effect = ${luaStr(r.effect)}, value = ${r.value} },`);
|
|
return `self.Relics = {\n${lines.join('\n')}\n}`;
|
|
}
|
|
|
|
function luaIntentsArray(intents) {
|
|
return '{ ' + intents.map((it) => `{ kind = ${luaStr(it.kind)}, value = ${it.value} }`).join(', ') + ' }';
|
|
}
|
|
function luaEnemiesTable(enemies) {
|
|
const lines = Object.entries(enemies).map(([id, e]) =>
|
|
`\t${id} = { name = ${luaStr(e.name)}, maxHp = ${e.maxHp}, intents = ${luaIntentsArray(e.intents)} },`);
|
|
return `self.Enemies = {\n${lines.join('\n')}\n}`;
|
|
}
|
|
function luaMapNodesTable(nodes) {
|
|
const lines = Object.entries(nodes).map(([id, n]) => {
|
|
const nx = '{ ' + n.next.map(luaStr).join(', ') + ' }';
|
|
const enemyField = n.enemy ? `enemy = ${luaStr(n.enemy)}, ` : '';
|
|
return `\t${id} = { type = ${luaStr(n.type)}, ${enemyField}row = ${n.row}, col = ${n.col}, next = ${nx} },`;
|
|
});
|
|
return `self.MapNodes = {\n${lines.join('\n')}\n}`;
|
|
}
|
|
function luaStartArray(start) {
|
|
return 'self.MapStart = { ' + start.map(luaStr).join(', ') + ' }';
|
|
}
|
|
|
|
// Lua 직렬화 헬퍼
|
|
function luaStr(s) {
|
|
return '"' + String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
|
|
}
|
|
function luaCardsTable(cards) {
|
|
const lines = Object.entries(cards).map(([id, c]) => {
|
|
const fields = [`name = ${luaStr(c.name)}`, `cost = ${c.cost}`, `desc = ${luaStr(c.desc)}`, `kind = ${luaStr(c.kind)}`];
|
|
if (c.damage != null) fields.push(`damage = ${c.damage}`);
|
|
if (c.block != null) fields.push(`block = ${c.block}`);
|
|
if (c.image != null) fields.push(`image = ${luaStr(c.image)}`);
|
|
return `\t${id} = { ${fields.join(', ')} },`;
|
|
});
|
|
return `self.Cards = {\n${lines.join('\n')}\n}`;
|
|
}
|
|
function luaDeckTable(deck) {
|
|
return `self.DrawPile = { ${deck.map(luaStr).join(', ')} }`;
|
|
}
|
|
|
|
const UI_FILE = 'ui/DefaultGroup.ui';
|
|
const COMMON_FILE = 'Global/common.gamelogic';
|
|
const UI_ROOT = '/ui/DefaultGroup';
|
|
const GENERATED_UI_SECTIONS = [
|
|
'DeckHud',
|
|
'DeckInspectHud',
|
|
'DeckAllHud',
|
|
'CombatHud',
|
|
'RewardHud',
|
|
'MapHud',
|
|
'ShopHud',
|
|
'RestHud',
|
|
'MainMenu',
|
|
'CharacterSelectHud',
|
|
];
|
|
const UI_APPEND_ORDER = [
|
|
'DeckHud',
|
|
'CombatHud',
|
|
'RewardHud',
|
|
'MapHud',
|
|
'ShopHud',
|
|
'RestHud',
|
|
'DeckInspectHud',
|
|
'DeckAllHud',
|
|
'MainMenu',
|
|
'CharacterSelectHud',
|
|
];
|
|
const DISABLED_STOCK_CONTROLS = ['Button_Attack', 'Button_Jump', 'UIJoystick'];
|
|
|
|
const TRANSPARENT = { r: 0, g: 0, b: 0, a: 0 };
|
|
const DARK = { r: 0.08, g: 0.09, b: 0.11, a: 0.92 };
|
|
const GOLD = { r: 0.94, g: 0.74, b: 0.26, a: 1 };
|
|
const ATTACK = { r: 0.86, g: 0.42, b: 0.38, a: 1 };
|
|
const DEFEND = { r: 0.42, g: 0.55, b: 0.85, a: 1 };
|
|
const SKILL = { r: 0.46, g: 0.68, b: 0.52, a: 1 };
|
|
|
|
const MAX_MONSTERS = 4;
|
|
const HEAD_OFFSET_Y = 1.4; // 몬스터 월드 원점 위로 띄울 높이(머리 위) — world→screen 변환 전 가산
|
|
|
|
const HP_BAR_W = 140;
|
|
const CARD_W = 180;
|
|
const CARD_H = 250;
|
|
const CARD_SPACING = 200;
|
|
const CARD_XS = [-400, -200, 0, 200, 400];
|
|
|
|
const ALIGN_CENTER = 0;
|
|
const ALIGN_BOTTOM_CENTER = 6;
|
|
|
|
function guid(prefix, n) {
|
|
// 유효한 8-4-4-4-12 hex GUID 생성. prefix는 충돌 방지용 네임스페이스 바이트로 매핑.
|
|
const ns = prefix === 'hud' ? 0xd0 : prefix === 'dck' ? 0xca : prefix === 'cmb' ? 0xcb : prefix === 'rwd' ? 0xcc : prefix === 'map' ? 0xcd : prefix === 'shp' ? 0xce : prefix === 'rst' ? 0xcf : prefix === 'menu' ? 0xe0 : prefix === 'ins' ? 0xe1 : prefix === 'all' ? 0xe2 : 0xfe;
|
|
const v = (ns * 0x100000 + n) >>> 0;
|
|
return `${v.toString(16).padStart(8, '0')}-0000-4000-8000-${v.toString(16).padStart(12, '0')}`;
|
|
}
|
|
|
|
function transform({ parentW, parentH, anchor, pivot, size, pos, align = 0 }) {
|
|
const offMin = { x: pos.x - pivot.x * size.x, y: pos.y - pivot.y * size.y };
|
|
const offMax = { x: pos.x + (1 - pivot.x) * size.x, y: pos.y + (1 - pivot.y) * size.y };
|
|
return {
|
|
'@type': 'MOD.Core.UITransformComponent',
|
|
ActivePlatform: 255,
|
|
AlignmentOption: align,
|
|
AnchorsMax: anchor,
|
|
AnchorsMin: anchor,
|
|
MobileOnly: false,
|
|
OffsetMax: offMax,
|
|
OffsetMin: offMin,
|
|
Pivot: pivot,
|
|
RectSize: size,
|
|
UIMode: 1,
|
|
UIScale: { x: 1, y: 1, z: 1 },
|
|
UIVersion: 2,
|
|
anchoredPosition: pos,
|
|
Position: { x: anchor.x * parentW - parentW / 2 + pos.x, y: anchor.y * parentH - parentH / 2 + pos.y, z: 0 },
|
|
QuaternionRotation: { x: 0, y: 0, z: 0, w: 1 },
|
|
Scale: { x: 1, y: 1, z: 1 },
|
|
Enable: true,
|
|
};
|
|
}
|
|
|
|
function sprite({ dataId = '', color = TRANSPARENT, type = 1, raycast = false }) {
|
|
return {
|
|
'@type': 'MOD.Core.SpriteGUIRendererComponent',
|
|
AnimClipPlayType: 0,
|
|
EndFrameIndex: 2147483647,
|
|
ImageRUID: { DataId: dataId },
|
|
LocalPosition: { x: 0, y: 0 },
|
|
LocalScale: { x: 1, y: 1 },
|
|
OverrideSorting: false,
|
|
PlayRate: 1,
|
|
PreserveSprite: 0,
|
|
StartFrameIndex: 0,
|
|
Color: color,
|
|
DropShadow: false,
|
|
DropShadowAngle: 30,
|
|
DropShadowColor: { r: 0, g: 0, b: 0, a: 0.72 },
|
|
DropShadowDistance: 32,
|
|
FillAmount: 1,
|
|
FillCenter: true,
|
|
FillClockWise: true,
|
|
FillMethod: 0,
|
|
FillOrigin: 0,
|
|
FlipX: false,
|
|
FlipY: false,
|
|
FrameColumn: 1,
|
|
FrameRate: 0,
|
|
FrameRow: 1,
|
|
Outline: false,
|
|
OutlineColor: { r: 0, g: 0, b: 0, a: 1 },
|
|
OutlineWidth: 3,
|
|
RaycastTarget: raycast,
|
|
Type: type,
|
|
Enable: true,
|
|
};
|
|
}
|
|
|
|
function button({ enabled = true } = {}) {
|
|
return {
|
|
'@type': 'MOD.Core.ButtonComponent',
|
|
Colors: {
|
|
NormalColor: { r: 1, g: 1, b: 1, a: 1 },
|
|
HighlightedColor: { r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1 },
|
|
PressedColor: { r: 0.784313738, g: 0.784313738, b: 0.784313738, a: 1 },
|
|
SelectedColor: { r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1 },
|
|
DisabledColor: { r: 0.784313738, g: 0.784313738, b: 0.784313738, a: 0.5019608 },
|
|
ColorMultiplier: 1,
|
|
FadeDuration: 0.1,
|
|
},
|
|
ImageRUIDs: {
|
|
HighlightedSprite: null,
|
|
PressedSprite: null,
|
|
SelectedSprite: null,
|
|
DisabledSprite: null,
|
|
},
|
|
KeyCode: 0,
|
|
OverrideSorting: false,
|
|
Transition: 1,
|
|
Enable: enabled,
|
|
};
|
|
}
|
|
|
|
function text({ value, fontSize, bold = false, color = { r: 1, g: 1, b: 1, a: 1 }, alignment = 4 }) {
|
|
return {
|
|
'@type': 'MOD.Core.TextComponent',
|
|
Alignment: alignment,
|
|
Bold: bold,
|
|
DropShadow: false,
|
|
DropShadowAngle: 30,
|
|
DropShadowColor: { r: 0, g: 0, b: 0, a: 0.72 },
|
|
DropShadowDistance: 32,
|
|
Font: 0,
|
|
FontColor: color,
|
|
FontSize: fontSize,
|
|
MaxSize: fontSize,
|
|
MinSize: 8,
|
|
OutlineColor: { r: 0.08, g: 0.08, b: 0.08, a: 1 },
|
|
OutlineDistance: { x: 1, y: -1 },
|
|
OutlineWidth: 1,
|
|
Overflow: 0,
|
|
OverrideSorting: false,
|
|
Padding: { left: 0, right: 0, top: 0, bottom: 0 },
|
|
SizeFit: false,
|
|
Text: value,
|
|
UseOutLine: true,
|
|
Enable: true,
|
|
};
|
|
}
|
|
|
|
function scrollLayoutGroup({ cellSize, spacing, columns }) {
|
|
return {
|
|
'@type': 'MOD.Core.ScrollLayoutGroupComponent',
|
|
CellSize: cellSize,
|
|
ChildAlignment: 0,
|
|
Constraint: 1,
|
|
ConstraintCount: columns,
|
|
GridChildAlignment: 0,
|
|
GridSpacing: spacing,
|
|
HorizontalScrollBarDirection: 0,
|
|
IgnoreMapLayerCheck: false,
|
|
OrderInLayer: 0,
|
|
OverrideSorting: false,
|
|
Padding: { left: 16, right: 16, top: 16, bottom: 16 },
|
|
ReverseArrangement: false,
|
|
ScrollBarBackgroundColor: { r: 1, g: 1, b: 1, a: 0.18 },
|
|
ScrollBarBgImageRUID: { DataId: '' },
|
|
ScrollBarHandleColor: { r: 0.94, g: 0.74, b: 0.26, a: 0.9 },
|
|
ScrollBarHandleImageRUID: { DataId: '' },
|
|
ScrollBarThickness: 12,
|
|
ScrollBarVisible: 1,
|
|
SortingLayer: 'UI',
|
|
Spacing: 0,
|
|
StartAxis: 0,
|
|
StartCorner: 0,
|
|
Type: 2,
|
|
UseScroll: true,
|
|
VerticalScrollBarDirection: 1,
|
|
Enable: true,
|
|
};
|
|
}
|
|
|
|
function popupLayerFor(path) {
|
|
if (path.startsWith('/ui/DefaultGroup/DeckAllHud')) return { root: '/ui/DefaultGroup/DeckAllHud', base: 4000 };
|
|
if (path.startsWith('/ui/DefaultGroup/DeckInspectHud')) return { root: '/ui/DefaultGroup/DeckInspectHud', base: 3000 };
|
|
return null;
|
|
}
|
|
|
|
function uiOrderFor(path, displayOrder) {
|
|
const popup = popupLayerFor(path);
|
|
if (popup != null) {
|
|
const relative = path.slice(popup.root.length).split('/').filter(Boolean);
|
|
return popup.base + relative.length * 100 + displayOrder;
|
|
}
|
|
return displayOrder;
|
|
}
|
|
|
|
function displayOrderFor(path, displayOrder) {
|
|
return uiOrderFor(path, displayOrder);
|
|
}
|
|
|
|
function applySortingOverride(path, components, displayOrder) {
|
|
if (popupLayerFor(path) == null) return components;
|
|
const order = uiOrderFor(path, displayOrder);
|
|
return components.map((component) => {
|
|
if (component['@type'] !== 'MOD.Core.SpriteGUIRendererComponent' && component['@type'] !== 'MOD.Core.TextComponent') {
|
|
return component;
|
|
}
|
|
return {
|
|
...component,
|
|
OverrideSorting: true,
|
|
SortingLayer: 'UI',
|
|
OrderInLayer: order,
|
|
};
|
|
});
|
|
}
|
|
|
|
function entity({ id, path, modelId, entryId, componentNames, components, displayOrder }) {
|
|
const parts = path.split('/');
|
|
const name = parts[parts.length - 1];
|
|
const sortedComponents = applySortingOverride(path, components, displayOrder);
|
|
return {
|
|
id,
|
|
path,
|
|
componentNames,
|
|
jsonString: {
|
|
name,
|
|
path,
|
|
nameEditable: true,
|
|
enable: true,
|
|
visible: true,
|
|
localize: true,
|
|
displayOrder: displayOrderFor(path, displayOrder),
|
|
pathConstraints: '/'.repeat(parts.length - 1),
|
|
revision: 1,
|
|
origin: {
|
|
type: 'Model',
|
|
entry_id: entryId,
|
|
sub_entity_id: null,
|
|
root_entity_id: null,
|
|
replaced_model_id: null,
|
|
},
|
|
modelId,
|
|
'@components': sortedComponents,
|
|
'@version': 1,
|
|
},
|
|
};
|
|
}
|
|
|
|
function uiPath(...parts) {
|
|
return [UI_ROOT, ...parts].join('/');
|
|
}
|
|
|
|
function sectionRoot(section) {
|
|
return uiPath(section);
|
|
}
|
|
|
|
function isGeneratedUiEntity(e) {
|
|
return GENERATED_UI_SECTIONS.some((section) => e.path.startsWith(sectionRoot(section)));
|
|
}
|
|
|
|
function appendUiSection(ui, section, entities) {
|
|
if (!GENERATED_UI_SECTIONS.includes(section)) {
|
|
throw new Error(`[gen-slaydeck] unknown generated UI section: ${section}`);
|
|
}
|
|
const root = sectionRoot(section);
|
|
for (const e of entities) {
|
|
if (!e.path.startsWith(root)) {
|
|
throw new Error(`[gen-slaydeck] ${section} section emitted unexpected path: ${e.path}`);
|
|
}
|
|
}
|
|
ui.ContentProto.Entities.push(...entities);
|
|
}
|
|
|
|
function upsertUi() {
|
|
const ui = JSON.parse(readFileSync(UI_FILE, 'utf8'));
|
|
const E = ui.ContentProto.Entities;
|
|
ui.ContentProto.Entities = E.filter((e) => !isGeneratedUiEntity(e));
|
|
|
|
const byPath = new Map(ui.ContentProto.Entities.map((e) => [e.path, e]));
|
|
const uiSections = new Map();
|
|
const emit = (section, entities) => {
|
|
if (uiSections.has(section)) {
|
|
throw new Error(`[gen-slaydeck] duplicate generated UI section: ${section}`);
|
|
}
|
|
uiSections.set(section, entities);
|
|
};
|
|
|
|
for (const path of DISABLED_STOCK_CONTROLS.map((name) => uiPath(name))) {
|
|
const e = byPath.get(path);
|
|
if (e != null) {
|
|
e.jsonString.enable = false;
|
|
e.jsonString.visible = false;
|
|
for (const component of e.jsonString['@components'] || []) {
|
|
component.Enable = false;
|
|
if (component.RaycastTarget != null) component.RaycastTarget = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 카드 미리보기(초기 정적 표시 — 런타임 RenderHand가 덮어씀): 카드 종류를 순환해 다양성 표시
|
|
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 };
|
|
});
|
|
|
|
for (let i = 1; i <= 5; i++) {
|
|
const card = byPath.get(`/ui/DefaultGroup/CardHand/Card${i}`);
|
|
if (!card) continue;
|
|
const tr = card.jsonString['@components'].find((c) => c['@type'] === 'MOD.Core.UITransformComponent');
|
|
const sp = card.jsonString['@components'].find((c) => c['@type'] === 'MOD.Core.SpriteGUIRendererComponent');
|
|
tr.RectSize = { x: CARD_W, y: CARD_H };
|
|
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.RaycastTarget = true;
|
|
const comps = card.jsonString['@components'];
|
|
if (!comps.some((c) => c['@type'] === 'MOD.Core.ButtonComponent')) {
|
|
comps.push(button());
|
|
}
|
|
if (!card.componentNames.includes('MOD.Core.ButtonComponent')) {
|
|
card.componentNames += ',MOD.Core.ButtonComponent';
|
|
}
|
|
if (!comps.some((c) => c['@type'] === 'MOD.Core.UITouchReceiveComponent')) {
|
|
comps.push({ '@type': 'MOD.Core.UITouchReceiveComponent', Enable: true });
|
|
}
|
|
if (!card.componentNames.includes('MOD.Core.UITouchReceiveComponent')) {
|
|
card.componentNames += ',MOD.Core.UITouchReceiveComponent';
|
|
}
|
|
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 }],
|
|
];
|
|
for (const [suffix, cfg] of children) {
|
|
const path = `/ui/DefaultGroup/CardHand/Card${i}/${suffix}`;
|
|
const dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8;
|
|
let child = byPath.get(path);
|
|
if (!child) {
|
|
child = entity({
|
|
id: guid('dck', i * 10 + children.findIndex(([s]) => s === suffix)),
|
|
path,
|
|
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 }),
|
|
],
|
|
});
|
|
ui.ContentProto.Entities.push(child);
|
|
byPath.set(path, child);
|
|
} else {
|
|
child.id = guid('dck', i * 10 + children.findIndex(([s]) => s === suffix));
|
|
child.jsonString.enable = true;
|
|
child.jsonString.visible = true;
|
|
child.jsonString.displayOrder = dOrder;
|
|
const ctr = child.jsonString['@components'].find((c) => c['@type'] === 'MOD.Core.UITransformComponent');
|
|
if (ctr) {
|
|
const pivot = { x: 0.5, y: 0.5 };
|
|
ctr.RectSize = cfg.size;
|
|
ctr.anchoredPosition = cfg.pos;
|
|
ctr.OffsetMin = { x: cfg.pos.x - pivot.x * cfg.size.x, y: cfg.pos.y - pivot.y * cfg.size.y };
|
|
ctr.OffsetMax = { x: cfg.pos.x + (1 - pivot.x) * cfg.size.x, y: cfg.pos.y + (1 - pivot.y) * cfg.size.y };
|
|
}
|
|
child.jsonString['@components'][2].Text = cfg.value;
|
|
child.jsonString['@components'][2].FontSize = cfg.fontSize;
|
|
child.jsonString['@components'][2].MaxSize = cfg.fontSize;
|
|
}
|
|
}
|
|
|
|
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],
|
|
];
|
|
for (const [suffix, dOrder, cfg, color, spriteType] of frameKids) {
|
|
const fPath = `/ui/DefaultGroup/CardHand/Card${i}/${suffix}`;
|
|
if (!byPath.get(fPath)) {
|
|
const fe = entity({
|
|
id: guid('dck', 200 + i * 10 + dOrder),
|
|
path: fPath,
|
|
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 }),
|
|
],
|
|
});
|
|
ui.ContentProto.Entities.push(fe);
|
|
byPath.set(fPath, fe);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 }),
|
|
],
|
|
}));
|
|
|
|
emit('DeckHud', hud);
|
|
|
|
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 }),
|
|
],
|
|
}));
|
|
let insN = 6;
|
|
const INSPECT_CARD_COUNT = 60;
|
|
const INSPECT_CARD_W = 158;
|
|
const INSPECT_CARD_H = 214;
|
|
for (let i = 1; i <= INSPECT_CARD_COUNT; i++) {
|
|
const cardPath = `/ui/DefaultGroup/DeckInspectHud/Grid/Card${i}`;
|
|
const card = entity({
|
|
id: guid('ins', insN++),
|
|
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({ color: ATTACK, type: 1 }),
|
|
],
|
|
});
|
|
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 dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8;
|
|
inspect.push(entity({
|
|
id: guid('ins', insN++),
|
|
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 }),
|
|
],
|
|
}));
|
|
}
|
|
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 }),
|
|
],
|
|
}));
|
|
}
|
|
}
|
|
emit('DeckInspectHud', inspect);
|
|
|
|
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 }),
|
|
],
|
|
}));
|
|
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 }),
|
|
],
|
|
}));
|
|
let allN = 6;
|
|
const ALL_DECK_CARD_COUNT = 120;
|
|
const ALL_DECK_CARD_W = 158;
|
|
const ALL_DECK_CARD_H = 214;
|
|
for (let i = 1; i <= ALL_DECK_CARD_COUNT; i++) {
|
|
const cardPath = `/ui/DefaultGroup/DeckAllHud/Grid/Card${i}`;
|
|
const card = entity({
|
|
id: guid('all', allN++),
|
|
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({ color: ATTACK, type: 1 }),
|
|
],
|
|
});
|
|
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 dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8;
|
|
allDeck.push(entity({
|
|
id: guid('all', allN++),
|
|
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 }),
|
|
],
|
|
}));
|
|
}
|
|
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 }),
|
|
],
|
|
}));
|
|
}
|
|
}
|
|
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 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 dmgPop = entity({
|
|
id: guid('cmb', 250 + i), path: `${base}/DmgPop`, 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: 120, y: 30 }, pos: { x: 0, y: 60 } }),
|
|
sprite({ color: TRANSPARENT }),
|
|
text({ value: '', fontSize: 24, bold: true, color: { r: 1, g: 0.35, b: 0.3, a: 1 }, alignment: 4 }),
|
|
],
|
|
});
|
|
dmgPop.jsonString.enable = false;
|
|
combat.push(dmgPop);
|
|
}
|
|
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 }),
|
|
],
|
|
}));
|
|
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 }],
|
|
['Relics', 60, 560, '유물: 없음', { r: 0.8, g: 0.7, b: 0.95, 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: suffix === 'Relics' ? 18 : 22, bold: true, color, 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: 150, y: 40 }, pos: { x: 510, 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 }),
|
|
],
|
|
}));
|
|
let rwdN = 2;
|
|
const rewardXs = [-300, 0, 300];
|
|
for (let i = 1; i <= 3; i++) {
|
|
const cardPath = `/ui/DefaultGroup/RewardHud/Reward${i}`;
|
|
reward.push(entity({
|
|
id: guid('rwd', rwdN++),
|
|
path: cardPath,
|
|
modelId: 'uisprite',
|
|
entryId: 'UISprite',
|
|
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent',
|
|
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 }),
|
|
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 dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : 8;
|
|
reward.push(entity({
|
|
id: guid('rwd', rwdN++),
|
|
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 }),
|
|
],
|
|
}));
|
|
}
|
|
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 }),
|
|
],
|
|
}));
|
|
}
|
|
}
|
|
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 }),
|
|
sprite({ color: { r: 0.05, g: 0.06, b: 0.09, a: 0.9 }, type: 1, raycast: true }),
|
|
],
|
|
});
|
|
mapHud.jsonString.enable = false;
|
|
map.push(mapHud);
|
|
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: 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: 510 } }),
|
|
sprite({ color: TRANSPARENT }),
|
|
text({ value: '다음 노드 선택', fontSize: 40, bold: true, color: GOLD, alignment: 4 }),
|
|
],
|
|
}));
|
|
let mapN = 2;
|
|
for (const [id, node] of Object.entries(MAP.nodes)) {
|
|
const nodePath = `/ui/DefaultGroup/MapHud/Node_${id}`;
|
|
const pos = { x: node.col * 180, y: (node.row - (MAX_ROW + 1) / 2) * 140 };
|
|
map.push(entity({
|
|
id: guid('map', mapN++),
|
|
path: nodePath,
|
|
modelId: 'uisprite',
|
|
entryId: 'UISprite',
|
|
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent',
|
|
displayOrder: node.row,
|
|
components: [
|
|
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 150, y: 80 }, pos }),
|
|
sprite({ color: { r: 0.3, g: 0.55, b: 0.85, a: 1 }, type: 1, raycast: true }),
|
|
button(),
|
|
],
|
|
}));
|
|
map.push(entity({
|
|
id: guid('map', mapN++),
|
|
path: `${nodePath}/Label`,
|
|
modelId: 'uitext',
|
|
entryId: 'UIText',
|
|
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
|
displayOrder: 0,
|
|
components: [
|
|
transform({ parentW: 150, parentH: 80, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 144, y: 72 }, pos: { x: 0, y: 0 } }),
|
|
sprite({ color: TRANSPARENT }),
|
|
text({ value: node.enemy ? `${TYPE_KO[node.type]}\n${ENEMIES.enemies[node.enemy].name}` : TYPE_KO[node.type], fontSize: 20, bold: true }),
|
|
],
|
|
}));
|
|
}
|
|
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 }),
|
|
],
|
|
}));
|
|
let shpN = 3;
|
|
const shopXs = [-300, 0, 300];
|
|
for (let i = 1; i <= 3; i++) {
|
|
const cardPath = `/ui/DefaultGroup/ShopHud/Card${i}`;
|
|
shop.push(entity({
|
|
id: guid('shp', shpN++),
|
|
path: cardPath,
|
|
modelId: 'uisprite',
|
|
entryId: 'UISprite',
|
|
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent',
|
|
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 }),
|
|
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 dOrder = suffix === 'Cost' ? 7 : suffix === 'Name' ? 6 : suffix === 'Desc' ? 8 : 9;
|
|
shop.push(entity({
|
|
id: guid('shp', shpN++),
|
|
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 }),
|
|
],
|
|
}));
|
|
}
|
|
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: '/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/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: -300 } }),
|
|
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);
|
|
|
|
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 }),
|
|
],
|
|
}));
|
|
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', 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', label: '\uB3C4\uC801', desc: '\uCD94\uD6C4 \uC5F4\uB9BC', x: 0, enabled: false, tint: { r: 0.18, g: 0.19, b: 0.21, a: 1 } },
|
|
{ key: 'Mage', label: '\uB9C8\uBC95\uC0AC', desc: '\uCD94\uD6C4 \uC5F4\uB9BC', x: 360, enabled: false, tint: { r: 0.18, g: 0.19, b: 0.21, 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', 120 + i),
|
|
path: `${base}/Name`,
|
|
modelId: 'uitext',
|
|
entryId: 'UIText',
|
|
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
|
displayOrder: 0,
|
|
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: 108 } }),
|
|
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 }),
|
|
],
|
|
}));
|
|
select.push(entity({
|
|
id: guid('menu', 130 + i),
|
|
path: `${base}/Portrait`,
|
|
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: 142, y: 142 }, pos: { x: 0, y: 8 } }),
|
|
sprite({ color: cls.tint, type: 1 }),
|
|
],
|
|
}));
|
|
select.push(entity({
|
|
id: guid('menu', 140 + i),
|
|
path: `${base}/Desc`,
|
|
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: 50 }, pos: { x: 0, y: -105 } }),
|
|
sprite({ color: TRANSPARENT }),
|
|
text({ value: cls.desc, fontSize: 20, color: cls.enabled ? { r: 0.86, g: 0.9, b: 0.94, a: 1 } : { r: 0.52, g: 0.55, b: 0.59, 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', 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[0].jsonString.enable = false;
|
|
emit('MainMenu', menu);
|
|
emit('CharacterSelectHud', select);
|
|
|
|
for (const section of UI_APPEND_ORDER) {
|
|
const entities = uiSections.get(section);
|
|
if (entities == null) {
|
|
throw new Error(`[gen-slaydeck] missing generated UI section: ${section}`);
|
|
}
|
|
appendUiSection(ui, section, entities);
|
|
}
|
|
|
|
JSON.parse(JSON.stringify(ui));
|
|
writeFileSync(UI_FILE, JSON.stringify(ui, null, 2), 'utf8');
|
|
}
|
|
|
|
function prop(Type, Name, DefaultValue = 'nil') {
|
|
return { Type, DefaultValue, SyncDirection: 0, Attributes: [], Name };
|
|
}
|
|
|
|
function method(Name, Code, Arguments = [], ExecSpace = 0, ReturnType = 'void') {
|
|
return {
|
|
Return: { Type: ReturnType, DefaultValue: null, SyncDirection: 0, Attributes: [], Name: null },
|
|
Arguments,
|
|
Code,
|
|
Scope: 2,
|
|
ExecSpace,
|
|
Attributes: [],
|
|
Name,
|
|
};
|
|
}
|
|
|
|
function codeblock(id, name, properties, methods) {
|
|
return {
|
|
Id: '',
|
|
GameId: '',
|
|
EntryKey: `codeblock://${id}`,
|
|
ContentType: 'x-mod/codeblock',
|
|
Content: '',
|
|
Usage: 0,
|
|
UsePublish: 1,
|
|
UseService: 0,
|
|
CoreVersion: '26.5.0.0',
|
|
StudioVersion: '',
|
|
DynamicLoading: 0,
|
|
ContentProto: {
|
|
Use: 'Json',
|
|
Json: {
|
|
CoreVersion: { Major: 0, Minor: 2 },
|
|
ScriptVersion: { Major: 1, Minor: 0 },
|
|
Description: '',
|
|
Id: id,
|
|
Language: 1,
|
|
Name: name,
|
|
Type: 1,
|
|
Source: 0,
|
|
Target: null,
|
|
Properties: properties,
|
|
Methods: methods,
|
|
EntityEventHandlers: [],
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
function writeCodeblocks() {
|
|
const RUN_LENGTH = 3;
|
|
const GOLD_PER_WIN = 15;
|
|
const CARD_PRICE = 30;
|
|
const REST_HEAL = 30;
|
|
const RELIC_PRICE = 60;
|
|
const ACT_COUNT = 3;
|
|
const ACT_MAPS = ['map01', 'map02', 'map03'];
|
|
const combat = codeblock('SlayDeckController', 'SlayDeckController', [
|
|
prop('any', 'DrawPile'),
|
|
prop('any', 'DiscardPile'),
|
|
prop('any', 'Hand'),
|
|
prop('number', 'Energy', '0'),
|
|
prop('number', 'MaxEnergy', '3'),
|
|
prop('number', 'Turn', '0'),
|
|
prop('number', 'TweenEventId', '0'),
|
|
prop('any', 'EndTurnHandler'),
|
|
prop('any', 'NewGameHandler'),
|
|
prop('any', 'WarriorSelectHandler'),
|
|
prop('any', 'StartGameHandler'),
|
|
prop('string', 'SelectedClass', '""'),
|
|
prop('any', 'DrawPileHandler'),
|
|
prop('any', 'DiscardPileHandler'),
|
|
prop('any', 'DeckInspectCloseHandler'),
|
|
prop('any', 'AllDeckHandler'),
|
|
prop('any', 'AllDeckCloseHandler'),
|
|
prop('string', 'DeckInspectKind', '""'),
|
|
prop('boolean', 'DeckAllOpen', 'false'),
|
|
prop('any', 'Cards'),
|
|
prop('number', 'PlayerHp', '0'),
|
|
prop('number', 'PlayerMaxHp', '80'),
|
|
prop('number', 'PlayerBlock', '0'),
|
|
prop('boolean', 'CombatOver', 'false'),
|
|
prop('any', 'Monsters'),
|
|
prop('any', 'Registered'),
|
|
prop('number', 'TargetIndex', '1'),
|
|
prop('any', 'RunDeck'),
|
|
prop('number', 'Gold', '0'),
|
|
prop('number', 'Floor', '0'),
|
|
prop('number', 'RunLength', String(RUN_LENGTH)),
|
|
prop('any', 'RewardChoices'),
|
|
prop('boolean', 'RunActive', 'false'),
|
|
prop('any', 'Enemies'),
|
|
prop('any', 'MapNodes'),
|
|
prop('any', 'MapStart'),
|
|
prop('string', 'CurrentNodeId', '""'),
|
|
prop('string', 'CurrentEnemyId', '""'),
|
|
prop('any', 'ShopChoices'),
|
|
prop('any', 'ShopBought'),
|
|
prop('any', 'Relics'),
|
|
prop('any', 'RunRelics'),
|
|
prop('any', 'RelicPool'),
|
|
prop('string', 'ShopRelic', '""'),
|
|
prop('boolean', 'ShopRelicBought', 'false'),
|
|
prop('number', 'DragSlot', '0'),
|
|
prop('boolean', 'FxBusy', 'false'),
|
|
prop('boolean', 'TurnBusy', 'false'),
|
|
], [
|
|
method('OnBeginPlay', `self:ShowMainMenu()`),
|
|
method('HideGameHud', `self:SetEntityEnabled("/ui/DefaultGroup/Button_Attack", false)
|
|
self:SetEntityEnabled("/ui/DefaultGroup/Button_Jump", false)
|
|
self:SetEntityEnabled("/ui/DefaultGroup/UIJoystick", false)
|
|
self:SetEntityEnabled("/ui/DefaultGroup/DeckHud", false)
|
|
self:SetEntityEnabled("/ui/DefaultGroup/CardHand", false)
|
|
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud", false)
|
|
self:SetEntityEnabled("/ui/DefaultGroup/RewardHud", false)
|
|
self:SetEntityEnabled("/ui/DefaultGroup/MapHud", false)
|
|
self:SetEntityEnabled("/ui/DefaultGroup/ShopHud", false)
|
|
self:SetEntityEnabled("/ui/DefaultGroup/RestHud", false)
|
|
self:SetEntityEnabled("/ui/DefaultGroup/DeckInspectHud", false)
|
|
self:SetEntityEnabled("/ui/DefaultGroup/DeckAllHud", false)`),
|
|
method('ShowState', `self:HideGameHud()
|
|
self:SetEntityEnabled("/ui/DefaultGroup/MainMenu", state == "menu")
|
|
self:SetEntityEnabled("/ui/DefaultGroup/CharacterSelectHud", state == "charselect")
|
|
if state == "map" then
|
|
self:SetEntityEnabled("/ui/DefaultGroup/MapHud", true)
|
|
elseif state == "combat" then
|
|
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud", true)
|
|
self:SetEntityEnabled("/ui/DefaultGroup/DeckHud", true)
|
|
self:SetEntityEnabled("/ui/DefaultGroup/CardHand", true)
|
|
elseif state == "shop" then
|
|
self:SetEntityEnabled("/ui/DefaultGroup/ShopHud", true)
|
|
elseif state == "rest" then
|
|
self:SetEntityEnabled("/ui/DefaultGroup/RestHud", true)
|
|
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'state' }]),
|
|
method('ShowMainMenu', `self.SelectedClass = ""
|
|
self:ShowState("menu")
|
|
self:SetText("/ui/DefaultGroup/MainMenu/Title", "메이플 덱 어드벤처")
|
|
self:SetText("/ui/DefaultGroup/MainMenu/Subtitle", "캐릭터를 고르고 덱을 만들어 모험을 시작하세요")
|
|
self:SetText("/ui/DefaultGroup/MainMenu/NewGameButton", "새 게임")
|
|
self:BindMenuButtons()`),
|
|
method('BindMenuButtons', `local buttonEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/NewGameButton")
|
|
if buttonEntity ~= nil and buttonEntity.ButtonComponent ~= nil then
|
|
if self.NewGameHandler ~= nil then
|
|
buttonEntity:DisconnectEvent(ButtonClickEvent, self.NewGameHandler)
|
|
self.NewGameHandler = nil
|
|
end
|
|
self.NewGameHandler = buttonEntity:ConnectEvent(ButtonClickEvent, function() self:ShowCharacterSelect() end)
|
|
end
|
|
local warrior = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/WarriorButton")
|
|
if warrior ~= nil and warrior.ButtonComponent ~= nil then
|
|
if self.WarriorSelectHandler ~= nil then
|
|
warrior:DisconnectEvent(ButtonClickEvent, self.WarriorSelectHandler)
|
|
self.WarriorSelectHandler = nil
|
|
end
|
|
self.WarriorSelectHandler = warrior:ConnectEvent(ButtonClickEvent, function() self:SelectClass("warrior") end)
|
|
end
|
|
local start = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/StartButton")
|
|
if start ~= nil and start.ButtonComponent ~= nil then
|
|
if self.StartGameHandler ~= nil then
|
|
start:DisconnectEvent(ButtonClickEvent, self.StartGameHandler)
|
|
self.StartGameHandler = nil
|
|
end
|
|
self.StartGameHandler = start:ConnectEvent(ButtonClickEvent, function() self:StartNewGame() end)
|
|
end`),
|
|
method('ShowCharacterSelect', `self.SelectedClass = ""
|
|
self:ShowState("charselect")
|
|
self:RenderCharacterSelect()`),
|
|
method('SelectClass', `self.SelectedClass = className
|
|
self:RenderCharacterSelect()`, [
|
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'className' },
|
|
]),
|
|
method('RenderCharacterSelect', `local warrior = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/WarriorButton")
|
|
if warrior ~= nil and warrior.SpriteGUIRendererComponent ~= nil then
|
|
if self.SelectedClass == "warrior" then
|
|
warrior.SpriteGUIRendererComponent.Color = Color(0.28, 0.36, 0.46, 1)
|
|
else
|
|
warrior.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)
|
|
end
|
|
end
|
|
if self.SelectedClass == "warrior" then
|
|
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "전사 선택됨")
|
|
else
|
|
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "전사를 선택하고 시작하세요")
|
|
end`),
|
|
method('StartNewGame', `if self.SelectedClass ~= "warrior" then
|
|
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "현재는 전사만 선택할 수 있습니다")
|
|
return
|
|
end
|
|
self:StartRun()`),
|
|
method('SetEntityEnabled', `local e = _EntityService:GetEntityByPath(path)
|
|
if e ~= nil then
|
|
e.Enable = enabled
|
|
end`, [
|
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'path' },
|
|
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'enabled' },
|
|
]),
|
|
method('StartRun', `self.PlayerMaxHp = 80
|
|
self.PlayerHp = self.PlayerMaxHp
|
|
self.Gold = 0
|
|
self.Floor = 1
|
|
self.RunLength = ${ACT_COUNT}
|
|
self.RunDeck = { ${CARDS.starterDeck.map(luaStr).join(', ')} }
|
|
self.RunActive = true
|
|
self.RunRelics = {}
|
|
${luaRelicsTable(RELICS.relics)}
|
|
self.RelicPool = { ${RELICS.relicPool.map(luaStr).join(', ')} }
|
|
${luaEnemiesTable(ENEMIES.enemies)}
|
|
${luaMapNodesTable(MAP.nodes)}
|
|
${luaStartArray(MAP.start)}
|
|
self.CurrentNodeId = ""
|
|
self.CurrentEnemyId = ""
|
|
self:BindButtons()
|
|
self:AddRelic("${RELICS.startingRelic}")
|
|
self:ShowMap()`),
|
|
method('StartCombat', `self:ShowState("combat")
|
|
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/Result", false)
|
|
self.MaxEnergy = 3
|
|
self.Turn = 0
|
|
self.PlayerBlock = 0
|
|
self.CombatOver = false
|
|
self.DiscardPile = {}
|
|
self.Hand = {}
|
|
${luaCardsTable(CARDS.cards)}
|
|
self.DrawPile = {}
|
|
for i = 1, #self.RunDeck do
|
|
self.DrawPile[i] = self.RunDeck[i]
|
|
end
|
|
self:Shuffle(self.DrawPile)
|
|
self:BuildMonsters()
|
|
self:RenderCombat()
|
|
self:StartPlayerTurn()
|
|
self:ApplyRelics("combatStart")
|
|
self:RenderCombat()`),
|
|
method('RegisterMonster', `if self.Registered == nil then
|
|
self.Registered = {}
|
|
end
|
|
local g = group
|
|
if g == nil or g == "" then g = "combat" end
|
|
local mp = mapName
|
|
if mp == nil then mp = "" end
|
|
table.insert(self.Registered, { entity = monster, enemyId = enemyId, group = g, map = mp })`, [
|
|
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'monster' },
|
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'enemyId' },
|
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'group' },
|
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'mapName' },
|
|
]),
|
|
method('BuildMonsters', `self.Monsters = {}
|
|
local g = "combat"
|
|
local node = self.MapNodes[self.CurrentNodeId]
|
|
if node ~= nil and node.type ~= nil then g = node.type end
|
|
local pmap = ""
|
|
local lp = _UserService.LocalPlayer
|
|
if lp ~= nil and lp.CurrentMapName ~= nil then pmap = lp.CurrentMapName end
|
|
local reg = self.Registered or {}
|
|
for i = 1, #reg do
|
|
if reg[i].entity ~= nil and isvalid(reg[i].entity) then
|
|
reg[i].entity:SetVisible(false)
|
|
end
|
|
end
|
|
local list = {}
|
|
for i = 1, #reg do
|
|
local r = reg[i]
|
|
if r.entity ~= nil and isvalid(r.entity) and r.group == g and (r.map == nil or r.map == "" or pmap == "" or r.map == pmap) then
|
|
local x = 0
|
|
if r.entity.TransformComponent ~= nil then
|
|
x = r.entity.TransformComponent.WorldPosition.x
|
|
end
|
|
table.insert(list, { entity = r.entity, enemyId = r.enemyId, x = x })
|
|
end
|
|
end
|
|
table.sort(list, function(a, b) return a.x < b.x end)
|
|
local mult = 1 + (self.Floor - 1) * 0.6
|
|
local n = #list
|
|
if n > ${MAX_MONSTERS} then n = ${MAX_MONSTERS} end
|
|
for i = 1, n do
|
|
local item = list[i]
|
|
local e = self.Enemies[item.enemyId]
|
|
if e == nil then e = { name = item.enemyId, maxHp = 10, intents = { { kind = "Attack", value = 5 } } } end
|
|
local intents = {}
|
|
for k = 1, #e.intents do
|
|
intents[k] = { kind = e.intents[k].kind, value = math.floor(e.intents[k].value * mult) }
|
|
end
|
|
local maxHp = math.floor(e.maxHp * mult)
|
|
self.Monsters[i] = { entity = item.entity, enemyId = item.enemyId, name = e.name,
|
|
hp = maxHp, maxHp = maxHp, block = 0, intents = intents, intentIdx = 1, alive = true, slot = i }
|
|
self:ReviveMonsterEntity(item.entity)
|
|
self:PositionMonsterSlot(i)
|
|
end
|
|
self.TargetIndex = 1`),
|
|
method('ReviveMonsterEntity', `if monster == nil or not isvalid(monster) then
|
|
return
|
|
end
|
|
monster:SetEnable(true)
|
|
monster:SetVisible(true)`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'monster' }]),
|
|
method('Shuffle', `if list == nil then
|
|
\treturn
|
|
end
|
|
for i = #list, 2, -1 do
|
|
\tlocal j = math.random(1, i)
|
|
\tlist[i], list[j] = list[j], list[i]
|
|
end`, [{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'list' }]),
|
|
method('BindButtons', `local endTurn = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckHud/EndTurnButton")
|
|
if endTurn ~= nil and endTurn.ButtonComponent ~= nil then
|
|
if self.EndTurnHandler ~= nil then
|
|
endTurn:DisconnectEvent(ButtonClickEvent, self.EndTurnHandler)
|
|
self.EndTurnHandler = nil
|
|
end
|
|
self.EndTurnHandler = endTurn:ConnectEvent(ButtonClickEvent, function() self:EndPlayerTurn() end)
|
|
end
|
|
local drawPile = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckHud/DrawPile")
|
|
if drawPile ~= nil and drawPile.ButtonComponent ~= nil then
|
|
if self.DrawPileHandler ~= nil then
|
|
drawPile:DisconnectEvent(ButtonClickEvent, self.DrawPileHandler)
|
|
self.DrawPileHandler = nil
|
|
end
|
|
self.DrawPileHandler = drawPile:ConnectEvent(ButtonClickEvent, function() self:OpenDeckInspect("draw") end)
|
|
end
|
|
local discardPile = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckHud/DiscardPile")
|
|
if discardPile ~= nil and discardPile.ButtonComponent ~= nil then
|
|
if self.DiscardPileHandler ~= nil then
|
|
discardPile:DisconnectEvent(ButtonClickEvent, self.DiscardPileHandler)
|
|
self.DiscardPileHandler = nil
|
|
end
|
|
self.DiscardPileHandler = discardPile:ConnectEvent(ButtonClickEvent, function() self:OpenDeckInspect("discard") end)
|
|
end
|
|
local inspectClose = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud/Close")
|
|
if inspectClose ~= nil and inspectClose.ButtonComponent ~= nil then
|
|
if self.DeckInspectCloseHandler ~= nil then
|
|
inspectClose:DisconnectEvent(ButtonClickEvent, self.DeckInspectCloseHandler)
|
|
self.DeckInspectCloseHandler = nil
|
|
end
|
|
self.DeckInspectCloseHandler = inspectClose:ConnectEvent(ButtonClickEvent, function() self:CloseDeckInspect() end)
|
|
end
|
|
local allDeckButton = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/TopBar/AllDeckButton")
|
|
if allDeckButton ~= nil and allDeckButton.ButtonComponent ~= nil then
|
|
if self.AllDeckHandler ~= nil then
|
|
allDeckButton:DisconnectEvent(ButtonClickEvent, self.AllDeckHandler)
|
|
self.AllDeckHandler = nil
|
|
end
|
|
self.AllDeckHandler = allDeckButton:ConnectEvent(ButtonClickEvent, function() self:OpenAllDeck() end)
|
|
end
|
|
local allDeckClose = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/Close")
|
|
if allDeckClose ~= nil and allDeckClose.ButtonComponent ~= nil then
|
|
if self.AllDeckCloseHandler ~= nil then
|
|
allDeckClose:DisconnectEvent(ButtonClickEvent, self.AllDeckCloseHandler)
|
|
self.AllDeckCloseHandler = nil
|
|
end
|
|
self.AllDeckCloseHandler = allDeckClose:ConnectEvent(ButtonClickEvent, function() self:CloseAllDeck() end)
|
|
end
|
|
for i = 1, 5 do
|
|
local cardEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(i))
|
|
if cardEntity ~= nil and cardEntity.UITouchReceiveComponent ~= nil then
|
|
cardEntity:ConnectEvent(UITouchBeginDragEvent, function(ev) self:OnCardDragBegin(i) end)
|
|
cardEntity:ConnectEvent(UITouchDragEvent, function(ev) self:OnCardDrag(i, ev.TouchPoint) end)
|
|
cardEntity:ConnectEvent(UITouchEndDragEvent, function(ev) self:OnCardDragEnd(i, ev.TouchPoint) end)
|
|
end
|
|
end
|
|
for i = 1, 3 do
|
|
local rc = _EntityService:GetEntityByPath("/ui/DefaultGroup/RewardHud/Reward" .. tostring(i))
|
|
if rc ~= nil and rc.ButtonComponent ~= nil then
|
|
rc:ConnectEvent(ButtonClickEvent, function() self:PickReward(i) end)
|
|
end
|
|
end
|
|
local skip = _EntityService:GetEntityByPath("/ui/DefaultGroup/RewardHud/Skip")
|
|
if skip ~= nil and skip.ButtonComponent ~= nil then
|
|
skip:ConnectEvent(ButtonClickEvent, function() self:PickReward(0) end)
|
|
end
|
|
local mapNodeIds = { ${Object.keys(MAP.nodes).map(luaStr).join(', ')} }
|
|
for i = 1, #mapNodeIds do
|
|
local nid = mapNodeIds[i]
|
|
local mn = _EntityService:GetEntityByPath("/ui/DefaultGroup/MapHud/Node_" .. nid)
|
|
if mn ~= nil and mn.ButtonComponent ~= nil then
|
|
mn:ConnectEvent(ButtonClickEvent, function() self:PickNode(nid) end)
|
|
end
|
|
end
|
|
for i = 1, 3 do
|
|
local sc = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Card" .. tostring(i))
|
|
if sc ~= nil and sc.ButtonComponent ~= nil then
|
|
sc:ConnectEvent(ButtonClickEvent, function() self:BuyCard(i) end)
|
|
end
|
|
end
|
|
local shopLeave = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Leave")
|
|
if shopLeave ~= nil and shopLeave.ButtonComponent ~= nil then
|
|
shopLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)
|
|
end
|
|
local shopRelic = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Relic")
|
|
if shopRelic ~= nil and shopRelic.ButtonComponent ~= nil then
|
|
shopRelic:ConnectEvent(ButtonClickEvent, function() self:BuyRelic() end)
|
|
end
|
|
local restLeave = _EntityService:GetEntityByPath("/ui/DefaultGroup/RestHud/Leave")
|
|
if restLeave ~= nil and restLeave.ButtonComponent ~= nil then
|
|
restLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)
|
|
end
|
|
for i = 1, ${MAX_MONSTERS} do
|
|
local ms = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(i))
|
|
if ms ~= nil and ms.ButtonComponent ~= nil then
|
|
ms:ConnectEvent(ButtonClickEvent, function() self:SetTarget(i) end)
|
|
end
|
|
end`),
|
|
method('StartPlayerTurn', `self.Turn = self.Turn + 1
|
|
self.Energy = self.MaxEnergy
|
|
self:ApplyRelics("turnStart")
|
|
self.PlayerBlock = 0
|
|
self:DrawCards(5)
|
|
self:RenderHand(true)
|
|
self:RenderCombat()`),
|
|
method('EndPlayerTurn', `if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then
|
|
return
|
|
end
|
|
for i = 1, #self.Hand do
|
|
\ttable.insert(self.DiscardPile, self.Hand[i])
|
|
end
|
|
self.Hand = {}
|
|
self:RenderHand(false)
|
|
self:RenderPiles()
|
|
self:EnemyTurn()`),
|
|
method('DrawCards', `for i = 1, amount do
|
|
\tif #self.DrawPile <= 0 then
|
|
\t\tself:RecycleDiscardIntoDraw()
|
|
\tend
|
|
\tif #self.DrawPile <= 0 then
|
|
\t\tbreak
|
|
\tend
|
|
\tlocal cardId = table.remove(self.DrawPile)
|
|
\ttable.insert(self.Hand, cardId)
|
|
end
|
|
self:RenderPiles()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }]),
|
|
method('RecycleDiscardIntoDraw', `if self.DiscardPile == nil or #self.DiscardPile <= 0 then
|
|
\treturn
|
|
end
|
|
self.DrawPile = {}
|
|
for i = 1, #self.DiscardPile do
|
|
\tself.DrawPile[i] = self.DiscardPile[i]
|
|
end
|
|
self.DiscardPile = {}
|
|
self:Shuffle(self.DrawPile)`),
|
|
method('RenderPiles', `self:SetText("/ui/DefaultGroup/DeckHud/DrawPile/Count", tostring(#self.DrawPile))
|
|
self:SetText("/ui/DefaultGroup/DeckHud/DiscardPile/Count", tostring(#self.DiscardPile))
|
|
self:SetText("/ui/DefaultGroup/DeckHud/EnergyOrb/Value", string.format("%d", self.Energy) .. "/" .. string.format("%d", self.MaxEnergy))
|
|
local inspect = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud")
|
|
if inspect ~= nil and inspect.Enable == true and self.DeckInspectKind ~= "" then
|
|
self:OpenDeckInspect(self.DeckInspectKind)
|
|
end`),
|
|
method('OpenDeckInspect', `self.DeckInspectKind = kind
|
|
if self.DeckAllOpen == true then
|
|
self.DeckAllOpen = false
|
|
local allHud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud")
|
|
if allHud ~= nil then
|
|
allHud.Enable = false
|
|
end
|
|
end
|
|
local pile = {}
|
|
local title = ""
|
|
if kind == "discard" then
|
|
pile = self.DiscardPile or {}
|
|
title = "버린 덱"
|
|
else
|
|
pile = self.DrawPile or {}
|
|
title = "뽑을 덱"
|
|
end
|
|
self:RenderDeckInspect(pile, title)
|
|
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud")
|
|
if hud ~= nil then
|
|
hud.Enable = true
|
|
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'kind' }]),
|
|
method('CloseDeckInspect', `self.DeckInspectKind = ""
|
|
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud")
|
|
if hud ~= nil then
|
|
hud.Enable = false
|
|
end`),
|
|
method('RenderDeckInspect', `local count = 0
|
|
if pile ~= nil then
|
|
count = #pile
|
|
end
|
|
local suffix = " (" .. tostring(count) .. ")"
|
|
if count > 60 then
|
|
suffix = suffix .. " - 60장까지 표시"
|
|
end
|
|
self:SetText("/ui/DefaultGroup/DeckInspectHud/Title", title .. suffix)
|
|
local empty = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud/Empty")
|
|
if empty ~= nil then
|
|
empty.Enable = count <= 0
|
|
end
|
|
for i = 1, 60 do
|
|
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud/Grid/Card" .. tostring(i))
|
|
if e ~= nil then
|
|
local cardId = nil
|
|
if pile ~= nil then
|
|
cardId = pile[i]
|
|
end
|
|
if cardId == nil then
|
|
e.Enable = false
|
|
else
|
|
e.Enable = true
|
|
self:ApplyInspectCardVisual(i, cardId)
|
|
end
|
|
end
|
|
end`, [
|
|
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'pile' },
|
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'title' },
|
|
]),
|
|
method('ApplyInspectCardVisual', `self:ApplyCardFace("/ui/DefaultGroup/DeckInspectHud/Grid/Card" .. tostring(slot), cardId)`, [
|
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
|
|
]),
|
|
method('OpenAllDeck', `local inspectHud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckInspectHud")
|
|
if inspectHud ~= nil then
|
|
inspectHud.Enable = false
|
|
end
|
|
self.DeckInspectKind = ""
|
|
self.DeckAllOpen = true
|
|
self:RenderAllDeck()
|
|
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud")
|
|
if hud ~= nil then
|
|
hud.Enable = true
|
|
end`),
|
|
method('CloseAllDeck', `self.DeckAllOpen = false
|
|
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud")
|
|
if hud ~= nil then
|
|
hud.Enable = false
|
|
end`),
|
|
method('RenderAllDeck', `local pile = self.RunDeck or {}
|
|
local count = #pile
|
|
self:SetText("/ui/DefaultGroup/DeckAllHud/Title", "모든 덱 (" .. tostring(count) .. ")")
|
|
local empty = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/Empty")
|
|
if empty ~= nil then
|
|
empty.Enable = count <= 0
|
|
end
|
|
for i = 1, 120 do
|
|
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/DeckAllHud/Grid/Card" .. tostring(i))
|
|
if e ~= nil then
|
|
local cardId = pile[i]
|
|
if cardId == nil then
|
|
e.Enable = false
|
|
else
|
|
e.Enable = true
|
|
self:ApplyAllDeckCardVisual(i, cardId)
|
|
end
|
|
end
|
|
end`),
|
|
method('ApplyAllDeckCardVisual', `self:ApplyCardFace("/ui/DefaultGroup/DeckAllHud/Grid/Card" .. tostring(slot), cardId)`, [
|
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
|
|
]),
|
|
method('RenderHand', `local drawStart = Vector2(-590, 8)
|
|
for i = 1, 5 do
|
|
\tlocal cardEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(i))
|
|
\tif cardEntity ~= nil then
|
|
\t\tlocal cardId = self.Hand[i]
|
|
\t\tif cardId == nil then
|
|
\t\t\tcardEntity.Enable = false
|
|
\t\telse
|
|
\t\t\tcardEntity.Enable = true
|
|
\t\t\tself:ApplyCardVisual(i, cardId)
|
|
\t\t\tif animate == true then
|
|
\t\t\t\tself:AnimateCardFrom(i, drawStart, Vector2((i - 3) * 200, 0), 0.16 + i * 0.045)
|
|
\t\t\tend
|
|
\t\tend
|
|
\tend
|
|
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" }
|
|
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 == "Skill" then
|
|
e.SpriteGUIRendererComponent.Color = Color(0.42, 0.55, 0.85, 1)
|
|
else
|
|
e.SpriteGUIRendererComponent.Color = Color(0.46, 0.68, 0.52, 1)
|
|
end
|
|
end
|
|
self:SetText(base .. "/Cost", string.format("%d", c.cost))
|
|
self:SetText(base .. "/Name", c.name)
|
|
self:SetText(base .. "/Desc", c.desc)
|
|
local art = _EntityService:GetEntityByPath(base .. "/Art")
|
|
if art ~= nil then
|
|
if c.image ~= nil and c.image ~= "" then
|
|
art.Enable = true
|
|
if art.SpriteGUIRendererComponent ~= nil then
|
|
art.SpriteGUIRendererComponent.ImageRUID = c.image
|
|
end
|
|
else
|
|
art.Enable = false
|
|
end
|
|
end`, [
|
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'base' },
|
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
|
|
]),
|
|
method('ApplyCardVisual', `self:ApplyCardFace("/ui/DefaultGroup/CardHand/Card" .. tostring(slot), cardId)`, [
|
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
|
|
]),
|
|
method('SetText', `local entity = _EntityService:GetEntityByPath(path)
|
|
if entity ~= nil and entity.TextComponent ~= nil then
|
|
\tentity.TextComponent.Text = value
|
|
end`, [
|
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'path' },
|
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'value' },
|
|
]),
|
|
method('AnimateCardFrom', `local cardEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(slot))
|
|
if cardEntity == nil or cardEntity.UITransformComponent == nil then
|
|
\treturn
|
|
end
|
|
local tr = cardEntity.UITransformComponent
|
|
tr.anchoredPosition = fromPos
|
|
local elapsed = 0
|
|
local eventId = 0
|
|
eventId = _TimerService:SetTimerRepeat(function()
|
|
\telapsed = elapsed + 1 / 60
|
|
\tlocal t = math.min(elapsed / duration, 1)
|
|
\tlocal eased = _TweenLogic:Ease(0, 1, 1, EaseType.SineEaseOut, t)
|
|
\ttr.anchoredPosition = Vector2(fromPos.x + (toPos.x - fromPos.x) * eased, fromPos.y + (toPos.y - fromPos.y) * eased)
|
|
\tif t >= 1 then
|
|
\t\t_TimerService:ClearTimer(eventId)
|
|
\tend
|
|
end, 1 / 60)`, [
|
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
|
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'fromPos' },
|
|
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'toPos' },
|
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'duration' },
|
|
]),
|
|
method('PlayCard', `if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then
|
|
return
|
|
end
|
|
if self.Hand == nil then
|
|
return
|
|
end
|
|
local cardId = self.Hand[slot]
|
|
if cardId == nil then
|
|
return
|
|
end
|
|
local c = self.Cards[cardId]
|
|
if c == nil then
|
|
return
|
|
end
|
|
if self.Energy < c.cost then
|
|
self:Toast("에너지가 부족합니다")
|
|
return
|
|
end
|
|
self.Energy = self.Energy - c.cost
|
|
if c.kind == "Attack" then
|
|
if c.damage ~= nil then
|
|
self:PlayAttackFx(self.TargetIndex, c.image, c.damage)
|
|
end
|
|
self:ApplyRelics("cardPlayed")
|
|
elseif c.kind == "Skill" then
|
|
if c.block ~= nil then
|
|
self.PlayerBlock = self.PlayerBlock + c.block
|
|
end
|
|
end
|
|
table.remove(self.Hand, slot)
|
|
table.insert(self.DiscardPile, cardId)
|
|
self:RenderHand(false)
|
|
self:RenderPiles()
|
|
self:RenderCombat()
|
|
self:CheckCombatEnd()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
|
method('OnCardDragBegin', `if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then
|
|
return
|
|
end
|
|
if self.Hand == nil or self.Hand[slot] == nil then
|
|
return
|
|
end
|
|
self.DragSlot = slot`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
|
method('OnCardDrag', `if self.DragSlot ~= slot then
|
|
return
|
|
end
|
|
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(slot))
|
|
if e ~= nil and e.UITransformComponent ~= nil then
|
|
local ui = _UILogic:ScreenToUIPosition(touchPoint)
|
|
e.UITransformComponent.anchoredPosition = Vector2(ui.x, ui.y + 360)
|
|
end`, [
|
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
|
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'touchPoint' },
|
|
]),
|
|
method('OnCardDragEnd', `if self.DragSlot ~= slot then
|
|
return
|
|
end
|
|
self.DragSlot = 0
|
|
local cardXs = { ${CARD_XS.join(', ')} }
|
|
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(slot))
|
|
if e ~= nil and e.UITransformComponent ~= nil then
|
|
e.UITransformComponent.anchoredPosition = Vector2(cardXs[slot], 0)
|
|
end
|
|
self:ResolveCardDrop(slot, touchPoint)`, [
|
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
|
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'touchPoint' },
|
|
]),
|
|
method('ResolveCardDrop', `if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then
|
|
return
|
|
end
|
|
local cardId = self.Hand[slot]
|
|
if cardId == nil then
|
|
return
|
|
end
|
|
local c = self.Cards[cardId]
|
|
if c == nil then
|
|
return
|
|
end
|
|
if c.kind == "Attack" then
|
|
local best = 0
|
|
local bestDist = 200
|
|
for i = 1, #self.Monsters do
|
|
local m = self.Monsters[i]
|
|
if m.alive == true and m.entity ~= nil and isvalid(m.entity) and m.entity.TransformComponent ~= nil then
|
|
local wp = m.entity.TransformComponent.WorldPosition
|
|
local sp = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + 0.7))
|
|
local dx = sp.x - touchPoint.x
|
|
local dy = sp.y - touchPoint.y
|
|
local d = math.sqrt(dx * dx + dy * dy)
|
|
if d < bestDist then
|
|
bestDist = d
|
|
best = i
|
|
end
|
|
end
|
|
end
|
|
if best > 0 then
|
|
self.TargetIndex = best
|
|
self:PlayCard(slot)
|
|
end
|
|
else
|
|
local ui = _UILogic:ScreenToUIPosition(touchPoint)
|
|
if ui.y > -180 then
|
|
self:PlayCard(slot)
|
|
end
|
|
end`, [
|
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
|
{ Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'touchPoint' },
|
|
]),
|
|
method('Toast', `log(message)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'message' }]),
|
|
method('DealDamageToTarget', `local m = self.Monsters[self.TargetIndex]
|
|
if m == nil or m.alive ~= true then
|
|
m = nil
|
|
for i = 1, #self.Monsters do
|
|
if self.Monsters[i].alive == true then m = self.Monsters[i]; self.TargetIndex = i; break end
|
|
end
|
|
end
|
|
if m == nil then
|
|
return
|
|
end
|
|
local dmg = amount
|
|
if m.block > 0 then
|
|
local absorbed = math.min(m.block, dmg)
|
|
m.block = m.block - absorbed
|
|
dmg = dmg - absorbed
|
|
end
|
|
m.hp = m.hp - dmg
|
|
if m.hp <= 0 then
|
|
m.hp = 0
|
|
self:KillMonster(m.slot)
|
|
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }]),
|
|
method('PlayAttackFx', `local m = self.Monsters[targetIndex]
|
|
if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then
|
|
self:DealDamageToTarget(damage)
|
|
self:RenderCombat()
|
|
self:CheckCombatEnd()
|
|
return
|
|
end
|
|
self.FxBusy = true
|
|
local fx = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/SkillFx")
|
|
if fx ~= nil then
|
|
if fx.SpriteGUIRendererComponent ~= nil and image ~= nil and image ~= "" then
|
|
fx.SpriteGUIRendererComponent.ImageRUID = image
|
|
end
|
|
if fx.UITransformComponent ~= nil and m.entity.TransformComponent ~= nil then
|
|
local wp = m.entity.TransformComponent.WorldPosition
|
|
local sp = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + 0.7))
|
|
fx.UITransformComponent.anchoredPosition = _UILogic:ScreenToUIPosition(sp)
|
|
end
|
|
fx.Enable = true
|
|
end
|
|
_TimerService:SetTimerOnce(function()
|
|
if fx ~= nil then fx.Enable = false end
|
|
self.FxBusy = false
|
|
self:DealDamageToTarget(damage)
|
|
self:ShowDmgPop(targetIndex, damage)
|
|
self:RenderCombat()
|
|
self:CheckCombatEnd()
|
|
end, 0.35)`, [
|
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'targetIndex' },
|
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'image' },
|
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'damage' },
|
|
]),
|
|
method('KillMonster', `local m = self.Monsters[slot]
|
|
if m == nil then
|
|
return
|
|
end
|
|
m.alive = false
|
|
if m.entity ~= nil and isvalid(m.entity) then
|
|
local ent = m.entity
|
|
_TimerService:SetTimerOnce(function() if isvalid(ent) then ent:SetVisible(false) end end, 0.4)
|
|
end
|
|
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(slot), false)
|
|
for i = 1, #self.Monsters do
|
|
if self.Monsters[i].alive == true then self.TargetIndex = i; break end
|
|
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
|
method('DealDamageToPlayer', `local dmg = amount
|
|
if self.PlayerBlock > 0 then
|
|
local absorbed = math.min(self.PlayerBlock, dmg)
|
|
self.PlayerBlock = self.PlayerBlock - absorbed
|
|
dmg = dmg - absorbed
|
|
end
|
|
self.PlayerHp = self.PlayerHp - dmg
|
|
if self.PlayerHp < 0 then
|
|
self.PlayerHp = 0
|
|
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }]),
|
|
method('EnemyTurn', `self.TurnBusy = true
|
|
self:EnemyActStep(1)`),
|
|
method('EnemyActStep', `local idx = 0
|
|
for i = fromIndex, #self.Monsters do
|
|
if self.Monsters[i].alive == true then idx = i; break end
|
|
end
|
|
if idx == 0 or self.PlayerHp <= 0 then
|
|
self:FinishEnemyTurn()
|
|
return
|
|
end
|
|
local m = self.Monsters[idx]
|
|
local base = "/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(idx)
|
|
self:SetEntityEnabled(base .. "/ActFrame", true)
|
|
_TimerService:SetTimerOnce(function()
|
|
m.block = 0
|
|
local intent = m.intents[m.intentIdx]
|
|
if intent ~= nil then
|
|
if intent.kind == "Attack" then
|
|
local before = self.PlayerHp
|
|
self:DealDamageToPlayer(intent.value)
|
|
self:ShowPlayerDmgPop(before - self.PlayerHp)
|
|
elseif intent.kind == "Defend" then
|
|
m.block = m.block + intent.value
|
|
end
|
|
end
|
|
m.intentIdx = m.intentIdx + 1
|
|
if m.intentIdx > #m.intents then
|
|
m.intentIdx = 1
|
|
end
|
|
self:RenderCombat()
|
|
self:SetEntityEnabled(base .. "/ActFrame", false)
|
|
_TimerService:SetTimerOnce(function() self:EnemyActStep(idx + 1) end, 0.15)
|
|
end, 0.45)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'fromIndex' }]),
|
|
method('FinishEnemyTurn', `self.TurnBusy = false
|
|
self:CheckCombatEnd()
|
|
if self.CombatOver == true then
|
|
return
|
|
end
|
|
_TimerService:SetTimerOnce(function() self:StartPlayerTurn() end, 0.45)`),
|
|
method('CheckCombatEnd', `local anyAlive = false
|
|
for i = 1, #self.Monsters do
|
|
if self.Monsters[i].alive == true then anyAlive = true; break end
|
|
end
|
|
if anyAlive == false then
|
|
self.CombatOver = true
|
|
self.Gold = self.Gold + ${GOLD_PER_WIN}
|
|
self:ApplyRelics("combatReward")
|
|
self:RenderRun()
|
|
local node = self.MapNodes[self.CurrentNodeId]
|
|
if node ~= nil and node.type == "elite" then
|
|
self:AddRelic(self.RelicPool[math.random(1, #self.RelicPool)])
|
|
end
|
|
if node ~= nil and node.type == "boss" then
|
|
if self.Floor < self.RunLength then
|
|
self.Floor = self.Floor + 1
|
|
self.CurrentNodeId = ""
|
|
self.CurrentEnemyId = ""
|
|
self:RenderRun()
|
|
self:TeleportToActMap()
|
|
self:ShowMap()
|
|
else
|
|
self:ShowResult("런 클리어!")
|
|
self.RunActive = false
|
|
end
|
|
else
|
|
self:OfferReward()
|
|
end
|
|
elseif self.PlayerHp <= 0 then
|
|
self.CombatOver = true
|
|
self:ShowResult("패배...")
|
|
self.RunActive = false
|
|
end`),
|
|
method('TeleportToActMap', `local maps = { ${ACT_MAPS.map((m) => `"${m}"`).join(', ')} }
|
|
local target = maps[self.Floor]
|
|
if target == nil then
|
|
return
|
|
end
|
|
local lp = _UserService.LocalPlayer
|
|
if lp == nil then
|
|
return
|
|
end
|
|
if lp.CurrentMapName == target then
|
|
return
|
|
end
|
|
_TeleportService:TeleportToMapPosition(lp, Vector3(-6, 0.03, 0), target)`),
|
|
method('ShowResult', `self:SetText("/ui/DefaultGroup/CombatHud/Result", text)
|
|
local entity = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/Result")
|
|
if entity ~= nil then
|
|
entity.Enable = true
|
|
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'text' }]),
|
|
method('RenderCombat', `for i = 1, ${MAX_MONSTERS} do
|
|
local base = "/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(i)
|
|
local m = self.Monsters[i]
|
|
if m ~= nil and m.alive == true then
|
|
self:SetEntityEnabled(base, true)
|
|
self:SetText(base .. "/Name", m.name)
|
|
self:SetText(base .. "/Hp", string.format("%d", m.hp) .. "/" .. string.format("%d", m.maxHp))
|
|
local intent = m.intents[m.intentIdx]
|
|
local t = ""
|
|
if intent ~= nil then
|
|
if intent.kind == "Attack" then t = "공격 " .. tostring(intent.value)
|
|
elseif intent.kind == "Defend" then t = "방어 " .. tostring(intent.value) end
|
|
end
|
|
self:SetText(base .. "/Intent", t)
|
|
self:SetEntityEnabled(base .. "/TargetFrame", i == self.TargetIndex)
|
|
local intentEntity = _EntityService:GetEntityByPath(base .. "/Intent")
|
|
if intentEntity ~= nil and intentEntity.TextComponent ~= nil and intent ~= nil then
|
|
if intent.kind == "Attack" then
|
|
intentEntity.TextComponent.FontColor = Color(1, 0.45, 0.35, 1)
|
|
else
|
|
intentEntity.TextComponent.FontColor = Color(0.5, 0.75, 1, 1)
|
|
end
|
|
end
|
|
self:SetHpBar(base .. "/HpBarFill", m.hp, m.maxHp, ${HP_BAR_W})
|
|
else
|
|
self:SetEntityEnabled(base, false)
|
|
end
|
|
end
|
|
self:SetText("/ui/DefaultGroup/CombatHud/PlayerPanel/HpText", string.format("%d", self.PlayerHp) .. "/" .. string.format("%d", self.PlayerMaxHp))
|
|
self:SetHpBar("/ui/DefaultGroup/CombatHud/PlayerPanel/HpBarFill", self.PlayerHp, self.PlayerMaxHp, 220)
|
|
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/PlayerPanel/BlockBadge", self.PlayerBlock > 0)
|
|
self:SetText("/ui/DefaultGroup/CombatHud/PlayerPanel/BlockBadge/Value", string.format("%d", self.PlayerBlock))
|
|
self:RenderRun()`),
|
|
method('ShowDmgPop', `local base = "/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(slot) .. "/DmgPop"
|
|
self:SetText(base, "-" .. string.format("%d", amount))
|
|
self:SetEntityEnabled(base, true)
|
|
_TimerService:SetTimerOnce(function() self:SetEntityEnabled(base, false) end, 0.6)`, [
|
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
|
|
]),
|
|
method('ShowPlayerDmgPop', `local base = "/ui/DefaultGroup/CombatHud/PlayerPanel/DmgPop"
|
|
if amount > 0 then
|
|
self:SetText(base, "-" .. string.format("%d", amount))
|
|
else
|
|
self:SetText(base, "막음")
|
|
end
|
|
self:SetEntityEnabled(base, true)
|
|
_TimerService:SetTimerOnce(function() self:SetEntityEnabled(base, false) end, 0.6)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }]),
|
|
method('SetHpBar', `local e = _EntityService:GetEntityByPath(path)
|
|
if e == nil or e.UITransformComponent == nil then
|
|
return
|
|
end
|
|
local ratio = 0
|
|
if maxHp > 0 then ratio = hp / maxHp end
|
|
if ratio < 0 then ratio = 0 end
|
|
local w = width * ratio
|
|
e.UITransformComponent.RectSize = Vector2(w, 14)`, [
|
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'path' },
|
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'hp' },
|
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'maxHp' },
|
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'width' },
|
|
]),
|
|
method('PositionMonsterSlot', `local m = self.Monsters[slot]
|
|
if m == nil or m.entity == nil or not isvalid(m.entity) then
|
|
return
|
|
end
|
|
local tr = m.entity.TransformComponent
|
|
if tr == nil then
|
|
return
|
|
end
|
|
local wp = tr.WorldPosition
|
|
local screen = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + ${HEAD_OFFSET_Y}))
|
|
local uipos = _UILogic:ScreenToUIPosition(screen)
|
|
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/MonsterSlot" .. tostring(slot))
|
|
if e ~= nil and e.UITransformComponent ~= nil then
|
|
e.UITransformComponent.anchoredPosition = uipos
|
|
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
|
method('SetTarget', `if self.Monsters[slot] ~= nil and self.Monsters[slot].alive == true then
|
|
self.TargetIndex = slot
|
|
self:RenderCombat()
|
|
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
|
method('RenderRun', `self:SetText("/ui/DefaultGroup/CombatHud/TopBar/Floor", "막 " .. string.format("%d", self.Floor) .. "/" .. string.format("%d", self.RunLength))
|
|
self:SetText("/ui/DefaultGroup/CombatHud/TopBar/Gold", "골드 " .. string.format("%d", self.Gold))`),
|
|
method('OfferReward', `self:SetEntityEnabled("/ui/DefaultGroup/CardHand", false)
|
|
self:SetEntityEnabled("/ui/DefaultGroup/DeckHud", false)
|
|
local pool = {}
|
|
for id, _ in pairs(self.Cards) do
|
|
table.insert(pool, id)
|
|
end
|
|
self.RewardChoices = {}
|
|
for i = 1, 3 do
|
|
self.RewardChoices[i] = pool[math.random(1, #pool)]
|
|
self:ApplyRewardVisual(i, self.RewardChoices[i])
|
|
end
|
|
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/RewardHud")
|
|
if hud ~= nil then
|
|
hud.Enable = true
|
|
end`),
|
|
method('ApplyRewardVisual', `self:ApplyCardFace("/ui/DefaultGroup/RewardHud/Reward" .. tostring(slot), cardId)`, [
|
|
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
|
|
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' },
|
|
]),
|
|
method('PickReward', `if self.CombatOver ~= true or self.RunActive ~= true then
|
|
return
|
|
end
|
|
if slot ~= 0 and self.RewardChoices ~= nil then
|
|
local id = self.RewardChoices[slot]
|
|
if id ~= nil then
|
|
table.insert(self.RunDeck, id)
|
|
end
|
|
end
|
|
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/RewardHud")
|
|
if hud ~= nil then
|
|
hud.Enable = false
|
|
end
|
|
self:ShowMap()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
|
method('ApplyRelics', `if self.RunRelics == nil then
|
|
return
|
|
end
|
|
for i = 1, #self.RunRelics do
|
|
local r = self.Relics[self.RunRelics[i]]
|
|
if r ~= nil and r.hook == hook then
|
|
if r.effect == "block" then
|
|
self.PlayerBlock = self.PlayerBlock + r.value
|
|
elseif r.effect == "energy" then
|
|
self.Energy = self.Energy + r.value
|
|
elseif r.effect == "healOnAttack" then
|
|
self.PlayerHp = self.PlayerHp + r.value
|
|
if self.PlayerHp > self.PlayerMaxHp then
|
|
self.PlayerHp = self.PlayerMaxHp
|
|
end
|
|
elseif r.effect == "gold" then
|
|
self.Gold = self.Gold + r.value
|
|
end
|
|
end
|
|
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'hook' }]),
|
|
method('AddRelic', `if self.RunRelics == nil then
|
|
self.RunRelics = {}
|
|
end
|
|
table.insert(self.RunRelics, id)
|
|
self:RenderRelics()`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'id' }]),
|
|
method('RenderRelics', `local names = ""
|
|
if self.RunRelics ~= nil then
|
|
for i = 1, #self.RunRelics do
|
|
local r = self.Relics[self.RunRelics[i]]
|
|
if r ~= nil then
|
|
if names == "" then
|
|
names = r.name
|
|
else
|
|
names = names .. ", " .. r.name
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if names == "" then
|
|
names = "없음"
|
|
end
|
|
self:SetText("/ui/DefaultGroup/CombatHud/TopBar/Relics", "유물: " .. names)`),
|
|
method('ShowMap', `self:ShowState("map")
|
|
self:RenderMap()`),
|
|
method('IsReachable', `local list
|
|
if self.CurrentNodeId == "" then
|
|
list = self.MapStart
|
|
else
|
|
local node = self.MapNodes[self.CurrentNodeId]
|
|
if node == nil then
|
|
return false
|
|
end
|
|
list = node.next
|
|
end
|
|
for i = 1, #list do
|
|
if list[i] == id then
|
|
return true
|
|
end
|
|
end
|
|
return false`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'id' }], 0, 'boolean'),
|
|
method('RenderMap', `for id, node in pairs(self.MapNodes) do
|
|
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/MapHud/Node_" .. id)
|
|
if e ~= nil then
|
|
local reachable = self:IsReachable(id)
|
|
if e.SpriteGUIRendererComponent ~= nil then
|
|
if reachable then
|
|
e.SpriteGUIRendererComponent.Color = Color(0.3, 0.55, 0.85, 1)
|
|
else
|
|
e.SpriteGUIRendererComponent.Color = Color(0.2, 0.22, 0.26, 0.6)
|
|
end
|
|
end
|
|
if e.ButtonComponent ~= nil then
|
|
e.ButtonComponent.Enable = reachable
|
|
end
|
|
end
|
|
end`),
|
|
method('PickNode', `if self.RunActive ~= true then
|
|
return
|
|
end
|
|
if self:IsReachable(id) ~= true then
|
|
return
|
|
end
|
|
self.CurrentNodeId = id
|
|
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/MapHud")
|
|
if hud ~= nil then
|
|
hud.Enable = false
|
|
end
|
|
local node = self.MapNodes[id]
|
|
if node.type == "shop" then
|
|
self:ShowShop()
|
|
elseif node.type == "rest" then
|
|
self:ShowRest()
|
|
else
|
|
self.CurrentEnemyId = node.enemy
|
|
self:StartCombat()
|
|
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'id' }]),
|
|
method('ShowShop', `local pool = {}
|
|
for cid, _ in pairs(self.Cards) do
|
|
table.insert(pool, cid)
|
|
end
|
|
self.ShopChoices = {}
|
|
self.ShopBought = { false, false, false }
|
|
for i = 1, 3 do
|
|
self.ShopChoices[i] = pool[math.random(1, #pool)]
|
|
end
|
|
self.ShopRelic = self.RelicPool[math.random(1, #self.RelicPool)]
|
|
self.ShopRelicBought = false
|
|
self:RenderShop()
|
|
self:ShowState("shop")`),
|
|
method('RenderShop', `self:SetText("/ui/DefaultGroup/ShopHud/Gold", "골드 " .. string.format("%d", self.Gold))
|
|
for i = 1, 3 do
|
|
local cid = self.ShopChoices[i]
|
|
local c = self.Cards[cid]
|
|
local base = "/ui/DefaultGroup/ShopHud/Card" .. tostring(i)
|
|
if c ~= nil then
|
|
self:ApplyCardFace(base, cid)
|
|
self:SetText(base .. "/Price", string.format("%d", ${CARD_PRICE}) .. " 골드")
|
|
local e = _EntityService:GetEntityByPath(base)
|
|
if e ~= nil and e.SpriteGUIRendererComponent ~= nil then
|
|
if self.ShopBought[i] == true then
|
|
e.SpriteGUIRendererComponent.Color = Color(0.2, 0.22, 0.26, 0.6)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local rr = self.Relics[self.ShopRelic]
|
|
if rr ~= nil then
|
|
self:SetText("/ui/DefaultGroup/ShopHud/Relic/Label", rr.name .. " — " .. rr.desc)
|
|
self:SetText("/ui/DefaultGroup/ShopHud/Relic/Price", string.format("%d", ${RELIC_PRICE}) .. " 골드")
|
|
local re = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Relic")
|
|
if re ~= nil and re.SpriteGUIRendererComponent ~= nil then
|
|
if self.ShopRelicBought == true then
|
|
re.SpriteGUIRendererComponent.Color = Color(0.2, 0.22, 0.26, 0.6)
|
|
else
|
|
re.SpriteGUIRendererComponent.Color = Color(0.7, 0.55, 0.85, 1)
|
|
end
|
|
end
|
|
end`),
|
|
method('BuyRelic', `if self.ShopRelicBought == true then
|
|
return
|
|
end
|
|
if self.Gold < ${RELIC_PRICE} then
|
|
return
|
|
end
|
|
self.Gold = self.Gold - ${RELIC_PRICE}
|
|
self:AddRelic(self.ShopRelic)
|
|
self.ShopRelicBought = true
|
|
self:RenderShop()
|
|
self:RenderRun()`),
|
|
method('BuyCard', `if self.ShopBought == nil or self.ShopBought[slot] == true then
|
|
return
|
|
end
|
|
if self.Gold < ${CARD_PRICE} then
|
|
return
|
|
end
|
|
self.Gold = self.Gold - ${CARD_PRICE}
|
|
table.insert(self.RunDeck, self.ShopChoices[slot])
|
|
self.ShopBought[slot] = true
|
|
self:RenderShop()
|
|
self:RenderRun()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
|
method('ShowRest', `local old = self.PlayerHp
|
|
self.PlayerHp = self.PlayerHp + ${REST_HEAL}
|
|
if self.PlayerHp > self.PlayerMaxHp then
|
|
self.PlayerHp = self.PlayerMaxHp
|
|
end
|
|
local healed = self.PlayerHp - old
|
|
self:SetText("/ui/DefaultGroup/RestHud/Info", "HP " .. string.format("%d", old) .. " → " .. string.format("%d", self.PlayerHp) .. " (+" .. string.format("%d", healed) .. ")")
|
|
self:RenderCombat()
|
|
self:ShowState("rest")`),
|
|
method('LeaveNode', `local s = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud")
|
|
if s ~= nil then
|
|
s.Enable = false
|
|
end
|
|
local r = _EntityService:GetEntityByPath("/ui/DefaultGroup/RestHud")
|
|
if r ~= nil then
|
|
r.Enable = false
|
|
end
|
|
self:ShowMap()`),
|
|
]);
|
|
for (const m of combat.ContentProto.Json.Methods) {
|
|
m.ExecSpace = 6;
|
|
}
|
|
writeFileSync('RootDesk/MyDesk/SlayDeckController.codeblock', JSON.stringify(combat, null, 2), 'utf8');
|
|
}
|
|
|
|
function patchCommon() {
|
|
const common = JSON.parse(readFileSync(COMMON_FILE, 'utf8'));
|
|
const entity = common.ContentProto.Entities.find((e) => e.path === '/common');
|
|
entity.componentNames = 'script.SlayDeckController';
|
|
entity.jsonString['@components'] = [
|
|
{ '@type': 'script.SlayDeckController', Enable: true, Energy: 0, MaxEnergy: 3, Turn: 0, TweenEventId: 0 },
|
|
];
|
|
JSON.parse(JSON.stringify(common));
|
|
writeFileSync(COMMON_FILE, JSON.stringify(common, null, 2), 'utf8');
|
|
}
|
|
|
|
upsertUi();
|
|
writeCodeblocks();
|
|
patchCommon();
|
|
|
|
console.log('Slay deck UI and combat codeblocks generated.');
|