From a2b8d6bfb94894a0c2c028f44e45e107875e08d7 Mon Sep 17 00:00:00 2001 From: gahusb Date: Sun, 14 Jun 2026 02:37:14 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat(bandit):=20=EC=82=AC=EC=9D=BC=EB=9F=B0?= =?UTF-8?q?=ED=8A=B8=20=EB=8F=84=EC=A0=81=20=EB=8D=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 도적 시작 직업을 선택 화면에서 활성화하고 bandit 스타터 덱으로 런을 시작하도록 생성기를 연결 - Slay the Spire 사일런트 카드 75장을 bandit 카드 풀에 추가하고 카드명/설명을 한글화 - 현재 전투 엔진이 지원하는 피해, 방어도, 드로우, 독, 약화, 취약, 광역, 다단히트, 회복, 파워 효과로 카드 효과를 매핑 - 도적 스타터 덱을 타격 5장, 수비 5장, 무력화, 생존자로 구성 - bandit 및 도적 전직 계열(shiv, poisoner, trickster)을 카드 프레임 매핑에 연결 - ui/DefaultGroup.ui와 SlayDeckController.codeblock을 생성기로 재생성 검증: - node --check tools/deck/gen-slaydeck.mjs - node --test tools/balance/sim-balance.test.mjs tools/map/rogue-map.test.mjs - 도적 카드 75장 및 한글화 잔여 영어/깨짐 없음 확인 --- RootDesk/MyDesk/SlayDeckController.codeblock | 19 +- data/cardframes.json | 3 +- data/cards.json | 738 +++++++++++++++++++ tools/deck/gen-slaydeck.mjs | 36 +- ui/DefaultGroup.ui | 312 +------- 5 files changed, 801 insertions(+), 307 deletions(-) diff --git a/RootDesk/MyDesk/SlayDeckController.codeblock b/RootDesk/MyDesk/SlayDeckController.codeblock index 246cf02..08ae87b 100644 --- a/RootDesk/MyDesk/SlayDeckController.codeblock +++ b/RootDesk/MyDesk/SlayDeckController.codeblock @@ -99,6 +99,13 @@ "Attributes": [], "Name": "WarriorSelectHandler" }, + { + "Type": "any", + "DefaultValue": "nil", + "SyncDirection": 0, + "Attributes": [], + "Name": "ThiefSelectHandler" + }, { "Type": "any", "DefaultValue": "nil", @@ -822,7 +829,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\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", + "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 thief = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CharacterSelectHud/ThiefButton\")\nif thief ~= nil and thief.ButtonComponent ~= nil then\n\tif self.ThiefSelectHandler ~= nil then\n\t\tthief:DisconnectEvent(ButtonClickEvent, self.ThiefSelectHandler)\n\t\tself.ThiefSelectHandler = nil\n\tend\n\tself.ThiefSelectHandler = thief:ConnectEvent(ButtonClickEvent, function() self:SelectClass(\"bandit\") 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": [], @@ -875,7 +882,7 @@ "Name": null }, "Arguments": [], - "Code": "local warrior = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CharacterSelectHud/WarriorButton\")\nif warrior ~= nil and warrior.SpriteGUIRendererComponent ~= nil then\n\tif self.SelectedClass == \"warrior\" then\n\t\twarrior.SpriteGUIRendererComponent.Color = Color(0.28, 0.36, 0.46, 1)\n\telse\n\t\twarrior.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)\n\tend\nend\nlocal mage = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CharacterSelectHud/MageButton\")\nif mage ~= nil and mage.SpriteGUIRendererComponent ~= nil then\n\tif self.SelectedClass == \"magician\" then\n\t\tmage.SpriteGUIRendererComponent.Color = Color(0.28, 0.36, 0.46, 1)\n\telse\n\t\tmage.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)\n\tend\nend\nif self.SelectedClass == \"warrior\" then\n\tself:SetText(\"/ui/DefaultGroup/CharacterSelectHud/Status\", \"전사 선택됨\")\nelseif self.SelectedClass == \"magician\" then\n\tself:SetText(\"/ui/DefaultGroup/CharacterSelectHud/Status\", \"마법사 선택됨\")\nelse\n\tself:SetText(\"/ui/DefaultGroup/CharacterSelectHud/Status\", \"직업을 선택하고 시작하세요\")\nend", + "Code": "local warrior = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CharacterSelectHud/WarriorButton\")\nif warrior ~= nil and warrior.SpriteGUIRendererComponent ~= nil then\n\tif self.SelectedClass == \"warrior\" then\n\t\twarrior.SpriteGUIRendererComponent.Color = Color(0.28, 0.36, 0.46, 1)\n\telse\n\t\twarrior.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)\n\tend\nend\nlocal mage = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CharacterSelectHud/MageButton\")\nif mage ~= nil and mage.SpriteGUIRendererComponent ~= nil then\n\tif self.SelectedClass == \"magician\" then\n\t\tmage.SpriteGUIRendererComponent.Color = Color(0.28, 0.36, 0.46, 1)\n\telse\n\t\tmage.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)\n\tend\nend\nlocal thief = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CharacterSelectHud/ThiefButton\")\nif thief ~= nil and thief.SpriteGUIRendererComponent ~= nil then\n\tif self.SelectedClass == \"bandit\" then\n\t\tthief.SpriteGUIRendererComponent.Color = Color(0.28, 0.36, 0.46, 1)\n\telse\n\t\tthief.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)\n\tend\nend\nif self.SelectedClass == \"warrior\" then\n\tself:SetText(\"/ui/DefaultGroup/CharacterSelectHud/Status\", \"전사 선택됨\")\nelseif self.SelectedClass == \"bandit\" then\n\tself:SetText(\"/ui/DefaultGroup/CharacterSelectHud/Status\", \"도적 선택됨\")\nelseif self.SelectedClass == \"magician\" then\n\tself:SetText(\"/ui/DefaultGroup/CharacterSelectHud/Status\", \"마법사 선택됨\")\nelse\n\tself:SetText(\"/ui/DefaultGroup/CharacterSelectHud/Status\", \"직업을 선택하고 시작하세요\")\nend", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -890,7 +897,7 @@ "Name": null }, "Arguments": [], - "Code": "if self.SelectedClass ~= \"warrior\" and self.SelectedClass ~= \"magician\" then\n\tself:SetText(\"/ui/DefaultGroup/CharacterSelectHud/Status\", \"직업을 먼저 선택하세요\")\n\treturn\nend\nself:StartRun()", + "Code": "if self.SelectedClass ~= \"warrior\" and self.SelectedClass ~= \"bandit\" and self.SelectedClass ~= \"magician\" then\n\tself:SetText(\"/ui/DefaultGroup/CharacterSelectHud/Status\", \"직업을 먼저 선택하세요\")\n\treturn\nend\nself:StartRun()", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -935,7 +942,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.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.CardFrames = {\n\twarrior = { normal = \"4bb57ef88ef449fdaf958f6cf37fe44b\", unique = \"4f71c124c8bc4e13b5e9fad392995f68\", legend = \"6d741a60c60743cb98ee740a1e2dbfed\" },\n\tmagician = { normal = \"d788d09f6f50467ebc67f01dec45f9e2\", unique = \"f5def2e8022b4e59a17d3c16414034fe\", legend = \"cff71f2e472041ce80c6fbd296f42e2d\" },\n\tbandit = { normal = \"9487b06867bc46269ed1d855420f457f\", unique = \"b3081fb2fb1445fa90b12b01481a78ef\", legend = \"c357d2daf31a489d95b8fa47e50dd879\" },\n}\nself.ClassToFrame = {\n\twarrior = \"warrior\",\n\tfighter = \"warrior\",\n\tpage = \"warrior\",\n\tspearman = \"warrior\",\n\tmagician = \"magician\",\n\tfirepoison = \"magician\",\n\ticelightning = \"magician\",\n\tcleric = \"magician\",\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\" }\nelseif self.SelectedClass == \"bandit\" then\n\tself.PlayerMaxHp = 70\n\tself.RunDeck = { \"SilentStrike\", \"SilentStrike\", \"SilentStrike\", \"SilentStrike\", \"SilentStrike\", \"SilentDefend\", \"SilentDefend\", \"SilentDefend\", \"SilentDefend\", \"SilentDefend\", \"Neutralize\", \"Survivor\" }\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\tbandit = {\n\t\t{ id = \"shiv\", name = \"Shiv\", desc = \"Many small attacks\\nBlade Dance\\nAccuracy · After Image\", starter = \"BladeDance\" },\n\t\t{ id = \"poisoner\", name = \"Poison\", desc = \"Poison scaling\\nDeadly Poison\\nCatalyst · Noxious Fumes\", starter = \"DeadlyPoison\" },\n\t\t{ id = \"trickster\", name = \"Trickster\", desc = \"Draw and tempo\\nAcrobatics\\nAdrenaline · Tools\", starter = \"Acrobatics\" },\n\t},\n}\nself.CardFrames = {\n\twarrior = { normal = \"4bb57ef88ef449fdaf958f6cf37fe44b\", unique = \"4f71c124c8bc4e13b5e9fad392995f68\", legend = \"6d741a60c60743cb98ee740a1e2dbfed\" },\n\tmagician = { normal = \"d788d09f6f50467ebc67f01dec45f9e2\", unique = \"f5def2e8022b4e59a17d3c16414034fe\", legend = \"cff71f2e472041ce80c6fbd296f42e2d\" },\n\tbandit = { normal = \"9487b06867bc46269ed1d855420f457f\", unique = \"b3081fb2fb1445fa90b12b01481a78ef\", legend = \"c357d2daf31a489d95b8fa47e50dd879\" },\n}\nself.ClassToFrame = {\n\twarrior = \"warrior\",\n\tfighter = \"warrior\",\n\tpage = \"warrior\",\n\tspearman = \"warrior\",\n\tmagician = \"magician\",\n\tfirepoison = \"magician\",\n\ticelightning = \"magician\",\n\tcleric = \"magician\",\n\tbandit = \"bandit\",\n\tshiv = \"bandit\",\n\tpoisoner = \"bandit\",\n\ttrickster = \"bandit\",\n}\nself:GenerateMap()\nself:BindButtons()\nself:AddRelic(\"ironHeart\")\nself:RenderPotions()\nself:ShowMap()", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -950,7 +957,7 @@ "Name": null }, "Arguments": [], - "Code": "self:ShowState(\"combat\")\nself:SetEntityEnabled(\"/ui/DefaultGroup/CombatHud/Result\", false)\nself:SetEntityEnabled(\"/ui/DefaultGroup/CombatHud/PotionMenu\", false)\nself:SetEntityEnabled(\"/ui/DefaultGroup/CombatHud/TooltipBox\", false)\nself:SetText(\"/ui/DefaultGroup/CombatHud/PlayerPanel/Name\", self:JobLabel())\nself.MaxEnergy = 3\nself.Turn = 0\nself.PlayerBlock = 0\nself.PlayerStr = 0\nself.PlayerWeak = 0\nself.PlayerVuln = 0\nself.PlayerPowers = {}\nself.FightAttackCount = 0\nself.FirstHpLossDone = false\nself.ClayBlockNext = 0\nself.CombatOver = false\nself.DiscardPile = {}\nself.Hand = {}\nself.Cards = {\n\tStrike = { name = \"파워 스트라이크\", cost = 1, desc = \"피해 6\", kind = \"Attack\", damage = 6, class = \"warrior\", rarity = \"normal\", image = \"a71b116807904ef2b38e1dc013e2f9a2\" },\n\tDefend = { name = \"아이언 바디\", cost = 1, desc = \"방어도 5\", kind = \"Skill\", block = 5, class = \"warrior\", rarity = \"normal\", image = \"1ae9b6741c5947a8b528a0f515b50e3e\" },\n\tBash = { name = \"슬래시 블러스트\", cost = 2, desc = \"피해 10\", kind = \"Attack\", damage = 10, class = \"warrior\", rarity = \"normal\", image = \"d5bc2953fcab4cfe9062af81c35aff86\" },\n\tWarLeap = { name = \"워 리프\", cost = 1, desc = \"피해 4, 방어도 3\", kind = \"Attack\", damage = 4, block = 3, class = \"warrior\", rarity = \"normal\", image = \"992dabf6aff2400e92b2f4f705d8ebe7\" },\n\tBrandish = { name = \"브랜디시\", cost = 2, desc = \"피해 13\", kind = \"Attack\", damage = 13, class = \"warrior\", rarity = \"unique\", image = \"21af4bccc5054a5dbc8245dfa7f08681\" },\n\tChargedBlow = { name = \"차지 블로우\", cost = 2, desc = \"피해 8, 취약 2\", kind = \"Attack\", damage = 8, vuln = 2, class = \"warrior\", rarity = \"unique\", image = \"fe83c7635b0e49ed83d75a2833adb53e\" },\n\tThreaten = { name = \"위협\", cost = 0, desc = \"약화 2 부여\", kind = \"Skill\", weak = 2, class = \"warrior\", rarity = \"normal\", image = \"64daadf1a98e490d9c14ef52ec776e63\" },\n\tEnrage = { name = \"인레이지\", cost = 1, desc = \"힘 +2\", kind = \"Skill\", strength = 2, class = \"warrior\", rarity = \"unique\", image = \"09370fc7551e47238fd103a80fba558e\" },\n\tRage = { name = \"분노\", cost = 1, desc = \"매 턴 시작 시 힘 +1\", kind = \"Power\", powerEffect = \"strengthPerTurn\", value = 1, class = \"warrior\", rarity = \"legend\", image = \"379d86e3de064959aa4612f71e84ccfb\" },\n\tComboAttack = { name = \"콤보 어택\", cost = 1, desc = \"피해 5 × 2회\", kind = \"Attack\", damage = 5, class = \"fighter\", rarity = \"unique\", hits = 2, image = \"1bc3e52b330648faae9eafd5a205e37b\" },\n\tBerserk = { name = \"버서크\", cost = 2, desc = \"매턴 에너지 +1, 취약 1 자가\", kind = \"Power\", powerEffect = \"energyPerTurn\", value = 1, class = \"fighter\", rarity = \"legend\", selfVuln = 1, image = \"cef30ea340c74e768bcee4e2cbe0577a\" },\n\tRisingAttack = { name = \"라이징 어택\", cost = 2, desc = \"피해 12\", kind = \"Attack\", damage = 12, class = \"fighter\", rarity = \"unique\", image = \"3a3d4b8bb5bd4137847caf883e4bf38e\" },\n\tThunderCharge = { name = \"썬더 차지\", cost = 1, desc = \"피해 7, 약화 1\", kind = \"Attack\", damage = 7, weak = 1, class = \"page\", rarity = \"unique\", image = \"f1b7e3041909411eb67af884b446e1e1\" },\n\tBlizzardCharge = { name = \"블리자드 차지\", cost = 1, desc = \"피해 7, 취약 1\", kind = \"Attack\", damage = 7, vuln = 1, class = \"page\", rarity = \"unique\", image = \"7915c70952ad432f99519ad79bf929a4\" },\n\tPowerGuard = { name = \"파워 가드\", cost = 1, desc = \"방어도 10\", kind = \"Skill\", block = 10, class = \"page\", rarity = \"unique\", image = \"90a9bf8eeb844b578b4e2d93ac43fedf\" },\n\tPierce = { name = \"피어스\", cost = 1, desc = \"피해 9, 방어 무시\", kind = \"Attack\", damage = 9, class = \"spearman\", rarity = \"unique\", pierce = true, image = \"e312e535a2bc4fed82d36f9c6027c9db\" },\n\tIronWall = { name = \"아이언 월\", cost = 2, desc = \"방어도 12\", kind = \"Skill\", block = 12, class = \"spearman\", rarity = \"unique\", image = \"92021d62341a4bce9cfd09d1b4b865db\" },\n\tHyperBody = { name = \"하이퍼 바디\", cost = 1, desc = \"매턴 방어도 +3\", kind = \"Power\", powerEffect = \"blockPerTurn\", value = 3, class = \"spearman\", rarity = \"legend\", image = \"b4020dbadee6401f9893a020fe4154b1\" },\n\tEnergyBolt = { name = \"에너지 볼트\", cost = 1, desc = \"피해 6\", kind = \"Attack\", damage = 6, class = \"magician\", rarity = \"normal\", image = \"a1ee3069fce14498b92998542679ae40\" },\n\tMagicGuard = { name = \"매직 가드\", cost = 1, desc = \"방어도 5\", kind = \"Skill\", block = 5, class = \"magician\", rarity = \"normal\", image = \"01b249c26eb34b8aaab774bf221907a1\" },\n\tMagicClaw = { name = \"매직 클로\", cost = 1, desc = \"피해 3 × 2회\", kind = \"Attack\", damage = 3, class = \"magician\", rarity = \"normal\", hits = 2, image = \"d6e7c04c436f42f19e9806ac5b4401ae\" },\n\tTeleport = { name = \"텔레포트\", cost = 1, desc = \"방어도 3, 드로 1\", kind = \"Skill\", block = 3, class = \"magician\", rarity = \"normal\", draw = 1, image = \"80c98c8e032b4f6c8371a24b4e1d8f14\" },\n\tSlow = { name = \"슬로우\", cost = 1, desc = \"약화 2 부여\", kind = \"Skill\", weak = 2, class = \"magician\", rarity = \"normal\", image = \"16f79f571a964430bf1953edc9a14c73\" },\n\tFireArrow = { name = \"파이어 애로우\", cost = 1, desc = \"피해 8\", kind = \"Attack\", damage = 8, class = \"firepoison\", rarity = \"unique\", image = \"78b9be4e711c440f84fc21e51e812bae\" },\n\tPoisonBreath = { name = \"포이즌 브레스\", cost = 1, desc = \"독 4 부여\", kind = \"Skill\", class = \"firepoison\", rarity = \"unique\", poison = 4, image = \"b4e8bd7508b54d208e4f2ad7414f8c0a\" },\n\tElementAmp = { name = \"엘레멘트 앰플\", cost = 1, desc = \"매 턴 힘 +1\", kind = \"Power\", powerEffect = \"strengthPerTurn\", value = 1, class = \"firepoison\", rarity = \"legend\", image = \"9859f3ab41b945f797d56cd83f95b25f\" },\n\tThunderBolt = { name = \"썬더 볼트\", cost = 2, desc = \"모든 적에게 피해 6\", kind = \"Attack\", damage = 6, class = \"icelightning\", rarity = \"legend\", aoe = true, image = \"c6685d33cb2641f09d11cfa2d5cc820c\" },\n\tColdBeam = { name = \"콜드 빔\", cost = 2, desc = \"피해 7, 약화 2\", kind = \"Attack\", damage = 7, weak = 2, class = \"icelightning\", rarity = \"unique\", image = \"e8f7c148c79f497d83014e3361f59f5c\" },\n\tChillingStep = { name = \"칠링 스텝\", cost = 1, desc = \"방어도 8\", kind = \"Skill\", block = 8, class = \"icelightning\", rarity = \"unique\", image = \"b2a7274d868241c78aa5780f2beecddf\" },\n\tHeal = { name = \"힐\", cost = 1, desc = \"HP 10 회복\", kind = \"Skill\", class = \"cleric\", rarity = \"unique\", heal = 10, image = \"b4127c181e2942e38821d4a9a1f14596\" },\n\tBless = { name = \"블레스\", cost = 1, desc = \"힘 +1, 방어도 5\", kind = \"Skill\", block = 5, strength = 1, class = \"cleric\", rarity = \"unique\", image = \"d45553db4a414011b67486dfa8a12fe5\" },\n\tHolyArrow = { name = \"홀리 애로우\", cost = 1, desc = \"피해 8\", kind = \"Attack\", damage = 8, class = \"cleric\", rarity = \"unique\", image = \"0265e103b4904f178b1c2bdcd54d5975\" },\n}\nself.DrawPile = {}\nfor i = 1, #self.RunDeck do\n\tself.DrawPile[i] = self.RunDeck[i]\nend\nself:Shuffle(self.DrawPile)\nself:BuildMonsters()\nself:RenderCombat()\nself:StartPlayerTurn()\nself:ApplyRelics(\"combatStart\")\nself:RenderCombat()", + "Code": "self:ShowState(\"combat\")\nself:SetEntityEnabled(\"/ui/DefaultGroup/CombatHud/Result\", false)\nself:SetEntityEnabled(\"/ui/DefaultGroup/CombatHud/PotionMenu\", false)\nself:SetEntityEnabled(\"/ui/DefaultGroup/CombatHud/TooltipBox\", false)\nself:SetText(\"/ui/DefaultGroup/CombatHud/PlayerPanel/Name\", self:JobLabel())\nself.MaxEnergy = 3\nself.Turn = 0\nself.PlayerBlock = 0\nself.PlayerStr = 0\nself.PlayerWeak = 0\nself.PlayerVuln = 0\nself.PlayerPowers = {}\nself.FightAttackCount = 0\nself.FirstHpLossDone = false\nself.ClayBlockNext = 0\nself.CombatOver = false\nself.DiscardPile = {}\nself.Hand = {}\nself.Cards = {\n\tStrike = { name = \"파워 스트라이크\", cost = 1, desc = \"피해 6\", kind = \"Attack\", damage = 6, class = \"warrior\", rarity = \"normal\", image = \"a71b116807904ef2b38e1dc013e2f9a2\" },\n\tDefend = { name = \"아이언 바디\", cost = 1, desc = \"방어도 5\", kind = \"Skill\", block = 5, class = \"warrior\", rarity = \"normal\", image = \"1ae9b6741c5947a8b528a0f515b50e3e\" },\n\tBash = { name = \"슬래시 블러스트\", cost = 2, desc = \"피해 10\", kind = \"Attack\", damage = 10, class = \"warrior\", rarity = \"normal\", image = \"d5bc2953fcab4cfe9062af81c35aff86\" },\n\tWarLeap = { name = \"워 리프\", cost = 1, desc = \"피해 4, 방어도 3\", kind = \"Attack\", damage = 4, block = 3, class = \"warrior\", rarity = \"normal\", image = \"992dabf6aff2400e92b2f4f705d8ebe7\" },\n\tBrandish = { name = \"브랜디시\", cost = 2, desc = \"피해 13\", kind = \"Attack\", damage = 13, class = \"warrior\", rarity = \"unique\", image = \"21af4bccc5054a5dbc8245dfa7f08681\" },\n\tChargedBlow = { name = \"차지 블로우\", cost = 2, desc = \"피해 8, 취약 2\", kind = \"Attack\", damage = 8, vuln = 2, class = \"warrior\", rarity = \"unique\", image = \"fe83c7635b0e49ed83d75a2833adb53e\" },\n\tThreaten = { name = \"위협\", cost = 0, desc = \"약화 2 부여\", kind = \"Skill\", weak = 2, class = \"warrior\", rarity = \"normal\", image = \"64daadf1a98e490d9c14ef52ec776e63\" },\n\tEnrage = { name = \"인레이지\", cost = 1, desc = \"힘 +2\", kind = \"Skill\", strength = 2, class = \"warrior\", rarity = \"unique\", image = \"09370fc7551e47238fd103a80fba558e\" },\n\tRage = { name = \"분노\", cost = 1, desc = \"매 턴 시작 시 힘 +1\", kind = \"Power\", powerEffect = \"strengthPerTurn\", value = 1, class = \"warrior\", rarity = \"legend\", image = \"379d86e3de064959aa4612f71e84ccfb\" },\n\tComboAttack = { name = \"콤보 어택\", cost = 1, desc = \"피해 5 × 2회\", kind = \"Attack\", damage = 5, class = \"fighter\", rarity = \"unique\", hits = 2, image = \"1bc3e52b330648faae9eafd5a205e37b\" },\n\tBerserk = { name = \"버서크\", cost = 2, desc = \"매턴 에너지 +1, 취약 1 자가\", kind = \"Power\", powerEffect = \"energyPerTurn\", value = 1, class = \"fighter\", rarity = \"legend\", selfVuln = 1, image = \"cef30ea340c74e768bcee4e2cbe0577a\" },\n\tRisingAttack = { name = \"라이징 어택\", cost = 2, desc = \"피해 12\", kind = \"Attack\", damage = 12, class = \"fighter\", rarity = \"unique\", image = \"3a3d4b8bb5bd4137847caf883e4bf38e\" },\n\tThunderCharge = { name = \"썬더 차지\", cost = 1, desc = \"피해 7, 약화 1\", kind = \"Attack\", damage = 7, weak = 1, class = \"page\", rarity = \"unique\", image = \"f1b7e3041909411eb67af884b446e1e1\" },\n\tBlizzardCharge = { name = \"블리자드 차지\", cost = 1, desc = \"피해 7, 취약 1\", kind = \"Attack\", damage = 7, vuln = 1, class = \"page\", rarity = \"unique\", image = \"7915c70952ad432f99519ad79bf929a4\" },\n\tPowerGuard = { name = \"파워 가드\", cost = 1, desc = \"방어도 10\", kind = \"Skill\", block = 10, class = \"page\", rarity = \"unique\", image = \"90a9bf8eeb844b578b4e2d93ac43fedf\" },\n\tPierce = { name = \"피어스\", cost = 1, desc = \"피해 9, 방어 무시\", kind = \"Attack\", damage = 9, class = \"spearman\", rarity = \"unique\", pierce = true, image = \"e312e535a2bc4fed82d36f9c6027c9db\" },\n\tIronWall = { name = \"아이언 월\", cost = 2, desc = \"방어도 12\", kind = \"Skill\", block = 12, class = \"spearman\", rarity = \"unique\", image = \"92021d62341a4bce9cfd09d1b4b865db\" },\n\tHyperBody = { name = \"하이퍼 바디\", cost = 1, desc = \"매턴 방어도 +3\", kind = \"Power\", powerEffect = \"blockPerTurn\", value = 3, class = \"spearman\", rarity = \"legend\", image = \"b4020dbadee6401f9893a020fe4154b1\" },\n\tEnergyBolt = { name = \"에너지 볼트\", cost = 1, desc = \"피해 6\", kind = \"Attack\", damage = 6, class = \"magician\", rarity = \"normal\", image = \"a1ee3069fce14498b92998542679ae40\" },\n\tMagicGuard = { name = \"매직 가드\", cost = 1, desc = \"방어도 5\", kind = \"Skill\", block = 5, class = \"magician\", rarity = \"normal\", image = \"01b249c26eb34b8aaab774bf221907a1\" },\n\tMagicClaw = { name = \"매직 클로\", cost = 1, desc = \"피해 3 × 2회\", kind = \"Attack\", damage = 3, class = \"magician\", rarity = \"normal\", hits = 2, image = \"d6e7c04c436f42f19e9806ac5b4401ae\" },\n\tTeleport = { name = \"텔레포트\", cost = 1, desc = \"방어도 3, 드로 1\", kind = \"Skill\", block = 3, class = \"magician\", rarity = \"normal\", draw = 1, image = \"80c98c8e032b4f6c8371a24b4e1d8f14\" },\n\tSlow = { name = \"슬로우\", cost = 1, desc = \"약화 2 부여\", kind = \"Skill\", weak = 2, class = \"magician\", rarity = \"normal\", image = \"16f79f571a964430bf1953edc9a14c73\" },\n\tFireArrow = { name = \"파이어 애로우\", cost = 1, desc = \"피해 8\", kind = \"Attack\", damage = 8, class = \"firepoison\", rarity = \"unique\", image = \"78b9be4e711c440f84fc21e51e812bae\" },\n\tPoisonBreath = { name = \"포이즌 브레스\", cost = 1, desc = \"독 4 부여\", kind = \"Skill\", class = \"firepoison\", rarity = \"unique\", poison = 4, image = \"b4e8bd7508b54d208e4f2ad7414f8c0a\" },\n\tElementAmp = { name = \"엘레멘트 앰플\", cost = 1, desc = \"매 턴 힘 +1\", kind = \"Power\", powerEffect = \"strengthPerTurn\", value = 1, class = \"firepoison\", rarity = \"legend\", image = \"9859f3ab41b945f797d56cd83f95b25f\" },\n\tThunderBolt = { name = \"썬더 볼트\", cost = 2, desc = \"모든 적에게 피해 6\", kind = \"Attack\", damage = 6, class = \"icelightning\", rarity = \"legend\", aoe = true, image = \"c6685d33cb2641f09d11cfa2d5cc820c\" },\n\tColdBeam = { name = \"콜드 빔\", cost = 2, desc = \"피해 7, 약화 2\", kind = \"Attack\", damage = 7, weak = 2, class = \"icelightning\", rarity = \"unique\", image = \"e8f7c148c79f497d83014e3361f59f5c\" },\n\tChillingStep = { name = \"칠링 스텝\", cost = 1, desc = \"방어도 8\", kind = \"Skill\", block = 8, class = \"icelightning\", rarity = \"unique\", image = \"b2a7274d868241c78aa5780f2beecddf\" },\n\tHeal = { name = \"힐\", cost = 1, desc = \"HP 10 회복\", kind = \"Skill\", class = \"cleric\", rarity = \"unique\", heal = 10, image = \"b4127c181e2942e38821d4a9a1f14596\" },\n\tBless = { name = \"블레스\", cost = 1, desc = \"힘 +1, 방어도 5\", kind = \"Skill\", block = 5, strength = 1, class = \"cleric\", rarity = \"unique\", image = \"d45553db4a414011b67486dfa8a12fe5\" },\n\tHolyArrow = { name = \"홀리 애로우\", cost = 1, desc = \"피해 8\", kind = \"Attack\", damage = 8, class = \"cleric\", rarity = \"unique\", image = \"0265e103b4904f178b1c2bdcd54d5975\" },\n\tSilentStrike = { name = \"타격\", cost = 1, desc = \"피해 6.\", kind = \"Attack\", damage = 6, class = \"bandit\", rarity = \"normal\" },\n\tSilentDefend = { name = \"수비\", cost = 1, desc = \"방어도 5.\", kind = \"Skill\", block = 5, class = \"bandit\", rarity = \"normal\" },\n\tNeutralize = { name = \"무력화\", cost = 0, desc = \"피해 3. 약화 1 부여.\", kind = \"Attack\", damage = 3, weak = 1, class = \"bandit\", rarity = \"normal\" },\n\tSurvivor = { name = \"생존자\", cost = 1, desc = \"방어도 8. 카드 1장 드로우.\", kind = \"Skill\", block = 8, class = \"bandit\", rarity = \"normal\", draw = 1 },\n\tAcrobatics = { name = \"곡예\", cost = 1, desc = \"카드 3장 드로우.\", kind = \"Skill\", class = \"bandit\", rarity = \"normal\", draw = 3 },\n\tBackflip = { name = \"백플립\", cost = 1, desc = \"방어도 5. 카드 2장 드로우.\", kind = \"Skill\", block = 5, class = \"bandit\", rarity = \"normal\", draw = 2 },\n\tBane = { name = \"파멸\", cost = 1, desc = \"피해 7을 2회.\", kind = \"Attack\", damage = 7, class = \"bandit\", rarity = \"normal\", hits = 2 },\n\tBladeDance = { name = \"칼날 춤\", cost = 1, desc = \"시브식 공격: 피해 4를 3회.\", kind = \"Attack\", damage = 4, class = \"bandit\", rarity = \"normal\", hits = 3 },\n\tCloakAndDagger = { name = \"망토와 단검\", cost = 1, desc = \"방어도 6. 피해 4.\", kind = \"Attack\", damage = 4, block = 6, class = \"bandit\", rarity = \"normal\" },\n\tDaggerSpray = { name = \"단검 분사\", cost = 1, desc = \"모든 적에게 피해 4를 2회.\", kind = \"Attack\", damage = 4, class = \"bandit\", rarity = \"normal\", hits = 2, aoe = true },\n\tDaggerThrow = { name = \"단검 투척\", cost = 1, desc = \"피해 9. 카드 1장 드로우.\", kind = \"Attack\", damage = 9, class = \"bandit\", rarity = \"normal\", draw = 1 },\n\tDeadlyPoison = { name = \"맹독\", cost = 1, desc = \"독 5 부여.\", kind = \"Skill\", class = \"bandit\", rarity = \"normal\", poison = 5 },\n\tDeflect = { name = \"흘리기\", cost = 0, desc = \"방어도 4.\", kind = \"Skill\", block = 4, class = \"bandit\", rarity = \"normal\" },\n\tDodgeAndRoll = { name = \"회피와 구르기\", cost = 1, desc = \"방어도 8.\", kind = \"Skill\", block = 8, class = \"bandit\", rarity = \"normal\" },\n\tFlyingKnee = { name = \"날아차기\", cost = 1, desc = \"피해 8.\", kind = \"Attack\", damage = 8, class = \"bandit\", rarity = \"normal\" },\n\tOutmaneuver = { name = \"책략\", cost = 1, desc = \"매 턴 에너지 +1.\", kind = \"Power\", powerEffect = \"energyPerTurn\", value = 1, class = \"bandit\", rarity = \"normal\" },\n\tPiercingWail = { name = \"꿰뚫는 비명\", cost = 1, desc = \"약화 3 부여.\", kind = \"Skill\", weak = 3, class = \"bandit\", rarity = \"normal\" },\n\tPoisonedStab = { name = \"독 찌르기\", cost = 1, desc = \"피해 6. 독 3 부여.\", kind = \"Attack\", damage = 6, class = \"bandit\", rarity = \"normal\", poison = 3 },\n\tPrepared = { name = \"준비\", cost = 0, desc = \"카드 1장 드로우.\", kind = \"Skill\", class = \"bandit\", rarity = \"normal\", draw = 1 },\n\tQuickSlash = { name = \"빠른 베기\", cost = 1, desc = \"피해 8. 카드 1장 드로우.\", kind = \"Attack\", damage = 8, class = \"bandit\", rarity = \"normal\", draw = 1 },\n\tSlice = { name = \"얇게 베기\", cost = 0, desc = \"피해 6.\", kind = \"Attack\", damage = 6, class = \"bandit\", rarity = \"normal\" },\n\tSneakyStrike = { name = \"기습 타격\", cost = 2, desc = \"피해 12.\", kind = \"Attack\", damage = 12, class = \"bandit\", rarity = \"normal\" },\n\tSuckerPunch = { name = \"불시의 일격\", cost = 1, desc = \"피해 7. 약화 1 부여.\", kind = \"Attack\", damage = 7, weak = 1, class = \"bandit\", rarity = \"normal\" },\n\tAccuracy = { name = \"정확도\", cost = 1, desc = \"시브 지원: 매 턴 힘 +1.\", kind = \"Power\", powerEffect = \"strengthPerTurn\", value = 1, class = \"bandit\", rarity = \"unique\" },\n\tAllOutAttack = { name = \"총공격\", cost = 1, desc = \"모든 적에게 피해 10.\", kind = \"Attack\", damage = 10, class = \"bandit\", rarity = \"unique\", aoe = true },\n\tBackstab = { name = \"등 찌르기\", cost = 0, desc = \"피해 11.\", kind = \"Attack\", damage = 11, class = \"bandit\", rarity = \"unique\" },\n\tBlur = { name = \"잔상\", cost = 1, desc = \"방어도 8.\", kind = \"Skill\", block = 8, class = \"bandit\", rarity = \"unique\" },\n\tBouncingFlask = { name = \"튕기는 플라스크\", cost = 2, desc = \"독 9 부여.\", kind = \"Skill\", class = \"bandit\", rarity = \"unique\", poison = 9 },\n\tCalculatedGamble = { name = \"계산된 도박\", cost = 0, desc = \"카드 3장 드로우.\", kind = \"Skill\", class = \"bandit\", rarity = \"unique\", draw = 3 },\n\tCaltrops = { name = \"마름쇠\", cost = 1, desc = \"매 턴 방어도 +3.\", kind = \"Power\", powerEffect = \"blockPerTurn\", value = 3, class = \"bandit\", rarity = \"unique\" },\n\tCatalyst = { name = \"촉매\", cost = 1, desc = \"독 8 부여.\", kind = \"Skill\", class = \"bandit\", rarity = \"unique\", poison = 8 },\n\tChoke = { name = \"목 조르기\", cost = 2, desc = \"피해 12. 취약 2 부여.\", kind = \"Attack\", damage = 12, vuln = 2, class = \"bandit\", rarity = \"unique\" },\n\tConcentrate = { name = \"집중\", cost = 0, desc = \"매 턴 에너지 +1.\", kind = \"Power\", powerEffect = \"energyPerTurn\", value = 1, class = \"bandit\", rarity = \"unique\" },\n\tCripplingCloud = { name = \"불구름\", cost = 2, desc = \"독 4와 약화 2 부여.\", kind = \"Skill\", weak = 2, class = \"bandit\", rarity = \"unique\", poison = 4 },\n\tDash = { name = \"질주\", cost = 2, desc = \"방어도 10. 피해 10.\", kind = \"Attack\", damage = 10, block = 10, class = \"bandit\", rarity = \"unique\" },\n\tDistraction = { name = \"교란\", cost = 1, desc = \"카드 1장 드로우.\", kind = \"Skill\", class = \"bandit\", rarity = \"unique\", draw = 1 },\n\tEndlessAgony = { name = \"끝없는 고통\", cost = 0, desc = \"피해 4를 2회.\", kind = \"Attack\", damage = 4, class = \"bandit\", rarity = \"unique\", hits = 2 },\n\tEscapePlan = { name = \"탈출 계획\", cost = 0, desc = \"카드 1장 드로우. 방어도 3.\", kind = \"Skill\", block = 3, class = \"bandit\", rarity = \"unique\", draw = 1 },\n\tEviscerate = { name = \"절개\", cost = 3, desc = \"피해 7을 3회.\", kind = \"Attack\", damage = 7, class = \"bandit\", rarity = \"unique\", hits = 3 },\n\tExpertise = { name = \"전문가\", cost = 1, desc = \"카드 3장 드로우.\", kind = \"Skill\", class = \"bandit\", rarity = \"unique\", draw = 3 },\n\tFinisher = { name = \"마무리\", cost = 1, desc = \"피해 6을 2회.\", kind = \"Attack\", damage = 6, class = \"bandit\", rarity = \"unique\", hits = 2 },\n\tFlechettes = { name = \"플레셰트\", cost = 1, desc = \"피해 4를 3회.\", kind = \"Attack\", damage = 4, class = \"bandit\", rarity = \"unique\", hits = 3 },\n\tFootwork = { name = \"발놀림\", cost = 1, desc = \"매 턴 방어도 +2.\", kind = \"Power\", powerEffect = \"blockPerTurn\", value = 2, class = \"bandit\", rarity = \"unique\" },\n\tHeelHook = { name = \"발뒤꿈치 걸기\", cost = 1, desc = \"피해 5. 카드 1장 드로우.\", kind = \"Attack\", damage = 5, class = \"bandit\", rarity = \"unique\", draw = 1 },\n\tInfiniteBlades = { name = \"무한의 칼날\", cost = 1, desc = \"시브 지원: 매 턴 힘 +1.\", kind = \"Power\", powerEffect = \"strengthPerTurn\", value = 1, class = \"bandit\", rarity = \"unique\" },\n\tLegSweep = { name = \"다리 걸기\", cost = 2, desc = \"방어도 11. 약화 2 부여.\", kind = \"Skill\", block = 11, weak = 2, class = \"bandit\", rarity = \"unique\" },\n\tMasterfulStab = { name = \"달인의 찌르기\", cost = 0, desc = \"피해 12.\", kind = \"Attack\", damage = 12, class = \"bandit\", rarity = \"unique\" },\n\tNoxiousFumes = { name = \"유독 가스\", cost = 1, desc = \"독 강화: 매 턴 힘 +1.\", kind = \"Power\", powerEffect = \"strengthPerTurn\", value = 1, class = \"bandit\", rarity = \"unique\" },\n\tPredator = { name = \"포식자\", cost = 2, desc = \"피해 15. 카드 2장 드로우.\", kind = \"Attack\", damage = 15, class = \"bandit\", rarity = \"unique\", draw = 2 },\n\tReflex = { name = \"반사 신경\", cost = 0, desc = \"카드 2장 드로우.\", kind = \"Skill\", class = \"bandit\", rarity = \"unique\", draw = 2 },\n\tRiddleWithHoles = { name = \"벌집 만들기\", cost = 2, desc = \"피해 3을 5회.\", kind = \"Attack\", damage = 3, class = \"bandit\", rarity = \"unique\", hits = 5 },\n\tSetup = { name = \"설치\", cost = 1, desc = \"카드 1장 드로우.\", kind = \"Skill\", class = \"bandit\", rarity = \"unique\", draw = 1 },\n\tSkewer = { name = \"꿰뚫기\", cost = 2, desc = \"피해 7을 3회.\", kind = \"Attack\", damage = 7, class = \"bandit\", rarity = \"unique\", hits = 3 },\n\tTactician = { name = \"전술가\", cost = 0, desc = \"매 턴 에너지 +1.\", kind = \"Power\", powerEffect = \"energyPerTurn\", value = 1, class = \"bandit\", rarity = \"unique\" },\n\tTerror = { name = \"공포\", cost = 1, desc = \"취약 5 부여.\", kind = \"Skill\", vuln = 5, class = \"bandit\", rarity = \"unique\" },\n\tWellLaidPlans = { name = \"치밀한 계획\", cost = 1, desc = \"매 턴 방어도 +2.\", kind = \"Power\", powerEffect = \"blockPerTurn\", value = 2, class = \"bandit\", rarity = \"unique\" },\n\tAThousandCuts = { name = \"천 개의 상처\", cost = 2, desc = \"매 턴 힘 +1.\", kind = \"Power\", powerEffect = \"strengthPerTurn\", value = 1, class = \"bandit\", rarity = \"legend\" },\n\tAdrenaline = { name = \"아드레날린\", cost = 0, desc = \"힘 +1. 카드 2장 드로우.\", kind = \"Skill\", strength = 1, class = \"bandit\", rarity = \"legend\", draw = 2 },\n\tAfterImage = { name = \"잔상 효과\", cost = 1, desc = \"매 턴 방어도 +1.\", kind = \"Power\", powerEffect = \"blockPerTurn\", value = 1, class = \"bandit\", rarity = \"legend\" },\n\tAlchemize = { name = \"연금술\", cost = 1, desc = \"HP 8 회복.\", kind = \"Skill\", class = \"bandit\", rarity = \"legend\", heal = 8 },\n\tBulletTime = { name = \"불릿 타임\", cost = 2, desc = \"매 턴 에너지 +2.\", kind = \"Power\", powerEffect = \"energyPerTurn\", value = 2, class = \"bandit\", rarity = \"legend\" },\n\tBurst = { name = \"폭발\", cost = 1, desc = \"매 턴 힘 +1.\", kind = \"Power\", powerEffect = \"strengthPerTurn\", value = 1, class = \"bandit\", rarity = \"legend\" },\n\tCorpseExplosion = { name = \"시체 폭발\", cost = 2, desc = \"독 6과 취약 2 부여.\", kind = \"Skill\", vuln = 2, class = \"bandit\", rarity = \"legend\", poison = 6 },\n\tDieDieDie = { name = \"죽어 죽어 죽어\", cost = 1, desc = \"모든 적에게 피해 13.\", kind = \"Attack\", damage = 13, class = \"bandit\", rarity = \"legend\", aoe = true },\n\tDoppelganger = { name = \"도플갱어\", cost = 2, desc = \"카드 3장 드로우.\", kind = \"Skill\", class = \"bandit\", rarity = \"legend\", draw = 3 },\n\tEnvenom = { name = \"맹독 바르기\", cost = 2, desc = \"독 지원: 매 턴 힘 +1.\", kind = \"Power\", powerEffect = \"strengthPerTurn\", value = 1, class = \"bandit\", rarity = \"legend\" },\n\tGlassKnife = { name = \"유리 칼\", cost = 1, desc = \"피해 8을 2회.\", kind = \"Attack\", damage = 8, class = \"bandit\", rarity = \"legend\", hits = 2 },\n\tGrandFinale = { name = \"대단원\", cost = 0, desc = \"모든 적에게 피해 50.\", kind = \"Attack\", damage = 50, class = \"bandit\", rarity = \"legend\", aoe = true },\n\tMalaise = { name = \"불쾌감\", cost = 2, desc = \"약화 3 부여.\", kind = \"Skill\", weak = 3, class = \"bandit\", rarity = \"legend\" },\n\tNightmare = { name = \"악몽\", cost = 3, desc = \"카드 3장 드로우.\", kind = \"Skill\", class = \"bandit\", rarity = \"legend\", draw = 3 },\n\tPhantasmalKiller = { name = \"환영 살인마\", cost = 1, desc = \"매 턴 힘 +2.\", kind = \"Power\", powerEffect = \"strengthPerTurn\", value = 2, class = \"bandit\", rarity = \"legend\" },\n\tStormOfSteel = { name = \"강철 폭풍\", cost = 1, desc = \"피해 4를 5회.\", kind = \"Attack\", damage = 4, class = \"bandit\", rarity = \"legend\", hits = 5 },\n\tToolsOfTheTrade = { name = \"거래의 도구\", cost = 1, desc = \"매 턴 에너지 +1.\", kind = \"Power\", powerEffect = \"energyPerTurn\", value = 1, class = \"bandit\", rarity = \"legend\" },\n\tUnload = { name = \"난사\", cost = 1, desc = \"피해 14.\", kind = \"Attack\", damage = 14, class = \"bandit\", rarity = \"legend\" },\n\tWraithForm = { name = \"망령화\", cost = 3, desc = \"매 턴 방어도 +8.\", kind = \"Power\", powerEffect = \"blockPerTurn\", value = 8, class = \"bandit\", rarity = \"legend\" },\n}\nself.DrawPile = {}\nfor i = 1, #self.RunDeck do\n\tself.DrawPile[i] = self.RunDeck[i]\nend\nself:Shuffle(self.DrawPile)\nself:BuildMonsters()\nself:RenderCombat()\nself:StartPlayerTurn()\nself:ApplyRelics(\"combatStart\")\nself:RenderCombat()", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -1973,7 +1980,7 @@ "Name": null }, "Arguments": [], - "Code": "if self.PlayerJob ~= \"\" and self.Jobs ~= nil then\n\tfor cls, list in pairs(self.Jobs) do\n\t\tfor i = 1, #list do\n\t\t\tif list[i].id == self.PlayerJob then\n\t\t\t\treturn list[i].name\n\t\t\tend\n\t\tend\n\tend\nend\nif self.SelectedClass == \"warrior\" then\n\treturn \"전사\"\nelseif self.SelectedClass == \"magician\" then\n\treturn \"마법사\"\nend\nreturn \"플레이어\"", + "Code": "if self.PlayerJob ~= \"\" and self.Jobs ~= nil then\n\tfor cls, list in pairs(self.Jobs) do\n\t\tfor i = 1, #list do\n\t\t\tif list[i].id == self.PlayerJob then\n\t\t\t\treturn list[i].name\n\t\t\tend\n\t\tend\n\tend\nend\nif self.SelectedClass == \"warrior\" then\n\treturn \"전사\"\nelseif self.SelectedClass == \"bandit\" then\n\treturn \"도적\"\nelseif self.SelectedClass == \"magician\" then\n\treturn \"마법사\"\nend\nreturn \"플레이어\"", "Scope": 2, "ExecSpace": 6, "Attributes": [], diff --git a/data/cardframes.json b/data/cardframes.json index 99267c0..d98bf21 100644 --- a/data/cardframes.json +++ b/data/cardframes.json @@ -6,7 +6,8 @@ }, "classToFrame": { "warrior": "warrior", "fighter": "warrior", "page": "warrior", "spearman": "warrior", - "magician": "magician", "firepoison": "magician", "icelightning": "magician", "cleric": "magician" + "magician": "magician", "firepoison": "magician", "icelightning": "magician", "cleric": "magician", + "bandit": "bandit", "shiv": "bandit", "poisoner": "bandit", "trickster": "bandit" }, "rewardWeights": { "normal": 70, "unique": 25, "legend": 5 } } diff --git a/data/cards.json b/data/cards.json index d30dd02..00ca47a 100644 --- a/data/cards.json +++ b/data/cards.json @@ -335,6 +335,730 @@ "desc": "피해 8", "image": "0265e103b4904f178b1c2bdcd54d5975", "rarity": "unique" + }, + "SilentStrike": { + "name": "타격", + "cost": 1, + "kind": "Attack", + "class": "bandit", + "rarity": "normal", + "damage": 6, + "desc": "피해 6." + }, + "SilentDefend": { + "name": "수비", + "cost": 1, + "kind": "Skill", + "class": "bandit", + "rarity": "normal", + "block": 5, + "desc": "방어도 5." + }, + "Neutralize": { + "name": "무력화", + "cost": 0, + "kind": "Attack", + "class": "bandit", + "rarity": "normal", + "damage": 3, + "weak": 1, + "desc": "피해 3. 약화 1 부여." + }, + "Survivor": { + "name": "생존자", + "cost": 1, + "kind": "Skill", + "class": "bandit", + "rarity": "normal", + "block": 8, + "draw": 1, + "desc": "방어도 8. 카드 1장 드로우." + }, + "Acrobatics": { + "name": "곡예", + "cost": 1, + "kind": "Skill", + "class": "bandit", + "rarity": "normal", + "draw": 3, + "desc": "카드 3장 드로우." + }, + "Backflip": { + "name": "백플립", + "cost": 1, + "kind": "Skill", + "class": "bandit", + "rarity": "normal", + "block": 5, + "draw": 2, + "desc": "방어도 5. 카드 2장 드로우." + }, + "Bane": { + "name": "파멸", + "cost": 1, + "kind": "Attack", + "class": "bandit", + "rarity": "normal", + "damage": 7, + "hits": 2, + "desc": "피해 7을 2회." + }, + "BladeDance": { + "name": "칼날 춤", + "cost": 1, + "kind": "Attack", + "class": "bandit", + "rarity": "normal", + "damage": 4, + "hits": 3, + "desc": "시브식 공격: 피해 4를 3회." + }, + "CloakAndDagger": { + "name": "망토와 단검", + "cost": 1, + "kind": "Attack", + "class": "bandit", + "rarity": "normal", + "block": 6, + "damage": 4, + "desc": "방어도 6. 피해 4." + }, + "DaggerSpray": { + "name": "단검 분사", + "cost": 1, + "kind": "Attack", + "class": "bandit", + "rarity": "normal", + "damage": 4, + "hits": 2, + "aoe": true, + "desc": "모든 적에게 피해 4를 2회." + }, + "DaggerThrow": { + "name": "단검 투척", + "cost": 1, + "kind": "Attack", + "class": "bandit", + "rarity": "normal", + "damage": 9, + "draw": 1, + "desc": "피해 9. 카드 1장 드로우." + }, + "DeadlyPoison": { + "name": "맹독", + "cost": 1, + "kind": "Skill", + "class": "bandit", + "rarity": "normal", + "poison": 5, + "desc": "독 5 부여." + }, + "Deflect": { + "name": "흘리기", + "cost": 0, + "kind": "Skill", + "class": "bandit", + "rarity": "normal", + "block": 4, + "desc": "방어도 4." + }, + "DodgeAndRoll": { + "name": "회피와 구르기", + "cost": 1, + "kind": "Skill", + "class": "bandit", + "rarity": "normal", + "block": 8, + "desc": "방어도 8." + }, + "FlyingKnee": { + "name": "날아차기", + "cost": 1, + "kind": "Attack", + "class": "bandit", + "rarity": "normal", + "damage": 8, + "desc": "피해 8." + }, + "Outmaneuver": { + "name": "책략", + "cost": 1, + "kind": "Power", + "class": "bandit", + "rarity": "normal", + "powerEffect": "energyPerTurn", + "value": 1, + "desc": "매 턴 에너지 +1." + }, + "PiercingWail": { + "name": "꿰뚫는 비명", + "cost": 1, + "kind": "Skill", + "class": "bandit", + "rarity": "normal", + "weak": 3, + "desc": "약화 3 부여." + }, + "PoisonedStab": { + "name": "독 찌르기", + "cost": 1, + "kind": "Attack", + "class": "bandit", + "rarity": "normal", + "damage": 6, + "poison": 3, + "desc": "피해 6. 독 3 부여." + }, + "Prepared": { + "name": "준비", + "cost": 0, + "kind": "Skill", + "class": "bandit", + "rarity": "normal", + "draw": 1, + "desc": "카드 1장 드로우." + }, + "QuickSlash": { + "name": "빠른 베기", + "cost": 1, + "kind": "Attack", + "class": "bandit", + "rarity": "normal", + "damage": 8, + "draw": 1, + "desc": "피해 8. 카드 1장 드로우." + }, + "Slice": { + "name": "얇게 베기", + "cost": 0, + "kind": "Attack", + "class": "bandit", + "rarity": "normal", + "damage": 6, + "desc": "피해 6." + }, + "SneakyStrike": { + "name": "기습 타격", + "cost": 2, + "kind": "Attack", + "class": "bandit", + "rarity": "normal", + "damage": 12, + "desc": "피해 12." + }, + "SuckerPunch": { + "name": "불시의 일격", + "cost": 1, + "kind": "Attack", + "class": "bandit", + "rarity": "normal", + "damage": 7, + "weak": 1, + "desc": "피해 7. 약화 1 부여." + }, + "Accuracy": { + "name": "정확도", + "cost": 1, + "kind": "Power", + "class": "bandit", + "rarity": "unique", + "powerEffect": "strengthPerTurn", + "value": 1, + "desc": "시브 지원: 매 턴 힘 +1." + }, + "AllOutAttack": { + "name": "총공격", + "cost": 1, + "kind": "Attack", + "class": "bandit", + "rarity": "unique", + "damage": 10, + "aoe": true, + "desc": "모든 적에게 피해 10." + }, + "Backstab": { + "name": "등 찌르기", + "cost": 0, + "kind": "Attack", + "class": "bandit", + "rarity": "unique", + "damage": 11, + "desc": "피해 11." + }, + "Blur": { + "name": "잔상", + "cost": 1, + "kind": "Skill", + "class": "bandit", + "rarity": "unique", + "block": 8, + "desc": "방어도 8." + }, + "BouncingFlask": { + "name": "튕기는 플라스크", + "cost": 2, + "kind": "Skill", + "class": "bandit", + "rarity": "unique", + "poison": 9, + "desc": "독 9 부여." + }, + "CalculatedGamble": { + "name": "계산된 도박", + "cost": 0, + "kind": "Skill", + "class": "bandit", + "rarity": "unique", + "draw": 3, + "desc": "카드 3장 드로우." + }, + "Caltrops": { + "name": "마름쇠", + "cost": 1, + "kind": "Power", + "class": "bandit", + "rarity": "unique", + "powerEffect": "blockPerTurn", + "value": 3, + "desc": "매 턴 방어도 +3." + }, + "Catalyst": { + "name": "촉매", + "cost": 1, + "kind": "Skill", + "class": "bandit", + "rarity": "unique", + "poison": 8, + "desc": "독 8 부여." + }, + "Choke": { + "name": "목 조르기", + "cost": 2, + "kind": "Attack", + "class": "bandit", + "rarity": "unique", + "damage": 12, + "vuln": 2, + "desc": "피해 12. 취약 2 부여." + }, + "Concentrate": { + "name": "집중", + "cost": 0, + "kind": "Power", + "class": "bandit", + "rarity": "unique", + "powerEffect": "energyPerTurn", + "value": 1, + "desc": "매 턴 에너지 +1." + }, + "CripplingCloud": { + "name": "불구름", + "cost": 2, + "kind": "Skill", + "class": "bandit", + "rarity": "unique", + "poison": 4, + "weak": 2, + "desc": "독 4와 약화 2 부여." + }, + "Dash": { + "name": "질주", + "cost": 2, + "kind": "Attack", + "class": "bandit", + "rarity": "unique", + "block": 10, + "damage": 10, + "desc": "방어도 10. 피해 10." + }, + "Distraction": { + "name": "교란", + "cost": 1, + "kind": "Skill", + "class": "bandit", + "rarity": "unique", + "draw": 1, + "desc": "카드 1장 드로우." + }, + "EndlessAgony": { + "name": "끝없는 고통", + "cost": 0, + "kind": "Attack", + "class": "bandit", + "rarity": "unique", + "damage": 4, + "hits": 2, + "desc": "피해 4를 2회." + }, + "EscapePlan": { + "name": "탈출 계획", + "cost": 0, + "kind": "Skill", + "class": "bandit", + "rarity": "unique", + "block": 3, + "draw": 1, + "desc": "카드 1장 드로우. 방어도 3." + }, + "Eviscerate": { + "name": "절개", + "cost": 3, + "kind": "Attack", + "class": "bandit", + "rarity": "unique", + "damage": 7, + "hits": 3, + "desc": "피해 7을 3회." + }, + "Expertise": { + "name": "전문가", + "cost": 1, + "kind": "Skill", + "class": "bandit", + "rarity": "unique", + "draw": 3, + "desc": "카드 3장 드로우." + }, + "Finisher": { + "name": "마무리", + "cost": 1, + "kind": "Attack", + "class": "bandit", + "rarity": "unique", + "damage": 6, + "hits": 2, + "desc": "피해 6을 2회." + }, + "Flechettes": { + "name": "플레셰트", + "cost": 1, + "kind": "Attack", + "class": "bandit", + "rarity": "unique", + "damage": 4, + "hits": 3, + "desc": "피해 4를 3회." + }, + "Footwork": { + "name": "발놀림", + "cost": 1, + "kind": "Power", + "class": "bandit", + "rarity": "unique", + "powerEffect": "blockPerTurn", + "value": 2, + "desc": "매 턴 방어도 +2." + }, + "HeelHook": { + "name": "발뒤꿈치 걸기", + "cost": 1, + "kind": "Attack", + "class": "bandit", + "rarity": "unique", + "damage": 5, + "draw": 1, + "desc": "피해 5. 카드 1장 드로우." + }, + "InfiniteBlades": { + "name": "무한의 칼날", + "cost": 1, + "kind": "Power", + "class": "bandit", + "rarity": "unique", + "powerEffect": "strengthPerTurn", + "value": 1, + "desc": "시브 지원: 매 턴 힘 +1." + }, + "LegSweep": { + "name": "다리 걸기", + "cost": 2, + "kind": "Skill", + "class": "bandit", + "rarity": "unique", + "block": 11, + "weak": 2, + "desc": "방어도 11. 약화 2 부여." + }, + "MasterfulStab": { + "name": "달인의 찌르기", + "cost": 0, + "kind": "Attack", + "class": "bandit", + "rarity": "unique", + "damage": 12, + "desc": "피해 12." + }, + "NoxiousFumes": { + "name": "유독 가스", + "cost": 1, + "kind": "Power", + "class": "bandit", + "rarity": "unique", + "powerEffect": "strengthPerTurn", + "value": 1, + "desc": "독 강화: 매 턴 힘 +1." + }, + "Predator": { + "name": "포식자", + "cost": 2, + "kind": "Attack", + "class": "bandit", + "rarity": "unique", + "damage": 15, + "draw": 2, + "desc": "피해 15. 카드 2장 드로우." + }, + "Reflex": { + "name": "반사 신경", + "cost": 0, + "kind": "Skill", + "class": "bandit", + "rarity": "unique", + "draw": 2, + "desc": "카드 2장 드로우." + }, + "RiddleWithHoles": { + "name": "벌집 만들기", + "cost": 2, + "kind": "Attack", + "class": "bandit", + "rarity": "unique", + "damage": 3, + "hits": 5, + "desc": "피해 3을 5회." + }, + "Setup": { + "name": "설치", + "cost": 1, + "kind": "Skill", + "class": "bandit", + "rarity": "unique", + "draw": 1, + "desc": "카드 1장 드로우." + }, + "Skewer": { + "name": "꿰뚫기", + "cost": 2, + "kind": "Attack", + "class": "bandit", + "rarity": "unique", + "damage": 7, + "hits": 3, + "desc": "피해 7을 3회." + }, + "Tactician": { + "name": "전술가", + "cost": 0, + "kind": "Power", + "class": "bandit", + "rarity": "unique", + "powerEffect": "energyPerTurn", + "value": 1, + "desc": "매 턴 에너지 +1." + }, + "Terror": { + "name": "공포", + "cost": 1, + "kind": "Skill", + "class": "bandit", + "rarity": "unique", + "vuln": 5, + "desc": "취약 5 부여." + }, + "WellLaidPlans": { + "name": "치밀한 계획", + "cost": 1, + "kind": "Power", + "class": "bandit", + "rarity": "unique", + "powerEffect": "blockPerTurn", + "value": 2, + "desc": "매 턴 방어도 +2." + }, + "AThousandCuts": { + "name": "천 개의 상처", + "cost": 2, + "kind": "Power", + "class": "bandit", + "rarity": "legend", + "powerEffect": "strengthPerTurn", + "value": 1, + "desc": "매 턴 힘 +1." + }, + "Adrenaline": { + "name": "아드레날린", + "cost": 0, + "kind": "Skill", + "class": "bandit", + "rarity": "legend", + "draw": 2, + "strength": 1, + "desc": "힘 +1. 카드 2장 드로우." + }, + "AfterImage": { + "name": "잔상 효과", + "cost": 1, + "kind": "Power", + "class": "bandit", + "rarity": "legend", + "powerEffect": "blockPerTurn", + "value": 1, + "desc": "매 턴 방어도 +1." + }, + "Alchemize": { + "name": "연금술", + "cost": 1, + "kind": "Skill", + "class": "bandit", + "rarity": "legend", + "heal": 8, + "desc": "HP 8 회복." + }, + "BulletTime": { + "name": "불릿 타임", + "cost": 2, + "kind": "Power", + "class": "bandit", + "rarity": "legend", + "powerEffect": "energyPerTurn", + "value": 2, + "desc": "매 턴 에너지 +2." + }, + "Burst": { + "name": "폭발", + "cost": 1, + "kind": "Power", + "class": "bandit", + "rarity": "legend", + "powerEffect": "strengthPerTurn", + "value": 1, + "desc": "매 턴 힘 +1." + }, + "CorpseExplosion": { + "name": "시체 폭발", + "cost": 2, + "kind": "Skill", + "class": "bandit", + "rarity": "legend", + "poison": 6, + "vuln": 2, + "desc": "독 6과 취약 2 부여." + }, + "DieDieDie": { + "name": "죽어 죽어 죽어", + "cost": 1, + "kind": "Attack", + "class": "bandit", + "rarity": "legend", + "damage": 13, + "aoe": true, + "desc": "모든 적에게 피해 13." + }, + "Doppelganger": { + "name": "도플갱어", + "cost": 2, + "kind": "Skill", + "class": "bandit", + "rarity": "legend", + "draw": 3, + "desc": "카드 3장 드로우." + }, + "Envenom": { + "name": "맹독 바르기", + "cost": 2, + "kind": "Power", + "class": "bandit", + "rarity": "legend", + "powerEffect": "strengthPerTurn", + "value": 1, + "desc": "독 지원: 매 턴 힘 +1." + }, + "GlassKnife": { + "name": "유리 칼", + "cost": 1, + "kind": "Attack", + "class": "bandit", + "rarity": "legend", + "damage": 8, + "hits": 2, + "desc": "피해 8을 2회." + }, + "GrandFinale": { + "name": "대단원", + "cost": 0, + "kind": "Attack", + "class": "bandit", + "rarity": "legend", + "damage": 50, + "aoe": true, + "desc": "모든 적에게 피해 50." + }, + "Malaise": { + "name": "불쾌감", + "cost": 2, + "kind": "Skill", + "class": "bandit", + "rarity": "legend", + "weak": 3, + "desc": "약화 3 부여." + }, + "Nightmare": { + "name": "악몽", + "cost": 3, + "kind": "Skill", + "class": "bandit", + "rarity": "legend", + "draw": 3, + "desc": "카드 3장 드로우." + }, + "PhantasmalKiller": { + "name": "환영 살인마", + "cost": 1, + "kind": "Power", + "class": "bandit", + "rarity": "legend", + "powerEffect": "strengthPerTurn", + "value": 2, + "desc": "매 턴 힘 +2." + }, + "StormOfSteel": { + "name": "강철 폭풍", + "cost": 1, + "kind": "Attack", + "class": "bandit", + "rarity": "legend", + "damage": 4, + "hits": 5, + "desc": "피해 4를 5회." + }, + "ToolsOfTheTrade": { + "name": "거래의 도구", + "cost": 1, + "kind": "Power", + "class": "bandit", + "rarity": "legend", + "powerEffect": "energyPerTurn", + "value": 1, + "desc": "매 턴 에너지 +1." + }, + "Unload": { + "name": "난사", + "cost": 1, + "kind": "Attack", + "class": "bandit", + "rarity": "legend", + "damage": 14, + "desc": "피해 14." + }, + "WraithForm": { + "name": "망령화", + "cost": 3, + "kind": "Power", + "class": "bandit", + "rarity": "legend", + "powerEffect": "blockPerTurn", + "value": 8, + "desc": "매 턴 방어도 +8." } }, "starterDecks": { @@ -361,6 +1085,20 @@ "MagicGuard", "MagicGuard", "MagicClaw" + ], + "bandit": [ + "SilentStrike", + "SilentStrike", + "SilentStrike", + "SilentStrike", + "SilentStrike", + "SilentDefend", + "SilentDefend", + "SilentDefend", + "SilentDefend", + "SilentDefend", + "Neutralize", + "Survivor" ] } } diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index b14e159..851df12 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -1,4 +1,4 @@ -import { readFileSync, writeFileSync } from 'node:fs'; +import { readFileSync, writeFileSync } from 'node:fs'; const CARDS = JSON.parse(readFileSync('data/cards.json', 'utf8')); const ENEMIES = JSON.parse(readFileSync('data/enemies.json', 'utf8')); @@ -6,6 +6,7 @@ const ENEMIES = JSON.parse(readFileSync('data/enemies.json', 'utf8')); // 검증 (fail-fast): 잘못된 데이터면 생성 중단 const CLASSES = { warrior: { label: '전사', maxHp: 80 }, + bandit: { label: '도적', maxHp: 70 }, magician: { label: '마법사', maxHp: 70 }, }; for (const cls of Object.keys(CLASSES)) { @@ -26,6 +27,11 @@ const JOBS = { { id: 'icelightning', name: '위자드(썬·콜)', desc: '광역·빙결 특화\n썬더 볼트(전체)\n콜드 빔 · 칠링 스텝', starter: 'ThunderBolt' }, { id: 'cleric', name: '클레릭', desc: '회복·축복 특화\n힐 · 블레스\n홀리 애로우', starter: 'Heal' }, ], + bandit: [ + { id: 'shiv', name: 'Shiv', desc: 'Many small attacks\nBlade Dance\nAccuracy · After Image', starter: 'BladeDance' }, + { id: 'poisoner', name: 'Poison', desc: 'Poison scaling\nDeadly Poison\nCatalyst · Noxious Fumes', starter: 'DeadlyPoison' }, + { id: 'trickster', name: 'Trickster', desc: 'Draw and tempo\nAcrobatics\nAdrenaline · Tools', starter: 'Acrobatics' }, + ], }; for (const [cls, jobs] of Object.entries(JOBS)) { for (const j of jobs) { @@ -2242,7 +2248,7 @@ function upsertUi() { })); const classCards = [ { key: 'Warrior', label: '\uC804\uC0AC', desc: '\uAC15\uD55C \uACF5\uACA9\uACFC \uBC29\uC5B4', x: -360, enabled: true, tint: { r: 0.74, g: 0.32, b: 0.28, a: 1 } }, - { key: 'Thief', label: '\uB3C4\uC801', desc: '\uCD94\uD6C4 \uC5F4\uB9BC', x: 0, enabled: false, tint: { r: 0.18, g: 0.19, b: 0.21, a: 1 } }, + { key: 'Thief', label: '\uB3C4\uC801', desc: 'Silent deck', x: 0, enabled: true, tint: { r: 0.26, g: 0.5, b: 0.34, a: 1 } }, { key: 'Mage', label: '\uB9C8\uBC95\uC0AC', desc: '\uB9C8\uBC95 \uC6D0\uAC70\uB9AC \uB51C\uB7EC', x: 360, enabled: true, tint: { r: 0.3, g: 0.4, b: 0.75, a: 1 } }, ]; for (let i = 0; i < classCards.length; i++) { @@ -2432,6 +2438,7 @@ function writeCodeblocks() { prop('any', 'EndTurnHandler'), prop('any', 'NewGameHandler'), prop('any', 'WarriorSelectHandler'), + prop('any', 'ThiefSelectHandler'), prop('any', 'MageSelectHandler'), prop('any', 'AscMinusHandler'), prop('any', 'AscPlusHandler'), @@ -2602,6 +2609,14 @@ if warrior ~= nil and warrior.ButtonComponent ~= nil then end self.WarriorSelectHandler = warrior:ConnectEvent(ButtonClickEvent, function() self:SelectClass("warrior") end) end +local thief = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/ThiefButton") +if thief ~= nil and thief.ButtonComponent ~= nil then + if self.ThiefSelectHandler ~= nil then + thief:DisconnectEvent(ButtonClickEvent, self.ThiefSelectHandler) + self.ThiefSelectHandler = nil + end + self.ThiefSelectHandler = thief:ConnectEvent(ButtonClickEvent, function() self:SelectClass("bandit") end) +end local mage = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/MageButton") if mage ~= nil and mage.ButtonComponent ~= nil then if self.MageSelectHandler ~= nil then @@ -2657,14 +2672,24 @@ if mage ~= nil and mage.SpriteGUIRendererComponent ~= nil then mage.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1) end end +local thief = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/ThiefButton") +if thief ~= nil and thief.SpriteGUIRendererComponent ~= nil then + if self.SelectedClass == "bandit" then + thief.SpriteGUIRendererComponent.Color = Color(0.28, 0.36, 0.46, 1) + else + thief.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1) + end +end if self.SelectedClass == "warrior" then self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "전사 선택됨") +elseif self.SelectedClass == "bandit" then + self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "도적 선택됨") elseif self.SelectedClass == "magician" then self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "마법사 선택됨") else self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "직업을 선택하고 시작하세요") end`), - method('StartNewGame', `if self.SelectedClass ~= "warrior" and self.SelectedClass ~= "magician" then + method('StartNewGame', `if self.SelectedClass ~= "warrior" and self.SelectedClass ~= "bandit" and self.SelectedClass ~= "magician" then self:SetText("/ui/DefaultGroup/CharacterSelectHud/Status", "직업을 먼저 선택하세요") return end @@ -2679,6 +2704,9 @@ end`, [ method('StartRun', `if self.SelectedClass == "magician" then self.PlayerMaxHp = ${CLASSES.magician.maxHp} self.RunDeck = { ${CARDS.starterDecks.magician.map(luaStr).join(', ')} } +elseif self.SelectedClass == "bandit" then + self.PlayerMaxHp = ${CLASSES.bandit.maxHp} + self.RunDeck = { ${CARDS.starterDecks.bandit.map(luaStr).join(', ')} } else self.PlayerMaxHp = ${CLASSES.warrior.maxHp} self.RunDeck = { ${CARDS.starterDecks.warrior.map(luaStr).join(', ')} } @@ -3745,6 +3773,8 @@ self:SetEntityEnabled("/ui/DefaultGroup/JobSelectHud", true)`), end if self.SelectedClass == "warrior" then return "전사" +elseif self.SelectedClass == "bandit" then + return "도적" elseif self.SelectedClass == "magician" then return "마법사" end diff --git a/ui/DefaultGroup.ui b/ui/DefaultGroup.ui index 2613f50..bb494ac 100644 --- a/ui/DefaultGroup.ui +++ b/ui/DefaultGroup.ui @@ -234993,9 +234993,9 @@ "PreserveSprite": 0, "StartFrameIndex": 0, "Color": { - "r": 0.11, - "g": 0.12, - "b": 0.14, + "r": 0.16, + "g": 0.2, + "b": 0.26, "a": 1 }, "DropShadow": false, @@ -235025,7 +235025,7 @@ "a": 1 }, "OutlineWidth": 3, - "RaycastTarget": false, + "RaycastTarget": true, "Type": 1, "Enable": true }, @@ -235074,7 +235074,7 @@ "KeyCode": 0, "OverrideSorting": false, "Transition": 1, - "Enable": false + "Enable": true } ], "@version": 1 @@ -235232,9 +235232,9 @@ "DropShadowDistance": 32, "Font": 0, "FontColor": { - "r": 0.55, - "g": 0.58, - "b": 0.62, + "r": 0.94, + "g": 0.74, + "b": 0.26, "a": 1 }, "FontSize": 34, @@ -235369,9 +235369,9 @@ "PreserveSprite": 0, "StartFrameIndex": 0, "Color": { - "r": 0.18, - "g": 0.19, - "b": 0.21, + "r": 0.26, + "g": 0.5, + "b": 0.34, "a": 1 }, "DropShadow": false, @@ -235561,9 +235561,9 @@ "DropShadowDistance": 32, "Font": 0, "FontColor": { - "r": 0.52, - "g": 0.55, - "b": 0.59, + "r": 0.86, + "g": 0.9, + "b": 0.94, "a": 1 }, "FontSize": 20, @@ -235589,7 +235589,7 @@ "bottom": 0 }, "SizeFit": false, - "Text": "추후 열림", + "Text": "Silent deck", "UseOutLine": true, "Enable": true } @@ -235597,288 +235597,6 @@ "@version": 1 } }, - { - "id": "0e000097-0000-4000-8000-00000e000097", - "path": "/ui/DefaultGroup/CharacterSelectHud/ThiefButton/LockBody", - "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent", - "jsonString": { - "name": "LockBody", - "path": "/ui/DefaultGroup/CharacterSelectHud/ThiefButton/LockBody", - "nameEditable": true, - "enable": true, - "visible": true, - "localize": true, - "displayOrder": 3, - "pathConstraints": "/////", - "revision": 1, - "origin": { - "type": "Model", - "entry_id": "UISprite", - "sub_entity_id": null, - "root_entity_id": null, - "replaced_model_id": null - }, - "modelId": "uisprite", - "@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": 38, - "y": 33 - }, - "OffsetMin": { - "x": -38, - "y": -25 - }, - "Pivot": { - "x": 0.5, - "y": 0.5 - }, - "RectSize": { - "x": 76, - "y": 58 - }, - "UIMode": 1, - "UIScale": { - "x": 1, - "y": 1, - "z": 1 - }, - "UIVersion": 2, - "anchoredPosition": { - "x": 0, - "y": 4 - }, - "Position": { - "x": 0, - "y": 4, - "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.78, - "g": 0.69, - "b": 0.42, - "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": false, - "Type": 1, - "Enable": true - } - ], - "@version": 1 - } - }, - { - "id": "0e0000a1-0000-4000-8000-00000e0000a1", - "path": "/ui/DefaultGroup/CharacterSelectHud/ThiefButton/LockShackle", - "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent", - "jsonString": { - "name": "LockShackle", - "path": "/ui/DefaultGroup/CharacterSelectHud/ThiefButton/LockShackle", - "nameEditable": true, - "enable": true, - "visible": true, - "localize": true, - "displayOrder": 4, - "pathConstraints": "/////", - "revision": 1, - "origin": { - "type": "Model", - "entry_id": "UISprite", - "sub_entity_id": null, - "root_entity_id": null, - "replaced_model_id": null - }, - "modelId": "uisprite", - "@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": 27, - "y": 69 - }, - "OffsetMin": { - "x": -27, - "y": 27 - }, - "Pivot": { - "x": 0.5, - "y": 0.5 - }, - "RectSize": { - "x": 54, - "y": 42 - }, - "UIMode": 1, - "UIScale": { - "x": 1, - "y": 1, - "z": 1 - }, - "UIVersion": 2, - "anchoredPosition": { - "x": 0, - "y": 48 - }, - "Position": { - "x": 0, - "y": 48, - "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.78, - "g": 0.69, - "b": 0.42, - "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": false, - "Type": 1, - "Enable": true - } - ], - "@version": 1 - } - }, { "id": "0e000070-0000-4000-8000-00000e000070", "path": "/ui/DefaultGroup/CharacterSelectHud/MageButton", From 4d3f6fc0af66bed5cd6e8b1144d08e91918ccf36 Mon Sep 17 00:00:00 2001 From: gahusb Date: Sun, 14 Jun 2026 02:42:11 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat(ui):=20=EC=B9=B4=EB=93=9C=20hover=20?= =?UTF-8?q?=ED=99=95=EB=8C=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 손패 카드에 마우스 진입/이탈 이벤트를 연결해 hover 시 1.12배로 확대 - 보상 카드와 상점 카드에도 UITouchReceiveComponent를 추가하고 같은 hover 확대 동작 적용 - ApplyCardFace에서 카드 렌더 시 UIScale을 기본값으로 리셋해 재사용 카드가 확대 상태로 남지 않도록 처리 - 생성기 변경 후 ui/DefaultGroup.ui와 SlayDeckController.codeblock 산출물 재생성 검증: - node --check tools/deck/gen-slaydeck.mjs - node --test tools/balance/sim-balance.test.mjs tools/map/rogue-map.test.mjs - SetCardHover/UITouchEnterEvent/UITouchReceiveComponent 산출물 카운트 확인 --- RootDesk/MyDesk/SlayDeckController.codeblock | 34 ++++++++++++++++-- tools/deck/gen-slaydeck.mjs | 34 ++++++++++++++++-- ui/DefaultGroup.ui | 36 ++++++++++++++++---- 3 files changed, 94 insertions(+), 10 deletions(-) diff --git a/RootDesk/MyDesk/SlayDeckController.codeblock b/RootDesk/MyDesk/SlayDeckController.codeblock index 08ae87b..bb7e05c 100644 --- a/RootDesk/MyDesk/SlayDeckController.codeblock +++ b/RootDesk/MyDesk/SlayDeckController.codeblock @@ -1077,7 +1077,7 @@ "Name": null }, "Arguments": [], - "Code": "local endTurn = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckHud/EndTurnButton\")\nif endTurn ~= nil and endTurn.ButtonComponent ~= nil then\n\tif self.EndTurnHandler ~= nil then\n\t\tendTurn:DisconnectEvent(ButtonClickEvent, self.EndTurnHandler)\n\t\tself.EndTurnHandler = nil\n\tend\n\tself.EndTurnHandler = endTurn:ConnectEvent(ButtonClickEvent, function() self:EndPlayerTurn() end)\nend\nlocal drawPile = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckHud/DrawPile\")\nif drawPile ~= nil and drawPile.ButtonComponent ~= nil then\n\tif self.DrawPileHandler ~= nil then\n\t\tdrawPile:DisconnectEvent(ButtonClickEvent, self.DrawPileHandler)\n\t\tself.DrawPileHandler = nil\n\tend\n\tself.DrawPileHandler = drawPile:ConnectEvent(ButtonClickEvent, function() self:OpenDeckInspect(\"draw\") end)\nend\nlocal discardPile = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckHud/DiscardPile\")\nif discardPile ~= nil and discardPile.ButtonComponent ~= nil then\n\tif self.DiscardPileHandler ~= nil then\n\t\tdiscardPile:DisconnectEvent(ButtonClickEvent, self.DiscardPileHandler)\n\t\tself.DiscardPileHandler = nil\n\tend\n\tself.DiscardPileHandler = discardPile:ConnectEvent(ButtonClickEvent, function() self:OpenDeckInspect(\"discard\") end)\nend\nlocal inspectClose = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckInspectHud/Close\")\nif inspectClose ~= nil and inspectClose.ButtonComponent ~= nil then\n\tif self.DeckInspectCloseHandler ~= nil then\n\t\tinspectClose:DisconnectEvent(ButtonClickEvent, self.DeckInspectCloseHandler)\n\t\tself.DeckInspectCloseHandler = nil\n\tend\n\tself.DeckInspectCloseHandler = inspectClose:ConnectEvent(ButtonClickEvent, function() self:CloseDeckInspect() end)\nend\nlocal allDeckButton = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/TopBar/AllDeckButton\")\nif allDeckButton ~= nil and allDeckButton.ButtonComponent ~= nil then\n\tif self.AllDeckHandler ~= nil then\n\t\tallDeckButton:DisconnectEvent(ButtonClickEvent, self.AllDeckHandler)\n\t\tself.AllDeckHandler = nil\n\tend\n\tself.AllDeckHandler = allDeckButton:ConnectEvent(ButtonClickEvent, function() self:OpenAllDeck() end)\nend\nlocal allDeckClose = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckAllHud/Close\")\nif allDeckClose ~= nil and allDeckClose.ButtonComponent ~= nil then\n\tif self.AllDeckCloseHandler ~= nil then\n\t\tallDeckClose:DisconnectEvent(ButtonClickEvent, self.AllDeckCloseHandler)\n\t\tself.AllDeckCloseHandler = nil\n\tend\n\tself.AllDeckCloseHandler = allDeckClose:ConnectEvent(ButtonClickEvent, function() self:CloseAllDeck() end)\nend\nfor i = 1, 5 do\n\tlocal cardEntity = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CardHand/Card\" .. tostring(i))\n\tif cardEntity ~= nil and cardEntity.UITouchReceiveComponent ~= nil then\n\t\tcardEntity:ConnectEvent(UITouchBeginDragEvent, function(ev) self:OnCardDragBegin(i) end)\n\t\tcardEntity:ConnectEvent(UITouchDragEvent, function(ev) self:OnCardDrag(i, ev.TouchPoint) end)\n\t\tcardEntity:ConnectEvent(UITouchEndDragEvent, function(ev) self:OnCardDragEnd(i, ev.TouchPoint) end)\n\tend\nend\nfor i = 1, 3 do\n\tlocal rc = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RewardHud/Reward\" .. tostring(i))\n\tif rc ~= nil and rc.ButtonComponent ~= nil then\n\t\trc:ConnectEvent(ButtonClickEvent, function() self:PickReward(i) end)\n\tend\nend\nlocal skip = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RewardHud/Skip\")\nif skip ~= nil and skip.ButtonComponent ~= nil then\n\tskip:ConnectEvent(ButtonClickEvent, function() self:PickReward(0) end)\nend\nlocal mapNodeIds = {}\nfor r = 1, 7 do\n\tfor c = 1, 4 do\n\t\ttable.insert(mapNodeIds, \"r\" .. tostring(r) .. \"c\" .. tostring(c))\n\tend\nend\ntable.insert(mapNodeIds, \"boss\")\nfor i = 1, #mapNodeIds do\n\tlocal nid = mapNodeIds[i]\n\tlocal mn = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/MapHud/Node_\" .. nid)\n\tif mn ~= nil and mn.ButtonComponent ~= nil then\n\t\tmn:ConnectEvent(ButtonClickEvent, function() self:PickNode(nid) end)\n\tend\nend\nfor i = 1, 3 do\n\tlocal sc = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/ShopHud/Card\" .. tostring(i))\n\tif sc ~= nil and sc.ButtonComponent ~= nil then\n\t\tsc:ConnectEvent(ButtonClickEvent, function() self:BuyCard(i) end)\n\tend\nend\nlocal shopLeave = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/ShopHud/Leave\")\nif shopLeave ~= nil and shopLeave.ButtonComponent ~= nil then\n\tshopLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)\nend\nlocal shopRelic = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/ShopHud/Relic\")\nif shopRelic ~= nil and shopRelic.ButtonComponent ~= nil then\n\tshopRelic:ConnectEvent(ButtonClickEvent, function() self:BuyRelic() end)\nend\nlocal restLeave = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RestHud/Leave\")\nif restLeave ~= nil and restLeave.ButtonComponent ~= nil then\n\trestLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)\nend\nfor i = 1, 4 do\n\tlocal ms = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/MonsterSlot\" .. tostring(i))\n\tif ms ~= nil and ms.ButtonComponent ~= nil then\n\t\tms:ConnectEvent(ButtonClickEvent, function() self:SetTarget(i) end)\n\tend\nend\nfor i = 1, 10 do\n\tlocal rs = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/TopBar/RelicSlot\" .. tostring(i))\n\tif rs ~= nil and rs.UITouchReceiveComponent ~= nil then\n\t\tlocal idx = i\n\t\trs:ConnectEvent(UITouchEnterEvent, function()\n\t\t\tlocal rid = nil\n\t\t\tif self.RunRelics ~= nil then rid = self.RunRelics[idx] end\n\t\t\tif rid ~= nil and self.Relics[rid] ~= nil then\n\t\t\t\tself:ShowTooltip(self.Relics[rid].name, self.Relics[rid].desc, -240 + (idx - 1) * 48)\n\t\t\tend\n\t\tend)\n\t\trs:ConnectEvent(UITouchExitEvent, function() self:HideTooltip() end)\n\tend\nend\nfor i = 1, 5 do\n\tlocal ps = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/TopBar/PotionSlot\" .. tostring(i))\n\tif ps ~= nil and ps.UITouchReceiveComponent ~= nil then\n\t\tlocal idx = i\n\t\tps:ConnectEvent(UITouchEnterEvent, function()\n\t\t\tlocal pid = nil\n\t\t\tif self.RunPotions ~= nil then pid = self.RunPotions[idx] end\n\t\t\tif pid ~= nil and self.Potions[pid] ~= nil then\n\t\t\t\tself:ShowTooltip(self.Potions[pid].name, self.Potions[pid].desc, 240 + (idx - 1) * 44)\n\t\t\tend\n\t\tend)\n\t\tps:ConnectEvent(UITouchExitEvent, function() self:HideTooltip() end)\n\t\tps:ConnectEvent(UITouchDownEvent, function() self:OpenPotionMenu(idx) end)\n\tend\nend\nlocal pmUse = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/PotionMenu/Use\")\nif pmUse ~= nil and pmUse.ButtonComponent ~= nil then\n\tpmUse:ConnectEvent(ButtonClickEvent, function() self:UsePotion() end)\nend\nlocal pmToss = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/PotionMenu/Toss\")\nif pmToss ~= nil and pmToss.ButtonComponent ~= nil then\n\tpmToss:ConnectEvent(ButtonClickEvent, function() self:TossPotion() end)\nend\nlocal pmClose = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/PotionMenu/Close\")\nif pmClose ~= nil and pmClose.ButtonComponent ~= nil then\n\tpmClose:ConnectEvent(ButtonClickEvent, function() self:ClosePotionMenu() end)\nend\nlocal shopPotion = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/ShopHud/Potion\")\nif shopPotion ~= nil and shopPotion.ButtonComponent ~= nil then\n\tshopPotion:ConnectEvent(ButtonClickEvent, function() self:BuyPotion() end)\nend\nlocal chest = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/TreasureHud/Chest\")\nif chest ~= nil and chest.ButtonComponent ~= nil then\n\tchest:ConnectEvent(ButtonClickEvent, function() self:OpenChest() end)\nend\nlocal treasureLeave = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/TreasureHud/Leave\")\nif treasureLeave ~= nil and treasureLeave.ButtonComponent ~= nil then\n\ttreasureLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)\nend\nlocal jcRelic = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/JobChoiceHud/RelicButton\")\nif jcRelic ~= nil and jcRelic.ButtonComponent ~= nil then\n\tjcRelic:ConnectEvent(ButtonClickEvent, function() self:PickJobReward(\"relic\") end)\nend\nlocal jcJob = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/JobChoiceHud/JobButton\")\nif jcJob ~= nil and jcJob.ButtonComponent ~= nil then\n\tjcJob:ConnectEvent(ButtonClickEvent, function() self:PickJobReward(\"job\") end)\nend\nfor i = 1, 3 do\n\tlocal slotIdx = i\n\tlocal jb = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/JobSelectHud/Job_slot\" .. tostring(i))\n\tif jb ~= nil and jb.ButtonComponent ~= nil then\n\t\tjb:ConnectEvent(ButtonClickEvent, function()\n\t\t\tif self.JobOpts ~= nil and self.JobOpts[slotIdx] ~= nil then\n\t\t\t\tself:SetJob(self.JobOpts[slotIdx].id)\n\t\t\tend\n\t\tend)\n\tend\nend", + "Code": "local endTurn = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckHud/EndTurnButton\")\nif endTurn ~= nil and endTurn.ButtonComponent ~= nil then\n\tif self.EndTurnHandler ~= nil then\n\t\tendTurn:DisconnectEvent(ButtonClickEvent, self.EndTurnHandler)\n\t\tself.EndTurnHandler = nil\n\tend\n\tself.EndTurnHandler = endTurn:ConnectEvent(ButtonClickEvent, function() self:EndPlayerTurn() end)\nend\nlocal drawPile = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckHud/DrawPile\")\nif drawPile ~= nil and drawPile.ButtonComponent ~= nil then\n\tif self.DrawPileHandler ~= nil then\n\t\tdrawPile:DisconnectEvent(ButtonClickEvent, self.DrawPileHandler)\n\t\tself.DrawPileHandler = nil\n\tend\n\tself.DrawPileHandler = drawPile:ConnectEvent(ButtonClickEvent, function() self:OpenDeckInspect(\"draw\") end)\nend\nlocal discardPile = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckHud/DiscardPile\")\nif discardPile ~= nil and discardPile.ButtonComponent ~= nil then\n\tif self.DiscardPileHandler ~= nil then\n\t\tdiscardPile:DisconnectEvent(ButtonClickEvent, self.DiscardPileHandler)\n\t\tself.DiscardPileHandler = nil\n\tend\n\tself.DiscardPileHandler = discardPile:ConnectEvent(ButtonClickEvent, function() self:OpenDeckInspect(\"discard\") end)\nend\nlocal inspectClose = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckInspectHud/Close\")\nif inspectClose ~= nil and inspectClose.ButtonComponent ~= nil then\n\tif self.DeckInspectCloseHandler ~= nil then\n\t\tinspectClose:DisconnectEvent(ButtonClickEvent, self.DeckInspectCloseHandler)\n\t\tself.DeckInspectCloseHandler = nil\n\tend\n\tself.DeckInspectCloseHandler = inspectClose:ConnectEvent(ButtonClickEvent, function() self:CloseDeckInspect() end)\nend\nlocal allDeckButton = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/TopBar/AllDeckButton\")\nif allDeckButton ~= nil and allDeckButton.ButtonComponent ~= nil then\n\tif self.AllDeckHandler ~= nil then\n\t\tallDeckButton:DisconnectEvent(ButtonClickEvent, self.AllDeckHandler)\n\t\tself.AllDeckHandler = nil\n\tend\n\tself.AllDeckHandler = allDeckButton:ConnectEvent(ButtonClickEvent, function() self:OpenAllDeck() end)\nend\nlocal allDeckClose = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/DeckAllHud/Close\")\nif allDeckClose ~= nil and allDeckClose.ButtonComponent ~= nil then\n\tif self.AllDeckCloseHandler ~= nil then\n\t\tallDeckClose:DisconnectEvent(ButtonClickEvent, self.AllDeckCloseHandler)\n\t\tself.AllDeckCloseHandler = nil\n\tend\n\tself.AllDeckCloseHandler = allDeckClose:ConnectEvent(ButtonClickEvent, function() self:CloseAllDeck() end)\nend\nfor i = 1, 5 do\n\tlocal cardEntity = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CardHand/Card\" .. tostring(i))\n\tif cardEntity ~= nil and cardEntity.UITouchReceiveComponent ~= nil then\n\t\tlocal cardPath = \"/ui/DefaultGroup/CardHand/Card\" .. tostring(i)\n\t\tcardEntity:ConnectEvent(UITouchEnterEvent, function() self:SetCardHover(cardPath, true) end)\n\t\tcardEntity:ConnectEvent(UITouchExitEvent, function() self:SetCardHover(cardPath, false) end)\n\t\tcardEntity:ConnectEvent(UITouchBeginDragEvent, function(ev) self:OnCardDragBegin(i) end)\n\t\tcardEntity:ConnectEvent(UITouchDragEvent, function(ev) self:OnCardDrag(i, ev.TouchPoint) end)\n\t\tcardEntity:ConnectEvent(UITouchEndDragEvent, function(ev) self:OnCardDragEnd(i, ev.TouchPoint) end)\n\tend\nend\nfor i = 1, 3 do\n\tlocal rc = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RewardHud/Reward\" .. tostring(i))\n\tif rc ~= nil and rc.ButtonComponent ~= nil then\n\t\trc:ConnectEvent(ButtonClickEvent, function() self:PickReward(i) end)\n\t\tif rc.UITouchReceiveComponent ~= nil then\n\t\t\tlocal cardPath = \"/ui/DefaultGroup/RewardHud/Reward\" .. tostring(i)\n\t\t\trc:ConnectEvent(UITouchEnterEvent, function() self:SetCardHover(cardPath, true) end)\n\t\t\trc:ConnectEvent(UITouchExitEvent, function() self:SetCardHover(cardPath, false) end)\n\t\tend\n\tend\nend\nlocal skip = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RewardHud/Skip\")\nif skip ~= nil and skip.ButtonComponent ~= nil then\n\tskip:ConnectEvent(ButtonClickEvent, function() self:PickReward(0) end)\nend\nlocal mapNodeIds = {}\nfor r = 1, 7 do\n\tfor c = 1, 4 do\n\t\ttable.insert(mapNodeIds, \"r\" .. tostring(r) .. \"c\" .. tostring(c))\n\tend\nend\ntable.insert(mapNodeIds, \"boss\")\nfor i = 1, #mapNodeIds do\n\tlocal nid = mapNodeIds[i]\n\tlocal mn = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/MapHud/Node_\" .. nid)\n\tif mn ~= nil and mn.ButtonComponent ~= nil then\n\t\tmn:ConnectEvent(ButtonClickEvent, function() self:PickNode(nid) end)\n\tend\nend\nfor i = 1, 3 do\n\tlocal sc = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/ShopHud/Card\" .. tostring(i))\n\tif sc ~= nil and sc.ButtonComponent ~= nil then\n\t\tsc:ConnectEvent(ButtonClickEvent, function() self:BuyCard(i) end)\n\t\tif sc.UITouchReceiveComponent ~= nil then\n\t\t\tlocal cardPath = \"/ui/DefaultGroup/ShopHud/Card\" .. tostring(i)\n\t\t\tsc:ConnectEvent(UITouchEnterEvent, function() self:SetCardHover(cardPath, true) end)\n\t\t\tsc:ConnectEvent(UITouchExitEvent, function() self:SetCardHover(cardPath, false) end)\n\t\tend\n\tend\nend\nlocal shopLeave = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/ShopHud/Leave\")\nif shopLeave ~= nil and shopLeave.ButtonComponent ~= nil then\n\tshopLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)\nend\nlocal shopRelic = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/ShopHud/Relic\")\nif shopRelic ~= nil and shopRelic.ButtonComponent ~= nil then\n\tshopRelic:ConnectEvent(ButtonClickEvent, function() self:BuyRelic() end)\nend\nlocal restLeave = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RestHud/Leave\")\nif restLeave ~= nil and restLeave.ButtonComponent ~= nil then\n\trestLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)\nend\nfor i = 1, 4 do\n\tlocal ms = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/MonsterSlot\" .. tostring(i))\n\tif ms ~= nil and ms.ButtonComponent ~= nil then\n\t\tms:ConnectEvent(ButtonClickEvent, function() self:SetTarget(i) end)\n\tend\nend\nfor i = 1, 10 do\n\tlocal rs = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/TopBar/RelicSlot\" .. tostring(i))\n\tif rs ~= nil and rs.UITouchReceiveComponent ~= nil then\n\t\tlocal idx = i\n\t\trs:ConnectEvent(UITouchEnterEvent, function()\n\t\t\tlocal rid = nil\n\t\t\tif self.RunRelics ~= nil then rid = self.RunRelics[idx] end\n\t\t\tif rid ~= nil and self.Relics[rid] ~= nil then\n\t\t\t\tself:ShowTooltip(self.Relics[rid].name, self.Relics[rid].desc, -240 + (idx - 1) * 48)\n\t\t\tend\n\t\tend)\n\t\trs:ConnectEvent(UITouchExitEvent, function() self:HideTooltip() end)\n\tend\nend\nfor i = 1, 5 do\n\tlocal ps = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/TopBar/PotionSlot\" .. tostring(i))\n\tif ps ~= nil and ps.UITouchReceiveComponent ~= nil then\n\t\tlocal idx = i\n\t\tps:ConnectEvent(UITouchEnterEvent, function()\n\t\t\tlocal pid = nil\n\t\t\tif self.RunPotions ~= nil then pid = self.RunPotions[idx] end\n\t\t\tif pid ~= nil and self.Potions[pid] ~= nil then\n\t\t\t\tself:ShowTooltip(self.Potions[pid].name, self.Potions[pid].desc, 240 + (idx - 1) * 44)\n\t\t\tend\n\t\tend)\n\t\tps:ConnectEvent(UITouchExitEvent, function() self:HideTooltip() end)\n\t\tps:ConnectEvent(UITouchDownEvent, function() self:OpenPotionMenu(idx) end)\n\tend\nend\nlocal pmUse = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/PotionMenu/Use\")\nif pmUse ~= nil and pmUse.ButtonComponent ~= nil then\n\tpmUse:ConnectEvent(ButtonClickEvent, function() self:UsePotion() end)\nend\nlocal pmToss = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/PotionMenu/Toss\")\nif pmToss ~= nil and pmToss.ButtonComponent ~= nil then\n\tpmToss:ConnectEvent(ButtonClickEvent, function() self:TossPotion() end)\nend\nlocal pmClose = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CombatHud/PotionMenu/Close\")\nif pmClose ~= nil and pmClose.ButtonComponent ~= nil then\n\tpmClose:ConnectEvent(ButtonClickEvent, function() self:ClosePotionMenu() end)\nend\nlocal shopPotion = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/ShopHud/Potion\")\nif shopPotion ~= nil and shopPotion.ButtonComponent ~= nil then\n\tshopPotion:ConnectEvent(ButtonClickEvent, function() self:BuyPotion() end)\nend\nlocal chest = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/TreasureHud/Chest\")\nif chest ~= nil and chest.ButtonComponent ~= nil then\n\tchest:ConnectEvent(ButtonClickEvent, function() self:OpenChest() end)\nend\nlocal treasureLeave = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/TreasureHud/Leave\")\nif treasureLeave ~= nil and treasureLeave.ButtonComponent ~= nil then\n\ttreasureLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)\nend\nlocal jcRelic = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/JobChoiceHud/RelicButton\")\nif jcRelic ~= nil and jcRelic.ButtonComponent ~= nil then\n\tjcRelic:ConnectEvent(ButtonClickEvent, function() self:PickJobReward(\"relic\") end)\nend\nlocal jcJob = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/JobChoiceHud/JobButton\")\nif jcJob ~= nil and jcJob.ButtonComponent ~= nil then\n\tjcJob:ConnectEvent(ButtonClickEvent, function() self:PickJobReward(\"job\") end)\nend\nfor i = 1, 3 do\n\tlocal slotIdx = i\n\tlocal jb = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/JobSelectHud/Job_slot\" .. tostring(i))\n\tif jb ~= nil and jb.ButtonComponent ~= nil then\n\t\tjb:ConnectEvent(ButtonClickEvent, function()\n\t\t\tif self.JobOpts ~= nil and self.JobOpts[slotIdx] ~= nil then\n\t\t\t\tself:SetJob(self.JobOpts[slotIdx].id)\n\t\t\tend\n\t\tend)\n\tend\nend", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -1386,12 +1386,42 @@ "Name": "cardId" } ], - "Code": "local c = self.Cards[cardId]\nif c == nil then\n\tc = { name = cardId, cost = 0, desc = \"\", kind = \"Skill\", class = \"warrior\", rarity = \"normal\" }\nend\nlocal e = _EntityService:GetEntityByPath(base)\nif e ~= nil and e.SpriteGUIRendererComponent ~= nil then\n\tlocal frames = self.CardFrames[self.ClassToFrame[c.class] or \"warrior\"]\n\tlocal ruid = nil\n\tif frames ~= nil then\n\t\truid = frames[c.rarity or \"normal\"]\n\tend\n\tif ruid ~= nil then\n\t\te.SpriteGUIRendererComponent.ImageRUID = ruid\n\t\te.SpriteGUIRendererComponent.Color = Color(1, 1, 1, 1)\n\tend\nend\nself:SetText(base .. \"/Cost\", string.format(\"%d\", c.cost))\nself:SetText(base .. \"/Name\", c.name)\nself:SetText(base .. \"/Desc\", c.desc)\nlocal art = _EntityService:GetEntityByPath(base .. \"/Art\")\nif art ~= nil then\n\tif c.image ~= nil and c.image ~= \"\" then\n\t\tart.Enable = true\n\t\tif art.SpriteGUIRendererComponent ~= nil then\n\t\t\tart.SpriteGUIRendererComponent.ImageRUID = c.image\n\t\tend\n\telse\n\t\tart.Enable = false\n\tend\nend", + "Code": "local c = self.Cards[cardId]\nif c == nil then\n\tc = { name = cardId, cost = 0, desc = \"\", kind = \"Skill\", class = \"warrior\", rarity = \"normal\" }\nend\nlocal e = _EntityService:GetEntityByPath(base)\nif e ~= nil and e.SpriteGUIRendererComponent ~= nil then\n\tif e.UITransformComponent ~= nil then\n\t\te.UITransformComponent.UIScale = Vector3(1, 1, 1)\n\tend\n\tlocal frames = self.CardFrames[self.ClassToFrame[c.class] or \"warrior\"]\n\tlocal ruid = nil\n\tif frames ~= nil then\n\t\truid = frames[c.rarity or \"normal\"]\n\tend\n\tif ruid ~= nil then\n\t\te.SpriteGUIRendererComponent.ImageRUID = ruid\n\t\te.SpriteGUIRendererComponent.Color = Color(1, 1, 1, 1)\n\tend\nend\nself:SetText(base .. \"/Cost\", string.format(\"%d\", c.cost))\nself:SetText(base .. \"/Name\", c.name)\nself:SetText(base .. \"/Desc\", c.desc)\nlocal art = _EntityService:GetEntityByPath(base .. \"/Art\")\nif art ~= nil then\n\tif c.image ~= nil and c.image ~= \"\" then\n\t\tart.Enable = true\n\t\tif art.SpriteGUIRendererComponent ~= nil then\n\t\t\tart.SpriteGUIRendererComponent.ImageRUID = c.image\n\t\tend\n\telse\n\t\tart.Enable = false\n\tend\nend", "Scope": 2, "ExecSpace": 6, "Attributes": [], "Name": "ApplyCardFace" }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "Type": "string", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "path" + }, + { + "Type": "boolean", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "hover" + } + ], + "Code": "local e = _EntityService:GetEntityByPath(path)\nif e == nil or e.UITransformComponent == nil then\n\treturn\nend\nif hover == true and e.Enable == true then\n\te.UITransformComponent.UIScale = Vector3(1.12, 1.12, 1)\nelse\n\te.UITransformComponent.UIScale = Vector3(1, 1, 1)\nend", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "SetCardHover" + }, { "Return": { "Type": "void", diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index 851df12..7c91ea8 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -1454,12 +1454,13 @@ function upsertUi() { path: cardPath, modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent', displayOrder: i, components: [ transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: CARD_W, y: CARD_H }, pos: { x: rewardXs[i - 1], y: 0 } }), sprite({ dataId: CARDFRAMES.frames.warrior.normal, color: WHITE, type: 0, raycast: true }), button(), + { '@type': 'MOD.Core.UITouchReceiveComponent', Enable: true }, ], })); const rewardLayout = cardFaceLayout(CARD_W); @@ -1663,12 +1664,13 @@ function upsertUi() { path: cardPath, modelId: 'uisprite', entryId: 'UISprite', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent', displayOrder: i, components: [ transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: CARD_W, y: CARD_H }, pos: { x: shopXs[i - 1], y: 20 } }), sprite({ dataId: CARDFRAMES.frames.warrior.normal, color: WHITE, type: 0, raycast: true }), button(), + { '@type': 'MOD.Core.UITouchReceiveComponent', Enable: true }, ], })); const shopLayout = cardFaceLayout(CARD_W); @@ -2901,6 +2903,9 @@ end for i = 1, 5 do local cardEntity = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(i)) if cardEntity ~= nil and cardEntity.UITouchReceiveComponent ~= nil then + local cardPath = "/ui/DefaultGroup/CardHand/Card" .. tostring(i) + cardEntity:ConnectEvent(UITouchEnterEvent, function() self:SetCardHover(cardPath, true) end) + cardEntity:ConnectEvent(UITouchExitEvent, function() self:SetCardHover(cardPath, false) end) cardEntity:ConnectEvent(UITouchBeginDragEvent, function(ev) self:OnCardDragBegin(i) end) cardEntity:ConnectEvent(UITouchDragEvent, function(ev) self:OnCardDrag(i, ev.TouchPoint) end) cardEntity:ConnectEvent(UITouchEndDragEvent, function(ev) self:OnCardDragEnd(i, ev.TouchPoint) end) @@ -2910,6 +2915,11 @@ for i = 1, 3 do local rc = _EntityService:GetEntityByPath("/ui/DefaultGroup/RewardHud/Reward" .. tostring(i)) if rc ~= nil and rc.ButtonComponent ~= nil then rc:ConnectEvent(ButtonClickEvent, function() self:PickReward(i) end) + if rc.UITouchReceiveComponent ~= nil then + local cardPath = "/ui/DefaultGroup/RewardHud/Reward" .. tostring(i) + rc:ConnectEvent(UITouchEnterEvent, function() self:SetCardHover(cardPath, true) end) + rc:ConnectEvent(UITouchExitEvent, function() self:SetCardHover(cardPath, false) end) + end end end local skip = _EntityService:GetEntityByPath("/ui/DefaultGroup/RewardHud/Skip") @@ -2934,6 +2944,11 @@ for i = 1, 3 do local sc = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Card" .. tostring(i)) if sc ~= nil and sc.ButtonComponent ~= nil then sc:ConnectEvent(ButtonClickEvent, function() self:BuyCard(i) end) + if sc.UITouchReceiveComponent ~= nil then + local cardPath = "/ui/DefaultGroup/ShopHud/Card" .. tostring(i) + sc:ConnectEvent(UITouchEnterEvent, function() self:SetCardHover(cardPath, true) end) + sc:ConnectEvent(UITouchExitEvent, function() self:SetCardHover(cardPath, false) end) + end end end local shopLeave = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Leave") @@ -3214,6 +3229,9 @@ if c == nil then end local e = _EntityService:GetEntityByPath(base) if e ~= nil and e.SpriteGUIRendererComponent ~= nil then + if e.UITransformComponent ~= nil then + e.UITransformComponent.UIScale = Vector3(1, 1, 1) + end local frames = self.CardFrames[self.ClassToFrame[c.class] or "warrior"] local ruid = nil if frames ~= nil then @@ -3241,6 +3259,18 @@ end`, [ { Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'base' }, { Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' }, ]), + method('SetCardHover', `local e = _EntityService:GetEntityByPath(path) +if e == nil or e.UITransformComponent == nil then + return +end +if hover == true and e.Enable == true then + e.UITransformComponent.UIScale = Vector3(1.12, 1.12, 1) +else + e.UITransformComponent.UIScale = Vector3(1, 1, 1) +end`, [ + { Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'path' }, + { Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'hover' }, + ]), method('ApplyCardVisual', `self:ApplyCardFace("/ui/DefaultGroup/CardHand/Card" .. tostring(slot), cardId)`, [ { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }, { Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' }, diff --git a/ui/DefaultGroup.ui b/ui/DefaultGroup.ui index bb494ac..481c3a5 100644 --- a/ui/DefaultGroup.ui +++ b/ui/DefaultGroup.ui @@ -22768,7 +22768,7 @@ { "id": "0e700002-0000-4000-8000-00000e700002", "path": "/ui/DefaultGroup/RewardHud/Reward1", - "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent", "jsonString": { "name": "Reward1", "path": "/ui/DefaultGroup/RewardHud/Reward1", @@ -22948,6 +22948,10 @@ "OverrideSorting": false, "Transition": 1, "Enable": true + }, + { + "@type": "MOD.Core.UITouchReceiveComponent", + "Enable": true } ], "@version": 1 @@ -23661,7 +23665,7 @@ { "id": "0e700009-0000-4000-8000-00000e700009", "path": "/ui/DefaultGroup/RewardHud/Reward2", - "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent", "jsonString": { "name": "Reward2", "path": "/ui/DefaultGroup/RewardHud/Reward2", @@ -23841,6 +23845,10 @@ "OverrideSorting": false, "Transition": 1, "Enable": true + }, + { + "@type": "MOD.Core.UITouchReceiveComponent", + "Enable": true } ], "@version": 1 @@ -24554,7 +24562,7 @@ { "id": "0e700010-0000-4000-8000-00000e700010", "path": "/ui/DefaultGroup/RewardHud/Reward3", - "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent", "jsonString": { "name": "Reward3", "path": "/ui/DefaultGroup/RewardHud/Reward3", @@ -24734,6 +24742,10 @@ "OverrideSorting": false, "Transition": 1, "Enable": true + }, + { + "@type": "MOD.Core.UITouchReceiveComponent", + "Enable": true } ], "@version": 1 @@ -64504,7 +64516,7 @@ { "id": "0e800003-0000-4000-8000-00000e800003", "path": "/ui/DefaultGroup/ShopHud/Card1", - "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent", "jsonString": { "name": "Card1", "path": "/ui/DefaultGroup/ShopHud/Card1", @@ -64684,6 +64696,10 @@ "OverrideSorting": false, "Transition": 1, "Enable": true + }, + { + "@type": "MOD.Core.UITouchReceiveComponent", + "Enable": true } ], "@version": 1 @@ -65585,7 +65601,7 @@ { "id": "0e80000b-0000-4000-8000-00000e80000b", "path": "/ui/DefaultGroup/ShopHud/Card2", - "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent", "jsonString": { "name": "Card2", "path": "/ui/DefaultGroup/ShopHud/Card2", @@ -65765,6 +65781,10 @@ "OverrideSorting": false, "Transition": 1, "Enable": true + }, + { + "@type": "MOD.Core.UITouchReceiveComponent", + "Enable": true } ], "@version": 1 @@ -66666,7 +66686,7 @@ { "id": "0e800013-0000-4000-8000-00000e800013", "path": "/ui/DefaultGroup/ShopHud/Card3", - "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.UITouchReceiveComponent", "jsonString": { "name": "Card3", "path": "/ui/DefaultGroup/ShopHud/Card3", @@ -66846,6 +66866,10 @@ "OverrideSorting": false, "Transition": 1, "Enable": true + }, + { + "@type": "MOD.Core.UITouchReceiveComponent", + "Enable": true } ], "@version": 1 From d3ae6c1c6276150154aeb723a707095df13cd211 Mon Sep 17 00:00:00 2001 From: gahusb Date: Sun, 14 Jun 2026 02:50:47 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat(ui):=20=EC=B9=B4=EB=93=9C=20hover=20?= =?UTF-8?q?=EB=B3=B4=EA=B0=84=20=ED=99=95=EB=8C=80=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - hover된 손패/보상/상점 카드를 1.5배까지 SineEaseOut 보간으로 확대 - 같은 줄의 다른 카드는 hover 카드 기준 좌우로 110px 밀어 겹침을 줄임 - hover 해제 시 같은 보간으로 scale 1.0 및 기본 위치로 복귀 - SlayDeckController.codeblock 산출물을 생성기로 재생성 검증: - node --check tools/deck/gen-slaydeck.mjs - node --test tools/balance/sim-balance.test.mjs tools/map/rogue-map.test.mjs --- RootDesk/MyDesk/SlayDeckController.codeblock | 2 +- tools/deck/gen-slaydeck.mjs | 72 ++++++++++++++++++-- 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/RootDesk/MyDesk/SlayDeckController.codeblock b/RootDesk/MyDesk/SlayDeckController.codeblock index bb7e05c..090e15a 100644 --- a/RootDesk/MyDesk/SlayDeckController.codeblock +++ b/RootDesk/MyDesk/SlayDeckController.codeblock @@ -1416,7 +1416,7 @@ "Name": "hover" } ], - "Code": "local e = _EntityService:GetEntityByPath(path)\nif e == nil or e.UITransformComponent == nil then\n\treturn\nend\nif hover == true and e.Enable == true then\n\te.UITransformComponent.UIScale = Vector3(1.12, 1.12, 1)\nelse\n\te.UITransformComponent.UIScale = Vector3(1, 1, 1)\nend", + "Code": "local prefix = \"\"\nlocal count = 0\nlocal xs = {}\nlocal baseY = 0\nlocal hoverIndex = 0\nlocal push = 110\nif string.find(path, \"/ui/DefaultGroup/CardHand/Card\") == 1 then\n\tprefix = \"/ui/DefaultGroup/CardHand/Card\"\n\tcount = 5\n\txs = { -400, -200, 0, 200, 400 }\n\tbaseY = 0\n\thoverIndex = tonumber(string.match(path, \"Card(%d+)\")) or 0\nelseif string.find(path, \"/ui/DefaultGroup/RewardHud/Reward\") == 1 then\n\tprefix = \"/ui/DefaultGroup/RewardHud/Reward\"\n\tcount = 3\n\txs = { -300, 0, 300 }\n\tbaseY = 0\n\thoverIndex = tonumber(string.match(path, \"Reward(%d+)\")) or 0\nelseif string.find(path, \"/ui/DefaultGroup/ShopHud/Card\") == 1 then\n\tprefix = \"/ui/DefaultGroup/ShopHud/Card\"\n\tcount = 3\n\txs = { -300, 0, 300 }\n\tbaseY = 20\n\thoverIndex = tonumber(string.match(path, \"Card(%d+)\")) or 0\nend\nif count <= 0 then\n\treturn\nend\nlocal items = {}\nfor i = 1, count do\n\tlocal e = _EntityService:GetEntityByPath(prefix .. tostring(i))\n\tif e ~= nil and e.UITransformComponent ~= nil then\n\t\tlocal tr = e.UITransformComponent\n\t\tlocal tx = xs[i]\n\t\tlocal ty = baseY\n\t\tlocal sc = 1\n\t\tif hover == true and hoverIndex > 0 then\n\t\t\tif i == hoverIndex and e.Enable == true then\n\t\t\t\tsc = 1.5\n\t\t\telseif i < hoverIndex then\n\t\t\t\ttx = tx - push\n\t\t\telseif i > hoverIndex then\n\t\t\t\ttx = tx + push\n\t\t\tend\n\t\tend\n\t\ttable.insert(items, { tr = tr, sx = tr.anchoredPosition.x, sy = tr.anchoredPosition.y, ss = tr.UIScale.x, tx = tx, ty = ty, ts = sc })\n\tend\nend\nlocal elapsed = 0\nlocal duration = 0.12\nlocal eventId = 0\neventId = _TimerService:SetTimerRepeat(function()\n\telapsed = elapsed + 1 / 60\n\tlocal t = math.min(elapsed / duration, 1)\n\tlocal eased = _TweenLogic:Ease(0, 1, 1, EaseType.SineEaseOut, t)\n\tfor i = 1, #items do\n\t\tlocal it = items[i]\n\t\tlocal x = it.sx + (it.tx - it.sx) * eased\n\t\tlocal y = it.sy + (it.ty - it.sy) * eased\n\t\tlocal s = it.ss + (it.ts - it.ss) * eased\n\t\tit.tr.anchoredPosition = Vector2(x, y)\n\t\tit.tr.UIScale = Vector3(s, s, 1)\n\tend\n\tif t >= 1 then\n\t\t_TimerService:ClearTimer(eventId)\n\tend\nend, 1 / 60)", "Scope": 2, "ExecSpace": 6, "Attributes": [], diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index 7c91ea8..e27c060 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -3259,15 +3259,73 @@ end`, [ { Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'base' }, { Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'cardId' }, ]), - method('SetCardHover', `local e = _EntityService:GetEntityByPath(path) -if e == nil or e.UITransformComponent == nil then + method('SetCardHover', `local prefix = "" +local count = 0 +local xs = {} +local baseY = 0 +local hoverIndex = 0 +local push = 110 +if string.find(path, "/ui/DefaultGroup/CardHand/Card") == 1 then + prefix = "/ui/DefaultGroup/CardHand/Card" + count = 5 + xs = { ${CARD_XS.join(', ')} } + baseY = 0 + hoverIndex = tonumber(string.match(path, "Card(%d+)")) or 0 +elseif string.find(path, "/ui/DefaultGroup/RewardHud/Reward") == 1 then + prefix = "/ui/DefaultGroup/RewardHud/Reward" + count = 3 + xs = { -300, 0, 300 } + baseY = 0 + hoverIndex = tonumber(string.match(path, "Reward(%d+)")) or 0 +elseif string.find(path, "/ui/DefaultGroup/ShopHud/Card") == 1 then + prefix = "/ui/DefaultGroup/ShopHud/Card" + count = 3 + xs = { -300, 0, 300 } + baseY = 20 + hoverIndex = tonumber(string.match(path, "Card(%d+)")) or 0 +end +if count <= 0 then return end -if hover == true and e.Enable == true then - e.UITransformComponent.UIScale = Vector3(1.12, 1.12, 1) -else - e.UITransformComponent.UIScale = Vector3(1, 1, 1) -end`, [ +local items = {} +for i = 1, count do + local e = _EntityService:GetEntityByPath(prefix .. tostring(i)) + if e ~= nil and e.UITransformComponent ~= nil then + local tr = e.UITransformComponent + local tx = xs[i] + local ty = baseY + local sc = 1 + if hover == true and hoverIndex > 0 then + if i == hoverIndex and e.Enable == true then + sc = 1.5 + elseif i < hoverIndex then + tx = tx - push + elseif i > hoverIndex then + tx = tx + push + end + end + table.insert(items, { tr = tr, sx = tr.anchoredPosition.x, sy = tr.anchoredPosition.y, ss = tr.UIScale.x, tx = tx, ty = ty, ts = sc }) + end +end +local elapsed = 0 +local duration = 0.12 +local eventId = 0 +eventId = _TimerService:SetTimerRepeat(function() + elapsed = elapsed + 1 / 60 + local t = math.min(elapsed / duration, 1) + local eased = _TweenLogic:Ease(0, 1, 1, EaseType.SineEaseOut, t) + for i = 1, #items do + local it = items[i] + local x = it.sx + (it.tx - it.sx) * eased + local y = it.sy + (it.ty - it.sy) * eased + local s = it.ss + (it.ts - it.ss) * eased + it.tr.anchoredPosition = Vector2(x, y) + it.tr.UIScale = Vector3(s, s, 1) + end + if t >= 1 then + _TimerService:ClearTimer(eventId) + end +end, 1 / 60)`, [ { Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'path' }, { Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'hover' }, ]), From a5f6a4509dba5863e68aa3fabf0032b2a61307f7 Mon Sep 17 00:00:00 2001 From: gahusb Date: Sun, 14 Jun 2026 02:55:38 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix(ui):=20=EC=B9=B4=EB=93=9C=20=EB=93=9C?= =?UTF-8?q?=EB=9E=98=EA=B7=B8=20=EC=A4=91=20hover=20=EB=B3=B4=EA=B0=84=20?= =?UTF-8?q?=EC=A4=91=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - hover 확대 보간 타이머 ID를 추적해 새 hover 또는 드래그 시작 시 기존 타이머를 종료 - 손패 카드 드래그 시작 시 모든 손패 카드의 scale과 위치를 기본값으로 즉시 정리 - 드래그 중 손패 hover enter/exit 처리를 무시해 드래그 위치와 hover 보간이 충돌하지 않도록 수정 - 드래그 종료 시 드래그 카드 scale을 1.0으로 복귀 검증: - node --check tools/deck/gen-slaydeck.mjs - node --test tools/balance/sim-balance.test.mjs tools/map/rogue-map.test.mjs --- RootDesk/MyDesk/SlayDeckController.codeblock | 13 +++++++--- tools/deck/gen-slaydeck.mjs | 27 +++++++++++++++++++- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/RootDesk/MyDesk/SlayDeckController.codeblock b/RootDesk/MyDesk/SlayDeckController.codeblock index 090e15a..a43cfe7 100644 --- a/RootDesk/MyDesk/SlayDeckController.codeblock +++ b/RootDesk/MyDesk/SlayDeckController.codeblock @@ -78,6 +78,13 @@ "Attributes": [], "Name": "TweenEventId" }, + { + "Type": "number", + "DefaultValue": "0", + "SyncDirection": 0, + "Attributes": [], + "Name": "CardHoverTweenId" + }, { "Type": "any", "DefaultValue": "nil", @@ -1416,7 +1423,7 @@ "Name": "hover" } ], - "Code": "local prefix = \"\"\nlocal count = 0\nlocal xs = {}\nlocal baseY = 0\nlocal hoverIndex = 0\nlocal push = 110\nif string.find(path, \"/ui/DefaultGroup/CardHand/Card\") == 1 then\n\tprefix = \"/ui/DefaultGroup/CardHand/Card\"\n\tcount = 5\n\txs = { -400, -200, 0, 200, 400 }\n\tbaseY = 0\n\thoverIndex = tonumber(string.match(path, \"Card(%d+)\")) or 0\nelseif string.find(path, \"/ui/DefaultGroup/RewardHud/Reward\") == 1 then\n\tprefix = \"/ui/DefaultGroup/RewardHud/Reward\"\n\tcount = 3\n\txs = { -300, 0, 300 }\n\tbaseY = 0\n\thoverIndex = tonumber(string.match(path, \"Reward(%d+)\")) or 0\nelseif string.find(path, \"/ui/DefaultGroup/ShopHud/Card\") == 1 then\n\tprefix = \"/ui/DefaultGroup/ShopHud/Card\"\n\tcount = 3\n\txs = { -300, 0, 300 }\n\tbaseY = 20\n\thoverIndex = tonumber(string.match(path, \"Card(%d+)\")) or 0\nend\nif count <= 0 then\n\treturn\nend\nlocal items = {}\nfor i = 1, count do\n\tlocal e = _EntityService:GetEntityByPath(prefix .. tostring(i))\n\tif e ~= nil and e.UITransformComponent ~= nil then\n\t\tlocal tr = e.UITransformComponent\n\t\tlocal tx = xs[i]\n\t\tlocal ty = baseY\n\t\tlocal sc = 1\n\t\tif hover == true and hoverIndex > 0 then\n\t\t\tif i == hoverIndex and e.Enable == true then\n\t\t\t\tsc = 1.5\n\t\t\telseif i < hoverIndex then\n\t\t\t\ttx = tx - push\n\t\t\telseif i > hoverIndex then\n\t\t\t\ttx = tx + push\n\t\t\tend\n\t\tend\n\t\ttable.insert(items, { tr = tr, sx = tr.anchoredPosition.x, sy = tr.anchoredPosition.y, ss = tr.UIScale.x, tx = tx, ty = ty, ts = sc })\n\tend\nend\nlocal elapsed = 0\nlocal duration = 0.12\nlocal eventId = 0\neventId = _TimerService:SetTimerRepeat(function()\n\telapsed = elapsed + 1 / 60\n\tlocal t = math.min(elapsed / duration, 1)\n\tlocal eased = _TweenLogic:Ease(0, 1, 1, EaseType.SineEaseOut, t)\n\tfor i = 1, #items do\n\t\tlocal it = items[i]\n\t\tlocal x = it.sx + (it.tx - it.sx) * eased\n\t\tlocal y = it.sy + (it.ty - it.sy) * eased\n\t\tlocal s = it.ss + (it.ts - it.ss) * eased\n\t\tit.tr.anchoredPosition = Vector2(x, y)\n\t\tit.tr.UIScale = Vector3(s, s, 1)\n\tend\n\tif t >= 1 then\n\t\t_TimerService:ClearTimer(eventId)\n\tend\nend, 1 / 60)", + "Code": "local prefix = \"\"\nlocal count = 0\nlocal xs = {}\nlocal baseY = 0\nlocal hoverIndex = 0\nlocal push = 110\nif string.find(path, \"/ui/DefaultGroup/CardHand/Card\") == 1 then\n\tif self.DragSlot ~= nil and self.DragSlot > 0 then\n\t\treturn\n\tend\n\tprefix = \"/ui/DefaultGroup/CardHand/Card\"\n\tcount = 5\n\txs = { -400, -200, 0, 200, 400 }\n\tbaseY = 0\n\thoverIndex = tonumber(string.match(path, \"Card(%d+)\")) or 0\nelseif string.find(path, \"/ui/DefaultGroup/RewardHud/Reward\") == 1 then\n\tprefix = \"/ui/DefaultGroup/RewardHud/Reward\"\n\tcount = 3\n\txs = { -300, 0, 300 }\n\tbaseY = 0\n\thoverIndex = tonumber(string.match(path, \"Reward(%d+)\")) or 0\nelseif string.find(path, \"/ui/DefaultGroup/ShopHud/Card\") == 1 then\n\tprefix = \"/ui/DefaultGroup/ShopHud/Card\"\n\tcount = 3\n\txs = { -300, 0, 300 }\n\tbaseY = 20\n\thoverIndex = tonumber(string.match(path, \"Card(%d+)\")) or 0\nend\nif count <= 0 then\n\treturn\nend\nif self.CardHoverTweenId ~= nil and self.CardHoverTweenId ~= 0 then\n\t_TimerService:ClearTimer(self.CardHoverTweenId)\n\tself.CardHoverTweenId = 0\nend\nlocal items = {}\nfor i = 1, count do\n\tlocal e = _EntityService:GetEntityByPath(prefix .. tostring(i))\n\tif e ~= nil and e.UITransformComponent ~= nil then\n\t\tlocal tr = e.UITransformComponent\n\t\tlocal tx = xs[i]\n\t\tlocal ty = baseY\n\t\tlocal sc = 1\n\t\tif hover == true and hoverIndex > 0 then\n\t\t\tif i == hoverIndex and e.Enable == true then\n\t\t\t\tsc = 1.5\n\t\t\telseif i < hoverIndex then\n\t\t\t\ttx = tx - push\n\t\t\telseif i > hoverIndex then\n\t\t\t\ttx = tx + push\n\t\t\tend\n\t\tend\n\t\ttable.insert(items, { tr = tr, sx = tr.anchoredPosition.x, sy = tr.anchoredPosition.y, ss = tr.UIScale.x, tx = tx, ty = ty, ts = sc })\n\tend\nend\nlocal elapsed = 0\nlocal duration = 0.12\nlocal eventId = 0\neventId = _TimerService:SetTimerRepeat(function()\n\telapsed = elapsed + 1 / 60\n\tlocal t = math.min(elapsed / duration, 1)\n\tlocal eased = _TweenLogic:Ease(0, 1, 1, EaseType.SineEaseOut, t)\n\tfor i = 1, #items do\n\t\tlocal it = items[i]\n\t\tlocal x = it.sx + (it.tx - it.sx) * eased\n\t\tlocal y = it.sy + (it.ty - it.sy) * eased\n\t\tlocal s = it.ss + (it.ts - it.ss) * eased\n\t\tit.tr.anchoredPosition = Vector2(x, y)\n\t\tit.tr.UIScale = Vector3(s, s, 1)\n\tend\n\tif t >= 1 then\n\t\t_TimerService:ClearTimer(eventId)\n\t\tif self.CardHoverTweenId == eventId then\n\t\t\tself.CardHoverTweenId = 0\n\t\tend\n\tend\nend, 1 / 60)\nself.CardHoverTweenId = eventId", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -1589,7 +1596,7 @@ "Name": "slot" } ], - "Code": "if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then\n\treturn\nend\nif self.Hand == nil or self.Hand[slot] == nil then\n\treturn\nend\nself.DragSlot = slot", + "Code": "if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then\n\treturn\nend\nif self.Hand == nil or self.Hand[slot] == nil then\n\treturn\nend\nif self.CardHoverTweenId ~= nil and self.CardHoverTweenId ~= 0 then\n\t_TimerService:ClearTimer(self.CardHoverTweenId)\n\tself.CardHoverTweenId = 0\nend\nlocal cardXs = { -400, -200, 0, 200, 400 }\nfor i = 1, 5 do\n\tlocal e = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CardHand/Card\" .. tostring(i))\n\tif e ~= nil and e.UITransformComponent ~= nil then\n\t\te.UITransformComponent.UIScale = Vector3(1, 1, 1)\n\t\te.UITransformComponent.anchoredPosition = Vector2(cardXs[i], 0)\n\tend\nend\nself.DragSlot = slot", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -1649,7 +1656,7 @@ "Name": "touchPoint" } ], - "Code": "if self.DragSlot ~= slot then\n\treturn\nend\nself.DragSlot = 0\nlocal cardXs = { -400, -200, 0, 200, 400 }\nlocal e = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CardHand/Card\" .. tostring(slot))\nif e ~= nil and e.UITransformComponent ~= nil then\n\te.UITransformComponent.anchoredPosition = Vector2(cardXs[slot], 0)\nend\nself:ResolveCardDrop(slot, touchPoint)", + "Code": "if self.DragSlot ~= slot then\n\treturn\nend\nself.DragSlot = 0\nlocal cardXs = { -400, -200, 0, 200, 400 }\nlocal e = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CardHand/Card\" .. tostring(slot))\nif e ~= nil and e.UITransformComponent ~= nil then\n\te.UITransformComponent.anchoredPosition = Vector2(cardXs[slot], 0)\n\te.UITransformComponent.UIScale = Vector3(1, 1, 1)\nend\nself:ResolveCardDrop(slot, touchPoint)", "Scope": 2, "ExecSpace": 6, "Attributes": [], diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index e27c060..a272fa1 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -2437,6 +2437,7 @@ function writeCodeblocks() { prop('number', 'MaxEnergy', '3'), prop('number', 'Turn', '0'), prop('number', 'TweenEventId', '0'), + prop('number', 'CardHoverTweenId', '0'), prop('any', 'EndTurnHandler'), prop('any', 'NewGameHandler'), prop('any', 'WarriorSelectHandler'), @@ -3266,6 +3267,9 @@ local baseY = 0 local hoverIndex = 0 local push = 110 if string.find(path, "/ui/DefaultGroup/CardHand/Card") == 1 then + if self.DragSlot ~= nil and self.DragSlot > 0 then + return + end prefix = "/ui/DefaultGroup/CardHand/Card" count = 5 xs = { ${CARD_XS.join(', ')} } @@ -3287,6 +3291,10 @@ end if count <= 0 then return end +if self.CardHoverTweenId ~= nil and self.CardHoverTweenId ~= 0 then + _TimerService:ClearTimer(self.CardHoverTweenId) + self.CardHoverTweenId = 0 +end local items = {} for i = 1, count do local e = _EntityService:GetEntityByPath(prefix .. tostring(i)) @@ -3324,8 +3332,12 @@ eventId = _TimerService:SetTimerRepeat(function() end if t >= 1 then _TimerService:ClearTimer(eventId) + if self.CardHoverTweenId == eventId then + self.CardHoverTweenId = 0 + end end -end, 1 / 60)`, [ +end, 1 / 60) +self.CardHoverTweenId = eventId`, [ { Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'path' }, { Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'hover' }, ]), @@ -3466,6 +3478,18 @@ end if self.Hand == nil or self.Hand[slot] == nil then return end +if self.CardHoverTweenId ~= nil and self.CardHoverTweenId ~= 0 then + _TimerService:ClearTimer(self.CardHoverTweenId) + self.CardHoverTweenId = 0 +end +local cardXs = { ${CARD_XS.join(', ')} } +for i = 1, 5 do + local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(i)) + if e ~= nil and e.UITransformComponent ~= nil then + e.UITransformComponent.UIScale = Vector3(1, 1, 1) + e.UITransformComponent.anchoredPosition = Vector2(cardXs[i], 0) + end +end self.DragSlot = slot`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]), method('OnCardDrag', `if self.DragSlot ~= slot then return @@ -3486,6 +3510,7 @@ local cardXs = { ${CARD_XS.join(', ')} } local e = _EntityService:GetEntityByPath("/ui/DefaultGroup/CardHand/Card" .. tostring(slot)) if e ~= nil and e.UITransformComponent ~= nil then e.UITransformComponent.anchoredPosition = Vector2(cardXs[slot], 0) + e.UITransformComponent.UIScale = Vector3(1, 1, 1) end self:ResolveCardDrop(slot, touchPoint)`, [ { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }, From 1f0a8099eea8f4a4caab68ae429a6faefac693c6 Mon Sep 17 00:00:00 2001 From: gahusb Date: Sun, 14 Jun 2026 02:59:34 +0900 Subject: [PATCH 5/5] =?UTF-8?q?fix(ui):=20=EC=B9=B4=EB=93=9C=20=ED=9A=A8?= =?UTF-8?q?=EA=B3=BC=20=EB=93=9C=EB=A1=9C=EC=9A=B0=20=EC=95=A0=EB=8B=88?= =?UTF-8?q?=EB=A9=94=EC=9D=B4=EC=85=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DrawCards에 animate 인자를 추가해 새로 뽑힌 손패 슬롯만 덱 위치에서 손패 위치로 이동하도록 처리 - 카드 효과의 draw 호출은 animate=true로 실행해 즉시 생성되는 느낌을 제거 - 턴 시작 드로우와 유물 드로우는 기존 렌더 흐름을 유지 - SlayDeckController.codeblock 산출물을 생성기로 재생성 검증: - node --check tools/deck/gen-slaydeck.mjs - node --test tools/balance/sim-balance.test.mjs tools/map/rogue-map.test.mjs --- RootDesk/MyDesk/SlayDeckController.codeblock | 11 ++++++++-- tools/deck/gen-slaydeck.mjs | 21 +++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/RootDesk/MyDesk/SlayDeckController.codeblock b/RootDesk/MyDesk/SlayDeckController.codeblock index a43cfe7..3f715ce 100644 --- a/RootDesk/MyDesk/SlayDeckController.codeblock +++ b/RootDesk/MyDesk/SlayDeckController.codeblock @@ -1135,9 +1135,16 @@ "SyncDirection": 0, "Attributes": [], "Name": "amount" + }, + { + "Type": "boolean", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "animate" } ], - "Code": "for i = 1, amount do\n\tif #self.DrawPile <= 0 then\n\t\tself:RecycleDiscardIntoDraw()\n\tend\n\tif #self.DrawPile <= 0 then\n\t\tbreak\n\tend\n\tlocal cardId = table.remove(self.DrawPile)\n\ttable.insert(self.Hand, cardId)\nend\nself:RenderPiles()", + "Code": "local drawnSlots = {}\nfor i = 1, amount do\n\tif #self.DrawPile <= 0 then\n\t\tself:RecycleDiscardIntoDraw()\n\tend\n\tif #self.DrawPile <= 0 then\n\t\tbreak\n\tend\n\tlocal cardId = table.remove(self.DrawPile)\n\ttable.insert(self.Hand, cardId)\n\tif #self.Hand <= 5 then\n\t\ttable.insert(drawnSlots, #self.Hand)\n\tend\nend\nself:RenderPiles()\nif animate == true and #drawnSlots > 0 then\n\tself:RenderHand(false)\n\tlocal drawStart = Vector2(-590, 8)\n\tfor i = 1, #drawnSlots do\n\t\tlocal slot = drawnSlots[i]\n\t\tself:AnimateCardFrom(slot, drawStart, Vector2((slot - 3) * 200, 0), 0.08 + i * 0.045)\n\tend\nend", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -1573,7 +1580,7 @@ "Name": "slot" } ], - "Code": "if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then\n\treturn\nend\nif self.Hand == nil then\n\treturn\nend\nlocal cardId = self.Hand[slot]\nif cardId == nil then\n\treturn\nend\nlocal c = self.Cards[cardId]\nif c == nil then\n\treturn\nend\nif self.Energy < c.cost then\n\tself:Toast(\"에너지가 부족합니다\")\n\treturn\nend\nself.Energy = self.Energy - c.cost\nif c.kind == \"Attack\" then\n\tif c.damage ~= nil then\n\t\tself:PlayerAttackMotion()\n\t\tlocal total = 0\n\t\tlocal hitN = c.hits or 1\n\t\tfor h = 1, hitN do\n\t\t\ttotal = total + self:CalcPlayerAttack(c.damage)\n\t\tend\n\t\tif c.aoe == true then\n\t\t\tself:PlayAoeFx(c.image, total)\n\t\telse\n\t\t\tself:PlayAttackFx(self.TargetIndex, c.image, total, c.pierce == true)\n\t\tend\n\tend\n\tif c.block ~= nil then\n\t\tself.PlayerBlock = self.PlayerBlock + c.block\n\tend\n\tself:ApplyRelics(\"cardPlayed\")\nelseif c.kind == \"Skill\" then\n\tif c.block ~= nil then\n\t\tself.PlayerBlock = self.PlayerBlock + c.block\n\tend\nelseif c.kind == \"Power\" then\n\tif c.powerEffect ~= nil then\n\t\ttable.insert(self.PlayerPowers, cardId)\n\tend\nend\nif c.strength ~= nil then\n\tself.PlayerStr = self.PlayerStr + c.strength\nend\nif c.selfVuln ~= nil then\n\tself.PlayerVuln = self.PlayerVuln + c.selfVuln\nend\nif c.heal ~= nil then\n\tself.PlayerHp = math.min(self.PlayerHp + c.heal, self.PlayerMaxHp)\nend\nif c.weak ~= nil or c.vuln ~= nil or c.poison ~= nil then\n\tlocal tm = self.Monsters[self.TargetIndex]\n\tif tm ~= nil and tm.alive == true then\n\t\tif c.weak ~= nil then tm.weak = tm.weak + c.weak end\n\t\tif c.poison ~= nil then tm.poison = (tm.poison or 0) + c.poison end\n\t\tif c.vuln ~= nil then\n\t\t\ttm.vuln = tm.vuln + c.vuln\n\t\t\tif self:HasRelic(\"championBelt\") then\n\t\t\t\ttm.weak = tm.weak + 1\n\t\t\tend\n\t\tend\n\tend\nend\ntable.remove(self.Hand, slot)\nif c.kind ~= \"Power\" then\n\ttable.insert(self.DiscardPile, cardId)\nend\nif c.draw ~= nil then\n\tself:DrawCards(c.draw)\nend\nself:RenderHand(false)\nself:RenderPiles()\nself:RenderCombat()\nself:CheckCombatEnd()", + "Code": "if self.CombatOver == true or self.FxBusy == true or self.TurnBusy == true then\n\treturn\nend\nif self.Hand == nil then\n\treturn\nend\nlocal cardId = self.Hand[slot]\nif cardId == nil then\n\treturn\nend\nlocal c = self.Cards[cardId]\nif c == nil then\n\treturn\nend\nif self.Energy < c.cost then\n\tself:Toast(\"에너지가 부족합니다\")\n\treturn\nend\nself.Energy = self.Energy - c.cost\nif c.kind == \"Attack\" then\n\tif c.damage ~= nil then\n\t\tself:PlayerAttackMotion()\n\t\tlocal total = 0\n\t\tlocal hitN = c.hits or 1\n\t\tfor h = 1, hitN do\n\t\t\ttotal = total + self:CalcPlayerAttack(c.damage)\n\t\tend\n\t\tif c.aoe == true then\n\t\t\tself:PlayAoeFx(c.image, total)\n\t\telse\n\t\t\tself:PlayAttackFx(self.TargetIndex, c.image, total, c.pierce == true)\n\t\tend\n\tend\n\tif c.block ~= nil then\n\t\tself.PlayerBlock = self.PlayerBlock + c.block\n\tend\n\tself:ApplyRelics(\"cardPlayed\")\nelseif c.kind == \"Skill\" then\n\tif c.block ~= nil then\n\t\tself.PlayerBlock = self.PlayerBlock + c.block\n\tend\nelseif c.kind == \"Power\" then\n\tif c.powerEffect ~= nil then\n\t\ttable.insert(self.PlayerPowers, cardId)\n\tend\nend\nif c.strength ~= nil then\n\tself.PlayerStr = self.PlayerStr + c.strength\nend\nif c.selfVuln ~= nil then\n\tself.PlayerVuln = self.PlayerVuln + c.selfVuln\nend\nif c.heal ~= nil then\n\tself.PlayerHp = math.min(self.PlayerHp + c.heal, self.PlayerMaxHp)\nend\nif c.weak ~= nil or c.vuln ~= nil or c.poison ~= nil then\n\tlocal tm = self.Monsters[self.TargetIndex]\n\tif tm ~= nil and tm.alive == true then\n\t\tif c.weak ~= nil then tm.weak = tm.weak + c.weak end\n\t\tif c.poison ~= nil then tm.poison = (tm.poison or 0) + c.poison end\n\t\tif c.vuln ~= nil then\n\t\t\ttm.vuln = tm.vuln + c.vuln\n\t\t\tif self:HasRelic(\"championBelt\") then\n\t\t\t\ttm.weak = tm.weak + 1\n\t\t\tend\n\t\tend\n\tend\nend\ntable.remove(self.Hand, slot)\nif c.kind ~= \"Power\" then\n\ttable.insert(self.DiscardPile, cardId)\nend\nif c.draw ~= nil then\n\tself:DrawCards(c.draw, true)\nend\nself:RenderHand(false)\nself:RenderPiles()\nself:RenderCombat()\nself:CheckCombatEnd()", "Scope": 2, "ExecSpace": 6, "Attributes": [], diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index a272fa1..09449bc 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -3079,7 +3079,8 @@ if self.PlayerVuln > 0 then self.PlayerVuln = self.PlayerVuln - 1 end self:RenderHand(false) self:RenderPiles() self:EnemyTurn()`), - method('DrawCards', `for i = 1, amount do + method('DrawCards', `local drawnSlots = {} +for i = 1, amount do \tif #self.DrawPile <= 0 then \t\tself:RecycleDiscardIntoDraw() \tend @@ -3088,8 +3089,22 @@ self:EnemyTurn()`), \tend \tlocal cardId = table.remove(self.DrawPile) \ttable.insert(self.Hand, cardId) +\tif #self.Hand <= 5 then +\t\ttable.insert(drawnSlots, #self.Hand) +\tend end -self:RenderPiles()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }]), +self:RenderPiles() +if animate == true and #drawnSlots > 0 then +\tself:RenderHand(false) +\tlocal drawStart = Vector2(-590, 8) +\tfor i = 1, #drawnSlots do +\t\tlocal slot = drawnSlots[i] +\t\tself:AnimateCardFrom(slot, drawStart, Vector2((slot - 3) * 200, 0), 0.08 + i * 0.045) +\tend +end`, [ + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }, + { Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'animate' }, + ]), method('RecycleDiscardIntoDraw', `if self.DiscardPile == nil or #self.DiscardPile <= 0 then \treturn end @@ -3466,7 +3481,7 @@ if c.kind ~= "Power" then table.insert(self.DiscardPile, cardId) end if c.draw ~= nil then - self:DrawCards(c.draw) + self:DrawCards(c.draw, true) end self:RenderHand(false) self:RenderPiles()