From 8d2e320d602de61b1c7206bb07fd12187b082ca1 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 26 Jun 2026 18:35:51 +0900 Subject: [PATCH] =?UTF-8?q?refactor(cb):=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=ED=9A=A1=EB=8B=A8=20=EA=B4=80=EC=8B=AC=EC=82=AC=20?= =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EB=B6=84=EB=A6=AC=20(=EC=9D=B4=EB=8F=99?= =?UTF-8?q?=C2=B7=EB=B3=B8=EB=AC=B8=20=EB=AC=B4=EB=B3=80=EA=B2=BD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 화면 전환·NPC·포지션 관심사를 state.mjs/render.mjs/runend.mjs에서 전용 모듈로 분리. 런타임은 단일 SlayDeckController codeblock 유지. - state.mjs → screens.mjs 개명 (화면 라우팅·버튼 바인딩) - npc.mjs 신규: OnLobbyNpcInteract - navigation.mjs 신규: GoLobbyMap·TeleportToActMap (월드 텔레포트) - layout.mjs 신규: PositionMonsterSlot (UI 슬롯 배치) - gen-slaydeck.mjs import·spread 갱신 검증: tools/verify/cbset.mjs (순서 무관 집합 비교) = 189/189 무손실, 본문/exec/params 변경 0. cbgap GAP 0, 테스트 93/93. 산출물 재생성(SlayDeckController.codeblock) 포함 — 메서드 순서만 바뀜. Co-Authored-By: Claude Opus 4.8 (1M context) --- RootDesk/MyDesk/SlayDeckController.codeblock | 152 ++--- tools/deck/cb/layout.mjs | 21 + tools/deck/cb/navigation.mjs | 34 ++ tools/deck/cb/npc.mjs | 18 + tools/deck/cb/render.mjs | 607 +++++++++---------- tools/deck/cb/runend.mjs | 61 +- tools/deck/cb/{state.mjs => screens.mjs} | 375 ++++++------ tools/deck/gen-slaydeck.mjs | 10 +- tools/verify/cbset.mjs | 40 ++ 9 files changed, 691 insertions(+), 627 deletions(-) create mode 100644 tools/deck/cb/layout.mjs create mode 100644 tools/deck/cb/navigation.mjs create mode 100644 tools/deck/cb/npc.mjs rename tools/deck/cb/{state.mjs => screens.mjs} (91%) create mode 100644 tools/verify/cbset.mjs diff --git a/RootDesk/MyDesk/SlayDeckController.codeblock b/RootDesk/MyDesk/SlayDeckController.codeblock index 952c981..ce2e63c 100644 --- a/RootDesk/MyDesk/SlayDeckController.codeblock +++ b/RootDesk/MyDesk/SlayDeckController.codeblock @@ -1342,44 +1342,6 @@ "Attributes": [], "Name": "ShowLobby" }, - { - "Return": { - "Type": "void", - "DefaultValue": null, - "SyncDirection": 0, - "Attributes": [], - "Name": null - }, - "Arguments": [], - "Code": "self.LobbyTpTries = 0\nlocal eventId = 0\nlocal function go()\n\tself.LobbyTpTries = self.LobbyTpTries + 1\n\tlocal lp = _UserService.LocalPlayer\n\tif lp ~= nil then\n\t\tif lp.CurrentMapName ~= \"lobby\" then\n\t\t\t_TeleportService:TeleportToMapPosition(lp, Vector3(-5, 0.03, 0), \"lobby\")\n\t\tend\n\t\t_TimerService:ClearTimer(eventId)\n\telseif self.LobbyTpTries > 50 then\n\t\t_TimerService:ClearTimer(eventId)\n\tend\nend\neventId = _TimerService:SetTimerRepeat(go, 0.1)", - "Scope": 2, - "ExecSpace": 6, - "Attributes": [], - "Name": "GoLobbyMap" - }, - { - "Return": { - "Type": "void", - "DefaultValue": null, - "SyncDirection": 0, - "Attributes": [], - "Name": null - }, - "Arguments": [ - { - "Type": "string", - "DefaultValue": "\"\"", - "SyncDirection": 0, - "Attributes": [], - "Name": "id" - } - ], - "Code": "if self.RunActive == true then\n\treturn\nend\nif id == \"run\" then\n\tself:ShowCharacterSelect()\nelseif id == \"codex\" then\n\tself:ShowCodex()\nelseif id == \"shop\" then\n\tself:ShowSoulShop()\nelseif id == \"board\" then\n\tself:ShowBoard()\nend", - "Scope": 2, - "ExecSpace": 6, - "Attributes": [], - "Name": "OnLobbyNpcInteract" - }, { "Return": { "Type": "void", @@ -1455,6 +1417,59 @@ "Attributes": [], "Name": "CloseBoard" }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "Type": "string", + "DefaultValue": "\"\"", + "SyncDirection": 0, + "Attributes": [], + "Name": "id" + } + ], + "Code": "if self.RunActive == true then\n\treturn\nend\nif id == \"run\" then\n\tself:ShowCharacterSelect()\nelseif id == \"codex\" then\n\tself:ShowCodex()\nelseif id == \"shop\" then\n\tself:ShowSoulShop()\nelseif id == \"board\" then\n\tself:ShowBoard()\nend", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "OnLobbyNpcInteract" + }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [], + "Code": "self.LobbyTpTries = 0\nlocal eventId = 0\nlocal function go()\n\tself.LobbyTpTries = self.LobbyTpTries + 1\n\tlocal lp = _UserService.LocalPlayer\n\tif lp ~= nil then\n\t\tif lp.CurrentMapName ~= \"lobby\" then\n\t\t\t_TeleportService:TeleportToMapPosition(lp, Vector3(-5, 0.03, 0), \"lobby\")\n\t\tend\n\t\t_TimerService:ClearTimer(eventId)\n\telseif self.LobbyTpTries > 50 then\n\t\t_TimerService:ClearTimer(eventId)\n\tend\nend\neventId = _TimerService:SetTimerRepeat(go, 0.1)", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "GoLobbyMap" + }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [], + "Code": "local maps = { \"map01\", \"map02\", \"map03\", \"map04\", \"map05\" }\nlocal target = maps[self.Floor]\nif target == nil then\n\treturn\nend\nlocal lp = _UserService.LocalPlayer\nif lp == nil then\n\treturn\nend\nif lp.CurrentMapName == target then\n\treturn\nend\n_TeleportService:TeleportToMapPosition(lp, Vector3(-6, 0.03, 0), target)", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "TeleportToActMap" + }, { "Return": { "Type": "void", @@ -3937,21 +3952,6 @@ "Attributes": [], "Name": "SetJob" }, - { - "Return": { - "Type": "void", - "DefaultValue": null, - "SyncDirection": 0, - "Attributes": [], - "Name": null - }, - "Arguments": [], - "Code": "local maps = { \"map01\", \"map02\", \"map03\", \"map04\", \"map05\" }\nlocal target = maps[self.Floor]\nif target == nil then\n\treturn\nend\nlocal lp = _UserService.LocalPlayer\nif lp == nil then\n\treturn\nend\nif lp.CurrentMapName == target then\n\treturn\nend\n_TeleportService:TeleportToMapPosition(lp, Vector3(-6, 0.03, 0), target)", - "Scope": 2, - "ExecSpace": 6, - "Attributes": [], - "Name": "TeleportToActMap" - }, { "Return": { "Type": "void", @@ -4230,29 +4230,6 @@ "Attributes": [], "Name": "SetHpBar" }, - { - "Return": { - "Type": "void", - "DefaultValue": null, - "SyncDirection": 0, - "Attributes": [], - "Name": null - }, - "Arguments": [ - { - "Type": "number", - "DefaultValue": null, - "SyncDirection": 0, - "Attributes": [], - "Name": "slot" - } - ], - "Code": "local m = self.Monsters[slot]\nif m == nil or m.entity == nil or not isvalid(m.entity) then\n\treturn\nend\nlocal tr = m.entity.TransformComponent\nif tr == nil then\n\treturn\nend\nlocal wp = tr.WorldPosition\nlocal screen = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + 1.4))\nlocal uipos = _UILogic:ScreenToUIPosition(screen)\nlocal e = _EntityService:GetEntityByPath(\"/ui/RunUIGroup/CombatHud/MonsterStatus\" .. tostring(slot))\nif e ~= nil and e.UITransformComponent ~= nil then\n\te.UITransformComponent.anchoredPosition = uipos\nend", - "Scope": 2, - "ExecSpace": 6, - "Attributes": [], - "Name": "PositionMonsterSlot" - }, { "Return": { "Type": "void", @@ -4291,6 +4268,29 @@ "Attributes": [], "Name": "RenderRun" }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "slot" + } + ], + "Code": "local m = self.Monsters[slot]\nif m == nil or m.entity == nil or not isvalid(m.entity) then\n\treturn\nend\nlocal tr = m.entity.TransformComponent\nif tr == nil then\n\treturn\nend\nlocal wp = tr.WorldPosition\nlocal screen = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + 1.4))\nlocal uipos = _UILogic:ScreenToUIPosition(screen)\nlocal e = _EntityService:GetEntityByPath(\"/ui/RunUIGroup/CombatHud/MonsterStatus\" .. tostring(slot))\nif e ~= nil and e.UITransformComponent ~= nil then\n\te.UITransformComponent.anchoredPosition = uipos\nend", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "PositionMonsterSlot" + }, { "Return": { "Type": "any", diff --git a/tools/deck/cb/layout.mjs b/tools/deck/cb/layout.mjs new file mode 100644 index 0000000..7308ff6 --- /dev/null +++ b/tools/deck/cb/layout.mjs @@ -0,0 +1,21 @@ +import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs'; +import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs'; +import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs'; + +export const layoutMethods = [ + 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/RunUIGroup/CombatHud/MonsterStatus" .. tostring(slot)) +if e ~= nil and e.UITransformComponent ~= nil then + e.UITransformComponent.anchoredPosition = uipos +end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]), +]; diff --git a/tools/deck/cb/navigation.mjs b/tools/deck/cb/navigation.mjs new file mode 100644 index 0000000..ca55d00 --- /dev/null +++ b/tools/deck/cb/navigation.mjs @@ -0,0 +1,34 @@ +import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs'; +import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs'; +import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs'; + +export const navigationMethods = [ + method('GoLobbyMap', `self.LobbyTpTries = 0 +local eventId = 0 +local function go() + self.LobbyTpTries = self.LobbyTpTries + 1 + local lp = _UserService.LocalPlayer + if lp ~= nil then + if lp.CurrentMapName ~= "${LOBBY_MAP}" then + _TeleportService:TeleportToMapPosition(lp, ${LOBBY_SPAWN}, "${LOBBY_MAP}") + end + _TimerService:ClearTimer(eventId) + elseif self.LobbyTpTries > 50 then + _TimerService:ClearTimer(eventId) + end +end +eventId = _TimerService:SetTimerRepeat(go, 0.1)`), + 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)`), +]; diff --git a/tools/deck/cb/npc.mjs b/tools/deck/cb/npc.mjs new file mode 100644 index 0000000..73815fe --- /dev/null +++ b/tools/deck/cb/npc.mjs @@ -0,0 +1,18 @@ +import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs'; +import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs'; +import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs'; + +export const npcMethods = [ + method('OnLobbyNpcInteract', `if self.RunActive == true then + return +end +if id == "run" then + self:ShowCharacterSelect() +elseif id == "codex" then + self:ShowCodex() +elseif id == "shop" then + self:ShowSoulShop() +elseif id == "board" then + self:ShowBoard() +end`, [{ Type: 'string', DefaultValue: '""', SyncDirection: 0, Attributes: [], Name: 'id' }]), +]; diff --git a/tools/deck/cb/render.mjs b/tools/deck/cb/render.mjs index d89883e..cff777d 100644 --- a/tools/deck/cb/render.mjs +++ b/tools/deck/cb/render.mjs @@ -1,311 +1,296 @@ -import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs'; -import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs'; -import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs'; - -export const renderMethods = [ - 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/RunUIGroup/CombatHud/MonsterStatus" .. 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 - elseif intent.kind == "AddCard" then - t = "저주 카드 추가" - end - end - self:SetText(base .. "/Intent", t) - local dragActive = self.DragTargetIndex ~= nil and self.DragTargetIndex > 0 - local shownTarget = self.TargetIndex - if dragActive == true then shownTarget = self.DragTargetIndex end - self:SetEntityEnabled(base .. "/TargetMarker", i == shownTarget and dragActive) - self:SetEntityEnabled(base .. "/TargetMarker/Label", i == shownTarget and dragActive) - 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) - elseif intent.kind == "AddCard" then - intentEntity.TextComponent.FontColor = Color(0.6, 0.85, 0.4, 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/RunUIGroup/CombatHud/PlayerPanel/HpText", string.format("%d", self.PlayerHp) .. "/" .. string.format("%d", self.PlayerMaxHp)) -self:SetHpBar("/ui/RunUIGroup/CombatHud/PlayerPanel/HpBarFill", self.PlayerHp, self.PlayerMaxHp, 220) -self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge", self.PlayerBlock > 0) -self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge/Value", string.format("%d", self.PlayerBlock)) -local pb = self:BuffsLabel(self.PlayerStr, self.PlayerWeak, self.PlayerVuln, 0) -if self.PlayerIntangible ~= nil and self.PlayerIntangible > 0 then - if pb ~= "" then pb = pb .. " " end - pb = pb .. "불가침" .. tostring(self.PlayerIntangible) -end -if self.PlayerDex ~= nil and self.PlayerDex > 0 then - if pb ~= "" then pb = pb .. " " end - pb = pb .. "민첩+" .. tostring(self.PlayerDex) -end -if self.PlayerThorns ~= nil and self.PlayerThorns > 0 then - if pb ~= "" then pb = pb .. " " end - pb = pb .. "가시" .. tostring(self.PlayerThorns) -end -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/RunUIGroup/CombatHud/PlayerPanel/Buffs", pb) -self:RenderRun()`), - method('ShowDmgPop', `local slotKey = string.format("%d", math.floor(slot or 0)) -local base = "/ui/RunUIGroup/CombatHud/DmgPop" .. slotKey -local pop = _EntityService:GetEntityByPath(base) -if pop == nil then - return -end -self.DmgPopSeq = (self.DmgPopSeq or 0) + 1 -local popSeq = self.DmgPopSeq -self:SetText(base, "") -local damageDigitRuids = { ${DAMAGE_DIGIT_RUIDS.map(luaStr).join(', ')} } -local shown = tostring(math.max(0, math.floor(amount))) -if string.len(shown) > ${DAMAGE_POP_MAX_DIGITS} then - shown = string.sub(shown, 1, ${DAMAGE_POP_MAX_DIGITS}) -end -local digits = {} -for i = 1, string.len(shown) do - table.insert(digits, tonumber(string.sub(shown, i, i)) or 0) -end -local totalW = #digits * ${DAMAGE_POP_DIGIT_W} + math.max(0, #digits - 1) * ${DAMAGE_POP_DIGIT_SPACING} -local startX = -totalW / 2 + ${DAMAGE_POP_DIGIT_W} / 2 -for i = 1, ${DAMAGE_POP_MAX_DIGITS} do - self:SetEntityEnabled(base .. "/Digit" .. tostring(i), false) -end -for i = 1, ${DAMAGE_POP_MAX_DIGITS} do - local digitPath = base .. "/Digit" .. tostring(i) - local digitEntity = _EntityService:GetEntityByPath(digitPath) - if digitEntity ~= nil and digitEntity.SpriteGUIRendererComponent ~= nil then - if digits[i] ~= nil then - digitEntity.SpriteGUIRendererComponent.ImageRUID = damageDigitRuids[digits[i] + 1] - digitEntity.SpriteGUIRendererComponent.Color = Color(1, 1, 1, 1) - if digitEntity.UITransformComponent ~= nil then - digitEntity.UITransformComponent.anchoredPosition = Vector2(startX + (i - 1) * (${DAMAGE_POP_DIGIT_W} + ${DAMAGE_POP_DIGIT_SPACING}), 0) - end - self:SetEntityEnabled(digitPath, true) - else - self:SetEntityEnabled(digitPath, false) - end - end -end -local popPos = nil -local m = self.Monsters[slot] -if m ~= nil and m.entity ~= nil and isvalid(m.entity) and m.entity.TransformComponent ~= nil then - local wp = m.entity.TransformComponent.WorldPosition - local screen = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + ${HEAD_OFFSET_Y + 0.45})) - popPos = _UILogic:ScreenToUIPosition(screen) -else - local slotEntity = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/MonsterStatus" .. slotKey) - if slotEntity ~= nil and slotEntity.UITransformComponent ~= nil then - local sp = slotEntity.UITransformComponent.anchoredPosition - popPos = Vector2(sp.x, sp.y + 76) - end -end -if pop ~= nil and pop.UITransformComponent ~= nil then - if popPos ~= nil then - pop.UITransformComponent.anchoredPosition = popPos - else - pop.UITransformComponent.anchoredPosition = Vector2(0, 120) - end -end -self:SetEntityEnabled(base, true) -for i = 1, 6 do - _TimerService:SetTimerOnce(function() - if self.DmgPopSeq ~= popSeq then - return - end - local p = _EntityService:GetEntityByPath(base) - if p ~= nil and p.UITransformComponent ~= nil then - local cur = p.UITransformComponent.anchoredPosition - p.UITransformComponent.anchoredPosition = Vector2(cur.x, cur.y + 7) - end - end, 0.045 * i) -end -_TimerService:SetTimerOnce(function() - if self.DmgPopSeq ~= popSeq then - return - end - self:SetEntityEnabled(base, false) -end, 0.48)`, [ - { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }, - { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }, - ]), - method('ShowPlayerDmgPop', `local base = "/ui/RunUIGroup/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('PlayerAttackMotion', `local lp = _UserService.LocalPlayer -if lp == nil then - return -end -if lp.StateComponent == nil then - return -end -pcall(function() lp.StateComponent:ChangeState("ATTACK") end) -_TimerService:SetTimerOnce(function() - if lp ~= nil and isvalid(lp) and lp.StateComponent ~= nil then - pcall(function() lp.StateComponent:ChangeState("IDLE") end) - end -end, 0.5)`), - method('PlayerHitMotion', `local lp = _UserService.LocalPlayer -if lp == nil then - return -end -if lp.StateComponent ~= nil then - pcall(function() lp.StateComponent:ChangeState("HIT") end) -end -local tr = lp.TransformComponent -if tr == nil then - return -end -local p = tr.Position -tr.Position = Vector3(p.x - 0.15, p.y, p.z) -_TimerService:SetTimerOnce(function() - if lp ~= nil and isvalid(lp) and lp.TransformComponent ~= nil then - lp.TransformComponent.Position = Vector3(p.x, p.y, p.z) - end -end, 0.15)`), - method('MonsterLunge', `local m = self.Monsters[idx] -if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then - return -end -if m.motionBusy == true then - return -end -m.motionBusy = true -local e = m.entity -local tr = e.TransformComponent -if tr == nil then - m.motionBusy = false - return -end -local p = tr.Position -tr.Position = Vector3(p.x - 0.35, p.y, p.z) -_TimerService:SetTimerOnce(function() - if isvalid(e) and e.TransformComponent ~= nil then - e.TransformComponent.Position = Vector3(p.x, p.y, p.z) - end - m.motionBusy = false -end, 0.18)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'idx' }]), - method('MonsterHitMotion', `local m = self.Monsters[slot] -if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then - return -end -local e = m.entity -if m.hitClip ~= nil and e.SpriteRendererComponent ~= nil then - e.SpriteRendererComponent.SpriteRUID = m.hitClip - _TimerService:SetTimerOnce(function() - if isvalid(e) and e.SpriteRendererComponent ~= nil and m.alive == true and m.standClip ~= nil then - e.SpriteRendererComponent.SpriteRUID = m.standClip - end - end, 0.5) -else - if m.motionBusy == true then - return - end - m.motionBusy = true - local tr = e.TransformComponent - if tr == nil then - m.motionBusy = false - return - end - local p = tr.Position - local seq = { 0.12, -0.12, 0 } - for i = 1, #seq do - local dx = seq[i] - _TimerService:SetTimerOnce(function() - if isvalid(e) and e.TransformComponent ~= nil then - e.TransformComponent.Position = Vector3(p.x + dx, p.y, p.z) - end - if i == #seq then - m.motionBusy = false - end - end, 0.06 * i) - end -end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]), - 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/RunUIGroup/CombatHud/MonsterStatus" .. 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/RunUIGroup/CombatHud/TopBar/Floor", floorText) -self:SetText("/ui/RunUIGroup/CombatHud/TopBar/Gold", "메소 " .. string.format("%d", self.Gold))`), -]; +import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs'; +import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs'; +import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs'; + +export const renderMethods = [ + 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/RunUIGroup/CombatHud/MonsterStatus" .. 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 + elseif intent.kind == "AddCard" then + t = "저주 카드 추가" + end + end + self:SetText(base .. "/Intent", t) + local dragActive = self.DragTargetIndex ~= nil and self.DragTargetIndex > 0 + local shownTarget = self.TargetIndex + if dragActive == true then shownTarget = self.DragTargetIndex end + self:SetEntityEnabled(base .. "/TargetMarker", i == shownTarget and dragActive) + self:SetEntityEnabled(base .. "/TargetMarker/Label", i == shownTarget and dragActive) + 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) + elseif intent.kind == "AddCard" then + intentEntity.TextComponent.FontColor = Color(0.6, 0.85, 0.4, 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/RunUIGroup/CombatHud/PlayerPanel/HpText", string.format("%d", self.PlayerHp) .. "/" .. string.format("%d", self.PlayerMaxHp)) +self:SetHpBar("/ui/RunUIGroup/CombatHud/PlayerPanel/HpBarFill", self.PlayerHp, self.PlayerMaxHp, 220) +self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge", self.PlayerBlock > 0) +self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge/Value", string.format("%d", self.PlayerBlock)) +local pb = self:BuffsLabel(self.PlayerStr, self.PlayerWeak, self.PlayerVuln, 0) +if self.PlayerIntangible ~= nil and self.PlayerIntangible > 0 then + if pb ~= "" then pb = pb .. " " end + pb = pb .. "불가침" .. tostring(self.PlayerIntangible) +end +if self.PlayerDex ~= nil and self.PlayerDex > 0 then + if pb ~= "" then pb = pb .. " " end + pb = pb .. "민첩+" .. tostring(self.PlayerDex) +end +if self.PlayerThorns ~= nil and self.PlayerThorns > 0 then + if pb ~= "" then pb = pb .. " " end + pb = pb .. "가시" .. tostring(self.PlayerThorns) +end +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/RunUIGroup/CombatHud/PlayerPanel/Buffs", pb) +self:RenderRun()`), + method('ShowDmgPop', `local slotKey = string.format("%d", math.floor(slot or 0)) +local base = "/ui/RunUIGroup/CombatHud/DmgPop" .. slotKey +local pop = _EntityService:GetEntityByPath(base) +if pop == nil then + return +end +self.DmgPopSeq = (self.DmgPopSeq or 0) + 1 +local popSeq = self.DmgPopSeq +self:SetText(base, "") +local damageDigitRuids = { ${DAMAGE_DIGIT_RUIDS.map(luaStr).join(', ')} } +local shown = tostring(math.max(0, math.floor(amount))) +if string.len(shown) > ${DAMAGE_POP_MAX_DIGITS} then + shown = string.sub(shown, 1, ${DAMAGE_POP_MAX_DIGITS}) +end +local digits = {} +for i = 1, string.len(shown) do + table.insert(digits, tonumber(string.sub(shown, i, i)) or 0) +end +local totalW = #digits * ${DAMAGE_POP_DIGIT_W} + math.max(0, #digits - 1) * ${DAMAGE_POP_DIGIT_SPACING} +local startX = -totalW / 2 + ${DAMAGE_POP_DIGIT_W} / 2 +for i = 1, ${DAMAGE_POP_MAX_DIGITS} do + self:SetEntityEnabled(base .. "/Digit" .. tostring(i), false) +end +for i = 1, ${DAMAGE_POP_MAX_DIGITS} do + local digitPath = base .. "/Digit" .. tostring(i) + local digitEntity = _EntityService:GetEntityByPath(digitPath) + if digitEntity ~= nil and digitEntity.SpriteGUIRendererComponent ~= nil then + if digits[i] ~= nil then + digitEntity.SpriteGUIRendererComponent.ImageRUID = damageDigitRuids[digits[i] + 1] + digitEntity.SpriteGUIRendererComponent.Color = Color(1, 1, 1, 1) + if digitEntity.UITransformComponent ~= nil then + digitEntity.UITransformComponent.anchoredPosition = Vector2(startX + (i - 1) * (${DAMAGE_POP_DIGIT_W} + ${DAMAGE_POP_DIGIT_SPACING}), 0) + end + self:SetEntityEnabled(digitPath, true) + else + self:SetEntityEnabled(digitPath, false) + end + end +end +local popPos = nil +local m = self.Monsters[slot] +if m ~= nil and m.entity ~= nil and isvalid(m.entity) and m.entity.TransformComponent ~= nil then + local wp = m.entity.TransformComponent.WorldPosition + local screen = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + ${HEAD_OFFSET_Y + 0.45})) + popPos = _UILogic:ScreenToUIPosition(screen) +else + local slotEntity = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/MonsterStatus" .. slotKey) + if slotEntity ~= nil and slotEntity.UITransformComponent ~= nil then + local sp = slotEntity.UITransformComponent.anchoredPosition + popPos = Vector2(sp.x, sp.y + 76) + end +end +if pop ~= nil and pop.UITransformComponent ~= nil then + if popPos ~= nil then + pop.UITransformComponent.anchoredPosition = popPos + else + pop.UITransformComponent.anchoredPosition = Vector2(0, 120) + end +end +self:SetEntityEnabled(base, true) +for i = 1, 6 do + _TimerService:SetTimerOnce(function() + if self.DmgPopSeq ~= popSeq then + return + end + local p = _EntityService:GetEntityByPath(base) + if p ~= nil and p.UITransformComponent ~= nil then + local cur = p.UITransformComponent.anchoredPosition + p.UITransformComponent.anchoredPosition = Vector2(cur.x, cur.y + 7) + end + end, 0.045 * i) +end +_TimerService:SetTimerOnce(function() + if self.DmgPopSeq ~= popSeq then + return + end + self:SetEntityEnabled(base, false) +end, 0.48)`, [ + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }, + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }, + ]), + method('ShowPlayerDmgPop', `local base = "/ui/RunUIGroup/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('PlayerAttackMotion', `local lp = _UserService.LocalPlayer +if lp == nil then + return +end +if lp.StateComponent == nil then + return +end +pcall(function() lp.StateComponent:ChangeState("ATTACK") end) +_TimerService:SetTimerOnce(function() + if lp ~= nil and isvalid(lp) and lp.StateComponent ~= nil then + pcall(function() lp.StateComponent:ChangeState("IDLE") end) + end +end, 0.5)`), + method('PlayerHitMotion', `local lp = _UserService.LocalPlayer +if lp == nil then + return +end +if lp.StateComponent ~= nil then + pcall(function() lp.StateComponent:ChangeState("HIT") end) +end +local tr = lp.TransformComponent +if tr == nil then + return +end +local p = tr.Position +tr.Position = Vector3(p.x - 0.15, p.y, p.z) +_TimerService:SetTimerOnce(function() + if lp ~= nil and isvalid(lp) and lp.TransformComponent ~= nil then + lp.TransformComponent.Position = Vector3(p.x, p.y, p.z) + end +end, 0.15)`), + method('MonsterLunge', `local m = self.Monsters[idx] +if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then + return +end +if m.motionBusy == true then + return +end +m.motionBusy = true +local e = m.entity +local tr = e.TransformComponent +if tr == nil then + m.motionBusy = false + return +end +local p = tr.Position +tr.Position = Vector3(p.x - 0.35, p.y, p.z) +_TimerService:SetTimerOnce(function() + if isvalid(e) and e.TransformComponent ~= nil then + e.TransformComponent.Position = Vector3(p.x, p.y, p.z) + end + m.motionBusy = false +end, 0.18)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'idx' }]), + method('MonsterHitMotion', `local m = self.Monsters[slot] +if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then + return +end +local e = m.entity +if m.hitClip ~= nil and e.SpriteRendererComponent ~= nil then + e.SpriteRendererComponent.SpriteRUID = m.hitClip + _TimerService:SetTimerOnce(function() + if isvalid(e) and e.SpriteRendererComponent ~= nil and m.alive == true and m.standClip ~= nil then + e.SpriteRendererComponent.SpriteRUID = m.standClip + end + end, 0.5) +else + if m.motionBusy == true then + return + end + m.motionBusy = true + local tr = e.TransformComponent + if tr == nil then + m.motionBusy = false + return + end + local p = tr.Position + local seq = { 0.12, -0.12, 0 } + for i = 1, #seq do + local dx = seq[i] + _TimerService:SetTimerOnce(function() + if isvalid(e) and e.TransformComponent ~= nil then + e.TransformComponent.Position = Vector3(p.x + dx, p.y, p.z) + end + if i == #seq then + m.motionBusy = false + end + end, 0.06 * i) + end +end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]), + 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('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/RunUIGroup/CombatHud/TopBar/Floor", floorText) +self:SetText("/ui/RunUIGroup/CombatHud/TopBar/Gold", "메소 " .. string.format("%d", self.Gold))`), +]; diff --git a/tools/deck/cb/runend.mjs b/tools/deck/cb/runend.mjs index 8749d3c..f70eecb 100644 --- a/tools/deck/cb/runend.mjs +++ b/tools/deck/cb/runend.mjs @@ -1,37 +1,24 @@ -import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs'; -import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs'; -import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs'; - -export const runEndMethods = [ - 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/RunUIGroup/CombatHud/Result", text) -local entity = _EntityService:GetEntityByPath("/ui/RunUIGroup/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:ShowLobby() end, 4)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'text' }]), -]; +import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs'; +import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs'; +import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs'; + +export const runEndMethods = [ + method('ShowResult', `self:SetText("/ui/RunUIGroup/CombatHud/Result", text) +local entity = _EntityService:GetEntityByPath("/ui/RunUIGroup/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:ShowLobby() end, 4)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'text' }]), +]; diff --git a/tools/deck/cb/state.mjs b/tools/deck/cb/screens.mjs similarity index 91% rename from tools/deck/cb/state.mjs rename to tools/deck/cb/screens.mjs index 8e260db..02849df 100644 --- a/tools/deck/cb/state.mjs +++ b/tools/deck/cb/screens.mjs @@ -1,201 +1,174 @@ -import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs'; -import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs'; -import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs'; - -export const stateMethods = [ - 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/RunUIGroup/DeckHud", false) -self:SetEntityEnabled("/ui/RunUIGroup/CardHand", false) -self:SetEntityEnabled("/ui/RunUIGroup/CombatHud", false) -self:SetEntityEnabled("/ui/RunUIGroup/RewardHud", false) -self:SetEntityEnabled("/ui/RunUIGroup/MapHud", false) -self:SetEntityEnabled("/ui/RunUIGroup/ShopHud", false) -self:SetEntityEnabled("/ui/RunUIGroup/RestHud", false) -self:SetEntityEnabled("/ui/RunUIGroup/TreasureHud", false) -self:SetEntityEnabled("/ui/SelectUIGroup/JobChoiceHud", false) -self:SetEntityEnabled("/ui/SelectUIGroup/JobSelectHud", false) -self:SetEntityEnabled("/ui/DeckUIGroup/DeckInspectHud", false) -self:SetEntityEnabled("/ui/DeckUIGroup/DeckAllHud", false) -self:SetEntityEnabled("/ui/LobbyUIGroup/LobbyHud", false) -self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", false) -self:SetEntityEnabled("/ui/LobbyUIGroup/SoulShopHud", false)`), - method('ActivateUIGroups', `local function grp(n) - local g = _EntityService:GetEntityByPath("/ui/" .. n) - if g ~= nil then g:SetEnable(true) end -end -grp("SelectUIGroup") -grp("LobbyUIGroup") -grp("RunUIGroup") -grp("DeckUIGroup")`, [], 2), - method('ShowState', `self:HideGameHud() -self:SetEntityEnabled("/ui/DefaultGroup/MainMenu", state == "menu") -self:SetEntityEnabled("/ui/SelectUIGroup/CharacterSelectHud", state == "charselect") -self:SetEntityEnabled("/ui/LobbyUIGroup/LobbyHud", state == "lobby") -if state == "map" then - self:SetEntityEnabled("/ui/RunUIGroup/MapHud", true) -elseif state == "combat" then - self:SetEntityEnabled("/ui/RunUIGroup/CombatHud", true) - self:SetEntityEnabled("/ui/RunUIGroup/DeckHud", true) - self:SetEntityEnabled("/ui/RunUIGroup/CardHand", true) -elseif state == "shop" then - self:SetEntityEnabled("/ui/RunUIGroup/ShopHud", true) -elseif state == "rest" then - self:SetEntityEnabled("/ui/RunUIGroup/RestHud", true) -elseif state == "treasure" then - self:SetEntityEnabled("/ui/RunUIGroup/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 or buttonEntity:AddComponent("ButtonComponent") ~= nil) then - if self.NewGameHandler ~= nil then - buttonEntity:DisconnectEvent(ButtonClickEvent, self.NewGameHandler) - self.NewGameHandler = nil - end - self.NewGameHandler = buttonEntity:ConnectEvent(ButtonClickEvent, function() self:ShowLobby() end) -end -local warrior = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/WarriorButton") -if warrior ~= nil and (warrior.ButtonComponent ~= nil or warrior:AddComponent("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 thief = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/BanditButton") -if thief ~= nil and (thief.ButtonComponent ~= nil or thief:AddComponent("ButtonComponent") ~= nil) then - if self.ThiefSelectHandler ~= nil then - thief:DisconnectEvent(ButtonClickEvent, self.ThiefSelectHandler) - self.ThiefSelectHandler = nil - end - self.ThiefSelectHandler = thief:ConnectEvent(ButtonClickEvent, function() self:SelectClass("bandit") end) -end -local mage = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/MageButton") -if mage ~= nil and (mage.ButtonComponent ~= nil or mage:AddComponent("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 allDeckClose = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud/Close") -if allDeckClose ~= nil and (allDeckClose.ButtonComponent ~= nil or allDeckClose:AddComponent("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 -self:BindClassDeckTabs() -local start = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/StartButton") -if start ~= nil and (start.ButtonComponent ~= nil or start:AddComponent("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 charBack = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/BackButton") -if charBack ~= nil and (charBack.ButtonComponent ~= nil or charBack:AddComponent("ButtonComponent") ~= nil) then - if self.CharBackHandler ~= nil then - charBack:DisconnectEvent(ButtonClickEvent, self.CharBackHandler) - self.CharBackHandler = nil - end - self.CharBackHandler = charBack:ConnectEvent(ButtonClickEvent, function() self:ShowLobby() end) -end -local ascMinus = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/AscMinus") -if ascMinus ~= nil and (ascMinus.ButtonComponent ~= nil or ascMinus:AddComponent("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 or ascPlus:AddComponent("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('ShowLobby', `self.SelectedClass = "" -self:RenderAscension() -self:RenderSoulLabel() -self:ShowState("lobby") -self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", false) -self:SetEntityEnabled("/ui/LobbyUIGroup/SoulShopHud", false) -self:BindLobbyButtons() -self:BindMenuButtons() -self:GoLobbyMap()`), - method('GoLobbyMap', `self.LobbyTpTries = 0 -local eventId = 0 -local function go() - self.LobbyTpTries = self.LobbyTpTries + 1 - local lp = _UserService.LocalPlayer - if lp ~= nil then - if lp.CurrentMapName ~= "${LOBBY_MAP}" then - _TeleportService:TeleportToMapPosition(lp, ${LOBBY_SPAWN}, "${LOBBY_MAP}") - end - _TimerService:ClearTimer(eventId) - elseif self.LobbyTpTries > 50 then - _TimerService:ClearTimer(eventId) - end -end -eventId = _TimerService:SetTimerRepeat(go, 0.1)`), - method('OnLobbyNpcInteract', `if self.RunActive == true then - return -end -if id == "run" then - self:ShowCharacterSelect() -elseif id == "codex" then - self:ShowCodex() -elseif id == "shop" then - self:ShowSoulShop() -elseif id == "board" then - self:ShowBoard() -end`, [{ Type: 'string', DefaultValue: '""', SyncDirection: 0, Attributes: [], Name: 'id' }]), - method('RenderSoulLabel', `local s = self.SoulPoints or 0 -self:SetText("/ui/LobbyUIGroup/LobbyHud/SoulLabel", "영혼 " .. string.format("%d", s)) -self:SetText("/ui/LobbyUIGroup/SoulShopHud/Souls", "영혼 " .. string.format("%d", s))`), - method('BindLobbyButtons', `if self.LobbyBound == true then - return -end -self.LobbyBound = true -local function bindClick(path, fn) - local e = _EntityService:GetEntityByPath(path) - if e ~= nil and (e.ButtonComponent ~= nil or e:AddComponent("ButtonComponent") ~= nil) then - e:ConnectEvent(ButtonClickEvent, fn) - end -end -bindClick("/ui/LobbyUIGroup/LobbyHud/AscMinus", function() self:AdjustAscension(-1) end) -bindClick("/ui/LobbyUIGroup/LobbyHud/AscPlus", function() self:AdjustAscension(1) end) -bindClick("/ui/LobbyUIGroup/BoardHud/Close", function() self:CloseBoard() end) -bindClick("/ui/LobbyUIGroup/SoulShopHud/Close", function() self:CloseSoulShop() end)`), - method('ShowCodex', `self.CodexMode = true -self.ClassDeckMode = true -local close = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud/Close") -if close ~= nil and (close.ButtonComponent ~= nil or close:AddComponent("ButtonComponent") ~= nil) then - if self.AllDeckCloseHandler ~= nil then - close:DisconnectEvent(ButtonClickEvent, self.AllDeckCloseHandler) - end - self.AllDeckCloseHandler = close:ConnectEvent(ButtonClickEvent, function() self:CloseAllDeck() end) -end -self:BindClassDeckTabs() -self:SetEntityEnabled("/ui/LobbyUIGroup/LobbyHud", false) -self:SetClassDeckTab("warrior") -local hud = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud") -if hud ~= nil then - hud.Enable = true -end -self:RenderAllDeck()`), - method('ShowBoard', `self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", true)`), - method('CloseBoard', `self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", false)`), -]; +import { method, RUN_LENGTH, GOLD_PER_WIN, CARD_PRICE, REST_HEAL, RELIC_PRICE, ACT_COUNT, ACT_MAPS, LOBBY_MAP, LOBBY_SPAWN } from '../lib/codeblock.mjs'; +import { CARDS, ENEMIES, CLASSES, JOBS, SOUL_UNLOCKS, CARDFRAMES, RARITIES, MAP_ROWS, MAP_COLS, CHEST_CLOSED_RUID, CHEST_OPEN_RUID, NODEICONS, CHARS, CAM, RELICS, POTIONS, luaSoulShopTable, frameRuid, luaFramesTable, luaNodeIconsTable, luaRelicsTable, luaPotionsTable, luaIntentsArray, luaEnemiesTable, luaStr, luaJobsTable, luaCardsTable, luaDeckTable } from '../lib/data.mjs'; +import { UI_FILE, COMMON_FILE, UI_ROOT, GENERATED_UI_SECTIONS, UI_APPEND_ORDER, DISABLED_STOCK_CONTROLS, TRANSPARENT, DARK, GOLD, ATTACK, DEFEND, SKILL, DAMAGE_DIGIT_RUIDS, DAMAGE_POP_MAX_DIGITS, DAMAGE_POP_DIGIT_W, DAMAGE_POP_DIGIT_H, DAMAGE_POP_DIGIT_SPACING, MAX_MONSTERS, HEAD_OFFSET_Y, HP_BAR_W, WHITE, CARD_NAME_TEXT, CARD_DESC_TEXT, cardFaceLayout, CARD_W, CARD_H, CARD_SPACING, CARD_XS, ALIGN_CENTER, ALIGN_BOTTOM_CENTER, guid, transform, sprite, button, text, scrollLayoutGroup, popupLayerFor, uiOrderFor, displayOrderFor, applySortingOverride, entity, uiPath, sectionRoot, isGeneratedUiEntity, appendUiSection } from '../lib/ui-helpers.mjs'; + +export const screensMethods = [ + 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/RunUIGroup/DeckHud", false) +self:SetEntityEnabled("/ui/RunUIGroup/CardHand", false) +self:SetEntityEnabled("/ui/RunUIGroup/CombatHud", false) +self:SetEntityEnabled("/ui/RunUIGroup/RewardHud", false) +self:SetEntityEnabled("/ui/RunUIGroup/MapHud", false) +self:SetEntityEnabled("/ui/RunUIGroup/ShopHud", false) +self:SetEntityEnabled("/ui/RunUIGroup/RestHud", false) +self:SetEntityEnabled("/ui/RunUIGroup/TreasureHud", false) +self:SetEntityEnabled("/ui/SelectUIGroup/JobChoiceHud", false) +self:SetEntityEnabled("/ui/SelectUIGroup/JobSelectHud", false) +self:SetEntityEnabled("/ui/DeckUIGroup/DeckInspectHud", false) +self:SetEntityEnabled("/ui/DeckUIGroup/DeckAllHud", false) +self:SetEntityEnabled("/ui/LobbyUIGroup/LobbyHud", false) +self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", false) +self:SetEntityEnabled("/ui/LobbyUIGroup/SoulShopHud", false)`), + method('ActivateUIGroups', `local function grp(n) + local g = _EntityService:GetEntityByPath("/ui/" .. n) + if g ~= nil then g:SetEnable(true) end +end +grp("SelectUIGroup") +grp("LobbyUIGroup") +grp("RunUIGroup") +grp("DeckUIGroup")`, [], 2), + method('ShowState', `self:HideGameHud() +self:SetEntityEnabled("/ui/DefaultGroup/MainMenu", state == "menu") +self:SetEntityEnabled("/ui/SelectUIGroup/CharacterSelectHud", state == "charselect") +self:SetEntityEnabled("/ui/LobbyUIGroup/LobbyHud", state == "lobby") +if state == "map" then + self:SetEntityEnabled("/ui/RunUIGroup/MapHud", true) +elseif state == "combat" then + self:SetEntityEnabled("/ui/RunUIGroup/CombatHud", true) + self:SetEntityEnabled("/ui/RunUIGroup/DeckHud", true) + self:SetEntityEnabled("/ui/RunUIGroup/CardHand", true) +elseif state == "shop" then + self:SetEntityEnabled("/ui/RunUIGroup/ShopHud", true) +elseif state == "rest" then + self:SetEntityEnabled("/ui/RunUIGroup/RestHud", true) +elseif state == "treasure" then + self:SetEntityEnabled("/ui/RunUIGroup/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 or buttonEntity:AddComponent("ButtonComponent") ~= nil) then + if self.NewGameHandler ~= nil then + buttonEntity:DisconnectEvent(ButtonClickEvent, self.NewGameHandler) + self.NewGameHandler = nil + end + self.NewGameHandler = buttonEntity:ConnectEvent(ButtonClickEvent, function() self:ShowLobby() end) +end +local warrior = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/WarriorButton") +if warrior ~= nil and (warrior.ButtonComponent ~= nil or warrior:AddComponent("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 thief = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/BanditButton") +if thief ~= nil and (thief.ButtonComponent ~= nil or thief:AddComponent("ButtonComponent") ~= nil) then + if self.ThiefSelectHandler ~= nil then + thief:DisconnectEvent(ButtonClickEvent, self.ThiefSelectHandler) + self.ThiefSelectHandler = nil + end + self.ThiefSelectHandler = thief:ConnectEvent(ButtonClickEvent, function() self:SelectClass("bandit") end) +end +local mage = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/MageButton") +if mage ~= nil and (mage.ButtonComponent ~= nil or mage:AddComponent("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 allDeckClose = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud/Close") +if allDeckClose ~= nil and (allDeckClose.ButtonComponent ~= nil or allDeckClose:AddComponent("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 +self:BindClassDeckTabs() +local start = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/StartButton") +if start ~= nil and (start.ButtonComponent ~= nil or start:AddComponent("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 charBack = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/BackButton") +if charBack ~= nil and (charBack.ButtonComponent ~= nil or charBack:AddComponent("ButtonComponent") ~= nil) then + if self.CharBackHandler ~= nil then + charBack:DisconnectEvent(ButtonClickEvent, self.CharBackHandler) + self.CharBackHandler = nil + end + self.CharBackHandler = charBack:ConnectEvent(ButtonClickEvent, function() self:ShowLobby() end) +end +local ascMinus = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/AscMinus") +if ascMinus ~= nil and (ascMinus.ButtonComponent ~= nil or ascMinus:AddComponent("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 or ascPlus:AddComponent("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('ShowLobby', `self.SelectedClass = "" +self:RenderAscension() +self:RenderSoulLabel() +self:ShowState("lobby") +self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", false) +self:SetEntityEnabled("/ui/LobbyUIGroup/SoulShopHud", false) +self:BindLobbyButtons() +self:BindMenuButtons() +self:GoLobbyMap()`), + method('RenderSoulLabel', `local s = self.SoulPoints or 0 +self:SetText("/ui/LobbyUIGroup/LobbyHud/SoulLabel", "영혼 " .. string.format("%d", s)) +self:SetText("/ui/LobbyUIGroup/SoulShopHud/Souls", "영혼 " .. string.format("%d", s))`), + method('BindLobbyButtons', `if self.LobbyBound == true then + return +end +self.LobbyBound = true +local function bindClick(path, fn) + local e = _EntityService:GetEntityByPath(path) + if e ~= nil and (e.ButtonComponent ~= nil or e:AddComponent("ButtonComponent") ~= nil) then + e:ConnectEvent(ButtonClickEvent, fn) + end +end +bindClick("/ui/LobbyUIGroup/LobbyHud/AscMinus", function() self:AdjustAscension(-1) end) +bindClick("/ui/LobbyUIGroup/LobbyHud/AscPlus", function() self:AdjustAscension(1) end) +bindClick("/ui/LobbyUIGroup/BoardHud/Close", function() self:CloseBoard() end) +bindClick("/ui/LobbyUIGroup/SoulShopHud/Close", function() self:CloseSoulShop() end)`), + method('ShowCodex', `self.CodexMode = true +self.ClassDeckMode = true +local close = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud/Close") +if close ~= nil and (close.ButtonComponent ~= nil or close:AddComponent("ButtonComponent") ~= nil) then + if self.AllDeckCloseHandler ~= nil then + close:DisconnectEvent(ButtonClickEvent, self.AllDeckCloseHandler) + end + self.AllDeckCloseHandler = close:ConnectEvent(ButtonClickEvent, function() self:CloseAllDeck() end) +end +self:BindClassDeckTabs() +self:SetEntityEnabled("/ui/LobbyUIGroup/LobbyHud", false) +self:SetClassDeckTab("warrior") +local hud = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud") +if hud ~= nil then + hud.Enable = true +end +self:RenderAllDeck()`), + method('ShowBoard', `self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", true)`), + method('CloseBoard', `self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", false)`), +]; diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index 4c3ceac..3da87e7 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -3,7 +3,10 @@ import { POTIONS } from './lib/data.mjs'; import { prop, codeblock, RUN_LENGTH } from './lib/codeblock.mjs'; import { bootMethods } from './cb/boot.mjs'; -import { stateMethods } from './cb/state.mjs'; +import { screensMethods } from './cb/screens.mjs'; +import { npcMethods } from './cb/npc.mjs'; +import { navigationMethods } from './cb/navigation.mjs'; +import { layoutMethods } from './cb/layout.mjs'; import { soulMethods } from './cb/soul.mjs'; import { charSelectMethods } from './cb/charselect.mjs'; import { runMethods } from './cb/run.mjs'; @@ -166,7 +169,9 @@ function writeCodeblocks() { prop('any', 'NextTurnAddCards'), ], [ ...bootMethods, - ...stateMethods, + ...screensMethods, + ...npcMethods, + ...navigationMethods, ...soulMethods, ...charSelectMethods, ...runMethods, @@ -177,6 +182,7 @@ function writeCodeblocks() { ...jobMethods, ...runEndMethods, ...renderMethods, + ...layoutMethods, ...rewardMethods, ...itemMethods, ...tooltipMethods, diff --git a/tools/verify/cbset.mjs b/tools/verify/cbset.mjs new file mode 100644 index 0000000..b651367 --- /dev/null +++ b/tools/verify/cbset.mjs @@ -0,0 +1,40 @@ +// 순서 무관 codeblock 메서드 집합 비교. 본문 미출력 — 이름·차이 카운트만. +// 메서드 이동 리팩터의 무손실 검증용: 워킹트리 codeblock vs ref(기본 HEAD). +// 사용: node tools/verify/cbset.mjs [ref] +import { readFileSync } from 'node:fs'; +import { execSync } from 'node:child_process'; + +const PATH = 'RootDesk/MyDesk/SlayDeckController.codeblock'; +const ref = process.argv[2] || 'HEAD'; + +function methodsOf(jsonText) { + const obj = JSON.parse(jsonText); + const arr = obj.ContentProto.Json.Methods; + const map = new Map(); + for (const m of arr) { + map.set(m.Name, { code: m.Code, exec: m.ExecSpace, params: JSON.stringify(m.Parameters || []) }); + } + return map; +} + +const work = methodsOf(readFileSync(PATH, 'utf8')); +const base = methodsOf(execSync(`git show ${ref}:${PATH}`, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 })); + +const onlyWork = [...work.keys()].filter((k) => !base.has(k)); +const onlyBase = [...base.keys()].filter((k) => !work.has(k)); +const changed = []; +for (const k of work.keys()) { + if (!base.has(k)) continue; + const a = work.get(k); + const b = base.get(k); + if (a.code !== b.code || a.exec !== b.exec || a.params !== b.params) changed.push(k); +} + +console.log(`ref=${ref} work=${work.size} base=${base.size}`); +console.log(`only-in-work (${onlyWork.length}): ${onlyWork.join(', ') || '-'}`); +console.log(`only-in-base (${onlyBase.length}): ${onlyBase.join(', ') || '-'}`); +console.log(`body/exec/params changed (${changed.length}): ${changed.join(', ') || '-'}`); + +const ok = onlyWork.length === 0 && onlyBase.length === 0 && changed.length === 0; +console.log(ok ? 'RESULT: IDENTICAL SET (무손실)' : 'RESULT: DIFFERENCES ABOVE'); +process.exit(ok ? 0 : 1);