refactor(cb): 컨트롤러 횡단 관심사 모듈 분리 (이동·본문 무변경)

화면 전환·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) <noreply@anthropic.com>
This commit is contained in:
2026-06-26 18:35:51 +09:00
parent 7b5e79bcf2
commit 8d2e320d60
9 changed files with 691 additions and 627 deletions

View File

@@ -1342,44 +1342,6 @@
"Attributes": [], "Attributes": [],
"Name": "ShowLobby" "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": { "Return": {
"Type": "void", "Type": "void",
@@ -1455,6 +1417,59 @@
"Attributes": [], "Attributes": [],
"Name": "CloseBoard" "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": { "Return": {
"Type": "void", "Type": "void",
@@ -3937,21 +3952,6 @@
"Attributes": [], "Attributes": [],
"Name": "SetJob" "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": { "Return": {
"Type": "void", "Type": "void",
@@ -4230,29 +4230,6 @@
"Attributes": [], "Attributes": [],
"Name": "SetHpBar" "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": { "Return": {
"Type": "void", "Type": "void",
@@ -4291,6 +4268,29 @@
"Attributes": [], "Attributes": [],
"Name": "RenderRun" "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": { "Return": {
"Type": "any", "Type": "any",

21
tools/deck/cb/layout.mjs Normal file
View File

@@ -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' }]),
];

View File

@@ -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)`),
];

18
tools/deck/cb/npc.mjs Normal file
View File

@@ -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' }]),
];

View File

@@ -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 { 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 { 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'; 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 = [ export const renderMethods = [
method('BuffsLabel', `local parts = {} method('BuffsLabel', `local parts = {}
if str ~= nil and str > 0 then table.insert(parts, "힘+" .. tostring(str)) end 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 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 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 if poison ~= nil and poison > 0 then table.insert(parts, "독" .. tostring(poison)) end
return table.concat(parts, " ")`, [ return table.concat(parts, " ")`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'str' }, { 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: 'weak' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'vuln' }, { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'vuln' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'poison' }, { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'poison' },
], 0, 'string'), ], 0, 'string'),
method('RenderCombat', `for i = 1, ${MAX_MONSTERS} do method('RenderCombat', `for i = 1, ${MAX_MONSTERS} do
local base = "/ui/RunUIGroup/CombatHud/MonsterStatus" .. tostring(i) local base = "/ui/RunUIGroup/CombatHud/MonsterStatus" .. tostring(i)
local m = self.Monsters[i] local m = self.Monsters[i]
if m ~= nil and m.alive == true then if m ~= nil and m.alive == true then
self:SetEntityEnabled(base, true) self:SetEntityEnabled(base, true)
self:SetText(base .. "/Name", m.name) self:SetText(base .. "/Name", m.name)
self:SetText(base .. "/Hp", string.format("%d", m.hp) .. "/" .. string.format("%d", m.maxHp)) self:SetText(base .. "/Hp", string.format("%d", m.hp) .. "/" .. string.format("%d", m.maxHp))
local intent = m.intents[m.intentIdx] local intent = m.intents[m.intentIdx]
local t = "" local t = ""
if intent ~= nil then if intent ~= nil then
if intent.kind == "Attack" then if intent.kind == "Attack" then
local atk = intent.value + m.str local atk = intent.value + m.str
if m.weak > 0 then atk = math.floor(atk * 0.75) end if m.weak > 0 then atk = math.floor(atk * 0.75) end
if self.PlayerVuln > 0 then atk = math.floor(atk * 1.5) end if self.PlayerVuln > 0 then atk = math.floor(atk * 1.5) end
t = "공격 " .. tostring(atk) t = "공격 " .. tostring(atk)
elseif intent.kind == "Defend" then t = "방어 " .. tostring(intent.value) elseif intent.kind == "Defend" then t = "방어 " .. tostring(intent.value)
elseif intent.kind == "Debuff" then elseif intent.kind == "Debuff" then
if intent.effect == "weak" then t = "약화 " .. tostring(intent.value) .. " 부여" if intent.effect == "weak" then t = "약화 " .. tostring(intent.value) .. " 부여"
else t = "취약 " .. tostring(intent.value) .. " 부여" end else t = "취약 " .. tostring(intent.value) .. " 부여" end
elseif intent.kind == "AddCard" then elseif intent.kind == "AddCard" then
t = "저주 카드 추가" t = "저주 카드 추가"
end end
end end
self:SetText(base .. "/Intent", t) self:SetText(base .. "/Intent", t)
local dragActive = self.DragTargetIndex ~= nil and self.DragTargetIndex > 0 local dragActive = self.DragTargetIndex ~= nil and self.DragTargetIndex > 0
local shownTarget = self.TargetIndex local shownTarget = self.TargetIndex
if dragActive == true then shownTarget = self.DragTargetIndex end if dragActive == true then shownTarget = self.DragTargetIndex end
self:SetEntityEnabled(base .. "/TargetMarker", i == shownTarget and dragActive) self:SetEntityEnabled(base .. "/TargetMarker", i == shownTarget and dragActive)
self:SetEntityEnabled(base .. "/TargetMarker/Label", i == shownTarget and dragActive) self:SetEntityEnabled(base .. "/TargetMarker/Label", i == shownTarget and dragActive)
local intentEntity = _EntityService:GetEntityByPath(base .. "/Intent") local intentEntity = _EntityService:GetEntityByPath(base .. "/Intent")
if intentEntity ~= nil and intentEntity.TextComponent ~= nil and intent ~= nil then if intentEntity ~= nil and intentEntity.TextComponent ~= nil and intent ~= nil then
if intent.kind == "Attack" then if intent.kind == "Attack" then
intentEntity.TextComponent.FontColor = Color(1, 0.45, 0.35, 1) intentEntity.TextComponent.FontColor = Color(1, 0.45, 0.35, 1)
elseif intent.kind == "Debuff" then elseif intent.kind == "Debuff" then
intentEntity.TextComponent.FontColor = Color(0.8, 0.5, 1, 1) intentEntity.TextComponent.FontColor = Color(0.8, 0.5, 1, 1)
elseif intent.kind == "AddCard" then elseif intent.kind == "AddCard" then
intentEntity.TextComponent.FontColor = Color(0.6, 0.85, 0.4, 1) intentEntity.TextComponent.FontColor = Color(0.6, 0.85, 0.4, 1)
else else
intentEntity.TextComponent.FontColor = Color(0.5, 0.75, 1, 1) intentEntity.TextComponent.FontColor = Color(0.5, 0.75, 1, 1)
end end
end end
self:SetHpBar(base .. "/HpBarFill", m.hp, m.maxHp, ${HP_BAR_W}) self:SetHpBar(base .. "/HpBarFill", m.hp, m.maxHp, ${HP_BAR_W})
self:SetEntityEnabled(base .. "/BlockBadge", m.block > 0) self:SetEntityEnabled(base .. "/BlockBadge", m.block > 0)
self:SetText(base .. "/BlockBadge/Value", string.format("%d", m.block)) 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)) self:SetText(base .. "/Buffs", self:BuffsLabel(m.str, m.weak, m.vuln, m.poison or 0))
else else
self:SetEntityEnabled(base, false) self:SetEntityEnabled(base, false)
end end
end end
self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/HpText", string.format("%d", self.PlayerHp) .. "/" .. string.format("%d", self.PlayerMaxHp)) 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:SetHpBar("/ui/RunUIGroup/CombatHud/PlayerPanel/HpBarFill", self.PlayerHp, self.PlayerMaxHp, 220)
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge", self.PlayerBlock > 0) self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge", self.PlayerBlock > 0)
self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge/Value", string.format("%d", self.PlayerBlock)) self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge/Value", string.format("%d", self.PlayerBlock))
local pb = self:BuffsLabel(self.PlayerStr, self.PlayerWeak, self.PlayerVuln, 0) local pb = self:BuffsLabel(self.PlayerStr, self.PlayerWeak, self.PlayerVuln, 0)
if self.PlayerIntangible ~= nil and self.PlayerIntangible > 0 then if self.PlayerIntangible ~= nil and self.PlayerIntangible > 0 then
if pb ~= "" then pb = pb .. " " end if pb ~= "" then pb = pb .. " " end
pb = pb .. "불가침" .. tostring(self.PlayerIntangible) pb = pb .. "불가침" .. tostring(self.PlayerIntangible)
end end
if self.PlayerDex ~= nil and self.PlayerDex > 0 then if self.PlayerDex ~= nil and self.PlayerDex > 0 then
if pb ~= "" then pb = pb .. " " end if pb ~= "" then pb = pb .. " " end
pb = pb .. "민첩+" .. tostring(self.PlayerDex) pb = pb .. "민첩+" .. tostring(self.PlayerDex)
end end
if self.PlayerThorns ~= nil and self.PlayerThorns > 0 then if self.PlayerThorns ~= nil and self.PlayerThorns > 0 then
if pb ~= "" then pb = pb .. " " end if pb ~= "" then pb = pb .. " " end
pb = pb .. "가시" .. tostring(self.PlayerThorns) pb = pb .. "가시" .. tostring(self.PlayerThorns)
end end
if self.PlayerPowers ~= nil and #self.PlayerPowers > 0 then if self.PlayerPowers ~= nil and #self.PlayerPowers > 0 then
local names = {} local names = {}
for i = 1, #self.PlayerPowers do for i = 1, #self.PlayerPowers do
local pc = self.Cards[self.PlayerPowers[i]] local pc = self.Cards[self.PlayerPowers[i]]
if pc ~= nil then table.insert(names, pc.name) end if pc ~= nil then table.insert(names, pc.name) end
end end
if pb ~= "" then pb = pb .. " · " end if pb ~= "" then pb = pb .. " · " end
pb = pb .. table.concat(names, " ") pb = pb .. table.concat(names, " ")
end end
self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/Buffs", pb) self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/Buffs", pb)
self:RenderRun()`), self:RenderRun()`),
method('ShowDmgPop', `local slotKey = string.format("%d", math.floor(slot or 0)) method('ShowDmgPop', `local slotKey = string.format("%d", math.floor(slot or 0))
local base = "/ui/RunUIGroup/CombatHud/DmgPop" .. slotKey local base = "/ui/RunUIGroup/CombatHud/DmgPop" .. slotKey
local pop = _EntityService:GetEntityByPath(base) local pop = _EntityService:GetEntityByPath(base)
if pop == nil then if pop == nil then
return return
end end
self.DmgPopSeq = (self.DmgPopSeq or 0) + 1 self.DmgPopSeq = (self.DmgPopSeq or 0) + 1
local popSeq = self.DmgPopSeq local popSeq = self.DmgPopSeq
self:SetText(base, "") self:SetText(base, "")
local damageDigitRuids = { ${DAMAGE_DIGIT_RUIDS.map(luaStr).join(', ')} } local damageDigitRuids = { ${DAMAGE_DIGIT_RUIDS.map(luaStr).join(', ')} }
local shown = tostring(math.max(0, math.floor(amount))) local shown = tostring(math.max(0, math.floor(amount)))
if string.len(shown) > ${DAMAGE_POP_MAX_DIGITS} then if string.len(shown) > ${DAMAGE_POP_MAX_DIGITS} then
shown = string.sub(shown, 1, ${DAMAGE_POP_MAX_DIGITS}) shown = string.sub(shown, 1, ${DAMAGE_POP_MAX_DIGITS})
end end
local digits = {} local digits = {}
for i = 1, string.len(shown) do for i = 1, string.len(shown) do
table.insert(digits, tonumber(string.sub(shown, i, i)) or 0) table.insert(digits, tonumber(string.sub(shown, i, i)) or 0)
end end
local totalW = #digits * ${DAMAGE_POP_DIGIT_W} + math.max(0, #digits - 1) * ${DAMAGE_POP_DIGIT_SPACING} 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 local startX = -totalW / 2 + ${DAMAGE_POP_DIGIT_W} / 2
for i = 1, ${DAMAGE_POP_MAX_DIGITS} do for i = 1, ${DAMAGE_POP_MAX_DIGITS} do
self:SetEntityEnabled(base .. "/Digit" .. tostring(i), false) self:SetEntityEnabled(base .. "/Digit" .. tostring(i), false)
end end
for i = 1, ${DAMAGE_POP_MAX_DIGITS} do for i = 1, ${DAMAGE_POP_MAX_DIGITS} do
local digitPath = base .. "/Digit" .. tostring(i) local digitPath = base .. "/Digit" .. tostring(i)
local digitEntity = _EntityService:GetEntityByPath(digitPath) local digitEntity = _EntityService:GetEntityByPath(digitPath)
if digitEntity ~= nil and digitEntity.SpriteGUIRendererComponent ~= nil then if digitEntity ~= nil and digitEntity.SpriteGUIRendererComponent ~= nil then
if digits[i] ~= nil then if digits[i] ~= nil then
digitEntity.SpriteGUIRendererComponent.ImageRUID = damageDigitRuids[digits[i] + 1] digitEntity.SpriteGUIRendererComponent.ImageRUID = damageDigitRuids[digits[i] + 1]
digitEntity.SpriteGUIRendererComponent.Color = Color(1, 1, 1, 1) digitEntity.SpriteGUIRendererComponent.Color = Color(1, 1, 1, 1)
if digitEntity.UITransformComponent ~= nil then if digitEntity.UITransformComponent ~= nil then
digitEntity.UITransformComponent.anchoredPosition = Vector2(startX + (i - 1) * (${DAMAGE_POP_DIGIT_W} + ${DAMAGE_POP_DIGIT_SPACING}), 0) digitEntity.UITransformComponent.anchoredPosition = Vector2(startX + (i - 1) * (${DAMAGE_POP_DIGIT_W} + ${DAMAGE_POP_DIGIT_SPACING}), 0)
end end
self:SetEntityEnabled(digitPath, true) self:SetEntityEnabled(digitPath, true)
else else
self:SetEntityEnabled(digitPath, false) self:SetEntityEnabled(digitPath, false)
end end
end end
end end
local popPos = nil local popPos = nil
local m = self.Monsters[slot] local m = self.Monsters[slot]
if m ~= nil and m.entity ~= nil and isvalid(m.entity) and m.entity.TransformComponent ~= nil then if m ~= nil and m.entity ~= nil and isvalid(m.entity) and m.entity.TransformComponent ~= nil then
local wp = m.entity.TransformComponent.WorldPosition local wp = m.entity.TransformComponent.WorldPosition
local screen = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + ${HEAD_OFFSET_Y + 0.45})) local screen = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + ${HEAD_OFFSET_Y + 0.45}))
popPos = _UILogic:ScreenToUIPosition(screen) popPos = _UILogic:ScreenToUIPosition(screen)
else else
local slotEntity = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/MonsterStatus" .. slotKey) local slotEntity = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/MonsterStatus" .. slotKey)
if slotEntity ~= nil and slotEntity.UITransformComponent ~= nil then if slotEntity ~= nil and slotEntity.UITransformComponent ~= nil then
local sp = slotEntity.UITransformComponent.anchoredPosition local sp = slotEntity.UITransformComponent.anchoredPosition
popPos = Vector2(sp.x, sp.y + 76) popPos = Vector2(sp.x, sp.y + 76)
end end
end end
if pop ~= nil and pop.UITransformComponent ~= nil then if pop ~= nil and pop.UITransformComponent ~= nil then
if popPos ~= nil then if popPos ~= nil then
pop.UITransformComponent.anchoredPosition = popPos pop.UITransformComponent.anchoredPosition = popPos
else else
pop.UITransformComponent.anchoredPosition = Vector2(0, 120) pop.UITransformComponent.anchoredPosition = Vector2(0, 120)
end end
end end
self:SetEntityEnabled(base, true) self:SetEntityEnabled(base, true)
for i = 1, 6 do for i = 1, 6 do
_TimerService:SetTimerOnce(function() _TimerService:SetTimerOnce(function()
if self.DmgPopSeq ~= popSeq then if self.DmgPopSeq ~= popSeq then
return return
end end
local p = _EntityService:GetEntityByPath(base) local p = _EntityService:GetEntityByPath(base)
if p ~= nil and p.UITransformComponent ~= nil then if p ~= nil and p.UITransformComponent ~= nil then
local cur = p.UITransformComponent.anchoredPosition local cur = p.UITransformComponent.anchoredPosition
p.UITransformComponent.anchoredPosition = Vector2(cur.x, cur.y + 7) p.UITransformComponent.anchoredPosition = Vector2(cur.x, cur.y + 7)
end end
end, 0.045 * i) end, 0.045 * i)
end end
_TimerService:SetTimerOnce(function() _TimerService:SetTimerOnce(function()
if self.DmgPopSeq ~= popSeq then if self.DmgPopSeq ~= popSeq then
return return
end end
self:SetEntityEnabled(base, false) self:SetEntityEnabled(base, false)
end, 0.48)`, [ end, 0.48)`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }, { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }, { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
]), ]),
method('ShowPlayerDmgPop', `local base = "/ui/RunUIGroup/CombatHud/PlayerPanel/DmgPop" method('ShowPlayerDmgPop', `local base = "/ui/RunUIGroup/CombatHud/PlayerPanel/DmgPop"
if amount > 0 then if amount > 0 then
self:SetText(base, "-" .. string.format("%d", amount)) self:SetText(base, "-" .. string.format("%d", amount))
else else
self:SetText(base, "막음") self:SetText(base, "막음")
end end
self:SetEntityEnabled(base, true) self:SetEntityEnabled(base, true)
_TimerService:SetTimerOnce(function() self:SetEntityEnabled(base, false) end, 0.6)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }]), _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 method('PlayerAttackMotion', `local lp = _UserService.LocalPlayer
if lp == nil then if lp == nil then
return return
end end
if lp.StateComponent == nil then if lp.StateComponent == nil then
return return
end end
pcall(function() lp.StateComponent:ChangeState("ATTACK") end) pcall(function() lp.StateComponent:ChangeState("ATTACK") end)
_TimerService:SetTimerOnce(function() _TimerService:SetTimerOnce(function()
if lp ~= nil and isvalid(lp) and lp.StateComponent ~= nil then if lp ~= nil and isvalid(lp) and lp.StateComponent ~= nil then
pcall(function() lp.StateComponent:ChangeState("IDLE") end) pcall(function() lp.StateComponent:ChangeState("IDLE") end)
end end
end, 0.5)`), end, 0.5)`),
method('PlayerHitMotion', `local lp = _UserService.LocalPlayer method('PlayerHitMotion', `local lp = _UserService.LocalPlayer
if lp == nil then if lp == nil then
return return
end end
if lp.StateComponent ~= nil then if lp.StateComponent ~= nil then
pcall(function() lp.StateComponent:ChangeState("HIT") end) pcall(function() lp.StateComponent:ChangeState("HIT") end)
end end
local tr = lp.TransformComponent local tr = lp.TransformComponent
if tr == nil then if tr == nil then
return return
end end
local p = tr.Position local p = tr.Position
tr.Position = Vector3(p.x - 0.15, p.y, p.z) tr.Position = Vector3(p.x - 0.15, p.y, p.z)
_TimerService:SetTimerOnce(function() _TimerService:SetTimerOnce(function()
if lp ~= nil and isvalid(lp) and lp.TransformComponent ~= nil then if lp ~= nil and isvalid(lp) and lp.TransformComponent ~= nil then
lp.TransformComponent.Position = Vector3(p.x, p.y, p.z) lp.TransformComponent.Position = Vector3(p.x, p.y, p.z)
end end
end, 0.15)`), end, 0.15)`),
method('MonsterLunge', `local m = self.Monsters[idx] method('MonsterLunge', `local m = self.Monsters[idx]
if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then
return return
end end
if m.motionBusy == true then if m.motionBusy == true then
return return
end end
m.motionBusy = true m.motionBusy = true
local e = m.entity local e = m.entity
local tr = e.TransformComponent local tr = e.TransformComponent
if tr == nil then if tr == nil then
m.motionBusy = false m.motionBusy = false
return return
end end
local p = tr.Position local p = tr.Position
tr.Position = Vector3(p.x - 0.35, p.y, p.z) tr.Position = Vector3(p.x - 0.35, p.y, p.z)
_TimerService:SetTimerOnce(function() _TimerService:SetTimerOnce(function()
if isvalid(e) and e.TransformComponent ~= nil then if isvalid(e) and e.TransformComponent ~= nil then
e.TransformComponent.Position = Vector3(p.x, p.y, p.z) e.TransformComponent.Position = Vector3(p.x, p.y, p.z)
end end
m.motionBusy = false m.motionBusy = false
end, 0.18)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'idx' }]), end, 0.18)`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'idx' }]),
method('MonsterHitMotion', `local m = self.Monsters[slot] method('MonsterHitMotion', `local m = self.Monsters[slot]
if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then
return return
end end
local e = m.entity local e = m.entity
if m.hitClip ~= nil and e.SpriteRendererComponent ~= nil then if m.hitClip ~= nil and e.SpriteRendererComponent ~= nil then
e.SpriteRendererComponent.SpriteRUID = m.hitClip e.SpriteRendererComponent.SpriteRUID = m.hitClip
_TimerService:SetTimerOnce(function() _TimerService:SetTimerOnce(function()
if isvalid(e) and e.SpriteRendererComponent ~= nil and m.alive == true and m.standClip ~= nil then if isvalid(e) and e.SpriteRendererComponent ~= nil and m.alive == true and m.standClip ~= nil then
e.SpriteRendererComponent.SpriteRUID = m.standClip e.SpriteRendererComponent.SpriteRUID = m.standClip
end end
end, 0.5) end, 0.5)
else else
if m.motionBusy == true then if m.motionBusy == true then
return return
end end
m.motionBusy = true m.motionBusy = true
local tr = e.TransformComponent local tr = e.TransformComponent
if tr == nil then if tr == nil then
m.motionBusy = false m.motionBusy = false
return return
end end
local p = tr.Position local p = tr.Position
local seq = { 0.12, -0.12, 0 } local seq = { 0.12, -0.12, 0 }
for i = 1, #seq do for i = 1, #seq do
local dx = seq[i] local dx = seq[i]
_TimerService:SetTimerOnce(function() _TimerService:SetTimerOnce(function()
if isvalid(e) and e.TransformComponent ~= nil then if isvalid(e) and e.TransformComponent ~= nil then
e.TransformComponent.Position = Vector3(p.x + dx, p.y, p.z) e.TransformComponent.Position = Vector3(p.x + dx, p.y, p.z)
end end
if i == #seq then if i == #seq then
m.motionBusy = false m.motionBusy = false
end end
end, 0.06 * i) end, 0.06 * i)
end end
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]), end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
method('SetHpBar', `local e = _EntityService:GetEntityByPath(path) method('SetHpBar', `local e = _EntityService:GetEntityByPath(path)
if e == nil or e.UITransformComponent == nil then if e == nil or e.UITransformComponent == nil then
return return
end end
local ratio = 0 local ratio = 0
if maxHp > 0 then ratio = hp / maxHp end if maxHp > 0 then ratio = hp / maxHp end
if ratio < 0 then ratio = 0 end if ratio < 0 then ratio = 0 end
local w = width * ratio local w = width * ratio
e.UITransformComponent.RectSize = Vector2(w, 14)`, [ e.UITransformComponent.RectSize = Vector2(w, 14)`, [
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'path' }, { 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: 'hp' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'maxHp' }, { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'maxHp' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'width' }, { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'width' },
]), ]),
method('PositionMonsterSlot', `local m = self.Monsters[slot] method('SetTarget', `if self.Monsters[slot] ~= nil and self.Monsters[slot].alive == true then
if m == nil or m.entity == nil or not isvalid(m.entity) then self.TargetIndex = slot
return self:RenderCombat()
end end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]),
local tr = m.entity.TransformComponent method('RenderRun', `local floorText = "막 " .. string.format("%d", self.Floor) .. "/" .. string.format("%d", self.RunLength) .. " · " .. string.format("%d", self.Depth) .. "층"
if tr == nil then if self.AscensionLevel > 0 then
return floorText = floorText .. " · 승천" .. string.format("%d", self.AscensionLevel)
end end
local wp = tr.WorldPosition self:SetText("/ui/RunUIGroup/CombatHud/TopBar/Floor", floorText)
local screen = _UILogic:WorldToScreenPosition(Vector2(wp.x, wp.y + ${HEAD_OFFSET_Y})) self:SetText("/ui/RunUIGroup/CombatHud/TopBar/Gold", "메소 " .. string.format("%d", self.Gold))`),
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))`),
];

View File

@@ -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 { 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 { 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'; 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 = [ export const runEndMethods = [
method('TeleportToActMap', `local maps = { ${ACT_MAPS.map((m) => `"${m}"`).join(', ')} } method('ShowResult', `self:SetText("/ui/RunUIGroup/CombatHud/Result", text)
local target = maps[self.Floor] local entity = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/Result")
if target == nil then if entity ~= nil then
return entity.Enable = true
end end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'text' }]),
local lp = _UserService.LocalPlayer method('EndRun', `local msg = text
if lp == nil then if text == "런 클리어!" and self.AscensionLevel >= self.AscensionUnlocked and self.AscensionUnlocked < 10 then
return self.AscensionUnlocked = self.AscensionUnlocked + 1
end local lp = _UserService.LocalPlayer
if lp.CurrentMapName == target then if lp ~= nil then
return self:SaveAscension(self.AscensionUnlocked, lp.PlayerComponent.UserId)
end end
_TeleportService:TeleportToMapPosition(lp, Vector3(-6, 0.03, 0), target)`), self:RenderAscension()
method('ShowResult', `self:SetText("/ui/RunUIGroup/CombatHud/Result", text) msg = "런 클리어! 승천 " .. string.format("%d", self.AscensionUnlocked) .. " 해금!"
local entity = _EntityService:GetEntityByPath("/ui/RunUIGroup/CombatHud/Result") end
if entity ~= nil then self:ShowResult(msg)
entity.Enable = true self.RunActive = false
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'text' }]), _TimerService:SetTimerOnce(function() self:ShowLobby() end, 4)`, [{ 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' }]),
];

View File

@@ -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 { 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 { 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'; 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 = [ export const screensMethods = [
method('HideGameHud', `self:SetEntityEnabled("/ui/DefaultGroup/Button_Attack", false) method('HideGameHud', `self:SetEntityEnabled("/ui/DefaultGroup/Button_Attack", false)
self:SetEntityEnabled("/ui/DefaultGroup/Button_Jump", false) self:SetEntityEnabled("/ui/DefaultGroup/Button_Jump", false)
self:SetEntityEnabled("/ui/DefaultGroup/UIJoystick", false) self:SetEntityEnabled("/ui/DefaultGroup/UIJoystick", false)
self:SetEntityEnabled("/ui/RunUIGroup/DeckHud", false) self:SetEntityEnabled("/ui/RunUIGroup/DeckHud", false)
self:SetEntityEnabled("/ui/RunUIGroup/CardHand", false) self:SetEntityEnabled("/ui/RunUIGroup/CardHand", false)
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud", false) self:SetEntityEnabled("/ui/RunUIGroup/CombatHud", false)
self:SetEntityEnabled("/ui/RunUIGroup/RewardHud", false) self:SetEntityEnabled("/ui/RunUIGroup/RewardHud", false)
self:SetEntityEnabled("/ui/RunUIGroup/MapHud", false) self:SetEntityEnabled("/ui/RunUIGroup/MapHud", false)
self:SetEntityEnabled("/ui/RunUIGroup/ShopHud", false) self:SetEntityEnabled("/ui/RunUIGroup/ShopHud", false)
self:SetEntityEnabled("/ui/RunUIGroup/RestHud", false) self:SetEntityEnabled("/ui/RunUIGroup/RestHud", false)
self:SetEntityEnabled("/ui/RunUIGroup/TreasureHud", false) self:SetEntityEnabled("/ui/RunUIGroup/TreasureHud", false)
self:SetEntityEnabled("/ui/SelectUIGroup/JobChoiceHud", false) self:SetEntityEnabled("/ui/SelectUIGroup/JobChoiceHud", false)
self:SetEntityEnabled("/ui/SelectUIGroup/JobSelectHud", false) self:SetEntityEnabled("/ui/SelectUIGroup/JobSelectHud", false)
self:SetEntityEnabled("/ui/DeckUIGroup/DeckInspectHud", false) self:SetEntityEnabled("/ui/DeckUIGroup/DeckInspectHud", false)
self:SetEntityEnabled("/ui/DeckUIGroup/DeckAllHud", false) self:SetEntityEnabled("/ui/DeckUIGroup/DeckAllHud", false)
self:SetEntityEnabled("/ui/LobbyUIGroup/LobbyHud", false) self:SetEntityEnabled("/ui/LobbyUIGroup/LobbyHud", false)
self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", false) self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", false)
self:SetEntityEnabled("/ui/LobbyUIGroup/SoulShopHud", false)`), self:SetEntityEnabled("/ui/LobbyUIGroup/SoulShopHud", false)`),
method('ActivateUIGroups', `local function grp(n) method('ActivateUIGroups', `local function grp(n)
local g = _EntityService:GetEntityByPath("/ui/" .. n) local g = _EntityService:GetEntityByPath("/ui/" .. n)
if g ~= nil then g:SetEnable(true) end if g ~= nil then g:SetEnable(true) end
end end
grp("SelectUIGroup") grp("SelectUIGroup")
grp("LobbyUIGroup") grp("LobbyUIGroup")
grp("RunUIGroup") grp("RunUIGroup")
grp("DeckUIGroup")`, [], 2), grp("DeckUIGroup")`, [], 2),
method('ShowState', `self:HideGameHud() method('ShowState', `self:HideGameHud()
self:SetEntityEnabled("/ui/DefaultGroup/MainMenu", state == "menu") self:SetEntityEnabled("/ui/DefaultGroup/MainMenu", state == "menu")
self:SetEntityEnabled("/ui/SelectUIGroup/CharacterSelectHud", state == "charselect") self:SetEntityEnabled("/ui/SelectUIGroup/CharacterSelectHud", state == "charselect")
self:SetEntityEnabled("/ui/LobbyUIGroup/LobbyHud", state == "lobby") self:SetEntityEnabled("/ui/LobbyUIGroup/LobbyHud", state == "lobby")
if state == "map" then if state == "map" then
self:SetEntityEnabled("/ui/RunUIGroup/MapHud", true) self:SetEntityEnabled("/ui/RunUIGroup/MapHud", true)
elseif state == "combat" then elseif state == "combat" then
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud", true) self:SetEntityEnabled("/ui/RunUIGroup/CombatHud", true)
self:SetEntityEnabled("/ui/RunUIGroup/DeckHud", true) self:SetEntityEnabled("/ui/RunUIGroup/DeckHud", true)
self:SetEntityEnabled("/ui/RunUIGroup/CardHand", true) self:SetEntityEnabled("/ui/RunUIGroup/CardHand", true)
elseif state == "shop" then elseif state == "shop" then
self:SetEntityEnabled("/ui/RunUIGroup/ShopHud", true) self:SetEntityEnabled("/ui/RunUIGroup/ShopHud", true)
elseif state == "rest" then elseif state == "rest" then
self:SetEntityEnabled("/ui/RunUIGroup/RestHud", true) self:SetEntityEnabled("/ui/RunUIGroup/RestHud", true)
elseif state == "treasure" then elseif state == "treasure" then
self:SetEntityEnabled("/ui/RunUIGroup/TreasureHud", true) self:SetEntityEnabled("/ui/RunUIGroup/TreasureHud", true)
end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'state' }]), end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'state' }]),
method('ShowMainMenu', `self.SelectedClass = "" method('ShowMainMenu', `self.SelectedClass = ""
self:RenderAscension() self:RenderAscension()
self:ShowState("menu") self:ShowState("menu")
self:SetText("/ui/DefaultGroup/MainMenu/Title", "메이플 덱 어드벤처") self:SetText("/ui/DefaultGroup/MainMenu/Title", "메이플 덱 어드벤처")
self:SetText("/ui/DefaultGroup/MainMenu/Subtitle", "캐릭터를 고르고 덱을 만들어 모험을 시작하세요") self:SetText("/ui/DefaultGroup/MainMenu/Subtitle", "캐릭터를 고르고 덱을 만들어 모험을 시작하세요")
self:SetText("/ui/DefaultGroup/MainMenu/NewGameButton", "새 게임") self:SetText("/ui/DefaultGroup/MainMenu/NewGameButton", "새 게임")
self:BindMenuButtons()`), self:BindMenuButtons()`),
method('BindMenuButtons', `local buttonEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/NewGameButton") method('BindMenuButtons', `local buttonEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/NewGameButton")
if buttonEntity ~= nil and (buttonEntity.ButtonComponent ~= nil or buttonEntity:AddComponent("ButtonComponent") ~= nil) then if buttonEntity ~= nil and (buttonEntity.ButtonComponent ~= nil or buttonEntity:AddComponent("ButtonComponent") ~= nil) then
if self.NewGameHandler ~= nil then if self.NewGameHandler ~= nil then
buttonEntity:DisconnectEvent(ButtonClickEvent, self.NewGameHandler) buttonEntity:DisconnectEvent(ButtonClickEvent, self.NewGameHandler)
self.NewGameHandler = nil self.NewGameHandler = nil
end end
self.NewGameHandler = buttonEntity:ConnectEvent(ButtonClickEvent, function() self:ShowLobby() end) self.NewGameHandler = buttonEntity:ConnectEvent(ButtonClickEvent, function() self:ShowLobby() end)
end end
local warrior = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/WarriorButton") local warrior = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/WarriorButton")
if warrior ~= nil and (warrior.ButtonComponent ~= nil or warrior:AddComponent("ButtonComponent") ~= nil) then if warrior ~= nil and (warrior.ButtonComponent ~= nil or warrior:AddComponent("ButtonComponent") ~= nil) then
if self.WarriorSelectHandler ~= nil then if self.WarriorSelectHandler ~= nil then
warrior:DisconnectEvent(ButtonClickEvent, self.WarriorSelectHandler) warrior:DisconnectEvent(ButtonClickEvent, self.WarriorSelectHandler)
self.WarriorSelectHandler = nil self.WarriorSelectHandler = nil
end end
self.WarriorSelectHandler = warrior:ConnectEvent(ButtonClickEvent, function() self:SelectClass("warrior") end) self.WarriorSelectHandler = warrior:ConnectEvent(ButtonClickEvent, function() self:SelectClass("warrior") end)
end end
local thief = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/BanditButton") local thief = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/BanditButton")
if thief ~= nil and (thief.ButtonComponent ~= nil or thief:AddComponent("ButtonComponent") ~= nil) then if thief ~= nil and (thief.ButtonComponent ~= nil or thief:AddComponent("ButtonComponent") ~= nil) then
if self.ThiefSelectHandler ~= nil then if self.ThiefSelectHandler ~= nil then
thief:DisconnectEvent(ButtonClickEvent, self.ThiefSelectHandler) thief:DisconnectEvent(ButtonClickEvent, self.ThiefSelectHandler)
self.ThiefSelectHandler = nil self.ThiefSelectHandler = nil
end end
self.ThiefSelectHandler = thief:ConnectEvent(ButtonClickEvent, function() self:SelectClass("bandit") end) self.ThiefSelectHandler = thief:ConnectEvent(ButtonClickEvent, function() self:SelectClass("bandit") end)
end end
local mage = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/MageButton") local mage = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/MageButton")
if mage ~= nil and (mage.ButtonComponent ~= nil or mage:AddComponent("ButtonComponent") ~= nil) then if mage ~= nil and (mage.ButtonComponent ~= nil or mage:AddComponent("ButtonComponent") ~= nil) then
if self.MageSelectHandler ~= nil then if self.MageSelectHandler ~= nil then
mage:DisconnectEvent(ButtonClickEvent, self.MageSelectHandler) mage:DisconnectEvent(ButtonClickEvent, self.MageSelectHandler)
self.MageSelectHandler = nil self.MageSelectHandler = nil
end end
self.MageSelectHandler = mage:ConnectEvent(ButtonClickEvent, function() self:SelectClass("magician") end) self.MageSelectHandler = mage:ConnectEvent(ButtonClickEvent, function() self:SelectClass("magician") end)
end end
local allDeckClose = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud/Close") local allDeckClose = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud/Close")
if allDeckClose ~= nil and (allDeckClose.ButtonComponent ~= nil or allDeckClose:AddComponent("ButtonComponent") ~= nil) then if allDeckClose ~= nil and (allDeckClose.ButtonComponent ~= nil or allDeckClose:AddComponent("ButtonComponent") ~= nil) then
if self.AllDeckCloseHandler ~= nil then if self.AllDeckCloseHandler ~= nil then
allDeckClose:DisconnectEvent(ButtonClickEvent, self.AllDeckCloseHandler) allDeckClose:DisconnectEvent(ButtonClickEvent, self.AllDeckCloseHandler)
self.AllDeckCloseHandler = nil self.AllDeckCloseHandler = nil
end end
self.AllDeckCloseHandler = allDeckClose:ConnectEvent(ButtonClickEvent, function() self:CloseAllDeck() end) self.AllDeckCloseHandler = allDeckClose:ConnectEvent(ButtonClickEvent, function() self:CloseAllDeck() end)
end end
self:BindClassDeckTabs() self:BindClassDeckTabs()
local start = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/StartButton") local start = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/StartButton")
if start ~= nil and (start.ButtonComponent ~= nil or start:AddComponent("ButtonComponent") ~= nil) then if start ~= nil and (start.ButtonComponent ~= nil or start:AddComponent("ButtonComponent") ~= nil) then
if self.StartGameHandler ~= nil then if self.StartGameHandler ~= nil then
start:DisconnectEvent(ButtonClickEvent, self.StartGameHandler) start:DisconnectEvent(ButtonClickEvent, self.StartGameHandler)
self.StartGameHandler = nil self.StartGameHandler = nil
end end
self.StartGameHandler = start:ConnectEvent(ButtonClickEvent, function() self:StartNewGame() end) self.StartGameHandler = start:ConnectEvent(ButtonClickEvent, function() self:StartNewGame() end)
end end
local charBack = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/BackButton") local charBack = _EntityService:GetEntityByPath("/ui/SelectUIGroup/CharacterSelectHud/BackButton")
if charBack ~= nil and (charBack.ButtonComponent ~= nil or charBack:AddComponent("ButtonComponent") ~= nil) then if charBack ~= nil and (charBack.ButtonComponent ~= nil or charBack:AddComponent("ButtonComponent") ~= nil) then
if self.CharBackHandler ~= nil then if self.CharBackHandler ~= nil then
charBack:DisconnectEvent(ButtonClickEvent, self.CharBackHandler) charBack:DisconnectEvent(ButtonClickEvent, self.CharBackHandler)
self.CharBackHandler = nil self.CharBackHandler = nil
end end
self.CharBackHandler = charBack:ConnectEvent(ButtonClickEvent, function() self:ShowLobby() end) self.CharBackHandler = charBack:ConnectEvent(ButtonClickEvent, function() self:ShowLobby() end)
end end
local ascMinus = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/AscMinus") local ascMinus = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/AscMinus")
if ascMinus ~= nil and (ascMinus.ButtonComponent ~= nil or ascMinus:AddComponent("ButtonComponent") ~= nil) then if ascMinus ~= nil and (ascMinus.ButtonComponent ~= nil or ascMinus:AddComponent("ButtonComponent") ~= nil) then
if self.AscMinusHandler ~= nil then if self.AscMinusHandler ~= nil then
ascMinus:DisconnectEvent(ButtonClickEvent, self.AscMinusHandler) ascMinus:DisconnectEvent(ButtonClickEvent, self.AscMinusHandler)
self.AscMinusHandler = nil self.AscMinusHandler = nil
end end
self.AscMinusHandler = ascMinus:ConnectEvent(ButtonClickEvent, function() self:AdjustAscension(-1) end) self.AscMinusHandler = ascMinus:ConnectEvent(ButtonClickEvent, function() self:AdjustAscension(-1) end)
end end
local ascPlus = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/AscPlus") local ascPlus = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/AscPlus")
if ascPlus ~= nil and (ascPlus.ButtonComponent ~= nil or ascPlus:AddComponent("ButtonComponent") ~= nil) then if ascPlus ~= nil and (ascPlus.ButtonComponent ~= nil or ascPlus:AddComponent("ButtonComponent") ~= nil) then
if self.AscPlusHandler ~= nil then if self.AscPlusHandler ~= nil then
ascPlus:DisconnectEvent(ButtonClickEvent, self.AscPlusHandler) ascPlus:DisconnectEvent(ButtonClickEvent, self.AscPlusHandler)
self.AscPlusHandler = nil self.AscPlusHandler = nil
end end
self.AscPlusHandler = ascPlus:ConnectEvent(ButtonClickEvent, function() self:AdjustAscension(1) end) self.AscPlusHandler = ascPlus:ConnectEvent(ButtonClickEvent, function() self:AdjustAscension(1) end)
end`), end`),
method('ShowLobby', `self.SelectedClass = "" method('ShowLobby', `self.SelectedClass = ""
self:RenderAscension() self:RenderAscension()
self:RenderSoulLabel() self:RenderSoulLabel()
self:ShowState("lobby") self:ShowState("lobby")
self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", false) self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", false)
self:SetEntityEnabled("/ui/LobbyUIGroup/SoulShopHud", false) self:SetEntityEnabled("/ui/LobbyUIGroup/SoulShopHud", false)
self:BindLobbyButtons() self:BindLobbyButtons()
self:BindMenuButtons() self:BindMenuButtons()
self:GoLobbyMap()`), self:GoLobbyMap()`),
method('GoLobbyMap', `self.LobbyTpTries = 0 method('RenderSoulLabel', `local s = self.SoulPoints or 0
local eventId = 0 self:SetText("/ui/LobbyUIGroup/LobbyHud/SoulLabel", "영혼 " .. string.format("%d", s))
local function go() self:SetText("/ui/LobbyUIGroup/SoulShopHud/Souls", "영혼 " .. string.format("%d", s))`),
self.LobbyTpTries = self.LobbyTpTries + 1 method('BindLobbyButtons', `if self.LobbyBound == true then
local lp = _UserService.LocalPlayer return
if lp ~= nil then end
if lp.CurrentMapName ~= "${LOBBY_MAP}" then self.LobbyBound = true
_TeleportService:TeleportToMapPosition(lp, ${LOBBY_SPAWN}, "${LOBBY_MAP}") local function bindClick(path, fn)
end local e = _EntityService:GetEntityByPath(path)
_TimerService:ClearTimer(eventId) if e ~= nil and (e.ButtonComponent ~= nil or e:AddComponent("ButtonComponent") ~= nil) then
elseif self.LobbyTpTries > 50 then e:ConnectEvent(ButtonClickEvent, fn)
_TimerService:ClearTimer(eventId) end
end end
end bindClick("/ui/LobbyUIGroup/LobbyHud/AscMinus", function() self:AdjustAscension(-1) end)
eventId = _TimerService:SetTimerRepeat(go, 0.1)`), bindClick("/ui/LobbyUIGroup/LobbyHud/AscPlus", function() self:AdjustAscension(1) end)
method('OnLobbyNpcInteract', `if self.RunActive == true then bindClick("/ui/LobbyUIGroup/BoardHud/Close", function() self:CloseBoard() end)
return bindClick("/ui/LobbyUIGroup/SoulShopHud/Close", function() self:CloseSoulShop() end)`),
end method('ShowCodex', `self.CodexMode = true
if id == "run" then self.ClassDeckMode = true
self:ShowCharacterSelect() local close = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud/Close")
elseif id == "codex" then if close ~= nil and (close.ButtonComponent ~= nil or close:AddComponent("ButtonComponent") ~= nil) then
self:ShowCodex() if self.AllDeckCloseHandler ~= nil then
elseif id == "shop" then close:DisconnectEvent(ButtonClickEvent, self.AllDeckCloseHandler)
self:ShowSoulShop() end
elseif id == "board" then self.AllDeckCloseHandler = close:ConnectEvent(ButtonClickEvent, function() self:CloseAllDeck() end)
self:ShowBoard() end
end`, [{ Type: 'string', DefaultValue: '""', SyncDirection: 0, Attributes: [], Name: 'id' }]), self:BindClassDeckTabs()
method('RenderSoulLabel', `local s = self.SoulPoints or 0 self:SetEntityEnabled("/ui/LobbyUIGroup/LobbyHud", false)
self:SetText("/ui/LobbyUIGroup/LobbyHud/SoulLabel", "영혼 " .. string.format("%d", s)) self:SetClassDeckTab("warrior")
self:SetText("/ui/LobbyUIGroup/SoulShopHud/Souls", "영혼 " .. string.format("%d", s))`), local hud = _EntityService:GetEntityByPath("/ui/DeckUIGroup/DeckAllHud")
method('BindLobbyButtons', `if self.LobbyBound == true then if hud ~= nil then
return hud.Enable = true
end end
self.LobbyBound = true self:RenderAllDeck()`),
local function bindClick(path, fn) method('ShowBoard', `self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", true)`),
local e = _EntityService:GetEntityByPath(path) method('CloseBoard', `self:SetEntityEnabled("/ui/LobbyUIGroup/BoardHud", false)`),
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)`),
];

View File

@@ -3,7 +3,10 @@
import { POTIONS } from './lib/data.mjs'; import { POTIONS } from './lib/data.mjs';
import { prop, codeblock, RUN_LENGTH } from './lib/codeblock.mjs'; import { prop, codeblock, RUN_LENGTH } from './lib/codeblock.mjs';
import { bootMethods } from './cb/boot.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 { soulMethods } from './cb/soul.mjs';
import { charSelectMethods } from './cb/charselect.mjs'; import { charSelectMethods } from './cb/charselect.mjs';
import { runMethods } from './cb/run.mjs'; import { runMethods } from './cb/run.mjs';
@@ -166,7 +169,9 @@ function writeCodeblocks() {
prop('any', 'NextTurnAddCards'), prop('any', 'NextTurnAddCards'),
], [ ], [
...bootMethods, ...bootMethods,
...stateMethods, ...screensMethods,
...npcMethods,
...navigationMethods,
...soulMethods, ...soulMethods,
...charSelectMethods, ...charSelectMethods,
...runMethods, ...runMethods,
@@ -177,6 +182,7 @@ function writeCodeblocks() {
...jobMethods, ...jobMethods,
...runEndMethods, ...runEndMethods,
...renderMethods, ...renderMethods,
...layoutMethods,
...rewardMethods, ...rewardMethods,
...itemMethods, ...itemMethods,
...tooltipMethods, ...tooltipMethods,

40
tools/verify/cbset.mjs Normal file
View File

@@ -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);