CharacterSelectHud/OpaqueBackdrop이 menu:190 선점 — AscMinus 미표시 원인 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4566 lines
185 KiB
JavaScript
4566 lines
185 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): 잘못된 데이터면 생성 중단
|
||
const CLASSES = {
|
||
warrior: { label: '전사', maxHp: 80 },
|
||
magician: { label: '마법사', maxHp: 70 },
|
||
};
|
||
for (const cls of Object.keys(CLASSES)) {
|
||
if (!CARDS.starterDecks?.[cls]) throw new Error(`[gen-slaydeck] starterDecks.${cls} 없음`);
|
||
for (const id of CARDS.starterDecks[cls]) {
|
||
if (!CARDS.cards[id]) throw new Error(`[gen-slaydeck] starterDecks.${cls}에 없는 카드 id 참조: ${id}`);
|
||
}
|
||
}
|
||
// 전직 옵션 (클래스별 2차 — JobSelectHud 동적 구성·SetJob 대표 카드)
|
||
const JOBS = {
|
||
warrior: [
|
||
{ id: 'fighter', name: '파이터', desc: '공격 특화\n콤보 어택 · 버서크\n라이징 어택', starter: 'ComboAttack' },
|
||
{ id: 'page', name: '페이지', desc: '속성 차지 특화\n썬더/블리자드 차지\n파워 가드', starter: 'ThunderCharge' },
|
||
{ id: 'spearman', name: '스피어맨', desc: '방어·관통 특화\n피어스 · 아이언 월\n하이퍼 바디', starter: 'Pierce' },
|
||
],
|
||
magician: [
|
||
{ id: 'firepoison', name: '위자드(불·독)', desc: '화염·독 특화\n파이어 애로우\n포이즌 브레스 · 앰플', starter: 'FireArrow' },
|
||
{ id: 'icelightning', name: '위자드(썬·콜)', desc: '광역·빙결 특화\n썬더 볼트(전체)\n콜드 빔 · 칠링 스텝', starter: 'ThunderBolt' },
|
||
{ id: 'cleric', name: '클레릭', desc: '회복·축복 특화\n힐 · 블레스\n홀리 애로우', starter: 'Heal' },
|
||
],
|
||
};
|
||
for (const [cls, jobs] of Object.entries(JOBS)) {
|
||
for (const j of jobs) {
|
||
if (!CARDS.cards[j.starter]) throw new Error(`[gen-slaydeck] JOBS.${cls}.${j.id} 대표 카드 없음: ${j.starter}`);
|
||
}
|
||
}
|
||
if (!ENEMIES.enemies[ENEMIES.activeEnemy]) {
|
||
throw new Error(`[gen-slaydeck] activeEnemy가 enemies에 없음: ${ENEMIES.activeEnemy}`);
|
||
}
|
||
|
||
// 맵은 런타임 절차 생성(GenerateMap Lua ↔ tools/map/rogue-map.mjs 미러). 정적 data/map.json 제거됨.
|
||
const MAP_ROWS = 7; // 걷는 행 1..7, 보스 row 8
|
||
const MAP_COLS = 4;
|
||
|
||
// 보물 상자 스프라이트 (공식 maplestory 리소스, 메이커 선별)
|
||
const CHEST_CLOSED_RUID = '43df67920c0d43298e0d93c02c6afa71';
|
||
const CHEST_OPEN_RUID = '09c5cee56fd640bf8ae3a18ce50f4759';
|
||
|
||
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}, icon = ${luaStr(r.icon || '')} },`);
|
||
return `self.Relics = {\n${lines.join('\n')}\n}`;
|
||
}
|
||
|
||
const POTIONS = JSON.parse(readFileSync('data/potions.json', 'utf8'));
|
||
for (const [pid, p] of Object.entries(POTIONS.potions)) {
|
||
if (!p.name || !p.effect || p.value == null) throw new Error(`[gen-slaydeck] potion 필드 누락: ${pid}`);
|
||
}
|
||
function luaPotionsTable(potions) {
|
||
const lines = Object.entries(potions).map(([id, p]) =>
|
||
`\t${id} = { name = ${luaStr(p.name)}, desc = ${luaStr(p.desc)}, effect = ${luaStr(p.effect)}, value = ${p.value}, icon = ${luaStr(p.icon || '')} },`);
|
||
return `self.Potions = {\n${lines.join('\n')}\n}`;
|
||
}
|
||
|
||
function luaIntentsArray(intents) {
|
||
return '{ ' + intents.map((it) => {
|
||
const fields = [`kind = ${luaStr(it.kind)}`, `value = ${it.value}`];
|
||
if (it.effect != null) fields.push(`effect = ${luaStr(it.effect)}`);
|
||
return `{ ${fields.join(', ')} }`;
|
||
}).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}`;
|
||
}
|
||
// Lua 직렬화 헬퍼
|
||
function luaStr(s) {
|
||
return '"' + String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n') + '"';
|
||
}
|
||
function luaJobsTable(jobs) {
|
||
const cls = Object.entries(jobs).map(([clsId, list]) => {
|
||
const items = list.map((j) => `\t\t{ id = ${luaStr(j.id)}, name = ${luaStr(j.name)}, desc = ${luaStr(j.desc)}, starter = ${luaStr(j.starter)} },`).join('\n');
|
||
return `\t${clsId} = {\n${items}\n\t},`;
|
||
}).join('\n');
|
||
return `self.Jobs = {\n${cls}\n}`;
|
||
}
|
||
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.strength != null) fields.push(`strength = ${c.strength}`);
|
||
if (c.weak != null) fields.push(`weak = ${c.weak}`);
|
||
if (c.vuln != null) fields.push(`vuln = ${c.vuln}`);
|
||
if (c.powerEffect != null) fields.push(`powerEffect = ${luaStr(c.powerEffect)}`);
|
||
if (c.value != null) fields.push(`value = ${c.value}`);
|
||
if (!c.class) throw new Error(`[gen-slaydeck] 카드 ${id}에 class 누락`);
|
||
fields.push(`class = ${luaStr(c.class)}`);
|
||
if (c.hits != null) fields.push(`hits = ${c.hits}`);
|
||
if (c.pierce === true) fields.push('pierce = true');
|
||
if (c.selfVuln != null) fields.push(`selfVuln = ${c.selfVuln}`);
|
||
if (c.draw != null) fields.push(`draw = ${c.draw}`);
|
||
if (c.heal != null) fields.push(`heal = ${c.heal}`);
|
||
if (c.poison != null) fields.push(`poison = ${c.poison}`);
|
||
if (c.aoe === true) fields.push('aoe = true');
|
||
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',
|
||
'TreasureHud',
|
||
'JobChoiceHud',
|
||
'JobSelectHud',
|
||
'MainMenu',
|
||
'CharacterSelectHud',
|
||
];
|
||
const UI_APPEND_ORDER = [
|
||
'DeckHud',
|
||
'CombatHud',
|
||
'RewardHud',
|
||
'MapHud',
|
||
'ShopHud',
|
||
'RestHud',
|
||
'TreasureHud',
|
||
'JobChoiceHud',
|
||
'JobSelectHud',
|
||
'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 : prefix === 'trs' ? 0xe3 : prefix === 'job' ? 0xe4 : 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 mBlockBadge = entity({
|
||
id: guid('cmb', 270 + i), path: `${base}/BlockBadge`, modelId: 'uisprite', entryId: 'UISprite',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
|
||
displayOrder: 6,
|
||
components: [
|
||
transform({ parentW: SLOT_W, parentH: SLOT_H, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 40, y: 36 }, pos: { x: -HP_BAR_W / 2 - 30, y: -14 } }),
|
||
sprite({ color: { r: 0.32, g: 0.5, b: 0.85, a: 1 }, type: 1 }),
|
||
],
|
||
});
|
||
mBlockBadge.jsonString.enable = false;
|
||
combat.push(mBlockBadge);
|
||
combat.push(entity({
|
||
id: guid('cmb', 280 + i), path: `${base}/BlockBadge/Value`, modelId: 'uitext', entryId: 'UIText',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||
displayOrder: 0,
|
||
components: [
|
||
transform({ parentW: 40, parentH: 36, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 40, y: 32 }, pos: { x: 0, y: 0 } }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: '0', fontSize: 17, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }),
|
||
],
|
||
}));
|
||
combat.push(entity({
|
||
id: guid('cmb', 290 + i), path: `${base}/Buffs`, modelId: 'uitext', entryId: 'UIText',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||
displayOrder: 7,
|
||
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 + 60, y: 22 }, pos: { x: 0, y: -58 } }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: '', fontSize: 15, bold: true, color: { r: 0.85, g: 0.65, b: 1, a: 1 }, alignment: 4 }),
|
||
],
|
||
}));
|
||
}
|
||
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 }),
|
||
],
|
||
}));
|
||
combat.push(entity({
|
||
id: guid('cmb', 217), path: `${PP}/Buffs`, modelId: 'uitext', entryId: 'UIText',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||
displayOrder: 6,
|
||
components: [
|
||
transform({ parentW: 300, parentH: 96, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 280, y: 22 }, pos: { x: 0, y: -44 } }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: '', fontSize: 14, bold: true, color: { r: 0.85, g: 0.65, 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 }],
|
||
];
|
||
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: 22, bold: true, color, alignment: 4 }),
|
||
],
|
||
}));
|
||
});
|
||
for (let i = 1; i <= 10; i++) {
|
||
combat.push(entity({
|
||
id: guid('cmb', 300 + i),
|
||
path: `/ui/DefaultGroup/CombatHud/TopBar/RelicSlot${i}`,
|
||
modelId: 'uisprite', entryId: 'UISprite',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.UITouchReceiveComponent',
|
||
displayOrder: 3 + i,
|
||
components: [
|
||
transform({ parentW: 1200, parentH: 52, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 40, y: 40 }, pos: { x: -240 + (i - 1) * 48, y: 0 } }),
|
||
sprite({ color: { r: 0.15, g: 0.16, b: 0.2, a: 0.6 }, type: 0, raycast: true }),
|
||
{ '@type': 'MOD.Core.UITouchReceiveComponent', Enable: true },
|
||
],
|
||
}));
|
||
}
|
||
combat.push(entity({
|
||
id: guid('cmb', 311),
|
||
path: '/ui/DefaultGroup/CombatHud/TopBar/RelicOverflow',
|
||
modelId: 'uitext', entryId: 'UIText',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||
displayOrder: 14,
|
||
components: [
|
||
transform({ parentW: 1200, parentH: 52, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 60, y: 30 }, pos: { x: 192, y: 0 } }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: '', fontSize: 18, bold: true, color: { r: 0.8, g: 0.7, b: 0.95, a: 1 }, alignment: 4 }),
|
||
],
|
||
}));
|
||
for (let i = 1; i <= 5; i++) {
|
||
combat.push(entity({
|
||
id: guid('cmb', 320 + i),
|
||
path: `/ui/DefaultGroup/CombatHud/TopBar/PotionSlot${i}`,
|
||
modelId: 'uisprite', entryId: 'UISprite',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.UITouchReceiveComponent',
|
||
displayOrder: 14 + i,
|
||
components: [
|
||
transform({ parentW: 1200, parentH: 52, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 40, y: 40 }, pos: { x: 240 + (i - 1) * 44, y: 0 } }),
|
||
sprite({ color: { r: 0.22, g: 0.25, b: 0.3, a: 0.9 }, type: 0, raycast: true }),
|
||
{ '@type': 'MOD.Core.UITouchReceiveComponent', Enable: true },
|
||
],
|
||
}));
|
||
}
|
||
const tooltipBox = entity({
|
||
id: guid('cmb', 330),
|
||
path: '/ui/DefaultGroup/CombatHud/TooltipBox',
|
||
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: 300, y: 80 }, pos: { x: 0, y: 400 }, align: ALIGN_CENTER }),
|
||
sprite({ color: { r: 0.04, g: 0.05, b: 0.08, a: 0.96 }, type: 1 }),
|
||
],
|
||
});
|
||
tooltipBox.jsonString.enable = false;
|
||
combat.push(tooltipBox);
|
||
combat.push(entity({
|
||
id: guid('cmb', 331),
|
||
path: '/ui/DefaultGroup/CombatHud/TooltipBox/Name',
|
||
modelId: 'uitext', entryId: 'UIText',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||
displayOrder: 0,
|
||
components: [
|
||
transform({ parentW: 300, parentH: 80, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 280, y: 28 }, pos: { x: 0, y: 18 } }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: '', fontSize: 19, bold: true, color: GOLD, alignment: 4 }),
|
||
],
|
||
}));
|
||
combat.push(entity({
|
||
id: guid('cmb', 332),
|
||
path: '/ui/DefaultGroup/CombatHud/TooltipBox/Desc',
|
||
modelId: 'uitext', entryId: 'UIText',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||
displayOrder: 1,
|
||
components: [
|
||
transform({ parentW: 300, parentH: 80, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 284, y: 30 }, pos: { x: 0, y: -14 } }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: '', fontSize: 15, bold: false, color: { r: 0.92, g: 0.92, b: 0.95, a: 1 }, alignment: 4 }),
|
||
],
|
||
}));
|
||
const potionMenu = entity({
|
||
id: guid('cmb', 340),
|
||
path: '/ui/DefaultGroup/CombatHud/PotionMenu',
|
||
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: 380, y: 180 }, pos: { x: 0, y: 120 }, align: ALIGN_CENTER }),
|
||
sprite({ color: { r: 0.07, g: 0.08, b: 0.12, a: 0.97 }, type: 1 }),
|
||
],
|
||
});
|
||
potionMenu.jsonString.enable = false;
|
||
combat.push(potionMenu);
|
||
combat.push(entity({
|
||
id: guid('cmb', 341),
|
||
path: '/ui/DefaultGroup/CombatHud/PotionMenu/Title',
|
||
modelId: 'uitext', entryId: 'UIText',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||
displayOrder: 0,
|
||
components: [
|
||
transform({ parentW: 380, parentH: 180, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 360, y: 36 }, pos: { x: 0, y: 52 } }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: '', fontSize: 19, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }),
|
||
],
|
||
}));
|
||
const pmButtons = [
|
||
['Use', '사용', -120, { r: 0.32, g: 0.55, b: 0.36, a: 1 }],
|
||
['Toss', '버리기', 0, { r: 0.6, g: 0.32, b: 0.3, a: 1 }],
|
||
['Close', '닫기', 120, { r: 0.25, g: 0.28, b: 0.35, a: 1 }],
|
||
];
|
||
pmButtons.forEach(([suffix, label, x, color], bi) => {
|
||
combat.push(entity({
|
||
id: guid('cmb', 342 + bi),
|
||
path: `/ui/DefaultGroup/CombatHud/PotionMenu/${suffix}`,
|
||
modelId: 'uibutton', entryId: 'UIButton',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent',
|
||
displayOrder: 1 + bi,
|
||
components: [
|
||
transform({ parentW: 380, parentH: 180, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 104, y: 46 }, pos: { x, y: -40 } }),
|
||
sprite({ color, type: 1, raycast: true }),
|
||
button(),
|
||
text({ value: label, fontSize: 20, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, 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: 140, y: 40 }, pos: { x: 528, 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 }),
|
||
],
|
||
}));
|
||
// 절차 생성 맵용 정적 그리드 — 노드 7행×4열 + 보스, 점선 도트. RenderMap이 런타임 토글.
|
||
const nodeX = (c) => -270 + (c - 1) * 180;
|
||
const nodeY = (r) => -330 + (r - 1) * 105;
|
||
const BOSS_POS = { x: 0, y: 405 };
|
||
let mapN = 2;
|
||
const pushMapNode = (id, pos, size, label) => {
|
||
const nodePath = `/ui/DefaultGroup/MapHud/Node_${id}`;
|
||
const nodeEnt = entity({
|
||
id: guid('map', mapN++),
|
||
path: nodePath,
|
||
modelId: 'uisprite',
|
||
entryId: 'UISprite',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent',
|
||
displayOrder: 5,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size, pos }),
|
||
sprite({ color: { r: 0.2, g: 0.22, b: 0.26, a: 1 }, type: 1, raycast: true }),
|
||
button(),
|
||
],
|
||
});
|
||
nodeEnt.jsonString.enable = false;
|
||
map.push(nodeEnt);
|
||
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: size.x, parentH: size.y, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: size.x + 20, y: 30 }, pos: { x: 0, y: 0 } }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: label, fontSize: id === 'boss' ? 18 : 15, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }),
|
||
],
|
||
}));
|
||
};
|
||
for (let r = 1; r <= MAP_ROWS; r++) {
|
||
for (let c = 1; c <= MAP_COLS; c++) {
|
||
pushMapNode(`r${r}c${c}`, { x: nodeX(c), y: nodeY(r) }, { x: 56, y: 56 }, '');
|
||
}
|
||
}
|
||
pushMapNode('boss', BOSS_POS, { x: 72, y: 72 }, '보스');
|
||
const pushDots = (dotId, from, to) => {
|
||
for (let k = 1; k <= 3; k++) {
|
||
const t = k / 4;
|
||
const dot = entity({
|
||
id: guid('map', mapN++),
|
||
path: `/ui/DefaultGroup/MapHud/Dot_${dotId}_${k}`,
|
||
modelId: 'uisprite',
|
||
entryId: 'UISprite',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
|
||
displayOrder: 1,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 8, y: 8 }, pos: { x: from.x + (to.x - from.x) * t, y: from.y + (to.y - from.y) * t } }),
|
||
sprite({ color: { r: 0.5, g: 0.5, b: 0.55, a: 0.8 }, type: 1 }),
|
||
],
|
||
});
|
||
dot.jsonString.enable = false;
|
||
map.push(dot);
|
||
}
|
||
};
|
||
for (let r = 1; r < MAP_ROWS; r++) {
|
||
for (let c = 1; c <= MAP_COLS; c++) {
|
||
for (let c2 = c - 1; c2 <= c + 1; c2++) {
|
||
if (c2 < 1 || c2 > MAP_COLS) continue;
|
||
pushDots(`r${r}c${c}_${c2}`, { x: nodeX(c), y: nodeY(r) }, { x: nodeX(c2), y: nodeY(r + 1) });
|
||
}
|
||
}
|
||
}
|
||
for (let c = 1; c <= MAP_COLS; c++) {
|
||
pushDots(`r${MAP_ROWS}c${c}_b`, { x: nodeX(c), y: nodeY(MAP_ROWS) }, BOSS_POS);
|
||
}
|
||
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/Potion',
|
||
modelId: 'uisprite',
|
||
entryId: 'UISprite',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent',
|
||
displayOrder: 11,
|
||
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: -278 } }),
|
||
sprite({ color: { r: 0.45, g: 0.7, b: 0.55, a: 1 }, type: 1, raycast: true }),
|
||
button(),
|
||
],
|
||
}));
|
||
shop.push(entity({
|
||
id: guid('shp', shpN++),
|
||
path: '/ui/DefaultGroup/ShopHud/Potion/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/Potion/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: '20 메소', 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: -380 } }),
|
||
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);
|
||
|
||
// 유물 방 — 보물 상자 (P8)
|
||
const treasure = [];
|
||
const treasureHud = entity({
|
||
id: guid('trs', 0),
|
||
path: '/ui/DefaultGroup/TreasureHud',
|
||
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 }),
|
||
],
|
||
});
|
||
treasureHud.jsonString.enable = false;
|
||
treasure.push(treasureHud);
|
||
treasure.push(entity({
|
||
id: guid('trs', 1),
|
||
path: '/ui/DefaultGroup/TreasureHud/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: 320 } }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: '보물 상자', fontSize: 40, bold: true, color: GOLD, alignment: 4 }),
|
||
],
|
||
}));
|
||
treasure.push(entity({
|
||
id: guid('trs', 2),
|
||
path: '/ui/DefaultGroup/TreasureHud/Chest',
|
||
modelId: 'uisprite',
|
||
entryId: 'UISprite',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent',
|
||
displayOrder: 1,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 180, y: 180 }, pos: { x: 0, y: 40 } }),
|
||
sprite({ dataId: CHEST_CLOSED_RUID, color: { r: 1, g: 1, b: 1, a: 1 }, type: 0, raycast: true }),
|
||
button(),
|
||
],
|
||
}));
|
||
treasure.push(entity({
|
||
id: guid('trs', 3),
|
||
path: '/ui/DefaultGroup/TreasureHud/Hint',
|
||
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: 500, y: 34 }, pos: { x: 0, y: -90 } }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: '상자를 클릭해 여세요', fontSize: 20, bold: false, color: { r: 0.85, g: 0.85, b: 0.9, a: 1 }, alignment: 4 }),
|
||
],
|
||
}));
|
||
const treasureReward = entity({
|
||
id: guid('trs', 4),
|
||
path: '/ui/DefaultGroup/TreasureHud/Reward',
|
||
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: 800, y: 44 }, pos: { x: 0, y: -160 } }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: '', fontSize: 28, bold: true, color: { r: 0.98, g: 0.85, b: 0.4, a: 1 }, alignment: 4 }),
|
||
],
|
||
});
|
||
treasureReward.jsonString.enable = false;
|
||
treasure.push(treasureReward);
|
||
treasure.push(entity({
|
||
id: guid('trs', 5),
|
||
path: '/ui/DefaultGroup/TreasureHud/Leave',
|
||
modelId: 'uibutton',
|
||
entryId: 'UIButton',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent',
|
||
displayOrder: 4,
|
||
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: -280 } }),
|
||
sprite({ color: DARK, type: 1, raycast: true }),
|
||
button(),
|
||
text({ value: '나가기', fontSize: 26, bold: true, color: GOLD, alignment: 4 }),
|
||
],
|
||
}));
|
||
emit('TreasureHud', treasure);
|
||
|
||
// 전직 선택 (P9) — 보스 보상: 유물 vs 2차 전직
|
||
const jobChoice = [];
|
||
const jobChoiceHud = entity({
|
||
id: guid('job', 0),
|
||
path: '/ui/DefaultGroup/JobChoiceHud',
|
||
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.06, b: 0.09, a: 0.92 }, type: 1, raycast: true }),
|
||
],
|
||
});
|
||
jobChoiceHud.jsonString.enable = false;
|
||
jobChoice.push(jobChoiceHud);
|
||
jobChoice.push(entity({
|
||
id: guid('job', 1),
|
||
path: '/ui/DefaultGroup/JobChoiceHud/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: 800, y: 60 }, pos: { x: 0, y: 220 } }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: '보스 처치 보상을 선택하세요', fontSize: 36, bold: true, color: GOLD, alignment: 4 }),
|
||
],
|
||
}));
|
||
const jcButtons = [
|
||
['RelicButton', '유물 획득', -240, { r: 0.7, g: 0.55, b: 0.85, a: 1 }],
|
||
['JobButton', '2차 전직', 240, { r: 0.86, g: 0.6, b: 0.3, a: 1 }],
|
||
];
|
||
jcButtons.forEach(([suffix, label, x, color], bi) => {
|
||
jobChoice.push(entity({
|
||
id: guid('job', 2 + bi),
|
||
path: `/ui/DefaultGroup/JobChoiceHud/${suffix}`,
|
||
modelId: 'uibutton', entryId: 'UIButton',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent',
|
||
displayOrder: 1 + bi,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 380, y: 140 }, pos: { x, y: 0 } }),
|
||
sprite({ color, type: 1, raycast: true }),
|
||
button(),
|
||
text({ value: label, fontSize: 32, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }),
|
||
],
|
||
}));
|
||
});
|
||
emit('JobChoiceHud', jobChoice);
|
||
|
||
const jobSelect = [];
|
||
const jobSelectHud = entity({
|
||
id: guid('job', 10),
|
||
path: '/ui/DefaultGroup/JobSelectHud',
|
||
modelId: 'uisprite', entryId: 'UISprite',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
|
||
displayOrder: 10,
|
||
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.94 }, type: 1, raycast: true }),
|
||
],
|
||
});
|
||
jobSelectHud.jsonString.enable = false;
|
||
jobSelect.push(jobSelectHud);
|
||
jobSelect.push(entity({
|
||
id: guid('job', 11),
|
||
path: '/ui/DefaultGroup/JobSelectHud/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: 800, y: 60 }, pos: { x: 0, y: 300 } }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: '2차 전직 — 직업을 선택하세요', fontSize: 36, bold: true, color: GOLD, alignment: 4 }),
|
||
],
|
||
}));
|
||
// 범용 슬롯 3개 — ShowJobSelect(Lua)가 클래스별 JOBS로 텍스트를 채움 (P10 동적화)
|
||
const jobs = [
|
||
['slot1', '', '', '', -440, { r: 0.82, g: 0.4, b: 0.34, a: 1 }],
|
||
['slot2', '', '', '', 0, { r: 0.4, g: 0.55, b: 0.85, a: 1 }],
|
||
['slot3', '', '', '', 440, { r: 0.42, g: 0.72, b: 0.46, a: 1 }],
|
||
];
|
||
jobs.forEach(([jobId, name, desc, starter, x, color], ji) => {
|
||
const base = `/ui/DefaultGroup/JobSelectHud/Job_${jobId}`;
|
||
jobSelect.push(entity({
|
||
id: guid('job', 12 + ji * 4),
|
||
path: base,
|
||
modelId: 'uibutton', entryId: 'UIButton',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent',
|
||
displayOrder: 1 + ji,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 380, y: 420 }, pos: { x, y: -20 } }),
|
||
sprite({ color, type: 1, raycast: true }),
|
||
button(),
|
||
],
|
||
}));
|
||
jobSelect.push(entity({
|
||
id: guid('job', 13 + ji * 4),
|
||
path: `${base}/Name`,
|
||
modelId: 'uitext', entryId: 'UIText',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||
displayOrder: 0,
|
||
components: [
|
||
transform({ parentW: 380, parentH: 420, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 360, y: 50 }, pos: { x: 0, y: 150 } }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: name, fontSize: 34, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }),
|
||
],
|
||
}));
|
||
jobSelect.push(entity({
|
||
id: guid('job', 14 + ji * 4),
|
||
path: `${base}/Desc`,
|
||
modelId: 'uitext', entryId: 'UIText',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||
displayOrder: 1,
|
||
components: [
|
||
transform({ parentW: 380, parentH: 420, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 340, y: 160 }, pos: { x: 0, y: 0 } }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: desc, fontSize: 22, bold: false, color: { r: 0.95, g: 0.95, b: 0.97, a: 1 }, alignment: 4 }),
|
||
],
|
||
}));
|
||
jobSelect.push(entity({
|
||
id: guid('job', 15 + ji * 4),
|
||
path: `${base}/Starter`,
|
||
modelId: 'uitext', entryId: 'UIText',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||
displayOrder: 2,
|
||
components: [
|
||
transform({ parentW: 380, parentH: 420, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 340, y: 32 }, pos: { x: 0, y: -160 } }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: starter, fontSize: 18, bold: true, color: GOLD, alignment: 4 }),
|
||
],
|
||
}));
|
||
});
|
||
emit('JobSelectHud', jobSelect);
|
||
|
||
const menu = [];
|
||
menu.push(entity({
|
||
id: guid('menu', 0),
|
||
path: '/ui/DefaultGroup/MainMenu',
|
||
modelId: 'uisprite',
|
||
entryId: 'UISprite',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
|
||
displayOrder: 20,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1920, y: 1080 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }),
|
||
sprite({ color: { r: 0.06, g: 0.09, b: 0.13, a: 1 }, type: 1, raycast: true }),
|
||
],
|
||
}));
|
||
menu.push(entity({
|
||
id: guid('menu', 50),
|
||
path: '/ui/DefaultGroup/MainMenu/OpaqueBackdrop',
|
||
modelId: 'uisprite',
|
||
entryId: 'UISprite',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
|
||
displayOrder: 0,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1920, y: 1080 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }),
|
||
sprite({ color: TRANSPARENT, type: 1, raycast: false }),
|
||
],
|
||
}));
|
||
menu.push(entity({
|
||
id: guid('menu', 1),
|
||
path: '/ui/DefaultGroup/MainMenu/Title',
|
||
modelId: 'uitext',
|
||
entryId: 'UIText',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||
displayOrder: 0,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 720, y: 100 }, pos: { x: 0, y: 180 }, align: ALIGN_CENTER }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: '슬레이 메이플', fontSize: 64, bold: true, color: GOLD, alignment: 0 }),
|
||
],
|
||
}));
|
||
menu.push(entity({
|
||
id: guid('menu', 2),
|
||
path: '/ui/DefaultGroup/MainMenu/Subtitle',
|
||
modelId: 'uitext',
|
||
entryId: 'UIText',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||
displayOrder: 1,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 760, y: 48 }, pos: { x: 0, y: 104 }, align: ALIGN_CENTER }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: '카드를 뽑고, 덱을 만들고, 첨탑을 오른다', fontSize: 24, color: { r: 0.82, g: 0.86, b: 0.9, a: 1 }, alignment: 0 }),
|
||
],
|
||
}));
|
||
menu.push(entity({
|
||
id: guid('menu', 3),
|
||
path: '/ui/DefaultGroup/MainMenu/NewGameButton',
|
||
modelId: 'uibutton',
|
||
entryId: 'UIButton',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent',
|
||
displayOrder: 2,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 260, y: 68 }, pos: { x: 0, y: -20 }, align: ALIGN_CENTER }),
|
||
sprite({ color: { r: 0.13, g: 0.15, b: 0.18, a: 1 }, type: 1, raycast: true }),
|
||
button(),
|
||
text({ value: '새 게임', fontSize: 30, bold: true, color: GOLD, alignment: 0 }),
|
||
],
|
||
}));
|
||
// 승천 선택 (P11): [-] 라벨 [+]
|
||
menu.push(entity({
|
||
id: guid('menu', 195),
|
||
path: '/ui/DefaultGroup/MainMenu/AscMinus',
|
||
modelId: 'uibutton',
|
||
entryId: 'UIButton',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent',
|
||
displayOrder: 5,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 52, y: 52 }, pos: { x: -170, y: -185 }, align: ALIGN_CENTER }),
|
||
sprite({ color: { r: 0.13, g: 0.15, b: 0.18, a: 1 }, type: 1, raycast: true }),
|
||
button(),
|
||
text({ value: '-', fontSize: 30, bold: true, color: GOLD, alignment: 4 }),
|
||
],
|
||
}));
|
||
menu.push(entity({
|
||
id: guid('menu', 196),
|
||
path: '/ui/DefaultGroup/MainMenu/AscLabel',
|
||
modelId: 'uitext',
|
||
entryId: 'UIText',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||
displayOrder: 6,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 250, y: 40 }, pos: { x: 0, y: -185 }, align: ALIGN_CENTER }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: '승천 0 / 해금 0', fontSize: 22, bold: true, color: { r: 0.85, g: 0.7, b: 0.95, a: 1 }, alignment: 4 }),
|
||
],
|
||
}));
|
||
menu.push(entity({
|
||
id: guid('menu', 197),
|
||
path: '/ui/DefaultGroup/MainMenu/AscPlus',
|
||
modelId: 'uibutton',
|
||
entryId: 'UIButton',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent',
|
||
displayOrder: 7,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 52, y: 52 }, pos: { x: 170, y: -185 }, align: ALIGN_CENTER }),
|
||
sprite({ color: { r: 0.13, g: 0.15, b: 0.18, a: 1 }, type: 1, raycast: true }),
|
||
button(),
|
||
text({ value: '+', fontSize: 30, bold: true, color: GOLD, alignment: 4 }),
|
||
],
|
||
}));
|
||
menu.push(entity({
|
||
id: guid('menu', 4),
|
||
path: '/ui/DefaultGroup/MainMenu/ContinueButton',
|
||
modelId: 'uibutton',
|
||
entryId: 'UIButton',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent',
|
||
displayOrder: 3,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 260, y: 58 }, pos: { x: 0, y: -100 }, align: ALIGN_CENTER }),
|
||
sprite({ color: { r: 0.1, g: 0.11, b: 0.13, a: 0.78 }, type: 1, raycast: false }),
|
||
button({ enabled: false }),
|
||
text({ value: '이어하기', fontSize: 24, bold: true, color: { r: 0.55, g: 0.58, b: 0.62, a: 1 }, alignment: 0 }),
|
||
],
|
||
}));
|
||
const select = [];
|
||
select.push(entity({
|
||
id: guid('menu', 100),
|
||
path: '/ui/DefaultGroup/CharacterSelectHud',
|
||
modelId: 'uisprite',
|
||
entryId: 'UISprite',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
|
||
displayOrder: 21,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1920, y: 1080 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }),
|
||
sprite({ color: { r: 0.05, g: 0.07, b: 0.11, a: 1 }, type: 1, raycast: true }),
|
||
],
|
||
}));
|
||
select.push(entity({
|
||
id: guid('menu', 190),
|
||
path: '/ui/DefaultGroup/CharacterSelectHud/OpaqueBackdrop',
|
||
modelId: 'uisprite',
|
||
entryId: 'UISprite',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
|
||
displayOrder: 0,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1920, y: 1080 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }),
|
||
sprite({ color: TRANSPARENT, type: 1, raycast: false }),
|
||
],
|
||
}));
|
||
select.push(entity({
|
||
id: guid('menu', 101),
|
||
path: '/ui/DefaultGroup/CharacterSelectHud/Title',
|
||
modelId: 'uitext',
|
||
entryId: 'UIText',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||
displayOrder: 1,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 760, y: 72 }, pos: { x: 0, y: 355 }, align: ALIGN_CENTER }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: '\uCE90\uB9AD\uD130 \uC120\uD0DD', fontSize: 42, bold: true, color: GOLD, alignment: 0 }),
|
||
],
|
||
}));
|
||
select.push(entity({
|
||
id: guid('menu', 102),
|
||
path: '/ui/DefaultGroup/CharacterSelectHud/Status',
|
||
modelId: 'uitext',
|
||
entryId: 'UIText',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||
displayOrder: 2,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 680, y: 44 }, pos: { x: 0, y: 298 }, align: ALIGN_CENTER }),
|
||
sprite({ color: TRANSPARENT }),
|
||
text({ value: '\uC804\uC0AC\uB97C \uC120\uD0DD\uD558\uACE0 \uC2DC\uC791\uD558\uC138\uC694', fontSize: 22, color: { r: 0.86, g: 0.9, b: 0.94, a: 1 }, alignment: 0 }),
|
||
],
|
||
}));
|
||
const classCards = [
|
||
{ key: 'Warrior', 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: '\uB9C8\uBC95 \uC6D0\uAC70\uB9AC \uB51C\uB7EC', x: 360, enabled: true, tint: { r: 0.3, g: 0.4, b: 0.75, a: 1 } },
|
||
];
|
||
for (let i = 0; i < classCards.length; i++) {
|
||
const cls = classCards[i];
|
||
const base = `/ui/DefaultGroup/CharacterSelectHud/${cls.key}Button`;
|
||
select.push(entity({
|
||
id: guid('menu', 110 + i),
|
||
path: base,
|
||
modelId: 'uibutton',
|
||
entryId: 'UIButton',
|
||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent',
|
||
displayOrder: 10 + i,
|
||
components: [
|
||
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 270, y: 330 }, pos: { x: cls.x, y: 40 }, align: ALIGN_CENTER }),
|
||
sprite({ color: cls.enabled ? { r: 0.16, g: 0.2, b: 0.26, a: 1 } : { r: 0.11, g: 0.12, b: 0.14, a: 1 }, type: 1, raycast: cls.enabled }),
|
||
button({ enabled: cls.enabled }),
|
||
],
|
||
}));
|
||
select.push(entity({
|
||
id: guid('menu', 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 = 25;
|
||
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', 'MageSelectHandler'),
|
||
prop('any', 'AscMinusHandler'),
|
||
prop('any', 'AscPlusHandler'),
|
||
prop('any', 'JobOpts'),
|
||
prop('any', 'Jobs'),
|
||
prop('number', 'AscensionLevel', '0'),
|
||
prop('number', 'AscensionUnlocked', '0'),
|
||
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'),
|
||
prop('number', 'PlayerStr', '0'),
|
||
prop('number', 'PlayerWeak', '0'),
|
||
prop('number', 'PlayerVuln', '0'),
|
||
prop('any', 'PlayerPowers'),
|
||
prop('any', 'Potions'),
|
||
prop('any', 'RunPotions'),
|
||
prop('number', 'PotionSlots', String(POTIONS.baseSlots)),
|
||
prop('string', 'ShopPotion', '""'),
|
||
prop('boolean', 'ShopPotionBought', 'false'),
|
||
prop('number', 'FightAttackCount', '0'),
|
||
prop('boolean', 'FirstHpLossDone', 'false'),
|
||
prop('number', 'ClayBlockNext', '0'),
|
||
prop('number', 'PotionMenuSlot', '0'),
|
||
prop('number', 'Depth', '0'),
|
||
prop('any', 'VisitedNodes'),
|
||
prop('boolean', 'ChestOpened', 'false'),
|
||
prop('string', 'PlayerJob', '""'),
|
||
], [
|
||
method('OnBeginPlay', `self:ShowMainMenu()
|
||
local lp = _UserService.LocalPlayer
|
||
if lp ~= nil then
|
||
self:ReqLoadAscension(lp.PlayerComponent.UserId)
|
||
end`),
|
||
method('ReqLoadAscension', `local ds = _DataStorageService:GetUserDataStorage(userId)
|
||
local errCode, value = ds:GetAndWait("ascensionUnlocked")
|
||
local n = 0
|
||
if errCode == 0 and value ~= nil and value ~= "" then
|
||
n = tonumber(value) or 0
|
||
end
|
||
self:RecvAscension(n, userId)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'userId' }], 5),
|
||
method('RecvAscension', `self.AscensionUnlocked = n
|
||
if self.AscensionLevel > self.AscensionUnlocked then
|
||
self.AscensionLevel = self.AscensionUnlocked
|
||
end
|
||
self:RenderAscension()`, [
|
||
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'n' },
|
||
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'userId' },
|
||
], 6),
|
||
method('SaveAscension', `local ds = _DataStorageService:GetUserDataStorage(userId)
|
||
ds:SetAndWait("ascensionUnlocked", tostring(n))`, [
|
||
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'n' },
|
||
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'userId' },
|
||
], 5),
|
||
method('AdjustAscension', `local v = self.AscensionLevel + delta
|
||
if v < 0 then v = 0 end
|
||
if v > self.AscensionUnlocked then v = self.AscensionUnlocked end
|
||
self.AscensionLevel = v
|
||
self:RenderAscension()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'delta' }]),
|
||
method('RenderAscension', `self:SetText("/ui/DefaultGroup/MainMenu/AscLabel", "승천 " .. string.format("%d", self.AscensionLevel) .. " / 해금 " .. string.format("%d", self.AscensionUnlocked))`),
|
||
method('AscHpMult', `local m = 1
|
||
if self.AscensionLevel >= 1 then m = m + 0.1 end
|
||
if self.AscensionLevel >= 6 then m = m + 0.1 end
|
||
return m`, [], 0, 'number'),
|
||
method('AscAtkMult', `local m = 1
|
||
if self.AscensionLevel >= 2 then m = m + 0.1 end
|
||
if self.AscensionLevel >= 7 then m = m + 0.1 end
|
||
return m`, [], 0, 'number'),
|
||
method('AscEliteBonus', `local b = 0
|
||
if self.AscensionLevel >= 4 then b = b + 0.2 end
|
||
if self.AscensionLevel >= 9 then b = b + 0.2 end
|
||
return b`, [], 0, 'number'),
|
||
method('AscGoldMult', `local m = 1
|
||
if self.AscensionLevel >= 5 then m = m - 0.25 end
|
||
if self.AscensionLevel >= 10 then m = m - 0.25 end
|
||
return m`, [], 0, 'number'),
|
||
method('AscStartHpPenalty', `local p = 0
|
||
if self.AscensionLevel >= 3 then p = p + 10 end
|
||
if self.AscensionLevel >= 8 then p = p + 10 end
|
||
return p`, [], 0, 'number'),
|
||
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/TreasureHud", false)
|
||
self:SetEntityEnabled("/ui/DefaultGroup/JobChoiceHud", false)
|
||
self:SetEntityEnabled("/ui/DefaultGroup/JobSelectHud", 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)
|
||
elseif state == "treasure" then
|
||
self:SetEntityEnabled("/ui/DefaultGroup/TreasureHud", true)
|
||
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'state' }]),
|
||
method('ShowMainMenu', `self.SelectedClass = ""
|
||
self:RenderAscension()
|
||
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 mage = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/MageButton")
|
||
if mage ~= nil and mage.ButtonComponent ~= nil then
|
||
if self.MageSelectHandler ~= nil then
|
||
mage:DisconnectEvent(ButtonClickEvent, self.MageSelectHandler)
|
||
self.MageSelectHandler = nil
|
||
end
|
||
self.MageSelectHandler = mage:ConnectEvent(ButtonClickEvent, function() self:SelectClass("magician") 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
|
||
local ascMinus = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/AscMinus")
|
||
if ascMinus ~= nil and ascMinus.ButtonComponent ~= nil then
|
||
if self.AscMinusHandler ~= nil then
|
||
ascMinus:DisconnectEvent(ButtonClickEvent, self.AscMinusHandler)
|
||
self.AscMinusHandler = nil
|
||
end
|
||
self.AscMinusHandler = ascMinus:ConnectEvent(ButtonClickEvent, function() self:AdjustAscension(-1) end)
|
||
end
|
||
local ascPlus = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/AscPlus")
|
||
if ascPlus ~= nil and ascPlus.ButtonComponent ~= nil then
|
||
if self.AscPlusHandler ~= nil then
|
||
ascPlus:DisconnectEvent(ButtonClickEvent, self.AscPlusHandler)
|
||
self.AscPlusHandler = nil
|
||
end
|
||
self.AscPlusHandler = ascPlus:ConnectEvent(ButtonClickEvent, function() self:AdjustAscension(1) 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
|
||
local mage = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/MageButton")
|
||
if mage ~= nil and mage.SpriteGUIRendererComponent ~= nil then
|
||
if self.SelectedClass == "magician" then
|
||
mage.SpriteGUIRendererComponent.Color = Color(0.28, 0.36, 0.46, 1)
|
||
else
|
||
mage.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)
|
||
end
|
||
end
|
||
if self.SelectedClass == "warrior" then
|
||
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "전사 선택됨")
|
||
elseif self.SelectedClass == "magician" then
|
||
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "마법사 선택됨")
|
||
else
|
||
self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "직업을 선택하고 시작하세요")
|
||
end`),
|
||
method('StartNewGame', `if self.SelectedClass ~= "warrior" and self.SelectedClass ~= "magician" 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', `if self.SelectedClass == "magician" then
|
||
self.PlayerMaxHp = ${CLASSES.magician.maxHp}
|
||
self.RunDeck = { ${CARDS.starterDecks.magician.map(luaStr).join(', ')} }
|
||
else
|
||
self.PlayerMaxHp = ${CLASSES.warrior.maxHp}
|
||
self.RunDeck = { ${CARDS.starterDecks.warrior.map(luaStr).join(', ')} }
|
||
end
|
||
self.PlayerMaxHp = self.PlayerMaxHp - self:AscStartHpPenalty()
|
||
self.PlayerHp = self.PlayerMaxHp
|
||
self.Gold = 0
|
||
self.Floor = 1
|
||
self.RunLength = ${ACT_COUNT}
|
||
self.RunActive = true
|
||
self.RunRelics = {}
|
||
self.RunPotions = {}
|
||
self.PotionSlots = ${POTIONS.baseSlots}
|
||
${luaPotionsTable(POTIONS.potions)}
|
||
${luaRelicsTable(RELICS.relics)}
|
||
self.RelicPool = { ${RELICS.relicPool.map(luaStr).join(', ')} }
|
||
${luaEnemiesTable(ENEMIES.enemies)}
|
||
self.CurrentNodeId = ""
|
||
self.CurrentEnemyId = ""
|
||
self.PlayerJob = ""
|
||
${luaJobsTable(JOBS)}
|
||
self:GenerateMap()
|
||
self:BindButtons()
|
||
self:AddRelic("${RELICS.startingRelic}")
|
||
self:RenderPotions()
|
||
self:ShowMap()`),
|
||
method('StartCombat', `self:ShowState("combat")
|
||
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/Result", false)
|
||
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/PotionMenu", false)
|
||
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/TooltipBox", false)
|
||
self:SetText("/ui/DefaultGroup/CombatHud/PlayerPanel/Name", self:JobLabel())
|
||
self.MaxEnergy = 3
|
||
self.Turn = 0
|
||
self.PlayerBlock = 0
|
||
self.PlayerStr = 0
|
||
self.PlayerWeak = 0
|
||
self.PlayerVuln = 0
|
||
self.PlayerPowers = {}
|
||
self.FightAttackCount = 0
|
||
self.FirstHpLossDone = false
|
||
self.ClayBlockNext = 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
|
||
if g == "elite" or g == "boss" then
|
||
mult = mult + self:AscEliteBonus()
|
||
end
|
||
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
|
||
local v = e.intents[k].value
|
||
if e.intents[k].kind == "Attack" then
|
||
v = math.floor(v * mult * self:AscAtkMult())
|
||
elseif e.intents[k].kind ~= "Debuff" then
|
||
v = math.floor(v * mult)
|
||
end
|
||
intents[k] = { kind = e.intents[k].kind, value = v, effect = e.intents[k].effect }
|
||
end
|
||
local maxHp = math.floor(e.maxHp * mult * self:AscHpMult())
|
||
self.Monsters[i] = { entity = item.entity, enemyId = item.enemyId, name = e.name,
|
||
hp = maxHp, maxHp = maxHp, block = 0, str = 0, weak = 0, vuln = 0, poison = 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 = {}
|
||
for r = 1, ${MAP_ROWS} do
|
||
for c = 1, ${MAP_COLS} do
|
||
table.insert(mapNodeIds, "r" .. tostring(r) .. "c" .. tostring(c))
|
||
end
|
||
end
|
||
table.insert(mapNodeIds, "boss")
|
||
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
|
||
for i = 1, 10 do
|
||
local rs = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/TopBar/RelicSlot" .. tostring(i))
|
||
if rs ~= nil and rs.UITouchReceiveComponent ~= nil then
|
||
local idx = i
|
||
rs:ConnectEvent(UITouchEnterEvent, function()
|
||
local rid = nil
|
||
if self.RunRelics ~= nil then rid = self.RunRelics[idx] end
|
||
if rid ~= nil and self.Relics[rid] ~= nil then
|
||
self:ShowTooltip(self.Relics[rid].name, self.Relics[rid].desc, -240 + (idx - 1) * 48)
|
||
end
|
||
end)
|
||
rs:ConnectEvent(UITouchExitEvent, function() self:HideTooltip() end)
|
||
end
|
||
end
|
||
for i = 1, 5 do
|
||
local ps = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/TopBar/PotionSlot" .. tostring(i))
|
||
if ps ~= nil and ps.UITouchReceiveComponent ~= nil then
|
||
local idx = i
|
||
ps:ConnectEvent(UITouchEnterEvent, function()
|
||
local pid = nil
|
||
if self.RunPotions ~= nil then pid = self.RunPotions[idx] end
|
||
if pid ~= nil and self.Potions[pid] ~= nil then
|
||
self:ShowTooltip(self.Potions[pid].name, self.Potions[pid].desc, 240 + (idx - 1) * 44)
|
||
end
|
||
end)
|
||
ps:ConnectEvent(UITouchExitEvent, function() self:HideTooltip() end)
|
||
ps:ConnectEvent(UITouchDownEvent, function() self:OpenPotionMenu(idx) end)
|
||
end
|
||
end
|
||
local pmUse = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/PotionMenu/Use")
|
||
if pmUse ~= nil and pmUse.ButtonComponent ~= nil then
|
||
pmUse:ConnectEvent(ButtonClickEvent, function() self:UsePotion() end)
|
||
end
|
||
local pmToss = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/PotionMenu/Toss")
|
||
if pmToss ~= nil and pmToss.ButtonComponent ~= nil then
|
||
pmToss:ConnectEvent(ButtonClickEvent, function() self:TossPotion() end)
|
||
end
|
||
local pmClose = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/PotionMenu/Close")
|
||
if pmClose ~= nil and pmClose.ButtonComponent ~= nil then
|
||
pmClose:ConnectEvent(ButtonClickEvent, function() self:ClosePotionMenu() end)
|
||
end
|
||
local shopPotion = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Potion")
|
||
if shopPotion ~= nil and shopPotion.ButtonComponent ~= nil then
|
||
shopPotion:ConnectEvent(ButtonClickEvent, function() self:BuyPotion() end)
|
||
end
|
||
local chest = _EntityService:GetEntityByPath("/ui/DefaultGroup/TreasureHud/Chest")
|
||
if chest ~= nil and chest.ButtonComponent ~= nil then
|
||
chest:ConnectEvent(ButtonClickEvent, function() self:OpenChest() end)
|
||
end
|
||
local treasureLeave = _EntityService:GetEntityByPath("/ui/DefaultGroup/TreasureHud/Leave")
|
||
if treasureLeave ~= nil and treasureLeave.ButtonComponent ~= nil then
|
||
treasureLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)
|
||
end
|
||
local jcRelic = _EntityService:GetEntityByPath("/ui/DefaultGroup/JobChoiceHud/RelicButton")
|
||
if jcRelic ~= nil and jcRelic.ButtonComponent ~= nil then
|
||
jcRelic:ConnectEvent(ButtonClickEvent, function() self:PickJobReward("relic") end)
|
||
end
|
||
local jcJob = _EntityService:GetEntityByPath("/ui/DefaultGroup/JobChoiceHud/JobButton")
|
||
if jcJob ~= nil and jcJob.ButtonComponent ~= nil then
|
||
jcJob:ConnectEvent(ButtonClickEvent, function() self:PickJobReward("job") end)
|
||
end
|
||
for i = 1, 3 do
|
||
local slotIdx = i
|
||
local jb = _EntityService:GetEntityByPath("/ui/DefaultGroup/JobSelectHud/Job_slot" .. tostring(i))
|
||
if jb ~= nil and jb.ButtonComponent ~= nil then
|
||
jb:ConnectEvent(ButtonClickEvent, function()
|
||
if self.JobOpts ~= nil and self.JobOpts[slotIdx] ~= nil then
|
||
self:SetJob(self.JobOpts[slotIdx].id)
|
||
end
|
||
end)
|
||
end
|
||
end`),
|
||
method('StartPlayerTurn', `self.Turn = self.Turn + 1
|
||
self.Energy = self.MaxEnergy
|
||
self:ApplyRelics("turnStart")
|
||
self.PlayerBlock = 0
|
||
if self.ClayBlockNext > 0 then
|
||
self.PlayerBlock = self.PlayerBlock + self.ClayBlockNext
|
||
self.ClayBlockNext = 0
|
||
end
|
||
if self.PlayerPowers ~= nil then
|
||
for i = 1, #self.PlayerPowers do
|
||
local pc = self.Cards[self.PlayerPowers[i]]
|
||
if pc ~= nil then
|
||
if pc.powerEffect == "strengthPerTurn" then
|
||
self.PlayerStr = self.PlayerStr + pc.value
|
||
elseif pc.powerEffect == "energyPerTurn" then
|
||
self.Energy = self.Energy + pc.value
|
||
elseif pc.powerEffect == "blockPerTurn" then
|
||
self.PlayerBlock = self.PlayerBlock + pc.value
|
||
end
|
||
end
|
||
end
|
||
end
|
||
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 = {}
|
||
if self.PlayerWeak > 0 then self.PlayerWeak = self.PlayerWeak - 1 end
|
||
if self.PlayerVuln > 0 then self.PlayerVuln = self.PlayerVuln - 1 end
|
||
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 == "Power" then
|
||
e.SpriteGUIRendererComponent.Color = Color(0.46, 0.68, 0.52, 1)
|
||
else
|
||
e.SpriteGUIRendererComponent.Color = Color(0.42, 0.55, 0.85, 1)
|
||
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('CalcPlayerAttack', `local base2 = base
|
||
self.FightAttackCount = self.FightAttackCount + 1
|
||
if self.FightAttackCount == 1 and self:HasRelic("akabeko") then
|
||
base2 = base2 + 8
|
||
end
|
||
local dmg = base2 + self.PlayerStr
|
||
if self:HasRelic("penNib") and self.FightAttackCount % 10 == 0 then
|
||
dmg = dmg * 2
|
||
end
|
||
if self.PlayerWeak > 0 then
|
||
dmg = math.floor(dmg * 0.75)
|
||
end
|
||
if dmg > 0 and dmg < 5 and self:HasRelic("boot") then
|
||
dmg = 5
|
||
end
|
||
if dmg < 0 then
|
||
dmg = 0
|
||
end
|
||
return dmg`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'base' }], 0, 'number'),
|
||
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
|
||
local total = 0
|
||
local hitN = c.hits or 1
|
||
for h = 1, hitN do
|
||
total = total + self:CalcPlayerAttack(c.damage)
|
||
end
|
||
if c.aoe == true then
|
||
self:PlayAoeFx(c.image, total)
|
||
else
|
||
self:PlayAttackFx(self.TargetIndex, c.image, total, c.pierce == true)
|
||
end
|
||
end
|
||
if c.block ~= nil then
|
||
self.PlayerBlock = self.PlayerBlock + c.block
|
||
end
|
||
self:ApplyRelics("cardPlayed")
|
||
elseif c.kind == "Skill" then
|
||
if c.block ~= nil then
|
||
self.PlayerBlock = self.PlayerBlock + c.block
|
||
end
|
||
elseif c.kind == "Power" then
|
||
if c.powerEffect ~= nil then
|
||
table.insert(self.PlayerPowers, cardId)
|
||
end
|
||
end
|
||
if c.strength ~= nil then
|
||
self.PlayerStr = self.PlayerStr + c.strength
|
||
end
|
||
if c.selfVuln ~= nil then
|
||
self.PlayerVuln = self.PlayerVuln + c.selfVuln
|
||
end
|
||
if c.heal ~= nil then
|
||
self.PlayerHp = math.min(self.PlayerHp + c.heal, self.PlayerMaxHp)
|
||
end
|
||
if c.weak ~= nil or c.vuln ~= nil or c.poison ~= nil then
|
||
local tm = self.Monsters[self.TargetIndex]
|
||
if tm ~= nil and tm.alive == true then
|
||
if c.weak ~= nil then tm.weak = tm.weak + c.weak end
|
||
if c.poison ~= nil then tm.poison = (tm.poison or 0) + c.poison end
|
||
if c.vuln ~= nil then
|
||
tm.vuln = tm.vuln + c.vuln
|
||
if self:HasRelic("championBelt") then
|
||
tm.weak = tm.weak + 1
|
||
end
|
||
end
|
||
end
|
||
end
|
||
table.remove(self.Hand, slot)
|
||
if c.kind ~= "Power" then
|
||
table.insert(self.DiscardPile, cardId)
|
||
end
|
||
if c.draw ~= nil then
|
||
self:DrawCards(c.draw)
|
||
end
|
||
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.vuln > 0 then
|
||
dmg = math.floor(dmg * 1.5)
|
||
end
|
||
if m.block > 0 and pierce ~= true 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' },
|
||
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'pierce' },
|
||
]),
|
||
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, pierce)
|
||
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
|
||
local shown = damage
|
||
local mt = self.Monsters[targetIndex]
|
||
if mt ~= nil and mt.alive == true and mt.vuln > 0 then
|
||
shown = math.floor(damage * 1.5)
|
||
end
|
||
self:DealDamageToTarget(damage, pierce)
|
||
self:ShowDmgPop(targetIndex, shown)
|
||
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' },
|
||
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'pierce' },
|
||
]),
|
||
method('PlayAoeFx', `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 then
|
||
fx.UITransformComponent.anchoredPosition = Vector2(300, 60)
|
||
end
|
||
fx.Enable = true
|
||
end
|
||
_TimerService:SetTimerOnce(function()
|
||
if fx ~= nil then fx.Enable = false end
|
||
self.FxBusy = false
|
||
for i = 1, #self.Monsters do
|
||
local m = self.Monsters[i]
|
||
if m ~= nil and m.alive == true then
|
||
local dmg = damage
|
||
if m.vuln > 0 then
|
||
dmg = math.floor(dmg * 1.5)
|
||
end
|
||
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
|
||
self:ShowDmgPop(i, dmg)
|
||
if m.hp <= 0 then
|
||
m.hp = 0
|
||
self:KillMonster(m.slot)
|
||
end
|
||
end
|
||
end
|
||
self:RenderCombat()
|
||
self:CheckCombatEnd()
|
||
end, 0.35)`, [
|
||
{ 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
|
||
if dmg > 0 then
|
||
self.PlayerHp = self.PlayerHp - dmg
|
||
if self:HasRelic("bronzeScales") and attackerSlot ~= nil and attackerSlot > 0 then
|
||
local am = self.Monsters[attackerSlot]
|
||
if am ~= nil and am.alive == true then
|
||
am.hp = am.hp - 3
|
||
if am.hp <= 0 then
|
||
am.hp = 0
|
||
self:KillMonster(am.slot)
|
||
end
|
||
end
|
||
end
|
||
if self:HasRelic("selfFormingClay") then
|
||
self.ClayBlockNext = self.ClayBlockNext + 3
|
||
end
|
||
if self:HasRelic("centennialPuzzle") and self.FirstHpLossDone == false then
|
||
self.FirstHpLossDone = true
|
||
self:DrawCards(3)
|
||
self:RenderHand(false)
|
||
end
|
||
end
|
||
if self.PlayerHp < 0 then
|
||
self.PlayerHp = 0
|
||
end`, [
|
||
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
|
||
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'attackerSlot' },
|
||
]),
|
||
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()
|
||
if m.poison ~= nil and m.poison > 0 then
|
||
m.hp = m.hp - m.poison
|
||
self:ShowDmgPop(idx, m.poison)
|
||
m.poison = m.poison - 1
|
||
if m.hp <= 0 then
|
||
m.hp = 0
|
||
self:KillMonster(m.slot)
|
||
self:RenderCombat()
|
||
self:SetEntityEnabled(base .. "/ActFrame", false)
|
||
_TimerService:SetTimerOnce(function() self:EnemyActStep(idx + 1) end, 0.15)
|
||
return
|
||
end
|
||
end
|
||
m.block = 0
|
||
local intent = m.intents[m.intentIdx]
|
||
if intent ~= nil then
|
||
if intent.kind == "Attack" then
|
||
local atk = intent.value + m.str
|
||
if m.weak > 0 then
|
||
atk = math.floor(atk * 0.75)
|
||
end
|
||
if self.PlayerVuln > 0 then
|
||
atk = math.floor(atk * 1.5)
|
||
end
|
||
local before = self.PlayerHp
|
||
self:DealDamageToPlayer(atk, idx)
|
||
self:ShowPlayerDmgPop(before - self.PlayerHp)
|
||
elseif intent.kind == "Defend" then
|
||
m.block = m.block + intent.value
|
||
elseif intent.kind == "Debuff" then
|
||
if intent.effect == "weak" then
|
||
self.PlayerWeak = self.PlayerWeak + intent.value
|
||
elseif intent.effect == "vuln" then
|
||
self.PlayerVuln = self.PlayerVuln + intent.value
|
||
end
|
||
end
|
||
end
|
||
m.intentIdx = m.intentIdx + 1
|
||
if m.intentIdx > #m.intents then
|
||
m.intentIdx = 1
|
||
end
|
||
if m.weak > 0 then m.weak = m.weak - 1 end
|
||
if m.vuln > 0 then m.vuln = m.vuln - 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 + math.floor(${GOLD_PER_WIN} * self:AscGoldMult())
|
||
self:ApplyRelics("combatEnd")
|
||
self:ApplyRelics("combatReward")
|
||
self:MaybeDropPotion()
|
||
self:RenderRun()
|
||
local node = self.MapNodes[self.CurrentNodeId]
|
||
if node ~= nil and node.type == "elite" then
|
||
self.Gold = self.Gold + 15
|
||
local nid = self:PickNewRelic()
|
||
if nid ~= "" then
|
||
self:AddRelic(nid)
|
||
local nr = self.Relics[nid]
|
||
if nr ~= nil then
|
||
self:Toast("유물 획득: " .. nr.name)
|
||
end
|
||
end
|
||
end
|
||
if node ~= nil and node.type == "boss" then
|
||
if self.PlayerJob == "" and self.Floor < self.RunLength then
|
||
self:ShowJobChoice()
|
||
else
|
||
local bid = self:PickNewRelic()
|
||
if bid ~= "" then
|
||
self:AddRelic(bid)
|
||
local br = self.Relics[bid]
|
||
if br ~= nil then
|
||
self:Toast("유물 획득: " .. br.name)
|
||
end
|
||
end
|
||
self:ContinueAfterBoss()
|
||
end
|
||
else
|
||
self:OfferReward()
|
||
end
|
||
elseif self.PlayerHp <= 0 then
|
||
self.CombatOver = true
|
||
self:EndRun("패배...")
|
||
end`),
|
||
method('ContinueAfterBoss', `if self.Floor < self.RunLength then
|
||
self.Floor = self.Floor + 1
|
||
self.CurrentNodeId = ""
|
||
self.CurrentEnemyId = ""
|
||
self:GenerateMap()
|
||
self:RenderRun()
|
||
self:TeleportToActMap()
|
||
self:ShowMap()
|
||
else
|
||
self:EndRun("런 클리어!")
|
||
end`),
|
||
method('ShowJobChoice', `self:SetEntityEnabled("/ui/DefaultGroup/CardHand", false)
|
||
self:SetEntityEnabled("/ui/DefaultGroup/DeckHud", false)
|
||
self:SetEntityEnabled("/ui/DefaultGroup/JobChoiceHud", true)`),
|
||
method('PickJobReward', `self:SetEntityEnabled("/ui/DefaultGroup/JobChoiceHud", false)
|
||
if kind == "relic" then
|
||
local bid = self:PickNewRelic()
|
||
if bid ~= "" then
|
||
self:AddRelic(bid)
|
||
local br = self.Relics[bid]
|
||
if br ~= nil then
|
||
self:Toast("유물 획득: " .. br.name)
|
||
end
|
||
end
|
||
self:ContinueAfterBoss()
|
||
else
|
||
self:ShowJobSelect()
|
||
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'kind' }]),
|
||
method('ShowJobSelect', `local opts = self.Jobs[self.SelectedClass]
|
||
if opts == nil then
|
||
opts = self.Jobs["warrior"]
|
||
end
|
||
self.JobOpts = opts
|
||
for i = 1, 3 do
|
||
local base = "/ui/DefaultGroup/JobSelectHud/Job_slot" .. tostring(i)
|
||
local o = opts[i]
|
||
if o ~= nil then
|
||
self:SetEntityEnabled(base, true)
|
||
self:SetText(base .. "/Name", o.name)
|
||
self:SetText(base .. "/Desc", o.desc)
|
||
local sc = self.Cards[o.starter]
|
||
if sc ~= nil then
|
||
self:SetText(base .. "/Starter", "대표 카드: " .. sc.name)
|
||
end
|
||
else
|
||
self:SetEntityEnabled(base, false)
|
||
end
|
||
end
|
||
self:SetEntityEnabled("/ui/DefaultGroup/JobSelectHud", true)`),
|
||
method('JobLabel', `if self.PlayerJob ~= "" and self.Jobs ~= nil then
|
||
for cls, list in pairs(self.Jobs) do
|
||
for i = 1, #list do
|
||
if list[i].id == self.PlayerJob then
|
||
return list[i].name
|
||
end
|
||
end
|
||
end
|
||
end
|
||
if self.SelectedClass == "warrior" then
|
||
return "전사"
|
||
elseif self.SelectedClass == "magician" then
|
||
return "마법사"
|
||
end
|
||
return "플레이어"`, [], 0, 'string'),
|
||
method('SetJob', `self.PlayerJob = jobId
|
||
local starter = ""
|
||
local opts = self.Jobs[self.SelectedClass] or {}
|
||
for i = 1, #opts do
|
||
if opts[i].id == jobId then
|
||
starter = opts[i].starter
|
||
end
|
||
end
|
||
if starter ~= "" then
|
||
table.insert(self.RunDeck, starter)
|
||
local sc = self.Cards[starter]
|
||
if sc ~= nil then
|
||
self:Toast("2차 전직: " .. self:JobLabel() .. "! 신규 카드 — " .. sc.name)
|
||
end
|
||
end
|
||
self:SetText("/ui/DefaultGroup/CombatHud/PlayerPanel/Name", self:JobLabel())
|
||
self:SetEntityEnabled("/ui/DefaultGroup/JobSelectHud", false)
|
||
self:ContinueAfterBoss()`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'jobId' }]),
|
||
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('EndRun', `local msg = text
|
||
if text == "런 클리어!" and self.AscensionLevel >= self.AscensionUnlocked and self.AscensionUnlocked < 10 then
|
||
self.AscensionUnlocked = self.AscensionUnlocked + 1
|
||
local lp = _UserService.LocalPlayer
|
||
if lp ~= nil then
|
||
self:SaveAscension(self.AscensionUnlocked, lp.PlayerComponent.UserId)
|
||
end
|
||
self:RenderAscension()
|
||
msg = "런 클리어! 승천 " .. string.format("%d", self.AscensionUnlocked) .. " 해금!"
|
||
end
|
||
self:ShowResult(msg)
|
||
self.RunActive = false
|
||
_TimerService:SetTimerOnce(function() self:ShowMainMenu() end, 4)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'text' }]),
|
||
method('BuffsLabel', `local parts = {}
|
||
if str ~= nil and str > 0 then table.insert(parts, "힘+" .. tostring(str)) end
|
||
if weak ~= nil and weak > 0 then table.insert(parts, "약화" .. tostring(weak)) end
|
||
if vuln ~= nil and vuln > 0 then table.insert(parts, "취약" .. tostring(vuln)) end
|
||
if poison ~= nil and poison > 0 then table.insert(parts, "독" .. tostring(poison)) end
|
||
return table.concat(parts, " ")`, [
|
||
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'str' },
|
||
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'weak' },
|
||
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'vuln' },
|
||
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'poison' },
|
||
], 0, 'string'),
|
||
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
|
||
local atk = intent.value + m.str
|
||
if m.weak > 0 then atk = math.floor(atk * 0.75) end
|
||
if self.PlayerVuln > 0 then atk = math.floor(atk * 1.5) end
|
||
t = "공격 " .. tostring(atk)
|
||
elseif intent.kind == "Defend" then t = "방어 " .. tostring(intent.value)
|
||
elseif intent.kind == "Debuff" then
|
||
if intent.effect == "weak" then t = "약화 " .. tostring(intent.value) .. " 부여"
|
||
else t = "취약 " .. tostring(intent.value) .. " 부여" end
|
||
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)
|
||
elseif intent.kind == "Debuff" then
|
||
intentEntity.TextComponent.FontColor = Color(0.8, 0.5, 1, 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})
|
||
self:SetEntityEnabled(base .. "/BlockBadge", m.block > 0)
|
||
self:SetText(base .. "/BlockBadge/Value", string.format("%d", m.block))
|
||
self:SetText(base .. "/Buffs", self:BuffsLabel(m.str, m.weak, m.vuln, m.poison or 0))
|
||
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))
|
||
local pb = self:BuffsLabel(self.PlayerStr, self.PlayerWeak, self.PlayerVuln, 0)
|
||
if self.PlayerPowers ~= nil and #self.PlayerPowers > 0 then
|
||
local names = {}
|
||
for i = 1, #self.PlayerPowers do
|
||
local pc = self.Cards[self.PlayerPowers[i]]
|
||
if pc ~= nil then table.insert(names, pc.name) end
|
||
end
|
||
if pb ~= "" then pb = pb .. " · " end
|
||
pb = pb .. table.concat(names, " ")
|
||
end
|
||
self:SetText("/ui/DefaultGroup/CombatHud/PlayerPanel/Buffs", pb)
|
||
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', `local floorText = "막 " .. string.format("%d", self.Floor) .. "/" .. string.format("%d", self.RunLength) .. " · " .. string.format("%d", self.Depth) .. "층"
|
||
if self.AscensionLevel > 0 then
|
||
floorText = floorText .. " · 승천" .. string.format("%d", self.AscensionLevel)
|
||
end
|
||
self:SetText("/ui/DefaultGroup/CombatHud/TopBar/Floor", floorText)
|
||
self:SetText("/ui/DefaultGroup/CombatHud/TopBar/Gold", "메소 " .. string.format("%d", self.Gold))`),
|
||
method('CardPool', `local pool = {}
|
||
for id, c in pairs(self.Cards) do
|
||
if c.class == self.SelectedClass or (self.PlayerJob ~= "" and c.class == self.PlayerJob) then
|
||
table.insert(pool, id)
|
||
end
|
||
end
|
||
table.sort(pool)
|
||
return pool`, [], 0, 'any'),
|
||
method('OfferReward', `self:SetEntityEnabled("/ui/DefaultGroup/CardHand", false)
|
||
self:SetEntityEnabled("/ui/DefaultGroup/DeckHud", false)
|
||
local pool = self:CardPool()
|
||
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('HasRelic', `if self.RunRelics == nil then
|
||
return false
|
||
end
|
||
for i = 1, #self.RunRelics do
|
||
if self.RunRelics[i] == id then
|
||
return true
|
||
end
|
||
end
|
||
return false`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'id' }], 0, 'boolean'),
|
||
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 == "strength" then
|
||
self.PlayerStr = self.PlayerStr + r.value
|
||
elseif r.effect == "draw" then
|
||
self:DrawCards(r.value)
|
||
self:RenderHand(false)
|
||
elseif r.effect == "heal" or r.effect == "healOnAttack" or r.effect == "healOnWin" then
|
||
self.PlayerHp = self.PlayerHp + r.value
|
||
if self.PlayerHp > self.PlayerMaxHp then
|
||
self.PlayerHp = self.PlayerMaxHp
|
||
end
|
||
elseif r.effect == "healIfLow" then
|
||
if self.PlayerHp * 2 <= self.PlayerMaxHp then
|
||
self.PlayerHp = self.PlayerHp + r.value
|
||
if self.PlayerHp > self.PlayerMaxHp then
|
||
self.PlayerHp = self.PlayerMaxHp
|
||
end
|
||
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)
|
||
local r = self.Relics[id]
|
||
if r ~= nil and r.hook == "passive" then
|
||
if r.effect == "potionSlots" then
|
||
self.PotionSlots = r.value
|
||
self:RenderPotions()
|
||
elseif r.effect == "maxHp" then
|
||
self.PlayerMaxHp = self.PlayerMaxHp + r.value
|
||
self.PlayerHp = self.PlayerHp + r.value
|
||
end
|
||
end
|
||
self:RenderRelics()`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'id' }]),
|
||
method('PickNewRelic', `local pool = {}
|
||
for i = 1, #self.RelicPool do
|
||
if self:HasRelic(self.RelicPool[i]) == false then
|
||
table.insert(pool, self.RelicPool[i])
|
||
end
|
||
end
|
||
if #pool == 0 then
|
||
self.Gold = self.Gold + 25
|
||
self:Toast("유물을 모두 모았습니다! 메소 +25")
|
||
return ""
|
||
end
|
||
return pool[math.random(1, #pool)]`, [], 0, 'string'),
|
||
method('AddPotion', `if self.RunPotions == nil then
|
||
self.RunPotions = {}
|
||
end
|
||
if #self.RunPotions >= self.PotionSlots then
|
||
self:Toast("물약 슬롯이 가득 찼습니다")
|
||
return false
|
||
end
|
||
table.insert(self.RunPotions, pid)
|
||
self:RenderPotions()
|
||
return true`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'pid' }], 0, 'boolean'),
|
||
method('MaybeDropPotion', `if math.random() > ${POTIONS.dropChance} then
|
||
return
|
||
end
|
||
local keys = {}
|
||
for pid, _ in pairs(self.Potions) do
|
||
table.insert(keys, pid)
|
||
end
|
||
table.sort(keys)
|
||
local pid = keys[math.random(1, #keys)]
|
||
if self:AddPotion(pid) == true then
|
||
local p = self.Potions[pid]
|
||
self:Toast("물약 획득: " .. p.name)
|
||
end`),
|
||
method('RenderPotions', `for i = 1, 5 do
|
||
local base = "/ui/DefaultGroup/CombatHud/TopBar/PotionSlot" .. tostring(i)
|
||
local e = _EntityService:GetEntityByPath(base)
|
||
if e ~= nil and e.SpriteGUIRendererComponent ~= nil then
|
||
local pid = nil
|
||
if self.RunPotions ~= nil then
|
||
pid = self.RunPotions[i]
|
||
end
|
||
if pid ~= nil and self.Potions[pid] ~= nil then
|
||
e.SpriteGUIRendererComponent.ImageRUID = self.Potions[pid].icon
|
||
e.SpriteGUIRendererComponent.Color = Color(1, 1, 1, 1)
|
||
elseif i > self.PotionSlots then
|
||
e.SpriteGUIRendererComponent.ImageRUID = ""
|
||
e.SpriteGUIRendererComponent.Color = Color(0.1, 0.1, 0.12, 0.85)
|
||
else
|
||
e.SpriteGUIRendererComponent.ImageRUID = ""
|
||
e.SpriteGUIRendererComponent.Color = Color(0.22, 0.25, 0.3, 0.9)
|
||
end
|
||
end
|
||
end`),
|
||
method('OpenPotionMenu', `if self.RunPotions == nil or self.RunPotions[slot] == nil then
|
||
return
|
||
end
|
||
self.PotionMenuSlot = slot
|
||
local pid = self.RunPotions[slot]
|
||
local p = self.Potions[pid]
|
||
if p ~= nil then
|
||
self:SetText("/ui/DefaultGroup/CombatHud/PotionMenu/Title", p.name .. " — " .. p.desc)
|
||
end
|
||
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/PotionMenu", true)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
|
||
method('ClosePotionMenu', `self.PotionMenuSlot = 0
|
||
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/PotionMenu", false)`),
|
||
method('UsePotion', `if self.PotionMenuSlot <= 0 then
|
||
return
|
||
end
|
||
if self.CombatOver == true or self.TurnBusy == true or self.FxBusy == true then
|
||
self:Toast("지금은 사용할 수 없습니다")
|
||
return
|
||
end
|
||
local combat = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud")
|
||
local hand = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand")
|
||
if combat == nil or combat.Enable ~= true or hand == nil or hand.Enable ~= true then
|
||
self:Toast("전투 중에만 사용할 수 있습니다")
|
||
return
|
||
end
|
||
local pid = self.RunPotions[self.PotionMenuSlot]
|
||
if pid == nil then
|
||
return
|
||
end
|
||
local p = self.Potions[pid]
|
||
if p == nil then
|
||
return
|
||
end
|
||
if p.effect == "heal" then
|
||
self.PlayerHp = math.min(self.PlayerHp + p.value, self.PlayerMaxHp)
|
||
elseif p.effect == "damage" then
|
||
self:DealDamageToTarget(p.value, false)
|
||
self:ShowDmgPop(self.TargetIndex, p.value)
|
||
elseif p.effect == "strength" then
|
||
self.PlayerStr = self.PlayerStr + p.value
|
||
elseif p.effect == "block" then
|
||
self.PlayerBlock = self.PlayerBlock + p.value
|
||
elseif p.effect == "energy" then
|
||
self.Energy = self.Energy + p.value
|
||
elseif p.effect == "weak" then
|
||
local tm = self.Monsters[self.TargetIndex]
|
||
if tm ~= nil and tm.alive == true then
|
||
tm.weak = tm.weak + p.value
|
||
end
|
||
end
|
||
table.remove(self.RunPotions, self.PotionMenuSlot)
|
||
self:Toast("물약 사용: " .. p.name)
|
||
self:ClosePotionMenu()
|
||
self:RenderPotions()
|
||
self:RenderPiles()
|
||
self:RenderCombat()
|
||
self:CheckCombatEnd()`),
|
||
method('TossPotion', `if self.PotionMenuSlot <= 0 then
|
||
return
|
||
end
|
||
local pid = self.RunPotions[self.PotionMenuSlot]
|
||
if pid ~= nil then
|
||
local p = self.Potions[pid]
|
||
table.remove(self.RunPotions, self.PotionMenuSlot)
|
||
if p ~= nil then
|
||
self:Toast("물약 버림: " .. p.name)
|
||
end
|
||
end
|
||
self:ClosePotionMenu()
|
||
self:RenderPotions()`),
|
||
method('RenderRelics', `local count = 0
|
||
if self.RunRelics ~= nil then
|
||
count = #self.RunRelics
|
||
end
|
||
for i = 1, 10 do
|
||
local base = "/ui/DefaultGroup/CombatHud/TopBar/RelicSlot" .. tostring(i)
|
||
local e = _EntityService:GetEntityByPath(base)
|
||
if e ~= nil and e.SpriteGUIRendererComponent ~= nil then
|
||
local rid = nil
|
||
if self.RunRelics ~= nil then
|
||
rid = self.RunRelics[i]
|
||
end
|
||
if rid ~= nil and self.Relics[rid] ~= nil and (i < 10 or count <= 10) then
|
||
e.SpriteGUIRendererComponent.ImageRUID = self.Relics[rid].icon
|
||
e.SpriteGUIRendererComponent.Color = Color(1, 1, 1, 1)
|
||
else
|
||
e.SpriteGUIRendererComponent.ImageRUID = ""
|
||
e.SpriteGUIRendererComponent.Color = Color(0.15, 0.16, 0.2, 0.6)
|
||
end
|
||
end
|
||
end
|
||
local of = ""
|
||
if count > 10 then
|
||
of = "+" .. tostring(count - 9)
|
||
end
|
||
self:SetText("/ui/DefaultGroup/CombatHud/TopBar/RelicOverflow", of)`),
|
||
method('ShowTooltip', `self:SetText("/ui/DefaultGroup/CombatHud/TooltipBox/Name", name)
|
||
self:SetText("/ui/DefaultGroup/CombatHud/TooltipBox/Desc", desc)
|
||
local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/TooltipBox")
|
||
if e ~= nil then
|
||
if e.UITransformComponent ~= nil then
|
||
e.UITransformComponent.anchoredPosition = Vector2(x, 400)
|
||
end
|
||
e.Enable = true
|
||
end`, [
|
||
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'name' },
|
||
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'desc' },
|
||
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'x' },
|
||
]),
|
||
method('HideTooltip', `self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/TooltipBox", false)`),
|
||
method('ShowMap', `self:ShowState("map")
|
||
self:RenderMap()`),
|
||
method('GenerateMap', `-- 절차 생성 — tools/map/rogue-map.mjs(JS 미러)와 로직 동기화 유지
|
||
self.MapNodes = {}
|
||
self.MapStart = {}
|
||
self.VisitedNodes = {}
|
||
self.Depth = 0
|
||
self.MapNodes["boss"] = { type = "boss", row = ${MAP_ROWS} + 1, col = 0, next = {} }
|
||
local cols = { 1, 2, 3, 4 }
|
||
for i = #cols, 2, -1 do
|
||
local j = math.random(1, i)
|
||
cols[i], cols[j] = cols[j], cols[i]
|
||
end
|
||
local starts = { cols[1], cols[2], math.random(1, ${MAP_COLS}), math.random(1, ${MAP_COLS}) }
|
||
for p = 1, 4 do
|
||
local c = starts[p]
|
||
local sid = "r1c" .. tostring(c)
|
||
if self.MapNodes[sid] == nil then
|
||
self.MapNodes[sid] = { type = "combat", row = 1, col = c, next = {} }
|
||
end
|
||
local found = false
|
||
for i = 1, #self.MapStart do
|
||
if self.MapStart[i] == sid then found = true end
|
||
end
|
||
if found == false then
|
||
table.insert(self.MapStart, sid)
|
||
end
|
||
for r = 1, ${MAP_ROWS} - 1 do
|
||
local nc = c + math.random(-1, 1)
|
||
if nc < 1 then nc = 1 end
|
||
if nc > ${MAP_COLS} then nc = ${MAP_COLS} end
|
||
local nid = "r" .. tostring(r + 1) .. "c" .. tostring(nc)
|
||
if self.MapNodes[nid] == nil then
|
||
self.MapNodes[nid] = { type = "combat", row = r + 1, col = nc, next = {} }
|
||
end
|
||
local fid = "r" .. tostring(r) .. "c" .. tostring(c)
|
||
local dup = false
|
||
for i = 1, #self.MapNodes[fid].next do
|
||
if self.MapNodes[fid].next[i] == nid then dup = true end
|
||
end
|
||
if dup == false then
|
||
table.insert(self.MapNodes[fid].next, nid)
|
||
end
|
||
c = nc
|
||
end
|
||
local lid = "r" .. tostring(${MAP_ROWS}) .. "c" .. tostring(c)
|
||
local bdup = false
|
||
for i = 1, #self.MapNodes[lid].next do
|
||
if self.MapNodes[lid].next[i] == "boss" then bdup = true end
|
||
end
|
||
if bdup == false then
|
||
table.insert(self.MapNodes[lid].next, "boss")
|
||
end
|
||
end
|
||
for r = 3, ${MAP_ROWS} do
|
||
for c = 1, ${MAP_COLS} do
|
||
local id = "r" .. tostring(r) .. "c" .. tostring(c)
|
||
local node = self.MapNodes[id]
|
||
if node ~= nil then
|
||
local eliteParent = false
|
||
for pid, pn in pairs(self.MapNodes) do
|
||
if pn.row == r - 1 and pn.type == "elite" then
|
||
for i = 1, #pn.next do
|
||
if pn.next[i] == id then eliteParent = true end
|
||
end
|
||
end
|
||
end
|
||
local w
|
||
if r == ${MAP_ROWS} then
|
||
w = { { "rest", 50 }, { "combat", 25 }, { "shop", 10 }, { "elite", 8 }, { "treasure", 7 } }
|
||
elseif r >= 4 then
|
||
w = { { "combat", 45 }, { "elite", 16 }, { "shop", 12 }, { "rest", 12 }, { "treasure", 15 } }
|
||
else
|
||
w = { { "combat", 45 }, { "shop", 12 }, { "rest", 12 } }
|
||
end
|
||
local total = 0
|
||
for i = 1, #w do
|
||
if w[i][1] == "elite" and eliteParent == true then
|
||
w[i][2] = 0
|
||
end
|
||
total = total + w[i][2]
|
||
end
|
||
local roll = math.random() * total
|
||
local acc = 0
|
||
for i = 1, #w do
|
||
acc = acc + w[i][2]
|
||
if roll <= acc then
|
||
node.type = w[i][1]
|
||
break
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end`),
|
||
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('RenderMapNode', `local base = "/ui/DefaultGroup/MapHud/Node_" .. id
|
||
local e = _EntityService:GetEntityByPath(base)
|
||
if e == nil then
|
||
return
|
||
end
|
||
local node = self.MapNodes[id]
|
||
if node == nil then
|
||
e.Enable = false
|
||
return
|
||
end
|
||
e.Enable = true
|
||
local tname = "전투"
|
||
local r0 = 0.78
|
||
local g0 = 0.36
|
||
local b0 = 0.32
|
||
if node.type == "elite" then
|
||
tname = "엘리트"
|
||
r0 = 0.62
|
||
g0 = 0.4
|
||
b0 = 0.85
|
||
elseif node.type == "shop" then
|
||
tname = "상점"
|
||
r0 = 0.9
|
||
g0 = 0.75
|
||
b0 = 0.35
|
||
elseif node.type == "rest" then
|
||
tname = "휴식"
|
||
r0 = 0.4
|
||
g0 = 0.75
|
||
b0 = 0.45
|
||
elseif node.type == "treasure" then
|
||
tname = "보물"
|
||
r0 = 0.35
|
||
g0 = 0.7
|
||
b0 = 0.75
|
||
elseif node.type == "boss" then
|
||
tname = "보스"
|
||
r0 = 0.85
|
||
g0 = 0.25
|
||
b0 = 0.25
|
||
end
|
||
self:SetText(base .. "/Label", tname)
|
||
local reachable = self:IsReachable(id)
|
||
local visited = false
|
||
if self.VisitedNodes ~= nil then
|
||
for i = 1, #self.VisitedNodes do
|
||
if self.VisitedNodes[i] == id then visited = true end
|
||
end
|
||
end
|
||
if e.SpriteGUIRendererComponent ~= nil then
|
||
if id == self.CurrentNodeId then
|
||
e.SpriteGUIRendererComponent.Color = Color(0.95, 0.8, 0.3, 1)
|
||
elseif visited == true then
|
||
e.SpriteGUIRendererComponent.Color = Color(0.18, 0.19, 0.22, 0.9)
|
||
elseif reachable == true then
|
||
e.SpriteGUIRendererComponent.Color = Color(r0, g0, b0, 1)
|
||
else
|
||
e.SpriteGUIRendererComponent.Color = Color(r0 * 0.45, g0 * 0.45, b0 * 0.45, 0.55)
|
||
end
|
||
end
|
||
if e.ButtonComponent ~= nil then
|
||
e.ButtonComponent.Enable = reachable
|
||
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'id' }]),
|
||
method('RenderMapDots', `local node = self.MapNodes[fromId]
|
||
local has = false
|
||
if node ~= nil then
|
||
for i = 1, #node.next do
|
||
if node.next[i] == toId then has = true end
|
||
end
|
||
end
|
||
for k = 1, 3 do
|
||
local d = _EntityService:GetEntityByPath("/ui/DefaultGroup/MapHud/Dot_" .. dotId .. "_" .. tostring(k))
|
||
if d ~= nil then
|
||
d.Enable = has
|
||
if has == true and d.SpriteGUIRendererComponent ~= nil then
|
||
if fromId == self.CurrentNodeId then
|
||
d.SpriteGUIRendererComponent.Color = Color(0.95, 0.8, 0.3, 1)
|
||
else
|
||
d.SpriteGUIRendererComponent.Color = Color(0.5, 0.5, 0.55, 0.8)
|
||
end
|
||
end
|
||
end
|
||
end`, [
|
||
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'dotId' },
|
||
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'fromId' },
|
||
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'toId' },
|
||
]),
|
||
method('RenderMap', `for r = 1, ${MAP_ROWS} do
|
||
for c = 1, ${MAP_COLS} do
|
||
self:RenderMapNode("r" .. tostring(r) .. "c" .. tostring(c))
|
||
end
|
||
end
|
||
self:RenderMapNode("boss")
|
||
for r = 1, ${MAP_ROWS} - 1 do
|
||
for c = 1, ${MAP_COLS} do
|
||
local fid = "r" .. tostring(r) .. "c" .. tostring(c)
|
||
for c2 = c - 1, c + 1 do
|
||
if c2 >= 1 and c2 <= ${MAP_COLS} then
|
||
self:RenderMapDots(fid .. "_" .. tostring(c2), fid, "r" .. tostring(r + 1) .. "c" .. tostring(c2))
|
||
end
|
||
end
|
||
end
|
||
end
|
||
for c = 1, ${MAP_COLS} do
|
||
local fid = "r" .. tostring(${MAP_ROWS}) .. "c" .. tostring(c)
|
||
self:RenderMapDots(fid .. "_b", fid, "boss")
|
||
end
|
||
`),
|
||
method('PickNode', `if self.RunActive ~= true then
|
||
return
|
||
end
|
||
if self:IsReachable(id) ~= true then
|
||
return
|
||
end
|
||
self.CurrentNodeId = id
|
||
if self.VisitedNodes == nil then
|
||
self.VisitedNodes = {}
|
||
end
|
||
table.insert(self.VisitedNodes, id)
|
||
local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/MapHud")
|
||
if hud ~= nil then
|
||
hud.Enable = false
|
||
end
|
||
local node = self.MapNodes[id]
|
||
self.Depth = node.row
|
||
self:RenderRun()
|
||
if node.type == "shop" then
|
||
self:ShowShop()
|
||
elseif node.type == "rest" then
|
||
self:ShowRest()
|
||
elseif node.type == "treasure" then
|
||
self:ShowTreasure()
|
||
else
|
||
self.CurrentEnemyId = ""
|
||
self:StartCombat()
|
||
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'id' }]),
|
||
method('ShowShop', `local pool = self:CardPool()
|
||
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
|
||
local pkeys = {}
|
||
for pid, _ in pairs(self.Potions) do
|
||
table.insert(pkeys, pid)
|
||
end
|
||
table.sort(pkeys)
|
||
self.ShopPotion = pkeys[math.random(1, #pkeys)]
|
||
self.ShopPotionBought = 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
|
||
local pp = self.Potions[self.ShopPotion]
|
||
if pp ~= nil then
|
||
self:SetText("/ui/DefaultGroup/ShopHud/Potion/Label", pp.name .. " — " .. pp.desc)
|
||
self:SetText("/ui/DefaultGroup/ShopHud/Potion/Price", string.format("%d", ${POTIONS.shopPrice}) .. " 메소")
|
||
local pe = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Potion")
|
||
if pe ~= nil and pe.SpriteGUIRendererComponent ~= nil then
|
||
if self.ShopPotionBought == true then
|
||
pe.SpriteGUIRendererComponent.Color = Color(0.2, 0.22, 0.26, 0.6)
|
||
else
|
||
pe.SpriteGUIRendererComponent.Color = Color(0.45, 0.7, 0.55, 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('BuyPotion', `if self.ShopPotionBought == true then
|
||
return
|
||
end
|
||
if self.Gold < ${POTIONS.shopPrice} then
|
||
return
|
||
end
|
||
if self.RunPotions ~= nil and #self.RunPotions >= self.PotionSlots then
|
||
self:Toast("물약 슬롯이 가득 찼습니다")
|
||
return
|
||
end
|
||
if self:AddPotion(self.ShopPotion) == true then
|
||
self.Gold = self.Gold - ${POTIONS.shopPrice}
|
||
self.ShopPotionBought = true
|
||
end
|
||
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
|
||
local t = _EntityService:GetEntityByPath("/ui/DefaultGroup/TreasureHud")
|
||
if t ~= nil then
|
||
t.Enable = false
|
||
end
|
||
self:ShowMap()`),
|
||
method('ShowTreasure', `self.ChestOpened = false
|
||
local chest = _EntityService:GetEntityByPath("/ui/DefaultGroup/TreasureHud/Chest")
|
||
if chest ~= nil then
|
||
if chest.SpriteGUIRendererComponent ~= nil then
|
||
chest.SpriteGUIRendererComponent.ImageRUID = "${CHEST_CLOSED_RUID}"
|
||
end
|
||
if chest.UITransformComponent ~= nil then
|
||
chest.UITransformComponent.anchoredPosition = Vector2(0, 40)
|
||
end
|
||
end
|
||
self:SetEntityEnabled("/ui/DefaultGroup/TreasureHud/Reward", false)
|
||
self:SetEntityEnabled("/ui/DefaultGroup/TreasureHud/Hint", true)
|
||
self:ShowState("treasure")`),
|
||
method('OpenChest', `if self.ChestOpened == true then
|
||
return
|
||
end
|
||
self.ChestOpened = true
|
||
self:SetEntityEnabled("/ui/DefaultGroup/TreasureHud/Hint", false)
|
||
local chest = _EntityService:GetEntityByPath("/ui/DefaultGroup/TreasureHud/Chest")
|
||
local steps = { 10, -10, 8, -8, 5, 0 }
|
||
for i = 1, #steps do
|
||
local dx = steps[i]
|
||
_TimerService:SetTimerOnce(function()
|
||
if chest ~= nil and isvalid(chest) and chest.UITransformComponent ~= nil then
|
||
chest.UITransformComponent.anchoredPosition = Vector2(dx, 40)
|
||
end
|
||
end, 0.08 * i)
|
||
end
|
||
_TimerService:SetTimerOnce(function()
|
||
if chest ~= nil and isvalid(chest) and chest.SpriteGUIRendererComponent ~= nil then
|
||
chest.SpriteGUIRendererComponent.ImageRUID = "${CHEST_OPEN_RUID}"
|
||
end
|
||
local g = 40 + math.random(0, 20)
|
||
local nid = self:PickNewRelic()
|
||
local msg = ""
|
||
if nid ~= "" then
|
||
self:AddRelic(nid)
|
||
local nr = self.Relics[nid]
|
||
msg = "유물 획득: " .. nr.name .. " · 메소 +" .. tostring(g)
|
||
else
|
||
g = g + 30
|
||
msg = "메소 +" .. tostring(g)
|
||
end
|
||
self.Gold = self.Gold + g
|
||
self:RenderRun()
|
||
self:SetText("/ui/DefaultGroup/TreasureHud/Reward", msg)
|
||
self:SetEntityEnabled("/ui/DefaultGroup/TreasureHud/Reward", true)
|
||
end, 0.55)`),
|
||
]);
|
||
for (const m of combat.ContentProto.Json.Methods) {
|
||
if (m.ExecSpace === 0) m.ExecSpace = 6; // 기본은 ClientOnly(6), 서버 RPC(Server=1·Client=2) 명시값은 보존
|
||
}
|
||
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.');
|