From bcdf9457c872114aab19d30ab9671f234ed460d8 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 12 Jun 2026 14:04:06 +0900 Subject: [PATCH 1/5] =?UTF-8?q?docs(ascension):=20P11=20=EC=84=A4=EA=B3=84?= =?UTF-8?q?=C2=B7=EA=B3=84=ED=9A=8D=20=E2=80=94=20=EC=8A=B9=EC=B2=9C=20A1~?= =?UTF-8?q?A10=C2=B7UserDataStorage=20=EA=B0=9C=EC=9D=B8=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=C2=B7=EC=84=9C=EB=B2=84=20RPC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../superpowers/plans/2026-06-12-ascension.md | 23 +++++++++ .../specs/2026-06-12-ascension-design.md | 47 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-12-ascension.md create mode 100644 docs/superpowers/specs/2026-06-12-ascension-design.md diff --git a/docs/superpowers/plans/2026-06-12-ascension.md b/docs/superpowers/plans/2026-06-12-ascension.md new file mode 100644 index 0000000..56c64f2 --- /dev/null +++ b/docs/superpowers/plans/2026-06-12-ascension.md @@ -0,0 +1,23 @@ +# P11 — 승천 + UserDataStorage 구현 계획 + +> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:executing-plans. 설계: `2026-06-12-ascension-design.md` + +### Task 1: 생성기 — ExecSpace 보존 + 서버 RPC 3종 +- [ ] `for m of Methods: m.ExecSpace = 6` → `if (m.ExecSpace === 0) m.ExecSpace = 6;` (명시값 보존) +- [ ] props: `AscensionLevel`(number 0)·`AscensionUnlocked`(number 0) +- [ ] `ReqLoadAscension(userId)`[ExecSpace 1]·`RecvAscension(n, userId)`[2]·`SaveAscension(n, userId)`[1] — 설계 코드 그대로, OnBeginPlay(6)에서 LocalPlayer.UserId로 ReqLoad +- [ ] 커밋 + +### Task 2: 생성기 — 모디파이어·해금·메뉴 UI +- [ ] 헬퍼 5종(AscHpMult/AscAtkMult/AscEliteBonus/AscGoldMult/AscStartHpPenalty) + StartRun/BuildMonsters/CheckCombatEnd/RenderRun 적용 +- [ ] EndRun 클리어 분기: 해금+1·SaveAscension·"런 클리어! 승천 N 해금!" +- [ ] MainMenu `AscMinus/AscLabel/AscPlus` + `AdjustAscension`/`RenderAscension` + BindMenuButtons 연결 +- [ ] 커밋 + +### Task 3: 재생성·메이커 검증·PR +- [ ] 재생성·테스트 44건 유지·grep -c 카운트 → 커밋 +- [ ] 메이커: 메뉴 승천 라벨/[-][+]·승천2로 런 시작(HP·적 배율 로그 확인)·강제 클리어→해금+1·재플레이 로드 → 스크린샷 +- [ ] push → gitea-pr.mjs PR·머지 → main pull → 메모리 갱신 + +## Self-Review +- RPC 파라미터 any 금지(허용 타입: string/number) 준수 ✓ / RecvAscension 마지막 인자 userId(특정 클라 응답) ✓ / 시뮬 비대상 명시 ✓ diff --git a/docs/superpowers/specs/2026-06-12-ascension-design.md b/docs/superpowers/specs/2026-06-12-ascension-design.md new file mode 100644 index 0000000..33d900a --- /dev/null +++ b/docs/superpowers/specs/2026-06-12-ascension-design.md @@ -0,0 +1,47 @@ +# P11 — 승천 시스템 + UserDataStorage 설계 + +날짜: 2026-06-12 (사용자 승인 — P9/P10/P11 중 3단계) +브랜치: `feature/p11-ascension` + +## 범위 + +1. **개인별 승천 저장** — `_DataStorageService:GetUserDataStorage(userId)` (유저별 영구, 메이커↔배포 분리). 이 프로젝트 첫 서버-클라 RPC. +2. **승천 선택 UI** — 메인 메뉴에서 0~해금치 선택([-]/[+]), 런 클리어 시 해금 +1 (최대 10), 클리어 문구 "승천 N 해금!" +3. **승천 모디파이어 A1~A10** (누적): + +| 단계 | 효과 | 단계 | 효과 | +|---|---|---|---| +| A1 | 적 HP +10% | A6 | 적 HP 추가 +10% | +| A2 | 적 피해 +10% | A7 | 적 피해 추가 +10% | +| A3 | 시작 HP -10 | A8 | 시작 HP 추가 -10 | +| A4 | 정예·보스 배율 +0.2 | A9 | 정예·보스 배율 추가 +0.2 | +| A5 | 승리 메소 -25% | A10 | 승리 메소 추가 -25% | + +4. TopBar에 `· 승천N` 표시 (0이면 생략) + +## 서버-클라 구조 (ExecSpace) + +codeblock JSON ExecSpace는 플래그 (실측: Server=1·Client=2·ClientOnly=6 — Monster(1)·UIToast/MapCamera(6) 부합): + +- `ReqLoadAscension(userId)` **[Server=1]** — 클라 OnBeginPlay에서 호출 → 서버에서 `GetAndWait("ascensionUnlocked")` → `RecvAscension(n, userId)` 호출 +- `RecvAscension(n, userId)` **[Client=2]** — 마지막 파라미터 userId로 **요청한 클라이언트에만** 응답 (MSW 공식 패턴) → `AscensionUnlocked` 갱신·메뉴 렌더 +- `SaveAscension(n, userId)` **[Server=1]** — `SetAndWait` +- 생성기의 `m.ExecSpace = 6` 일괄 적용을 "명시값(≠0)은 보존"으로 수정 + +## 적용 지점 + +- `StartRun`: 시작 HP에서 A3/A8 차감, `AscensionLevel`은 메뉴 선택값 유지 +- `BuildMonsters`: maxHp ×(1+A1/A6), Attack 인텐트 ×(1+A2/A7), elite/boss 그룹이면 막 배율 +A4/A9 +- `CheckCombatEnd`: 승리 메소 ×(1-A5/A10) floor +- `EndRun("런 클리어!")`: `AscensionLevel >= AscensionUnlocked and Unlocked < 10`이면 해금+1·저장·결과 문구 교체 +- 헬퍼: `AscHpMult`/`AscAtkMult`/`AscEliteBonus`/`AscGoldMult`/`AscStartHpPenalty` + +## UI (MainMenu) + +- `AscRow`: `AscMinus`[-] · `AscLabel`("승천 L / 해금 U") · `AscPlus`[+] — 새 게임 버튼 아래 +- `AdjustAscension(delta)` clamp 0..Unlocked, `RenderAscension` + +## 검증 + +1. 시뮬: 모디파이어는 Lua 전용(런 메타) — 시뮬 비대상 명시. 기존 44건 유지 +2. 메이커: 로드(첫 0)→승천 2 선택→적 HP/피해 배율·시작 HP 확인→클리어→해금+1·저장→재시작 후 로드 확인(메이커 스토리지), 빌드·런타임 0에러 From b635cb3a63610f4cb47d749e6fb2575f1fd15734 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 12 Jun 2026 14:10:30 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat(ascension):=20=EC=8A=B9=EC=B2=9C=20A1~?= =?UTF-8?q?A10=C2=B7UserDataStorage=20=EA=B0=9C=EC=9D=B8=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=C2=B7=EC=84=9C=EB=B2=84=20RPC=20(=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EA=B8=B0+=EC=82=B0=EC=B6=9C=EB=AC=BC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ReqLoadAscension[Server]/RecvAscension[Client·특정 유저 응답]/SaveAscension[Server] - ExecSpace 일괄 6 → 명시값 보존 (첫 서버-클라 RPC) - 모디파이어: 적 HP/피해 ×1.1~1.2·정예 배율 +0.2/0.4·시작 HP -10/-20·메소 ×0.75/0.5 - 메인 메뉴 승천 [-]/라벨/[+], 클리어 시 해금+1·저장·'승천 N 해금!' 표시 - TopBar '· 승천N', 테스트 40건 유지 Co-Authored-By: Claude Opus 4.8 (1M context) --- RootDesk/MyDesk/SlayDeckController.codeblock | 240 ++++++- tools/deck/gen-slaydeck.mjs | 147 ++++- ui/DefaultGroup.ui | 658 +++++++++++++++++++ 3 files changed, 1030 insertions(+), 15 deletions(-) diff --git a/RootDesk/MyDesk/SlayDeckController.codeblock b/RootDesk/MyDesk/SlayDeckController.codeblock index c49f59b..d4263f7 100644 --- a/RootDesk/MyDesk/SlayDeckController.codeblock +++ b/RootDesk/MyDesk/SlayDeckController.codeblock @@ -106,6 +106,20 @@ "Attributes": [], "Name": "MageSelectHandler" }, + { + "Type": "any", + "DefaultValue": "nil", + "SyncDirection": 0, + "Attributes": [], + "Name": "AscMinusHandler" + }, + { + "Type": "any", + "DefaultValue": "nil", + "SyncDirection": 0, + "Attributes": [], + "Name": "AscPlusHandler" + }, { "Type": "any", "DefaultValue": "nil", @@ -120,6 +134,20 @@ "Attributes": [], "Name": "Jobs" }, + { + "Type": "number", + "DefaultValue": "0", + "SyncDirection": 0, + "Attributes": [], + "Name": "AscensionLevel" + }, + { + "Type": "number", + "DefaultValue": "0", + "SyncDirection": 0, + "Attributes": [], + "Name": "AscensionUnlocked" + }, { "Type": "any", "DefaultValue": "nil", @@ -516,12 +544,208 @@ "Name": null }, "Arguments": [], - "Code": "self:ShowMainMenu()", + "Code": "self:ShowMainMenu()\nlocal lp = _UserService.LocalPlayer\nif lp ~= nil then\n\tself:ReqLoadAscension(lp.UserId)\nend", "Scope": 2, "ExecSpace": 6, "Attributes": [], "Name": "OnBeginPlay" }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "Type": "string", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "userId" + } + ], + "Code": "local ds = _DataStorageService:GetUserDataStorage(userId)\nlocal errCode, value = ds:GetAndWait(\"ascensionUnlocked\")\nlocal n = 0\nif errCode == 0 and value ~= nil and value ~= \"\" then\n\tn = tonumber(value) or 0\nend\nself:RecvAscension(n, userId)", + "Scope": 2, + "ExecSpace": 1, + "Attributes": [], + "Name": "ReqLoadAscension" + }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "n" + }, + { + "Type": "string", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "userId" + } + ], + "Code": "self.AscensionUnlocked = n\nif self.AscensionLevel > self.AscensionUnlocked then\n\tself.AscensionLevel = self.AscensionUnlocked\nend\nself:RenderAscension()", + "Scope": 2, + "ExecSpace": 2, + "Attributes": [], + "Name": "RecvAscension" + }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "n" + }, + { + "Type": "string", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "userId" + } + ], + "Code": "local ds = _DataStorageService:GetUserDataStorage(userId)\nds:SetAndWait(\"ascensionUnlocked\", tostring(n))", + "Scope": 2, + "ExecSpace": 1, + "Attributes": [], + "Name": "SaveAscension" + }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "delta" + } + ], + "Code": "local v = self.AscensionLevel + delta\nif v < 0 then v = 0 end\nif v > self.AscensionUnlocked then v = self.AscensionUnlocked end\nself.AscensionLevel = v\nself:RenderAscension()", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "AdjustAscension" + }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [], + "Code": "self:SetText(\"/ui/DefaultGroup/MainMenu/AscLabel\", \"승천 \" .. string.format(\"%d\", self.AscensionLevel) .. \" / 해금 \" .. string.format(\"%d\", self.AscensionUnlocked))", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "RenderAscension" + }, + { + "Return": { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [], + "Code": "local m = 1\nif self.AscensionLevel >= 1 then m = m + 0.1 end\nif self.AscensionLevel >= 6 then m = m + 0.1 end\nreturn m", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "AscHpMult" + }, + { + "Return": { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [], + "Code": "local m = 1\nif self.AscensionLevel >= 2 then m = m + 0.1 end\nif self.AscensionLevel >= 7 then m = m + 0.1 end\nreturn m", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "AscAtkMult" + }, + { + "Return": { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [], + "Code": "local b = 0\nif self.AscensionLevel >= 4 then b = b + 0.2 end\nif self.AscensionLevel >= 9 then b = b + 0.2 end\nreturn b", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "AscEliteBonus" + }, + { + "Return": { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [], + "Code": "local m = 1\nif self.AscensionLevel >= 5 then m = m - 0.25 end\nif self.AscensionLevel >= 10 then m = m - 0.25 end\nreturn m", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "AscGoldMult" + }, + { + "Return": { + "Type": "number", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [], + "Code": "local p = 0\nif self.AscensionLevel >= 3 then p = p + 10 end\nif self.AscensionLevel >= 8 then p = p + 10 end\nreturn p", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "AscStartHpPenalty" + }, { "Return": { "Type": "void", @@ -569,7 +793,7 @@ "Name": null }, "Arguments": [], - "Code": "self.SelectedClass = \"\"\nself:ShowState(\"menu\")\nself:SetText(\"/ui/DefaultGroup/MainMenu/Title\", \"메이플 덱 어드벤처\")\nself:SetText(\"/ui/DefaultGroup/MainMenu/Subtitle\", \"캐릭터를 고르고 덱을 만들어 모험을 시작하세요\")\nself:SetText(\"/ui/DefaultGroup/MainMenu/NewGameButton\", \"새 게임\")\nself:BindMenuButtons()", + "Code": "self.SelectedClass = \"\"\nself:RenderAscension()\nself:ShowState(\"menu\")\nself:SetText(\"/ui/DefaultGroup/MainMenu/Title\", \"메이플 덱 어드벤처\")\nself:SetText(\"/ui/DefaultGroup/MainMenu/Subtitle\", \"캐릭터를 고르고 덱을 만들어 모험을 시작하세요\")\nself:SetText(\"/ui/DefaultGroup/MainMenu/NewGameButton\", \"새 게임\")\nself:BindMenuButtons()", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -584,7 +808,7 @@ "Name": null }, "Arguments": [], - "Code": "local buttonEntity = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/MainMenu/NewGameButton\")\nif buttonEntity ~= nil and buttonEntity.ButtonComponent ~= nil then\n\tif self.NewGameHandler ~= nil then\n\t\tbuttonEntity:DisconnectEvent(ButtonClickEvent, self.NewGameHandler)\n\t\tself.NewGameHandler = nil\n\tend\n\tself.NewGameHandler = buttonEntity:ConnectEvent(ButtonClickEvent, function() self:ShowCharacterSelect() end)\nend\nlocal warrior = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CharacterSelectHud/WarriorButton\")\nif warrior ~= nil and warrior.ButtonComponent ~= nil then\n\tif self.WarriorSelectHandler ~= nil then\n\t\twarrior:DisconnectEvent(ButtonClickEvent, self.WarriorSelectHandler)\n\t\tself.WarriorSelectHandler = nil\n\tend\n\tself.WarriorSelectHandler = warrior:ConnectEvent(ButtonClickEvent, function() self:SelectClass(\"warrior\") end)\nend\nlocal mage = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CharacterSelectHud/MageButton\")\nif mage ~= nil and mage.ButtonComponent ~= nil then\n\tif self.MageSelectHandler ~= nil then\n\t\tmage:DisconnectEvent(ButtonClickEvent, self.MageSelectHandler)\n\t\tself.MageSelectHandler = nil\n\tend\n\tself.MageSelectHandler = mage:ConnectEvent(ButtonClickEvent, function() self:SelectClass(\"magician\") end)\nend\nlocal start = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CharacterSelectHud/StartButton\")\nif start ~= nil and start.ButtonComponent ~= nil then\n\tif self.StartGameHandler ~= nil then\n\t\tstart:DisconnectEvent(ButtonClickEvent, self.StartGameHandler)\n\t\tself.StartGameHandler = nil\n\tend\n\tself.StartGameHandler = start:ConnectEvent(ButtonClickEvent, function() self:StartNewGame() end)\nend", + "Code": "local buttonEntity = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/MainMenu/NewGameButton\")\nif buttonEntity ~= nil and buttonEntity.ButtonComponent ~= nil then\n\tif self.NewGameHandler ~= nil then\n\t\tbuttonEntity:DisconnectEvent(ButtonClickEvent, self.NewGameHandler)\n\t\tself.NewGameHandler = nil\n\tend\n\tself.NewGameHandler = buttonEntity:ConnectEvent(ButtonClickEvent, function() self:ShowCharacterSelect() end)\nend\nlocal warrior = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CharacterSelectHud/WarriorButton\")\nif warrior ~= nil and warrior.ButtonComponent ~= nil then\n\tif self.WarriorSelectHandler ~= nil then\n\t\twarrior:DisconnectEvent(ButtonClickEvent, self.WarriorSelectHandler)\n\t\tself.WarriorSelectHandler = nil\n\tend\n\tself.WarriorSelectHandler = warrior:ConnectEvent(ButtonClickEvent, function() self:SelectClass(\"warrior\") end)\nend\nlocal mage = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CharacterSelectHud/MageButton\")\nif mage ~= nil and mage.ButtonComponent ~= nil then\n\tif self.MageSelectHandler ~= nil then\n\t\tmage:DisconnectEvent(ButtonClickEvent, self.MageSelectHandler)\n\t\tself.MageSelectHandler = nil\n\tend\n\tself.MageSelectHandler = mage:ConnectEvent(ButtonClickEvent, function() self:SelectClass(\"magician\") end)\nend\nlocal start = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CharacterSelectHud/StartButton\")\nif start ~= nil and start.ButtonComponent ~= nil then\n\tif self.StartGameHandler ~= nil then\n\t\tstart:DisconnectEvent(ButtonClickEvent, self.StartGameHandler)\n\t\tself.StartGameHandler = nil\n\tend\n\tself.StartGameHandler = start:ConnectEvent(ButtonClickEvent, function() self:StartNewGame() end)\nend\nlocal ascMinus = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/MainMenu/AscMinus\")\nif ascMinus ~= nil and ascMinus.ButtonComponent ~= nil then\n\tif self.AscMinusHandler ~= nil then\n\t\tascMinus:DisconnectEvent(ButtonClickEvent, self.AscMinusHandler)\n\t\tself.AscMinusHandler = nil\n\tend\n\tself.AscMinusHandler = ascMinus:ConnectEvent(ButtonClickEvent, function() self:AdjustAscension(-1) end)\nend\nlocal ascPlus = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/MainMenu/AscPlus\")\nif ascPlus ~= nil and ascPlus.ButtonComponent ~= nil then\n\tif self.AscPlusHandler ~= nil then\n\t\tascPlus:DisconnectEvent(ButtonClickEvent, self.AscPlusHandler)\n\t\tself.AscPlusHandler = nil\n\tend\n\tself.AscPlusHandler = ascPlus:ConnectEvent(ButtonClickEvent, function() self:AdjustAscension(1) end)\nend", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -697,7 +921,7 @@ "Name": null }, "Arguments": [], - "Code": "if self.SelectedClass == \"magician\" then\n\tself.PlayerMaxHp = 70\n\tself.RunDeck = { \"EnergyBolt\", \"EnergyBolt\", \"EnergyBolt\", \"EnergyBolt\", \"EnergyBolt\", \"MagicGuard\", \"MagicGuard\", \"MagicGuard\", \"MagicGuard\", \"MagicClaw\" }\nelse\n\tself.PlayerMaxHp = 80\n\tself.RunDeck = { \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Defend\", \"Defend\", \"Defend\", \"Defend\", \"Bash\" }\nend\nself.PlayerHp = self.PlayerMaxHp\nself.Gold = 0\nself.Floor = 1\nself.RunLength = 3\nself.RunActive = true\nself.RunRelics = {}\nself.RunPotions = {}\nself.PotionSlots = 3\nself.Potions = {\n\tredPotion = { name = \"빨간 포션\", desc = \"HP 20 회복\", effect = \"heal\", value = 20, icon = \"393e2a0d8da544899eaa8b22c97f832b\" },\n\tfirebomb = { name = \"화염병\", desc = \"적에게 피해 20\", effect = \"damage\", value = 20, icon = \"7ddb464c2574456289a4eb72ce86f193\" },\n\twarriorElixir = { name = \"전사의 물약\", desc = \"힘 +2\", effect = \"strength\", value = 2, icon = \"7cfbd410581e4073815daaf5f3e6c72f\" },\n\tguardPotion = { name = \"수호의 물약\", desc = \"방어도 +12\", effect = \"block\", value = 12, icon = \"8f8402dfa0f746e18bf606ed74302c0a\" },\n\tmanaElixir = { name = \"마나 엘릭서\", desc = \"에너지 +2\", effect = \"energy\", value = 2, icon = \"ec2778c366f6477ab0f8e7f06bcd73f4\" },\n\tcursedVial = { name = \"저주의 병\", desc = \"적에게 약화 3\", effect = \"weak\", value = 3, icon = \"a9a2763fdb6849dcba3028c737487680\" },\n}\nself.Relics = {\n\tironHeart = { name = \"강철 심장\", desc = \"전투 시작 시 방어도 +6\", hook = \"combatStart\", effect = \"block\", value = 6, icon = \"e555b3a62f3c49dbb2c53784e6bd481f\" },\n\tenergyCore = { name = \"에너지 코어\", desc = \"턴 시작 시 에너지 +1\", hook = \"turnStart\", effect = \"energy\", value = 1, icon = \"a41014f28b47434ab9f49ef104523862\" },\n\tvampire = { name = \"흡혈 송곳니\", desc = \"공격 카드 사용 시 HP +1\", hook = \"cardPlayed\", effect = \"healOnAttack\", value = 1, icon = \"ed64cde7e6c44b9e99502847e54f04e9\" },\n\tgoldIdol = { name = \"황금 우상\", desc = \"전투 승리 시 골드 +10\", hook = \"combatReward\", effect = \"gold\", value = 10, icon = \"03bb05c92b8f45edb0f3dad2e118fd5a\" },\n\tpotionBelt = { name = \"장인의 벨트\", desc = \"물약 슬롯이 5칸으로 늘어난다\", hook = \"passive\", effect = \"potionSlots\", value = 5, icon = \"36725b4566ac40d4902e2ab2113c2096\" },\n\tburningBlood = { name = \"자쿰의 투구\", desc = \"전투 승리 시 HP 6 회복\", hook = \"combatEnd\", effect = \"healOnWin\", value = 6, icon = \"07f994825ce34131b419d43e890c878d\" },\n\tvajra = { name = \"미스릴 해머\", desc = \"전투 시작 시 힘 +1\", hook = \"combatStart\", effect = \"strength\", value = 1, icon = \"59d2579d46dc41d590a9e6b141ad458b\" },\n\tanchor = { name = \"메이플 실드\", desc = \"첫 턴 방어도 +10\", hook = \"combatStart\", effect = \"block\", value = 10, icon = \"6349413e08cc49848862591863d056a0\" },\n\tbagOfPrep = { name = \"모험가의 배낭\", desc = \"첫 턴 드로우 +2\", hook = \"combatStart\", effect = \"draw\", value = 2, icon = \"77b240cb8af245b4801a714380267ae9\" },\n\tbloodVial = { name = \"피의 목걸이\", desc = \"전투 시작 시 HP 2 회복\", hook = \"combatStart\", effect = \"heal\", value = 2, icon = \"c782e949506a42c49eb139c7e65527d7\" },\n\tbronzeScales = { name = \"브론즈 체인메일\", desc = \"피격 시 공격자에게 3 반사\", hook = \"onPlayerDamaged\", effect = \"thorns\", value = 3, icon = \"87272346b145412391622cf803f888d1\" },\n\tstrawberry = { name = \"건강의 반지\", desc = \"획득 시 최대 HP +7\", hook = \"passive\", effect = \"maxHp\", value = 7, icon = \"58f643e29c354c2783a5ce9a72ec155c\" },\n\tpenNib = { name = \"황금 깃펜\", desc = \"10번째 공격마다 피해 2배\", hook = \"attackCalc\", effect = \"penNib\", value = 10, icon = \"4d38d721cc064d14b31b9e9a92754139\" },\n\tboot = { name = \"브론즈 부츠\", desc = \"5 미만 공격 피해가 5로\", hook = \"attackCalc\", effect = \"boot\", value = 5, icon = \"d572b3aa4dac4162aa0d9e551b055dce\" },\n\takabeko = { name = \"황소 투구\", desc = \"전투 첫 공격 피해 +8\", hook = \"attackCalc\", effect = \"akabeko\", value = 8, icon = \"eb3330a6e2274eff958639f8792119d3\" },\n\tcentennialPuzzle = { name = \"백년의 부적\", desc = \"전투 첫 피격 시 드로우 3\", hook = \"onPlayerDamaged\", effect = \"firstLossDraw\", value = 3, icon = \"cfe5ed6556b944fc83ab58b774bb2b73\" },\n\tmeatOnBone = { name = \"고기 망치\", desc = \"승리 시 HP 50% 이하면 12 회복\", hook = \"combatEnd\", effect = \"healIfLow\", value = 12, icon = \"a93e8e87f184411c98c96b877d9f8b10\" },\n\tselfFormingClay = { name = \"점토 갑옷\", desc = \"피해를 받으면 다음 턴 방어 +3\", hook = \"onPlayerDamaged\", effect = \"clayBlock\", value = 3, icon = \"bb446793c5204d5db7d33563fe79f648\" },\n\tchampionBelt = { name = \"챔피언 벨트\", desc = \"취약 부여 시 약화 1 추가\", hook = \"cardDebuff\", effect = \"vulnAddsWeak\", value = 1, icon = \"7ca8c63026034113a561d6adf679fed2\" },\n}\nself.RelicPool = { \"energyCore\", \"vampire\", \"goldIdol\", \"potionBelt\", \"burningBlood\", \"vajra\", \"anchor\", \"bagOfPrep\", \"bloodVial\", \"bronzeScales\", \"strawberry\", \"penNib\", \"boot\", \"akabeko\", \"centennialPuzzle\", \"meatOnBone\", \"selfFormingClay\", \"championBelt\" }\nself.Enemies = {\n\tslime = { name = \"슬라임\", maxHp = 45, intents = { { kind = \"Attack\", value = 10 }, { kind = \"Attack\", value = 6 }, { kind = \"Defend\", value = 8 } } },\n\tslime_elite = { name = \"정예 슬라임\", maxHp = 70, intents = { { kind = \"Attack\", value = 14 }, { kind = \"Attack\", value = 8 }, { kind = \"Defend\", value = 10 }, { kind = \"Debuff\", value = 1, effect = \"weak\" } } },\n\tslime_boss = { name = \"슬라임 킹\", maxHp = 120, intents = { { kind = \"Attack\", value = 18 }, { kind = \"Defend\", value = 12 }, { kind = \"Debuff\", value = 2, effect = \"vuln\" }, { kind = \"Attack\", value = 10 }, { kind = \"Attack\", value = 22 } } },\n\torange_mushroom = { name = \"주황버섯\", maxHp = 16, intents = { { kind = \"Attack\", value = 5 }, { kind = \"Attack\", value = 5 }, { kind = \"Defend\", value = 4 }, { kind = \"Attack\", value = 8 } } },\n\tblue_mushroom = { name = \"파란버섯\", maxHp = 22, intents = { { kind = \"Attack\", value = 4 }, { kind = \"Attack\", value = 4 }, { kind = \"Attack\", value = 10 } } },\n\tpig = { name = \"돼지\", maxHp = 18, intents = { { kind = \"Attack\", value = 6 }, { kind = \"Attack\", value = 6 }, { kind = \"Defend\", value = 5 } } },\n\tgreen_mushroom = { name = \"초록버섯\", maxHp = 20, intents = { { kind = \"Attack\", value = 7 }, { kind = \"Defend\", value = 3 }, { kind = \"Attack\", value = 9 } } },\n\tmushmom = { name = \"머쉬맘\", maxHp = 75, intents = { { kind = \"Defend\", value = 10 }, { kind = \"Debuff\", value = 2, effect = \"weak\" }, { kind = \"Attack\", value = 16 }, { kind = \"Attack\", value = 9 }, { kind = \"Defend\", value = 6 } } },\n\tmodified_snail = { name = \"변형된 달팽이\", maxHp = 60, intents = { { kind = \"Attack\", value = 12 }, { kind = \"Defend\", value = 8 }, { kind = \"Attack\", value = 7 }, { kind = \"Attack\", value = 14 }, { kind = \"Debuff\", value = 1, effect = \"weak\" } } },\n\tking_slime = { name = \"킹 슬라임\", maxHp = 130, intents = { { kind = \"Attack\", value = 18 }, { kind = \"Defend\", value = 14 }, { kind = \"Debuff\", value = 2, effect = \"vuln\" }, { kind = \"Attack\", value = 12 }, { kind = \"Attack\", value = 24 } } },\n}\nself.CurrentNodeId = \"\"\nself.CurrentEnemyId = \"\"\nself.PlayerJob = \"\"\nself.Jobs = {\n\twarrior = {\n\t\t{ id = \"fighter\", name = \"파이터\", desc = \"공격 특화\\n콤보 어택 · 버서크\\n라이징 어택\", starter = \"ComboAttack\" },\n\t\t{ id = \"page\", name = \"페이지\", desc = \"속성 차지 특화\\n썬더/블리자드 차지\\n파워 가드\", starter = \"ThunderCharge\" },\n\t\t{ id = \"spearman\", name = \"스피어맨\", desc = \"방어·관통 특화\\n피어스 · 아이언 월\\n하이퍼 바디\", starter = \"Pierce\" },\n\t},\n\tmagician = {\n\t\t{ id = \"firepoison\", name = \"위자드(불·독)\", desc = \"화염·독 특화\\n파이어 애로우\\n포이즌 브레스 · 앰플\", starter = \"FireArrow\" },\n\t\t{ id = \"icelightning\", name = \"위자드(썬·콜)\", desc = \"광역·빙결 특화\\n썬더 볼트(전체)\\n콜드 빔 · 칠링 스텝\", starter = \"ThunderBolt\" },\n\t\t{ id = \"cleric\", name = \"클레릭\", desc = \"회복·축복 특화\\n힐 · 블레스\\n홀리 애로우\", starter = \"Heal\" },\n\t},\n}\nself:GenerateMap()\nself:BindButtons()\nself:AddRelic(\"ironHeart\")\nself:RenderPotions()\nself:ShowMap()", + "Code": "if self.SelectedClass == \"magician\" then\n\tself.PlayerMaxHp = 70\n\tself.RunDeck = { \"EnergyBolt\", \"EnergyBolt\", \"EnergyBolt\", \"EnergyBolt\", \"EnergyBolt\", \"MagicGuard\", \"MagicGuard\", \"MagicGuard\", \"MagicGuard\", \"MagicClaw\" }\nelse\n\tself.PlayerMaxHp = 80\n\tself.RunDeck = { \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Defend\", \"Defend\", \"Defend\", \"Defend\", \"Bash\" }\nend\nself.PlayerMaxHp = self.PlayerMaxHp - self:AscStartHpPenalty()\nself.PlayerHp = self.PlayerMaxHp\nself.Gold = 0\nself.Floor = 1\nself.RunLength = 3\nself.RunActive = true\nself.RunRelics = {}\nself.RunPotions = {}\nself.PotionSlots = 3\nself.Potions = {\n\tredPotion = { name = \"빨간 포션\", desc = \"HP 20 회복\", effect = \"heal\", value = 20, icon = \"393e2a0d8da544899eaa8b22c97f832b\" },\n\tfirebomb = { name = \"화염병\", desc = \"적에게 피해 20\", effect = \"damage\", value = 20, icon = \"7ddb464c2574456289a4eb72ce86f193\" },\n\twarriorElixir = { name = \"전사의 물약\", desc = \"힘 +2\", effect = \"strength\", value = 2, icon = \"7cfbd410581e4073815daaf5f3e6c72f\" },\n\tguardPotion = { name = \"수호의 물약\", desc = \"방어도 +12\", effect = \"block\", value = 12, icon = \"8f8402dfa0f746e18bf606ed74302c0a\" },\n\tmanaElixir = { name = \"마나 엘릭서\", desc = \"에너지 +2\", effect = \"energy\", value = 2, icon = \"ec2778c366f6477ab0f8e7f06bcd73f4\" },\n\tcursedVial = { name = \"저주의 병\", desc = \"적에게 약화 3\", effect = \"weak\", value = 3, icon = \"a9a2763fdb6849dcba3028c737487680\" },\n}\nself.Relics = {\n\tironHeart = { name = \"강철 심장\", desc = \"전투 시작 시 방어도 +6\", hook = \"combatStart\", effect = \"block\", value = 6, icon = \"e555b3a62f3c49dbb2c53784e6bd481f\" },\n\tenergyCore = { name = \"에너지 코어\", desc = \"턴 시작 시 에너지 +1\", hook = \"turnStart\", effect = \"energy\", value = 1, icon = \"a41014f28b47434ab9f49ef104523862\" },\n\tvampire = { name = \"흡혈 송곳니\", desc = \"공격 카드 사용 시 HP +1\", hook = \"cardPlayed\", effect = \"healOnAttack\", value = 1, icon = \"ed64cde7e6c44b9e99502847e54f04e9\" },\n\tgoldIdol = { name = \"황금 우상\", desc = \"전투 승리 시 골드 +10\", hook = \"combatReward\", effect = \"gold\", value = 10, icon = \"03bb05c92b8f45edb0f3dad2e118fd5a\" },\n\tpotionBelt = { name = \"장인의 벨트\", desc = \"물약 슬롯이 5칸으로 늘어난다\", hook = \"passive\", effect = \"potionSlots\", value = 5, icon = \"36725b4566ac40d4902e2ab2113c2096\" },\n\tburningBlood = { name = \"자쿰의 투구\", desc = \"전투 승리 시 HP 6 회복\", hook = \"combatEnd\", effect = \"healOnWin\", value = 6, icon = \"07f994825ce34131b419d43e890c878d\" },\n\tvajra = { name = \"미스릴 해머\", desc = \"전투 시작 시 힘 +1\", hook = \"combatStart\", effect = \"strength\", value = 1, icon = \"59d2579d46dc41d590a9e6b141ad458b\" },\n\tanchor = { name = \"메이플 실드\", desc = \"첫 턴 방어도 +10\", hook = \"combatStart\", effect = \"block\", value = 10, icon = \"6349413e08cc49848862591863d056a0\" },\n\tbagOfPrep = { name = \"모험가의 배낭\", desc = \"첫 턴 드로우 +2\", hook = \"combatStart\", effect = \"draw\", value = 2, icon = \"77b240cb8af245b4801a714380267ae9\" },\n\tbloodVial = { name = \"피의 목걸이\", desc = \"전투 시작 시 HP 2 회복\", hook = \"combatStart\", effect = \"heal\", value = 2, icon = \"c782e949506a42c49eb139c7e65527d7\" },\n\tbronzeScales = { name = \"브론즈 체인메일\", desc = \"피격 시 공격자에게 3 반사\", hook = \"onPlayerDamaged\", effect = \"thorns\", value = 3, icon = \"87272346b145412391622cf803f888d1\" },\n\tstrawberry = { name = \"건강의 반지\", desc = \"획득 시 최대 HP +7\", hook = \"passive\", effect = \"maxHp\", value = 7, icon = \"58f643e29c354c2783a5ce9a72ec155c\" },\n\tpenNib = { name = \"황금 깃펜\", desc = \"10번째 공격마다 피해 2배\", hook = \"attackCalc\", effect = \"penNib\", value = 10, icon = \"4d38d721cc064d14b31b9e9a92754139\" },\n\tboot = { name = \"브론즈 부츠\", desc = \"5 미만 공격 피해가 5로\", hook = \"attackCalc\", effect = \"boot\", value = 5, icon = \"d572b3aa4dac4162aa0d9e551b055dce\" },\n\takabeko = { name = \"황소 투구\", desc = \"전투 첫 공격 피해 +8\", hook = \"attackCalc\", effect = \"akabeko\", value = 8, icon = \"eb3330a6e2274eff958639f8792119d3\" },\n\tcentennialPuzzle = { name = \"백년의 부적\", desc = \"전투 첫 피격 시 드로우 3\", hook = \"onPlayerDamaged\", effect = \"firstLossDraw\", value = 3, icon = \"cfe5ed6556b944fc83ab58b774bb2b73\" },\n\tmeatOnBone = { name = \"고기 망치\", desc = \"승리 시 HP 50% 이하면 12 회복\", hook = \"combatEnd\", effect = \"healIfLow\", value = 12, icon = \"a93e8e87f184411c98c96b877d9f8b10\" },\n\tselfFormingClay = { name = \"점토 갑옷\", desc = \"피해를 받으면 다음 턴 방어 +3\", hook = \"onPlayerDamaged\", effect = \"clayBlock\", value = 3, icon = \"bb446793c5204d5db7d33563fe79f648\" },\n\tchampionBelt = { name = \"챔피언 벨트\", desc = \"취약 부여 시 약화 1 추가\", hook = \"cardDebuff\", effect = \"vulnAddsWeak\", value = 1, icon = \"7ca8c63026034113a561d6adf679fed2\" },\n}\nself.RelicPool = { \"energyCore\", \"vampire\", \"goldIdol\", \"potionBelt\", \"burningBlood\", \"vajra\", \"anchor\", \"bagOfPrep\", \"bloodVial\", \"bronzeScales\", \"strawberry\", \"penNib\", \"boot\", \"akabeko\", \"centennialPuzzle\", \"meatOnBone\", \"selfFormingClay\", \"championBelt\" }\nself.Enemies = {\n\tslime = { name = \"슬라임\", maxHp = 45, intents = { { kind = \"Attack\", value = 10 }, { kind = \"Attack\", value = 6 }, { kind = \"Defend\", value = 8 } } },\n\tslime_elite = { name = \"정예 슬라임\", maxHp = 70, intents = { { kind = \"Attack\", value = 14 }, { kind = \"Attack\", value = 8 }, { kind = \"Defend\", value = 10 }, { kind = \"Debuff\", value = 1, effect = \"weak\" } } },\n\tslime_boss = { name = \"슬라임 킹\", maxHp = 120, intents = { { kind = \"Attack\", value = 18 }, { kind = \"Defend\", value = 12 }, { kind = \"Debuff\", value = 2, effect = \"vuln\" }, { kind = \"Attack\", value = 10 }, { kind = \"Attack\", value = 22 } } },\n\torange_mushroom = { name = \"주황버섯\", maxHp = 16, intents = { { kind = \"Attack\", value = 5 }, { kind = \"Attack\", value = 5 }, { kind = \"Defend\", value = 4 }, { kind = \"Attack\", value = 8 } } },\n\tblue_mushroom = { name = \"파란버섯\", maxHp = 22, intents = { { kind = \"Attack\", value = 4 }, { kind = \"Attack\", value = 4 }, { kind = \"Attack\", value = 10 } } },\n\tpig = { name = \"돼지\", maxHp = 18, intents = { { kind = \"Attack\", value = 6 }, { kind = \"Attack\", value = 6 }, { kind = \"Defend\", value = 5 } } },\n\tgreen_mushroom = { name = \"초록버섯\", maxHp = 20, intents = { { kind = \"Attack\", value = 7 }, { kind = \"Defend\", value = 3 }, { kind = \"Attack\", value = 9 } } },\n\tmushmom = { name = \"머쉬맘\", maxHp = 75, intents = { { kind = \"Defend\", value = 10 }, { kind = \"Debuff\", value = 2, effect = \"weak\" }, { kind = \"Attack\", value = 16 }, { kind = \"Attack\", value = 9 }, { kind = \"Defend\", value = 6 } } },\n\tmodified_snail = { name = \"변형된 달팽이\", maxHp = 60, intents = { { kind = \"Attack\", value = 12 }, { kind = \"Defend\", value = 8 }, { kind = \"Attack\", value = 7 }, { kind = \"Attack\", value = 14 }, { kind = \"Debuff\", value = 1, effect = \"weak\" } } },\n\tking_slime = { name = \"킹 슬라임\", maxHp = 130, intents = { { kind = \"Attack\", value = 18 }, { kind = \"Defend\", value = 14 }, { kind = \"Debuff\", value = 2, effect = \"vuln\" }, { kind = \"Attack\", value = 12 }, { kind = \"Attack\", value = 24 } } },\n}\nself.CurrentNodeId = \"\"\nself.CurrentEnemyId = \"\"\nself.PlayerJob = \"\"\nself.Jobs = {\n\twarrior = {\n\t\t{ id = \"fighter\", name = \"파이터\", desc = \"공격 특화\\n콤보 어택 · 버서크\\n라이징 어택\", starter = \"ComboAttack\" },\n\t\t{ id = \"page\", name = \"페이지\", desc = \"속성 차지 특화\\n썬더/블리자드 차지\\n파워 가드\", starter = \"ThunderCharge\" },\n\t\t{ id = \"spearman\", name = \"스피어맨\", desc = \"방어·관통 특화\\n피어스 · 아이언 월\\n하이퍼 바디\", starter = \"Pierce\" },\n\t},\n\tmagician = {\n\t\t{ id = \"firepoison\", name = \"위자드(불·독)\", desc = \"화염·독 특화\\n파이어 애로우\\n포이즌 브레스 · 앰플\", starter = \"FireArrow\" },\n\t\t{ id = \"icelightning\", name = \"위자드(썬·콜)\", desc = \"광역·빙결 특화\\n썬더 볼트(전체)\\n콜드 빔 · 칠링 스텝\", starter = \"ThunderBolt\" },\n\t\t{ id = \"cleric\", name = \"클레릭\", desc = \"회복·축복 특화\\n힐 · 블레스\\n홀리 애로우\", starter = \"Heal\" },\n\t},\n}\nself:GenerateMap()\nself:BindButtons()\nself:AddRelic(\"ironHeart\")\nself:RenderPotions()\nself:ShowMap()", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -771,7 +995,7 @@ "Name": null }, "Arguments": [], - "Code": "self.Monsters = {}\nlocal g = \"combat\"\nlocal node = self.MapNodes[self.CurrentNodeId]\nif node ~= nil and node.type ~= nil then g = node.type end\nlocal pmap = \"\"\nlocal lp = _UserService.LocalPlayer\nif lp ~= nil and lp.CurrentMapName ~= nil then pmap = lp.CurrentMapName end\nlocal reg = self.Registered or {}\nfor i = 1, #reg do\n\tif reg[i].entity ~= nil and isvalid(reg[i].entity) then\n\t\treg[i].entity:SetVisible(false)\n\tend\nend\nlocal list = {}\nfor i = 1, #reg do\n\tlocal r = reg[i]\n\tif r.entity ~= nil and isvalid(r.entity) and r.group == g and (r.map == nil or r.map == \"\" or pmap == \"\" or r.map == pmap) then\n\t\tlocal x = 0\n\t\tif r.entity.TransformComponent ~= nil then\n\t\t\tx = r.entity.TransformComponent.WorldPosition.x\n\t\tend\n\t\ttable.insert(list, { entity = r.entity, enemyId = r.enemyId, x = x })\n\tend\nend\ntable.sort(list, function(a, b) return a.x < b.x end)\nlocal mult = 1 + (self.Floor - 1) * 0.6\nlocal n = #list\nif n > 4 then n = 4 end\nfor i = 1, n do\n\tlocal item = list[i]\n\tlocal e = self.Enemies[item.enemyId]\n\tif e == nil then e = { name = item.enemyId, maxHp = 10, intents = { { kind = \"Attack\", value = 5 } } } end\n\tlocal intents = {}\n\tfor k = 1, #e.intents do\n\t\tlocal v = e.intents[k].value\n\t\tif e.intents[k].kind ~= \"Debuff\" then\n\t\t\tv = math.floor(v * mult)\n\t\tend\n\t\tintents[k] = { kind = e.intents[k].kind, value = v, effect = e.intents[k].effect }\n\tend\n\tlocal maxHp = math.floor(e.maxHp * mult)\n\tself.Monsters[i] = { entity = item.entity, enemyId = item.enemyId, name = e.name,\n\t\thp = maxHp, maxHp = maxHp, block = 0, str = 0, weak = 0, vuln = 0, poison = 0,\n\t\tintents = intents, intentIdx = 1, alive = true, slot = i }\n\tself:ReviveMonsterEntity(item.entity)\n\tself:PositionMonsterSlot(i)\nend\nself.TargetIndex = 1", + "Code": "self.Monsters = {}\nlocal g = \"combat\"\nlocal node = self.MapNodes[self.CurrentNodeId]\nif node ~= nil and node.type ~= nil then g = node.type end\nlocal pmap = \"\"\nlocal lp = _UserService.LocalPlayer\nif lp ~= nil and lp.CurrentMapName ~= nil then pmap = lp.CurrentMapName end\nlocal reg = self.Registered or {}\nfor i = 1, #reg do\n\tif reg[i].entity ~= nil and isvalid(reg[i].entity) then\n\t\treg[i].entity:SetVisible(false)\n\tend\nend\nlocal list = {}\nfor i = 1, #reg do\n\tlocal r = reg[i]\n\tif r.entity ~= nil and isvalid(r.entity) and r.group == g and (r.map == nil or r.map == \"\" or pmap == \"\" or r.map == pmap) then\n\t\tlocal x = 0\n\t\tif r.entity.TransformComponent ~= nil then\n\t\t\tx = r.entity.TransformComponent.WorldPosition.x\n\t\tend\n\t\ttable.insert(list, { entity = r.entity, enemyId = r.enemyId, x = x })\n\tend\nend\ntable.sort(list, function(a, b) return a.x < b.x end)\nlocal mult = 1 + (self.Floor - 1) * 0.6\nif g == \"elite\" or g == \"boss\" then\n\tmult = mult + self:AscEliteBonus()\nend\nlocal n = #list\nif n > 4 then n = 4 end\nfor i = 1, n do\n\tlocal item = list[i]\n\tlocal e = self.Enemies[item.enemyId]\n\tif e == nil then e = { name = item.enemyId, maxHp = 10, intents = { { kind = \"Attack\", value = 5 } } } end\n\tlocal intents = {}\n\tfor k = 1, #e.intents do\n\t\tlocal v = e.intents[k].value\n\t\tif e.intents[k].kind == \"Attack\" then\n\t\t\tv = math.floor(v * mult * self:AscAtkMult())\n\t\telseif e.intents[k].kind ~= \"Debuff\" then\n\t\t\tv = math.floor(v * mult)\n\t\tend\n\t\tintents[k] = { kind = e.intents[k].kind, value = v, effect = e.intents[k].effect }\n\tend\n\tlocal maxHp = math.floor(e.maxHp * mult * self:AscHpMult())\n\tself.Monsters[i] = { entity = item.entity, enemyId = item.enemyId, name = e.name,\n\t\thp = maxHp, maxHp = maxHp, block = 0, str = 0, weak = 0, vuln = 0, poison = 0,\n\t\tintents = intents, intentIdx = 1, alive = true, slot = i }\n\tself:ReviveMonsterEntity(item.entity)\n\tself:PositionMonsterSlot(i)\nend\nself.TargetIndex = 1", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -1652,7 +1876,7 @@ "Name": null }, "Arguments": [], - "Code": "local anyAlive = false\nfor i = 1, #self.Monsters do\n\tif self.Monsters[i].alive == true then anyAlive = true; break end\nend\nif anyAlive == false then\n\tself.CombatOver = true\n\tself.Gold = self.Gold + 25\n\tself:ApplyRelics(\"combatEnd\")\n\tself:ApplyRelics(\"combatReward\")\n\tself:MaybeDropPotion()\n\tself:RenderRun()\n\tlocal node = self.MapNodes[self.CurrentNodeId]\n\tif node ~= nil and node.type == \"elite\" then\n\t\tself.Gold = self.Gold + 15\n\t\tlocal nid = self:PickNewRelic()\n\t\tif nid ~= \"\" then\n\t\t\tself:AddRelic(nid)\n\t\t\tlocal nr = self.Relics[nid]\n\t\t\tif nr ~= nil then\n\t\t\t\tself:Toast(\"유물 획득: \" .. nr.name)\n\t\t\tend\n\t\tend\n\tend\n\tif node ~= nil and node.type == \"boss\" then\n\t\tif self.PlayerJob == \"\" and self.Floor < self.RunLength then\n\t\t\tself:ShowJobChoice()\n\t\telse\n\t\t\tlocal bid = self:PickNewRelic()\n\t\t\tif bid ~= \"\" then\n\t\t\t\tself:AddRelic(bid)\n\t\t\t\tlocal br = self.Relics[bid]\n\t\t\t\tif br ~= nil then\n\t\t\t\t\tself:Toast(\"유물 획득: \" .. br.name)\n\t\t\t\tend\n\t\t\tend\n\t\t\tself:ContinueAfterBoss()\n\t\tend\n\telse\n\t\tself:OfferReward()\n\tend\nelseif self.PlayerHp <= 0 then\n\tself.CombatOver = true\n\tself:EndRun(\"패배...\")\nend", + "Code": "local anyAlive = false\nfor i = 1, #self.Monsters do\n\tif self.Monsters[i].alive == true then anyAlive = true; break end\nend\nif anyAlive == false then\n\tself.CombatOver = true\n\tself.Gold = self.Gold + math.floor(25 * self:AscGoldMult())\n\tself:ApplyRelics(\"combatEnd\")\n\tself:ApplyRelics(\"combatReward\")\n\tself:MaybeDropPotion()\n\tself:RenderRun()\n\tlocal node = self.MapNodes[self.CurrentNodeId]\n\tif node ~= nil and node.type == \"elite\" then\n\t\tself.Gold = self.Gold + 15\n\t\tlocal nid = self:PickNewRelic()\n\t\tif nid ~= \"\" then\n\t\t\tself:AddRelic(nid)\n\t\t\tlocal nr = self.Relics[nid]\n\t\t\tif nr ~= nil then\n\t\t\t\tself:Toast(\"유물 획득: \" .. nr.name)\n\t\t\tend\n\t\tend\n\tend\n\tif node ~= nil and node.type == \"boss\" then\n\t\tif self.PlayerJob == \"\" and self.Floor < self.RunLength then\n\t\t\tself:ShowJobChoice()\n\t\telse\n\t\t\tlocal bid = self:PickNewRelic()\n\t\t\tif bid ~= \"\" then\n\t\t\t\tself:AddRelic(bid)\n\t\t\t\tlocal br = self.Relics[bid]\n\t\t\t\tif br ~= nil then\n\t\t\t\t\tself:Toast(\"유물 획득: \" .. br.name)\n\t\t\t\tend\n\t\t\tend\n\t\t\tself:ContinueAfterBoss()\n\t\tend\n\telse\n\t\tself:OfferReward()\n\tend\nelseif self.PlayerHp <= 0 then\n\tself.CombatOver = true\n\tself:EndRun(\"패배...\")\nend", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -1819,7 +2043,7 @@ "Name": "text" } ], - "Code": "self:ShowResult(text)\nself.RunActive = false\n_TimerService:SetTimerOnce(function() self:ShowMainMenu() end, 4)", + "Code": "local msg = text\nif text == \"런 클리어!\" and self.AscensionLevel >= self.AscensionUnlocked and self.AscensionUnlocked < 10 then\n\tself.AscensionUnlocked = self.AscensionUnlocked + 1\n\tlocal lp = _UserService.LocalPlayer\n\tif lp ~= nil then\n\t\tself:SaveAscension(self.AscensionUnlocked, lp.UserId)\n\tend\n\tself:RenderAscension()\n\tmsg = \"런 클리어! 승천 \" .. string.format(\"%d\", self.AscensionUnlocked) .. \" 해금!\"\nend\nself:ShowResult(msg)\nself.RunActive = false\n_TimerService:SetTimerOnce(function() self:ShowMainMenu() end, 4)", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -2036,7 +2260,7 @@ "Name": null }, "Arguments": [], - "Code": "self:SetText(\"/ui/DefaultGroup/CombatHud/TopBar/Floor\", \"막 \" .. string.format(\"%d\", self.Floor) .. \"/\" .. string.format(\"%d\", self.RunLength) .. \" · \" .. string.format(\"%d\", self.Depth) .. \"층\")\nself:SetText(\"/ui/DefaultGroup/CombatHud/TopBar/Gold\", \"메소 \" .. string.format(\"%d\", self.Gold))", + "Code": "local floorText = \"막 \" .. string.format(\"%d\", self.Floor) .. \"/\" .. string.format(\"%d\", self.RunLength) .. \" · \" .. string.format(\"%d\", self.Depth) .. \"층\"\nif self.AscensionLevel > 0 then\n\tfloorText = floorText .. \" · 승천\" .. string.format(\"%d\", self.AscensionLevel)\nend\nself:SetText(\"/ui/DefaultGroup/CombatHud/TopBar/Floor\", floorText)\nself:SetText(\"/ui/DefaultGroup/CombatHud/TopBar/Gold\", \"메소 \" .. string.format(\"%d\", self.Gold))", "Scope": 2, "ExecSpace": 6, "Attributes": [], diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index a94c85c..a694e42 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -2110,6 +2110,48 @@ function upsertUi() { text({ value: '새 게임', fontSize: 30, bold: true, color: GOLD, alignment: 0 }), ], })); + // 승천 선택 (P11): [-] 라벨 [+] + menu.push(entity({ + id: guid('menu', 190), + path: '/ui/DefaultGroup/MainMenu/AscMinus', + modelId: 'uibutton', + entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', + displayOrder: 5, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 52, y: 52 }, pos: { x: -170, y: -185 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.13, g: 0.15, b: 0.18, a: 1 }, type: 1, raycast: true }), + button(), + text({ value: '-', fontSize: 30, bold: true, color: GOLD, alignment: 4 }), + ], + })); + menu.push(entity({ + id: guid('menu', 191), + path: '/ui/DefaultGroup/MainMenu/AscLabel', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 6, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 250, y: 40 }, pos: { x: 0, y: -185 }, align: ALIGN_CENTER }), + sprite({ color: TRANSPARENT }), + text({ value: '승천 0 / 해금 0', fontSize: 22, bold: true, color: { r: 0.85, g: 0.7, b: 0.95, a: 1 }, alignment: 4 }), + ], + })); + menu.push(entity({ + id: guid('menu', 192), + path: '/ui/DefaultGroup/MainMenu/AscPlus', + modelId: 'uibutton', + entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', + displayOrder: 7, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 52, y: 52 }, pos: { x: 170, y: -185 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.13, g: 0.15, b: 0.18, a: 1 }, type: 1, raycast: true }), + button(), + text({ value: '+', fontSize: 30, bold: true, color: GOLD, alignment: 4 }), + ], + })); menu.push(entity({ id: guid('menu', 4), path: '/ui/DefaultGroup/MainMenu/ContinueButton', @@ -2360,8 +2402,12 @@ function writeCodeblocks() { prop('any', 'NewGameHandler'), prop('any', 'WarriorSelectHandler'), prop('any', 'MageSelectHandler'), + prop('any', 'AscMinusHandler'), + prop('any', 'AscPlusHandler'), prop('any', 'JobOpts'), prop('any', 'Jobs'), + prop('number', 'AscensionLevel', '0'), + prop('number', 'AscensionUnlocked', '0'), prop('any', 'StartGameHandler'), prop('string', 'SelectedClass', '""'), prop('any', 'DrawPileHandler'), @@ -2418,7 +2464,57 @@ function writeCodeblocks() { prop('boolean', 'ChestOpened', 'false'), prop('string', 'PlayerJob', '""'), ], [ - method('OnBeginPlay', `self:ShowMainMenu()`), + method('OnBeginPlay', `self:ShowMainMenu() +local lp = _UserService.LocalPlayer +if lp ~= nil then + self:ReqLoadAscension(lp.UserId) +end`), + method('ReqLoadAscension', `local ds = _DataStorageService:GetUserDataStorage(userId) +local errCode, value = ds:GetAndWait("ascensionUnlocked") +local n = 0 +if errCode == 0 and value ~= nil and value ~= "" then + n = tonumber(value) or 0 +end +self:RecvAscension(n, userId)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'userId' }], 1), + method('RecvAscension', `self.AscensionUnlocked = n +if self.AscensionLevel > self.AscensionUnlocked then + self.AscensionLevel = self.AscensionUnlocked +end +self:RenderAscension()`, [ + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'n' }, + { Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'userId' }, + ], 2), + method('SaveAscension', `local ds = _DataStorageService:GetUserDataStorage(userId) +ds:SetAndWait("ascensionUnlocked", tostring(n))`, [ + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'n' }, + { Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'userId' }, + ], 1), + method('AdjustAscension', `local v = self.AscensionLevel + delta +if v < 0 then v = 0 end +if v > self.AscensionUnlocked then v = self.AscensionUnlocked end +self.AscensionLevel = v +self:RenderAscension()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'delta' }]), + method('RenderAscension', `self:SetText("/ui/DefaultGroup/MainMenu/AscLabel", "승천 " .. string.format("%d", self.AscensionLevel) .. " / 해금 " .. string.format("%d", self.AscensionUnlocked))`), + method('AscHpMult', `local m = 1 +if self.AscensionLevel >= 1 then m = m + 0.1 end +if self.AscensionLevel >= 6 then m = m + 0.1 end +return m`, [], 0, 'number'), + method('AscAtkMult', `local m = 1 +if self.AscensionLevel >= 2 then m = m + 0.1 end +if self.AscensionLevel >= 7 then m = m + 0.1 end +return m`, [], 0, 'number'), + method('AscEliteBonus', `local b = 0 +if self.AscensionLevel >= 4 then b = b + 0.2 end +if self.AscensionLevel >= 9 then b = b + 0.2 end +return b`, [], 0, 'number'), + method('AscGoldMult', `local m = 1 +if self.AscensionLevel >= 5 then m = m - 0.25 end +if self.AscensionLevel >= 10 then m = m - 0.25 end +return m`, [], 0, 'number'), + method('AscStartHpPenalty', `local p = 0 +if self.AscensionLevel >= 3 then p = p + 10 end +if self.AscensionLevel >= 8 then p = p + 10 end +return p`, [], 0, 'number'), method('HideGameHud', `self:SetEntityEnabled("/ui/DefaultGroup/Button_Attack", false) self:SetEntityEnabled("/ui/DefaultGroup/Button_Jump", false) self:SetEntityEnabled("/ui/DefaultGroup/UIJoystick", false) @@ -2451,6 +2547,7 @@ elseif state == "treasure" then self:SetEntityEnabled("/ui/DefaultGroup/TreasureHud", true) end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'state' }]), method('ShowMainMenu', `self.SelectedClass = "" +self:RenderAscension() self:ShowState("menu") self:SetText("/ui/DefaultGroup/MainMenu/Title", "메이플 덱 어드벤처") self:SetText("/ui/DefaultGroup/MainMenu/Subtitle", "캐릭터를 고르고 덱을 만들어 모험을 시작하세요") @@ -2487,6 +2584,22 @@ if start ~= nil and start.ButtonComponent ~= nil then self.StartGameHandler = nil end self.StartGameHandler = start:ConnectEvent(ButtonClickEvent, function() self:StartNewGame() end) +end +local ascMinus = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/AscMinus") +if ascMinus ~= nil and ascMinus.ButtonComponent ~= nil then + if self.AscMinusHandler ~= nil then + ascMinus:DisconnectEvent(ButtonClickEvent, self.AscMinusHandler) + self.AscMinusHandler = nil + end + self.AscMinusHandler = ascMinus:ConnectEvent(ButtonClickEvent, function() self:AdjustAscension(-1) end) +end +local ascPlus = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/AscPlus") +if ascPlus ~= nil and ascPlus.ButtonComponent ~= nil then + if self.AscPlusHandler ~= nil then + ascPlus:DisconnectEvent(ButtonClickEvent, self.AscPlusHandler) + self.AscPlusHandler = nil + end + self.AscPlusHandler = ascPlus:ConnectEvent(ButtonClickEvent, function() self:AdjustAscension(1) end) end`), method('ShowCharacterSelect', `self.SelectedClass = "" self:ShowState("charselect") @@ -2537,6 +2650,7 @@ else self.PlayerMaxHp = ${CLASSES.warrior.maxHp} self.RunDeck = { ${CARDS.starterDecks.warrior.map(luaStr).join(', ')} } end +self.PlayerMaxHp = self.PlayerMaxHp - self:AscStartHpPenalty() self.PlayerHp = self.PlayerMaxHp self.Gold = 0 self.Floor = 1 @@ -2626,6 +2740,9 @@ for i = 1, #reg do end table.sort(list, function(a, b) return a.x < b.x end) local mult = 1 + (self.Floor - 1) * 0.6 +if g == "elite" or g == "boss" then + mult = mult + self:AscEliteBonus() +end local n = #list if n > ${MAX_MONSTERS} then n = ${MAX_MONSTERS} end for i = 1, n do @@ -2635,12 +2752,14 @@ for i = 1, n do local intents = {} for k = 1, #e.intents do local v = e.intents[k].value - if e.intents[k].kind ~= "Debuff" then + if e.intents[k].kind == "Attack" then + v = math.floor(v * mult * self:AscAtkMult()) + elseif e.intents[k].kind ~= "Debuff" then v = math.floor(v * mult) end intents[k] = { kind = e.intents[k].kind, value = v, effect = e.intents[k].effect } end - local maxHp = math.floor(e.maxHp * mult) + local maxHp = math.floor(e.maxHp * mult * self:AscHpMult()) self.Monsters[i] = { entity = item.entity, enemyId = item.enemyId, name = e.name, hp = maxHp, maxHp = maxHp, block = 0, str = 0, weak = 0, vuln = 0, poison = 0, intents = intents, intentIdx = 1, alive = true, slot = i } @@ -3476,7 +3595,7 @@ for i = 1, #self.Monsters do end if anyAlive == false then self.CombatOver = true - self.Gold = self.Gold + ${GOLD_PER_WIN} + self.Gold = self.Gold + math.floor(${GOLD_PER_WIN} * self:AscGoldMult()) self:ApplyRelics("combatEnd") self:ApplyRelics("combatReward") self:MaybeDropPotion() @@ -3614,7 +3733,17 @@ local entity = _EntityService:GetEntityByPath("/ui/DefaultGroup/CombatHud/Result if entity ~= nil then entity.Enable = true end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'text' }]), - method('EndRun', `self:ShowResult(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.UserId) + end + self:RenderAscension() + msg = "런 클리어! 승천 " .. string.format("%d", self.AscensionUnlocked) .. " 해금!" +end +self:ShowResult(msg) self.RunActive = false _TimerService:SetTimerOnce(function() self:ShowMainMenu() end, 4)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'text' }]), method('BuffsLabel', `local parts = {} @@ -3733,7 +3862,11 @@ end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], N self.TargetIndex = slot self:RenderCombat() end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]), - method('RenderRun', `self:SetText("/ui/DefaultGroup/CombatHud/TopBar/Floor", "막 " .. string.format("%d", self.Floor) .. "/" .. string.format("%d", self.RunLength) .. " · " .. string.format("%d", self.Depth) .. "층") + method('RenderRun', `local floorText = "막 " .. string.format("%d", self.Floor) .. "/" .. string.format("%d", self.RunLength) .. " · " .. string.format("%d", self.Depth) .. "층" +if self.AscensionLevel > 0 then + floorText = floorText .. " · 승천" .. string.format("%d", self.AscensionLevel) +end +self:SetText("/ui/DefaultGroup/CombatHud/TopBar/Floor", floorText) self:SetText("/ui/DefaultGroup/CombatHud/TopBar/Gold", "메소 " .. string.format("%d", self.Gold))`), method('CardPool', `local pool = {} for id, c in pairs(self.Cards) do @@ -4409,7 +4542,7 @@ _TimerService:SetTimerOnce(function() end, 0.55)`), ]); for (const m of combat.ContentProto.Json.Methods) { - m.ExecSpace = 6; + if (m.ExecSpace === 0) m.ExecSpace = 6; // 기본은 ClientOnly(6), 서버 RPC(Server=1·Client=2) 명시값은 보존 } writeFileSync('RootDesk/MyDesk/SlayDeckController.codeblock', JSON.stringify(combat, null, 2), 'utf8'); } diff --git a/ui/DefaultGroup.ui b/ui/DefaultGroup.ui index d93ab2a..6cf4009 100644 --- a/ui/DefaultGroup.ui +++ b/ui/DefaultGroup.ui @@ -287218,6 +287218,664 @@ "@version": 1 } }, + { + "id": "0e0000be-0000-4000-8000-00000e0000be", + "path": "/ui/DefaultGroup/MainMenu/AscMinus", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "AscMinus", + "path": "/ui/DefaultGroup/MainMenu/AscMinus", + "nameEditable": true, + "enable": true, + "visible": true, + "localize": true, + "displayOrder": 5, + "pathConstraints": "////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UIButton", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uibutton", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": -144, + "y": -159 + }, + "OffsetMin": { + "x": -196, + "y": -211 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 52, + "y": 52 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": -170, + "y": -185 + }, + "Position": { + "x": -170, + "y": -185, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0.13, + "g": 0.15, + "b": 0.18, + "a": 1 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": true, + "Type": 1, + "Enable": true + }, + { + "@type": "MOD.Core.ButtonComponent", + "Colors": { + "NormalColor": { + "r": 1, + "g": 1, + "b": 1, + "a": 1 + }, + "HighlightedColor": { + "r": 0.9607843, + "g": 0.9607843, + "b": 0.9607843, + "a": 1 + }, + "PressedColor": { + "r": 0.784313738, + "g": 0.784313738, + "b": 0.784313738, + "a": 1 + }, + "SelectedColor": { + "r": 0.9607843, + "g": 0.9607843, + "b": 0.9607843, + "a": 1 + }, + "DisabledColor": { + "r": 0.784313738, + "g": 0.784313738, + "b": 0.784313738, + "a": 0.5019608 + }, + "ColorMultiplier": 1, + "FadeDuration": 0.1 + }, + "ImageRUIDs": { + "HighlightedSprite": null, + "PressedSprite": null, + "SelectedSprite": null, + "DisabledSprite": null + }, + "KeyCode": 0, + "OverrideSorting": false, + "Transition": 1, + "Enable": true + }, + { + "@type": "MOD.Core.TextComponent", + "Alignment": 4, + "Bold": true, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "Font": 0, + "FontColor": { + "r": 0.94, + "g": 0.74, + "b": 0.26, + "a": 1 + }, + "FontSize": 30, + "MaxSize": 30, + "MinSize": 8, + "OutlineColor": { + "r": 0.08, + "g": 0.08, + "b": 0.08, + "a": 1 + }, + "OutlineDistance": { + "x": 1, + "y": -1 + }, + "OutlineWidth": 1, + "Overflow": 0, + "OverrideSorting": false, + "Padding": { + "left": 0, + "right": 0, + "top": 0, + "bottom": 0 + }, + "SizeFit": false, + "Text": "-", + "UseOutLine": true, + "Enable": true + } + ], + "@version": 1 + } + }, + { + "id": "0e0000bf-0000-4000-8000-00000e0000bf", + "path": "/ui/DefaultGroup/MainMenu/AscLabel", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "AscLabel", + "path": "/ui/DefaultGroup/MainMenu/AscLabel", + "nameEditable": true, + "enable": true, + "visible": true, + "localize": true, + "displayOrder": 6, + "pathConstraints": "////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UIText", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uitext", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": 125, + "y": -165 + }, + "OffsetMin": { + "x": -125, + "y": -205 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 250, + "y": 40 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 0, + "y": -185 + }, + "Position": { + "x": 0, + "y": -185, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0, + "g": 0, + "b": 0, + "a": 0 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": false, + "Type": 1, + "Enable": true + }, + { + "@type": "MOD.Core.TextComponent", + "Alignment": 4, + "Bold": true, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "Font": 0, + "FontColor": { + "r": 0.85, + "g": 0.7, + "b": 0.95, + "a": 1 + }, + "FontSize": 22, + "MaxSize": 22, + "MinSize": 8, + "OutlineColor": { + "r": 0.08, + "g": 0.08, + "b": 0.08, + "a": 1 + }, + "OutlineDistance": { + "x": 1, + "y": -1 + }, + "OutlineWidth": 1, + "Overflow": 0, + "OverrideSorting": false, + "Padding": { + "left": 0, + "right": 0, + "top": 0, + "bottom": 0 + }, + "SizeFit": false, + "Text": "승천 0 / 해금 0", + "UseOutLine": true, + "Enable": true + } + ], + "@version": 1 + } + }, + { + "id": "0e0000c0-0000-4000-8000-00000e0000c0", + "path": "/ui/DefaultGroup/MainMenu/AscPlus", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "AscPlus", + "path": "/ui/DefaultGroup/MainMenu/AscPlus", + "nameEditable": true, + "enable": true, + "visible": true, + "localize": true, + "displayOrder": 7, + "pathConstraints": "////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UIButton", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uibutton", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": 196, + "y": -159 + }, + "OffsetMin": { + "x": 144, + "y": -211 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 52, + "y": 52 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 170, + "y": -185 + }, + "Position": { + "x": 170, + "y": -185, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0.13, + "g": 0.15, + "b": 0.18, + "a": 1 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": true, + "Type": 1, + "Enable": true + }, + { + "@type": "MOD.Core.ButtonComponent", + "Colors": { + "NormalColor": { + "r": 1, + "g": 1, + "b": 1, + "a": 1 + }, + "HighlightedColor": { + "r": 0.9607843, + "g": 0.9607843, + "b": 0.9607843, + "a": 1 + }, + "PressedColor": { + "r": 0.784313738, + "g": 0.784313738, + "b": 0.784313738, + "a": 1 + }, + "SelectedColor": { + "r": 0.9607843, + "g": 0.9607843, + "b": 0.9607843, + "a": 1 + }, + "DisabledColor": { + "r": 0.784313738, + "g": 0.784313738, + "b": 0.784313738, + "a": 0.5019608 + }, + "ColorMultiplier": 1, + "FadeDuration": 0.1 + }, + "ImageRUIDs": { + "HighlightedSprite": null, + "PressedSprite": null, + "SelectedSprite": null, + "DisabledSprite": null + }, + "KeyCode": 0, + "OverrideSorting": false, + "Transition": 1, + "Enable": true + }, + { + "@type": "MOD.Core.TextComponent", + "Alignment": 4, + "Bold": true, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "Font": 0, + "FontColor": { + "r": 0.94, + "g": 0.74, + "b": 0.26, + "a": 1 + }, + "FontSize": 30, + "MaxSize": 30, + "MinSize": 8, + "OutlineColor": { + "r": 0.08, + "g": 0.08, + "b": 0.08, + "a": 1 + }, + "OutlineDistance": { + "x": 1, + "y": -1 + }, + "OutlineWidth": 1, + "Overflow": 0, + "OverrideSorting": false, + "Padding": { + "left": 0, + "right": 0, + "top": 0, + "bottom": 0 + }, + "SizeFit": false, + "Text": "+", + "UseOutLine": true, + "Enable": true + } + ], + "@version": 1 + } + }, { "id": "0e000004-0000-4000-8000-00000e000004", "path": "/ui/DefaultGroup/MainMenu/ContinueButton", From 2c28935d95568d74bf5ff20e0c2133ed6b2d1516 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 12 Jun 2026 14:11:48 +0900 Subject: [PATCH 3/5] =?UTF-8?q?fix(ascension):=20UserId=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=EC=9D=84=20PlayerComponent.UserId=EB=A1=9C=20(?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EA=B2=BD=EA=B3=A0=202=EA=B1=B4=20?= =?UTF-8?q?=ED=95=B4=EC=86=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- RootDesk/MyDesk/SlayDeckController.codeblock | 4 ++-- tools/deck/gen-slaydeck.mjs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/RootDesk/MyDesk/SlayDeckController.codeblock b/RootDesk/MyDesk/SlayDeckController.codeblock index d4263f7..82f3aa0 100644 --- a/RootDesk/MyDesk/SlayDeckController.codeblock +++ b/RootDesk/MyDesk/SlayDeckController.codeblock @@ -544,7 +544,7 @@ "Name": null }, "Arguments": [], - "Code": "self:ShowMainMenu()\nlocal lp = _UserService.LocalPlayer\nif lp ~= nil then\n\tself:ReqLoadAscension(lp.UserId)\nend", + "Code": "self:ShowMainMenu()\nlocal lp = _UserService.LocalPlayer\nif lp ~= nil then\n\tself:ReqLoadAscension(lp.PlayerComponent.UserId)\nend", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -2043,7 +2043,7 @@ "Name": "text" } ], - "Code": "local msg = text\nif text == \"런 클리어!\" and self.AscensionLevel >= self.AscensionUnlocked and self.AscensionUnlocked < 10 then\n\tself.AscensionUnlocked = self.AscensionUnlocked + 1\n\tlocal lp = _UserService.LocalPlayer\n\tif lp ~= nil then\n\t\tself:SaveAscension(self.AscensionUnlocked, lp.UserId)\n\tend\n\tself:RenderAscension()\n\tmsg = \"런 클리어! 승천 \" .. string.format(\"%d\", self.AscensionUnlocked) .. \" 해금!\"\nend\nself:ShowResult(msg)\nself.RunActive = false\n_TimerService:SetTimerOnce(function() self:ShowMainMenu() end, 4)", + "Code": "local msg = text\nif text == \"런 클리어!\" and self.AscensionLevel >= self.AscensionUnlocked and self.AscensionUnlocked < 10 then\n\tself.AscensionUnlocked = self.AscensionUnlocked + 1\n\tlocal lp = _UserService.LocalPlayer\n\tif lp ~= nil then\n\t\tself:SaveAscension(self.AscensionUnlocked, lp.PlayerComponent.UserId)\n\tend\n\tself:RenderAscension()\n\tmsg = \"런 클리어! 승천 \" .. string.format(\"%d\", self.AscensionUnlocked) .. \" 해금!\"\nend\nself:ShowResult(msg)\nself.RunActive = false\n_TimerService:SetTimerOnce(function() self:ShowMainMenu() end, 4)", "Scope": 2, "ExecSpace": 6, "Attributes": [], diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index a694e42..b6490f1 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -2467,7 +2467,7 @@ function writeCodeblocks() { method('OnBeginPlay', `self:ShowMainMenu() local lp = _UserService.LocalPlayer if lp ~= nil then - self:ReqLoadAscension(lp.UserId) + self:ReqLoadAscension(lp.PlayerComponent.UserId) end`), method('ReqLoadAscension', `local ds = _DataStorageService:GetUserDataStorage(userId) local errCode, value = ds:GetAndWait("ascensionUnlocked") @@ -3738,7 +3738,7 @@ if text == "런 클리어!" and self.AscensionLevel >= self.AscensionUnlocked an self.AscensionUnlocked = self.AscensionUnlocked + 1 local lp = _UserService.LocalPlayer if lp ~= nil then - self:SaveAscension(self.AscensionUnlocked, lp.UserId) + self:SaveAscension(self.AscensionUnlocked, lp.PlayerComponent.UserId) end self:RenderAscension() msg = "런 클리어! 승천 " .. string.format("%d", self.AscensionUnlocked) .. " 해금!" From 9989a6167516478e4ebb93951568ce7f177791c4 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 12 Jun 2026 14:20:05 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix(ascension):=20RPC=20ExecSpace=20?= =?UTF-8?q?=EC=8B=A4=EC=B8=A1=20=EB=B3=B4=EC=A0=95=20=E2=80=94=20Server=3D?= =?UTF-8?q?5=C2=B7Client=3D6=20(=ED=94=84=EB=A1=9C=EB=B8=8C=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 1은 ServerOnly(클라 호출 무시)라 저장/로드 RPC 미동작 → 5로 수정 - RecvAscension 6(Client): 서버→특정 클라 userId 라우팅 실측 확인 - 설계 문서 ExecSpace 표 갱신, 프로브 메서드 제거 Co-Authored-By: Claude Opus 4.8 (1M context) --- RootDesk/MyDesk/SlayDeckController.codeblock | 6 +++--- docs/superpowers/specs/2026-06-12-ascension-design.md | 8 ++++---- tools/deck/gen-slaydeck.mjs | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/RootDesk/MyDesk/SlayDeckController.codeblock b/RootDesk/MyDesk/SlayDeckController.codeblock index 82f3aa0..6b3b506 100644 --- a/RootDesk/MyDesk/SlayDeckController.codeblock +++ b/RootDesk/MyDesk/SlayDeckController.codeblock @@ -569,7 +569,7 @@ ], "Code": "local ds = _DataStorageService:GetUserDataStorage(userId)\nlocal errCode, value = ds:GetAndWait(\"ascensionUnlocked\")\nlocal n = 0\nif errCode == 0 and value ~= nil and value ~= \"\" then\n\tn = tonumber(value) or 0\nend\nself:RecvAscension(n, userId)", "Scope": 2, - "ExecSpace": 1, + "ExecSpace": 5, "Attributes": [], "Name": "ReqLoadAscension" }, @@ -599,7 +599,7 @@ ], "Code": "self.AscensionUnlocked = n\nif self.AscensionLevel > self.AscensionUnlocked then\n\tself.AscensionLevel = self.AscensionUnlocked\nend\nself:RenderAscension()", "Scope": 2, - "ExecSpace": 2, + "ExecSpace": 6, "Attributes": [], "Name": "RecvAscension" }, @@ -629,7 +629,7 @@ ], "Code": "local ds = _DataStorageService:GetUserDataStorage(userId)\nds:SetAndWait(\"ascensionUnlocked\", tostring(n))", "Scope": 2, - "ExecSpace": 1, + "ExecSpace": 5, "Attributes": [], "Name": "SaveAscension" }, diff --git a/docs/superpowers/specs/2026-06-12-ascension-design.md b/docs/superpowers/specs/2026-06-12-ascension-design.md index 33d900a..95ab769 100644 --- a/docs/superpowers/specs/2026-06-12-ascension-design.md +++ b/docs/superpowers/specs/2026-06-12-ascension-design.md @@ -21,11 +21,11 @@ ## 서버-클라 구조 (ExecSpace) -codeblock JSON ExecSpace는 플래그 (실측: Server=1·Client=2·ClientOnly=6 — Monster(1)·UIToast/MapCamera(6) 부합): +codeblock JSON ExecSpace 실측(프로브): **Server=5**(클라→서버), **Client=6**(서버→클라·마지막 인자 userId로 특정 유저 라우팅), 1=ServerOnly(클라 호출 무시), 2=ClientOnly(서버 호출 무시). 기존 메서드의 6은 Client라 클라 호출 시 제자리 실행으로 동작 동일: -- `ReqLoadAscension(userId)` **[Server=1]** — 클라 OnBeginPlay에서 호출 → 서버에서 `GetAndWait("ascensionUnlocked")` → `RecvAscension(n, userId)` 호출 -- `RecvAscension(n, userId)` **[Client=2]** — 마지막 파라미터 userId로 **요청한 클라이언트에만** 응답 (MSW 공식 패턴) → `AscensionUnlocked` 갱신·메뉴 렌더 -- `SaveAscension(n, userId)` **[Server=1]** — `SetAndWait` +- `ReqLoadAscension(userId)` **[Server=5]** — 클라 OnBeginPlay에서 호출 → 서버에서 `GetAndWait("ascensionUnlocked")` → `RecvAscension(n, userId)` 호출 +- `RecvAscension(n, userId)` **[Client=6]** — 마지막 파라미터 userId로 **요청한 클라이언트에만** 응답 (MSW 공식 패턴) → `AscensionUnlocked` 갱신·메뉴 렌더 +- `SaveAscension(n, userId)` **[Server=5]** — `SetAndWait` - 생성기의 `m.ExecSpace = 6` 일괄 적용을 "명시값(≠0)은 보존"으로 수정 ## 적용 지점 diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index b6490f1..18bc5d9 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -2475,7 +2475,7 @@ local n = 0 if errCode == 0 and value ~= nil and value ~= "" then n = tonumber(value) or 0 end -self:RecvAscension(n, userId)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'userId' }], 1), +self:RecvAscension(n, userId)`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'userId' }], 5), method('RecvAscension', `self.AscensionUnlocked = n if self.AscensionLevel > self.AscensionUnlocked then self.AscensionLevel = self.AscensionUnlocked @@ -2483,12 +2483,12 @@ end self:RenderAscension()`, [ { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'n' }, { Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'userId' }, - ], 2), + ], 6), method('SaveAscension', `local ds = _DataStorageService:GetUserDataStorage(userId) ds:SetAndWait("ascensionUnlocked", tostring(n))`, [ { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'n' }, { Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'userId' }, - ], 1), + ], 5), method('AdjustAscension', `local v = self.AscensionLevel + delta if v < 0 then v = 0 end if v > self.AscensionUnlocked then v = self.AscensionUnlocked end From 7aed1943b7de7ed1623f3bbfd8f40c3a6c4f3072 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 12 Jun 2026 14:24:36 +0900 Subject: [PATCH 5/5] =?UTF-8?q?fix(ascension):=20=EB=A9=94=EB=89=B4=20Asc?= =?UTF-8?q?=20=EC=97=94=ED=8B=B0=ED=8B=B0=20guid=20=EC=B6=A9=EB=8F=8C=20?= =?UTF-8?q?=ED=95=B4=EC=86=8C=20(190=E2=86=92195~197)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CharacterSelectHud/OpaqueBackdrop이 menu:190 선점 — AscMinus 미표시 원인 Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/deck/gen-slaydeck.mjs | 6 +++--- ui/DefaultGroup.ui | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index 18bc5d9..e762533 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -2112,7 +2112,7 @@ function upsertUi() { })); // 승천 선택 (P11): [-] 라벨 [+] menu.push(entity({ - id: guid('menu', 190), + id: guid('menu', 195), path: '/ui/DefaultGroup/MainMenu/AscMinus', modelId: 'uibutton', entryId: 'UIButton', @@ -2126,7 +2126,7 @@ function upsertUi() { ], })); menu.push(entity({ - id: guid('menu', 191), + id: guid('menu', 196), path: '/ui/DefaultGroup/MainMenu/AscLabel', modelId: 'uitext', entryId: 'UIText', @@ -2139,7 +2139,7 @@ function upsertUi() { ], })); menu.push(entity({ - id: guid('menu', 192), + id: guid('menu', 197), path: '/ui/DefaultGroup/MainMenu/AscPlus', modelId: 'uibutton', entryId: 'UIButton', diff --git a/ui/DefaultGroup.ui b/ui/DefaultGroup.ui index 6cf4009..c06b1b7 100644 --- a/ui/DefaultGroup.ui +++ b/ui/DefaultGroup.ui @@ -287219,7 +287219,7 @@ } }, { - "id": "0e0000be-0000-4000-8000-00000e0000be", + "id": "0e0000c3-0000-4000-8000-00000e0000c3", "path": "/ui/DefaultGroup/MainMenu/AscMinus", "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent", "jsonString": { @@ -287454,7 +287454,7 @@ } }, { - "id": "0e0000bf-0000-4000-8000-00000e0000bf", + "id": "0e0000c4-0000-4000-8000-00000e0000c4", "path": "/ui/DefaultGroup/MainMenu/AscLabel", "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", "jsonString": { @@ -287642,7 +287642,7 @@ } }, { - "id": "0e0000c0-0000-4000-8000-00000e0000c0", + "id": "0e0000c5-0000-4000-8000-00000e0000c5", "path": "/ui/DefaultGroup/MainMenu/AscPlus", "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent", "jsonString": {