feat(lobby): 로비 맵 흐름 통합 — 텔레포트·NPC 디스패치·StartRun map01·LobbyHud 슬림화 (P15)

- OnBeginPlay: 공격 키(Ctrl, 로비 한정) 바인딩
- ShowLobby→GoLobbyMap: 월드 시작·런 종료 시 로비 맵 텔레포트
- OnLobbyNpcInteract(id): 월드 NPC→기존 기능 패널 디스패치
- StartRun: 1막 진입 시 map01 물리 텔레포트(BuildMonsters CurrentMapName 필터 대응)
- LobbyHud: 버튼-행 제거, 투명·비레이캐스트화(맵 노출·클릭 통과), 영혼/승천 미니 정보바만 유지
- BindLobbyButtons: NPC 바인딩 제거

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-14 12:36:13 +09:00
parent de1e69de7d
commit 0e064cc1e9
3 changed files with 132 additions and 3114 deletions

File diff suppressed because one or more lines are too long

View File

@@ -2425,17 +2425,17 @@ function upsertUi() {
displayOrder: 11,
components: [
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1920, y: 1080 }, pos: { x: 0, y: 0 }, align: ALIGN_CENTER }),
sprite({ color: { r: 0.07, g: 0.08, b: 0.13, a: 1 }, type: 1, raycast: true }),
// 로비가 물리 맵이 됨 — 투명 + 비레이캐스트로 맵을 가리지 않고 월드 NPC 클릭이 통과되게 함.
sprite({ color: TRANSPARENT, type: 1, raycast: false }),
],
});
lobbyRoot.jsonString.enable = false;
lobby.push(lobbyRoot);
const lobTexts = [
['Title', 0, 470, 760, '메이플 로비', 48, GOLD],
['Subtitle', 0, 408, 900, 'NPC를 클릭해 런을 준비하세요', 24, { r: 0.82, g: 0.86, b: 0.92, a: 1 }],
['SoulLabel', 700, 470, 320, '영혼 0', 28, { r: 0.6, g: 0.85, b: 1, a: 1 }],
['AscLabel', -560, 470, 380, '승천 0 / 해금 0', 22, { r: 0.9, g: 0.7, b: 0.5, a: 1 }],
['Hint', 0, -430, 1300, '런을 클리어하거나 패배하면 로비로 돌아옵니다', 20, { r: 0.6, g: 0.64, b: 0.7, a: 1 }],
['Title', 0, 478, 760, '메이플 로비', 40, GOLD],
['SoulLabel', 700, 478, 320, '영혼 0', 28, { r: 0.6, g: 0.85, b: 1, a: 1 }],
['AscLabel', -560, 478, 380, '승천 0 / 해금 0', 22, { r: 0.9, g: 0.7, b: 0.5, a: 1 }],
['Hint', 0, -478, 1500, 'NPC에게 다가가 ↑ 또는 클릭으로 대화 · ← → 이동 · Ctrl 공격', 20, { r: 0.72, g: 0.76, b: 0.82, a: 1 }],
];
for (const [suffix, x, y, w, value, fs, color] of lobTexts) {
lobby.push(entity({
@@ -2466,62 +2466,7 @@ function upsertUi() {
],
}));
}
const npcs = [
{ key: 'NpcRun', x: -540, name: '모험가', role: '런 시작', tint: { r: 0.74, g: 0.32, b: 0.28, a: 1 } },
{ key: 'NpcCodex', x: -180, name: '사서', role: '카드 도감', tint: { r: 0.3, g: 0.55, b: 0.5, a: 1 } },
{ key: 'NpcShop', x: 180, name: '상인', role: '영혼 상점', tint: { r: 0.55, g: 0.45, b: 0.75, a: 1 } },
{ key: 'NpcBoard', x: 540, name: '안내원', role: '게시판', tint: { r: 0.4, g: 0.5, b: 0.7, a: 1 } },
];
for (const npc of npcs) {
const base = `/ui/DefaultGroup/LobbyHud/${npc.key}`;
lobby.push(entity({
id: guid('lob', lobId++),
path: base,
modelId: 'uibutton', entryId: 'UIButton',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent',
displayOrder: 4,
components: [
transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 240, y: 320 }, pos: { x: npc.x, y: 20 }, align: ALIGN_CENTER }),
sprite({ color: { r: 0.13, g: 0.15, b: 0.2, a: 1 }, type: 1, raycast: true }),
button(),
],
}));
lobby.push(entity({
id: guid('lob', lobId++),
path: `${base}/Figure`,
modelId: 'uisprite', entryId: 'UISprite',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
displayOrder: 1,
components: [
transform({ parentW: 240, parentH: 320, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 130, y: 130 }, pos: { x: 0, y: 30 } }),
sprite({ color: npc.tint, type: 1 }),
],
}));
lobby.push(entity({
id: guid('lob', lobId++),
path: `${base}/Name`,
modelId: 'uitext', entryId: 'UIText',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
displayOrder: 2,
components: [
transform({ parentW: 240, parentH: 320, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 220, y: 40 }, pos: { x: 0, y: 122 } }),
sprite({ color: TRANSPARENT }),
text({ value: npc.name, fontSize: 28, bold: true, color: GOLD, alignment: 4 }),
],
}));
lobby.push(entity({
id: guid('lob', lobId++),
path: `${base}/Role`,
modelId: 'uitext', entryId: 'UIText',
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
displayOrder: 2,
components: [
transform({ parentW: 240, parentH: 320, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 220, y: 36 }, pos: { x: 0, y: -122 } }),
sprite({ color: TRANSPARENT }),
text({ value: npc.role, fontSize: 22, bold: false, color: { r: 0.86, g: 0.9, b: 0.94, a: 1 }, alignment: 4 }),
],
}));
}
// NPC 4종은 로비 물리 맵의 월드 엔티티(map/lobby.map + LobbyNpc codeblock)로 이동. UI 버튼 행 제거.
emit('LobbyHud', lobby);
// ── BoardHud — 게시판(공지/팁) ──
@@ -2743,6 +2688,8 @@ function writeCodeblocks() {
const RELIC_PRICE = 60;
const ACT_COUNT = 5;
const ACT_MAPS = ['map01', 'map02', 'map03', 'map04', 'map05'];
const LOBBY_MAP = 'lobby';
const LOBBY_SPAWN = 'Vector3(-5, 0.03, 0)'; // 정찰: map01 지면 좌측
const combat = codeblock('SlayDeckController', 'SlayDeckController', [
prop('any', 'DrawPile'),
prop('any', 'DiscardPile'),
@@ -2771,6 +2718,7 @@ function writeCodeblocks() {
prop('any', 'AllDeckCloseHandler'),
prop('number', 'SoulPoints', '0'),
prop('boolean', 'LobbyBound', 'false'),
prop('number', 'LobbyTpTries', '0'),
prop('boolean', 'CodexMode', 'false'),
prop('any', 'CodexCards'),
prop('any', 'SoulUnlocks'),
@@ -2837,7 +2785,15 @@ local lp = _UserService.LocalPlayer
if lp ~= nil then
self:ReqLoadAscension(lp.PlayerComponent.UserId)
self:ReqLoadSouls(lp.PlayerComponent.UserId)
end`),
end
_InputService:ConnectEvent(KeyDownEvent, function(e)
if e.key == KeyboardKey.LeftControl then
local lp2 = _UserService.LocalPlayer
if lp2 ~= nil and lp2.CurrentMapName == "${LOBBY_MAP}" and self.RunActive ~= true then
self:PlayerAttackMotion()
end
end
end)`),
method('ReqLoadAscension', `local ds = _DataStorageService:GetUserDataStorage(userId)
local errCode, value = ds:GetAndWait("ascensionUnlocked")
local n = 0
@@ -2990,7 +2946,35 @@ self:ShowState("lobby")
self:SetEntityEnabled("/ui/DefaultGroup/BoardHud", false)
self:SetEntityEnabled("/ui/DefaultGroup/SoulShopHud", false)
self:BindLobbyButtons()
self:BindMenuButtons()`),
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/DefaultGroup/LobbyHud/SoulLabel", "영혼 " .. string.format("%d", s))
self:SetText("/ui/DefaultGroup/SoulShopHud/Souls", "영혼 " .. string.format("%d", s))`),
@@ -3004,10 +2988,6 @@ local function bindClick(path, fn)
e:ConnectEvent(ButtonClickEvent, fn)
end
end
bindClick("/ui/DefaultGroup/LobbyHud/NpcRun", function() self:ShowCharacterSelect() end)
bindClick("/ui/DefaultGroup/LobbyHud/NpcCodex", function() self:ShowCodex() end)
bindClick("/ui/DefaultGroup/LobbyHud/NpcShop", function() self:ShowSoulShop() end)
bindClick("/ui/DefaultGroup/LobbyHud/NpcBoard", function() self:ShowBoard() end)
bindClick("/ui/DefaultGroup/LobbyHud/AscMinus", function() self:AdjustAscension(-1) end)
bindClick("/ui/DefaultGroup/LobbyHud/AscPlus", function() self:AdjustAscension(1) end)
bindClick("/ui/DefaultGroup/BoardHud/Close", function() self:CloseBoard() end)
@@ -3229,6 +3209,7 @@ self:BindButtons()
self:AddRelic("${RELICS.startingRelic}")
self:ApplySoulUnlocks()
self:RenderPotions()
self:TeleportToActMap()
self:ShowMap()`),
method('StartCombat', `self:ShowState("combat")
self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/Result", false)

File diff suppressed because it is too large Load Diff