From 5ebc781f81a821afe167e96d59f118b3e585240f Mon Sep 17 00:00:00 2001 From: gahusb Date: Tue, 9 Jun 2026 03:53:37 +0900 Subject: [PATCH] =?UTF-8?q?feat(E5):=20=EC=9C=A0=EB=AC=BC=20=EC=8B=9C?= =?UTF-8?q?=EC=8A=A4=ED=85=9C=20=E2=80=94=20=ED=9B=85=20=ED=8C=A8=EC=8B=9C?= =?UTF-8?q?=EB=B8=8C=20+=203=ED=9A=8D=EB=93=9D=EA=B2=BD=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 훅 기반 유물(패시브) + 시작/엘리트/상점 획득. - data/relics.json: 유물 4종(강철심장 combatStart 방어+6, 에너지코어 turnStart 에너지+1, 흡혈 cardPlayed HP+1, 황금우상 combatReward 골드+10) + startingRelic + relicPool - ApplyRelics(hook): RunRelics 순회·effect 적용. 4지점 연결(StartCombat/StartPlayerTurn/PlayCard Attack/CheckCombatEnd) - 획득: AddRelic 공용 — StartRun 시작 유물(C), 엘리트 승리 무작위(A), 상점 BuyRelic 골드-60(B) - UI: CombatHud 유물 바(RenderRelics)·ShopHud 유물 슬롯 - 생성기: relics.json 로드/검증/luaRelicsTable, RELIC_PRICE=60 - 메이커 Play 검증: 방어+6·에너지4·공격HP+1·승리골드+25·엘리트/상점 유물 획득 - 범위 밖: 부정 유물·조건부 효과·유물 제거·보스 유물 Co-Authored-By: Claude Opus 4.8 (1M context) --- RootDesk/MyDesk/SlayDeckController.codeblock | 127 +++- data/relics.json | 10 + tools/gen-slaydeck.mjs | 155 +++- ui/DefaultGroup.ui | 752 +++++++++++++++++++ 4 files changed, 1035 insertions(+), 9 deletions(-) create mode 100644 data/relics.json diff --git a/RootDesk/MyDesk/SlayDeckController.codeblock b/RootDesk/MyDesk/SlayDeckController.codeblock index 760555a..1a61a28 100644 --- a/RootDesk/MyDesk/SlayDeckController.codeblock +++ b/RootDesk/MyDesk/SlayDeckController.codeblock @@ -252,6 +252,41 @@ "SyncDirection": 0, "Attributes": [], "Name": "ShopBought" + }, + { + "Type": "any", + "DefaultValue": "nil", + "SyncDirection": 0, + "Attributes": [], + "Name": "Relics" + }, + { + "Type": "any", + "DefaultValue": "nil", + "SyncDirection": 0, + "Attributes": [], + "Name": "RunRelics" + }, + { + "Type": "any", + "DefaultValue": "nil", + "SyncDirection": 0, + "Attributes": [], + "Name": "RelicPool" + }, + { + "Type": "string", + "DefaultValue": "\"\"", + "SyncDirection": 0, + "Attributes": [], + "Name": "ShopRelic" + }, + { + "Type": "boolean", + "DefaultValue": "false", + "SyncDirection": 0, + "Attributes": [], + "Name": "ShopRelicBought" } ], "Methods": [ @@ -279,7 +314,7 @@ "Name": null }, "Arguments": [], - "Code": "self.PlayerMaxHp = 80\nself.PlayerHp = self.PlayerMaxHp\nself.Gold = 0\nself.Floor = 0\nself.RunLength = 4\nself.RunDeck = { \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Defend\", \"Defend\", \"Defend\", \"Defend\", \"Bash\" }\nself.RunActive = true\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 } } },\n\tslime_boss = { name = \"슬라임 킹\", maxHp = 120, intents = { { kind = \"Attack\", value = 18 }, { kind = \"Defend\", value = 12 }, { kind = \"Attack\", value = 10 }, { kind = \"Attack\", value = 22 } } },\n}\nself.MapNodes = {\n\tA = { type = \"combat\", enemy = \"slime\", row = 1, col = -1, next = { \"C\", \"D\" } },\n\tB = { type = \"combat\", enemy = \"slime\", row = 1, col = 1, next = { \"C\", \"D\" } },\n\tC = { type = \"rest\", row = 2, col = -1, next = { \"E\", \"F\" } },\n\tD = { type = \"shop\", row = 2, col = 1, next = { \"E\", \"F\" } },\n\tE = { type = \"elite\", enemy = \"slime_elite\", row = 3, col = -1, next = { \"BOSS\" } },\n\tF = { type = \"combat\", enemy = \"slime\", row = 3, col = 1, next = { \"BOSS\" } },\n\tBOSS = { type = \"boss\", enemy = \"slime_boss\", row = 4, col = 0, next = { } },\n}\nself.MapStart = { \"A\", \"B\" }\nself.CurrentNodeId = \"\"\nself.CurrentEnemyId = \"\"\nself:BindButtons()\nself:ShowMap()", + "Code": "self.PlayerMaxHp = 80\nself.PlayerHp = self.PlayerMaxHp\nself.Gold = 0\nself.Floor = 0\nself.RunLength = 4\nself.RunDeck = { \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Strike\", \"Defend\", \"Defend\", \"Defend\", \"Defend\", \"Bash\" }\nself.RunActive = true\nself.RunRelics = {}\nself.Relics = {\n\tironHeart = { name = \"강철 심장\", desc = \"전투 시작 시 방어도 +6\", hook = \"combatStart\", effect = \"block\", value = 6 },\n\tenergyCore = { name = \"에너지 코어\", desc = \"턴 시작 시 에너지 +1\", hook = \"turnStart\", effect = \"energy\", value = 1 },\n\tvampire = { name = \"흡혈 송곳니\", desc = \"공격 카드 사용 시 HP +1\", hook = \"cardPlayed\", effect = \"healOnAttack\", value = 1 },\n\tgoldIdol = { name = \"황금 우상\", desc = \"전투 승리 시 골드 +10\", hook = \"combatReward\", effect = \"gold\", value = 10 },\n}\nself.RelicPool = { \"energyCore\", \"vampire\", \"goldIdol\" }\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 } } },\n\tslime_boss = { name = \"슬라임 킹\", maxHp = 120, intents = { { kind = \"Attack\", value = 18 }, { kind = \"Defend\", value = 12 }, { kind = \"Attack\", value = 10 }, { kind = \"Attack\", value = 22 } } },\n}\nself.MapNodes = {\n\tA = { type = \"combat\", enemy = \"slime\", row = 1, col = -1, next = { \"C\", \"D\" } },\n\tB = { type = \"combat\", enemy = \"slime\", row = 1, col = 1, next = { \"C\", \"D\" } },\n\tC = { type = \"rest\", row = 2, col = -1, next = { \"E\", \"F\" } },\n\tD = { type = \"shop\", row = 2, col = 1, next = { \"E\", \"F\" } },\n\tE = { type = \"elite\", enemy = \"slime_elite\", row = 3, col = -1, next = { \"BOSS\" } },\n\tF = { type = \"combat\", enemy = \"slime\", row = 3, col = 1, next = { \"BOSS\" } },\n\tBOSS = { type = \"boss\", enemy = \"slime_boss\", row = 4, col = 0, next = { } },\n}\nself.MapStart = { \"A\", \"B\" }\nself.CurrentNodeId = \"\"\nself.CurrentEnemyId = \"\"\nself:BindButtons()\nself:AddRelic(\"ironHeart\")\nself:ShowMap()", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -294,7 +329,7 @@ "Name": null }, "Arguments": [], - "Code": "self.MaxEnergy = 3\nself.Turn = 0\nlocal node = self.MapNodes[self.CurrentNodeId]\nif node ~= nil then\n\tself.Floor = node.row\nend\nlocal enemy = self.Enemies[self.CurrentEnemyId]\nself.PlayerBlock = 0\nself.EnemyName = enemy.name\nself.EnemyMaxHp = enemy.maxHp\nself.EnemyHp = self.EnemyMaxHp\nself.EnemyBlock = 0\nself.EnemyIntents = enemy.intents\nself.EnemyIntentIndex = 1\nself.CombatOver = false\nself.DiscardPile = {}\nself.Hand = {}\nself.Cards = {\n\tStrike = { name = \"타격\", cost = 1, desc = \"피해 6\", kind = \"Attack\", damage = 6 },\n\tDefend = { name = \"방어\", cost = 1, desc = \"방어도 5\", kind = \"Skill\", block = 5 },\n\tBash = { name = \"강타\", cost = 2, desc = \"피해 10\", kind = \"Attack\", damage = 10 },\n}\nself.DrawPile = {}\nfor i = 1, #self.RunDeck do\n\tself.DrawPile[i] = self.RunDeck[i]\nend\nself:Shuffle(self.DrawPile)\nself:RenderCombat()\nself:StartPlayerTurn()", + "Code": "self.MaxEnergy = 3\nself.Turn = 0\nlocal node = self.MapNodes[self.CurrentNodeId]\nif node ~= nil then\n\tself.Floor = node.row\nend\nlocal enemy = self.Enemies[self.CurrentEnemyId]\nself.PlayerBlock = 0\nself.EnemyName = enemy.name\nself.EnemyMaxHp = enemy.maxHp\nself.EnemyHp = self.EnemyMaxHp\nself.EnemyBlock = 0\nself.EnemyIntents = enemy.intents\nself.EnemyIntentIndex = 1\nself.CombatOver = false\nself.DiscardPile = {}\nself.Hand = {}\nself.Cards = {\n\tStrike = { name = \"타격\", cost = 1, desc = \"피해 6\", kind = \"Attack\", damage = 6 },\n\tDefend = { name = \"방어\", cost = 1, desc = \"방어도 5\", kind = \"Skill\", block = 5 },\n\tBash = { name = \"강타\", cost = 2, desc = \"피해 10\", kind = \"Attack\", damage = 10 },\n}\nself.DrawPile = {}\nfor i = 1, #self.RunDeck do\n\tself.DrawPile[i] = self.RunDeck[i]\nend\nself:Shuffle(self.DrawPile)\nself:RenderCombat()\nself:StartPlayerTurn()\nself:ApplyRelics(\"combatStart\")\nself:RenderCombat()", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -332,7 +367,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\nfor i = 1, 5 do\n\tlocal cardEntity = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CardHand/Card\" .. tostring(i))\n\tif cardEntity ~= nil and cardEntity.ButtonComponent ~= nil then\n\t\tcardEntity:ConnectEvent(ButtonClickEvent, function() self:PlayCard(i) 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 = { \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"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 restLeave = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/RestHud/Leave\")\nif restLeave ~= nil and restLeave.ButtonComponent ~= nil then\n\trestLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end)\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\nfor i = 1, 5 do\n\tlocal cardEntity = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/CardHand/Card\" .. tostring(i))\n\tif cardEntity ~= nil and cardEntity.ButtonComponent ~= nil then\n\t\tcardEntity:ConnectEvent(ButtonClickEvent, function() self:PlayCard(i) 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 = { \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"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", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -347,7 +382,7 @@ "Name": null }, "Arguments": [], - "Code": "self.Turn = self.Turn + 1\nself.Energy = self.MaxEnergy\nself.PlayerBlock = 0\nself:DrawCards(5)\nself:RenderHand(true)\nself:RenderCombat()", + "Code": "self.Turn = self.Turn + 1\nself.Energy = self.MaxEnergy\nself:ApplyRelics(\"turnStart\")\nself.PlayerBlock = 0\nself:DrawCards(5)\nself:RenderHand(true)\nself:RenderCombat()", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -565,7 +600,7 @@ "Name": "slot" } ], - "Code": "if self.CombatOver == 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:DealDamageToEnemy(c.damage)\n\tend\nelseif c.kind == \"Skill\" then\n\tif c.block ~= nil then\n\t\tself.PlayerBlock = self.PlayerBlock + c.block\n\tend\nend\ntable.remove(self.Hand, slot)\ntable.insert(self.DiscardPile, cardId)\nself:RenderHand(false)\nself:RenderPiles()\nself:RenderCombat()\nself:CheckCombatEnd()", + "Code": "if self.CombatOver == 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:DealDamageToEnemy(c.damage)\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\nend\ntable.remove(self.Hand, slot)\ntable.insert(self.DiscardPile, cardId)\nself:RenderHand(false)\nself:RenderPiles()\nself:RenderCombat()\nself:CheckCombatEnd()", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -664,7 +699,7 @@ "Name": null }, "Arguments": [], - "Code": "if self.EnemyHp <= 0 then\n\tself.CombatOver = true\n\tself.Gold = self.Gold + 15\n\tself:RenderRun()\n\tlocal node = self.MapNodes[self.CurrentNodeId]\n\tif node ~= nil and node.type == \"boss\" then\n\t\tself:ShowResult(\"런 클리어!\")\n\t\tself.RunActive = false\n\telse\n\t\tself:OfferReward()\n\tend\nelseif self.PlayerHp <= 0 then\n\tself.CombatOver = true\n\tself:ShowResult(\"패배...\")\n\tself.RunActive = false\nend", + "Code": "if self.EnemyHp <= 0 then\n\tself.CombatOver = true\n\tself.Gold = self.Gold + 15\n\tself:ApplyRelics(\"combatReward\")\n\tself:RenderRun()\n\tlocal node = self.MapNodes[self.CurrentNodeId]\n\tif node ~= nil and node.type == \"elite\" then\n\t\tself:AddRelic(self.RelicPool[math.random(1, #self.RelicPool)])\n\tend\n\tif node ~= nil and node.type == \"boss\" then\n\t\tself:ShowResult(\"런 클리어!\")\n\t\tself.RunActive = false\n\telse\n\t\tself:OfferReward()\n\tend\nelseif self.PlayerHp <= 0 then\n\tself.CombatOver = true\n\tself:ShowResult(\"패배...\")\n\tself.RunActive = false\nend", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -791,6 +826,67 @@ "Attributes": [], "Name": "PickReward" }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "Type": "string", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "hook" + } + ], + "Code": "if self.RunRelics == nil then\n\treturn\nend\nfor i = 1, #self.RunRelics do\n\tlocal r = self.Relics[self.RunRelics[i]]\n\tif r ~= nil and r.hook == hook then\n\t\tif r.effect == \"block\" then\n\t\t\tself.PlayerBlock = self.PlayerBlock + r.value\n\t\telseif r.effect == \"energy\" then\n\t\t\tself.Energy = self.Energy + r.value\n\t\telseif r.effect == \"healOnAttack\" then\n\t\t\tself.PlayerHp = self.PlayerHp + r.value\n\t\t\tif self.PlayerHp > self.PlayerMaxHp then\n\t\t\t\tself.PlayerHp = self.PlayerMaxHp\n\t\t\tend\n\t\telseif r.effect == \"gold\" then\n\t\t\tself.Gold = self.Gold + r.value\n\t\tend\n\tend\nend", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "ApplyRelics" + }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [ + { + "Type": "string", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": "id" + } + ], + "Code": "if self.RunRelics == nil then\n\tself.RunRelics = {}\nend\ntable.insert(self.RunRelics, id)\nself:RenderRelics()", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "AddRelic" + }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [], + "Code": "local names = \"\"\nif self.RunRelics ~= nil then\n\tfor i = 1, #self.RunRelics do\n\t\tlocal r = self.Relics[self.RunRelics[i]]\n\t\tif r ~= nil then\n\t\t\tif names == \"\" then\n\t\t\t\tnames = r.name\n\t\t\telse\n\t\t\t\tnames = names .. \", \" .. r.name\n\t\t\tend\n\t\tend\n\tend\nend\nif names == \"\" then\n\tnames = \"없음\"\nend\nself:SetText(\"/ui/DefaultGroup/CombatHud/Relics\", \"유물: \" .. names)", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "RenderRelics" + }, { "Return": { "Type": "void", @@ -876,7 +972,7 @@ "Name": null }, "Arguments": [], - "Code": "local pool = {}\nfor cid, _ in pairs(self.Cards) do\n\ttable.insert(pool, cid)\nend\nself.ShopChoices = {}\nself.ShopBought = { false, false, false }\nfor i = 1, 3 do\n\tself.ShopChoices[i] = pool[math.random(1, #pool)]\nend\nself:RenderShop()\nlocal hud = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/ShopHud\")\nif hud ~= nil then\n\thud.Enable = true\nend", + "Code": "local pool = {}\nfor cid, _ in pairs(self.Cards) do\n\ttable.insert(pool, cid)\nend\nself.ShopChoices = {}\nself.ShopBought = { false, false, false }\nfor i = 1, 3 do\n\tself.ShopChoices[i] = pool[math.random(1, #pool)]\nend\nself.ShopRelic = self.RelicPool[math.random(1, #self.RelicPool)]\nself.ShopRelicBought = false\nself:RenderShop()\nlocal hud = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/ShopHud\")\nif hud ~= nil then\n\thud.Enable = true\nend", "Scope": 2, "ExecSpace": 6, "Attributes": [], @@ -891,12 +987,27 @@ "Name": null }, "Arguments": [], - "Code": "self:SetText(\"/ui/DefaultGroup/ShopHud/Gold\", \"골드 \" .. string.format(\"%d\", self.Gold))\nfor i = 1, 3 do\n\tlocal cid = self.ShopChoices[i]\n\tlocal c = self.Cards[cid]\n\tlocal base = \"/ui/DefaultGroup/ShopHud/Card\" .. tostring(i)\n\tif c ~= nil then\n\t\tself:SetText(base .. \"/Name\", c.name)\n\t\tself:SetText(base .. \"/Cost\", tostring(c.cost))\n\t\tself:SetText(base .. \"/Desc\", c.desc)\n\t\tself:SetText(base .. \"/Price\", string.format(\"%d\", 30) .. \" 골드\")\n\t\tlocal e = _EntityService:GetEntityByPath(base)\n\t\tif e ~= nil and e.SpriteGUIRendererComponent ~= nil then\n\t\t\tif self.ShopBought[i] == true then\n\t\t\t\te.SpriteGUIRendererComponent.Color = Color(0.2, 0.22, 0.26, 0.6)\n\t\t\telseif c.kind == \"Attack\" then\n\t\t\t\te.SpriteGUIRendererComponent.Color = Color(0.86, 0.42, 0.38, 1)\n\t\t\telseif c.kind == \"Skill\" then\n\t\t\t\te.SpriteGUIRendererComponent.Color = Color(0.42, 0.55, 0.85, 1)\n\t\t\telse\n\t\t\t\te.SpriteGUIRendererComponent.Color = Color(0.46, 0.68, 0.52, 1)\n\t\t\tend\n\t\tend\n\tend\nend", + "Code": "self:SetText(\"/ui/DefaultGroup/ShopHud/Gold\", \"골드 \" .. string.format(\"%d\", self.Gold))\nfor i = 1, 3 do\n\tlocal cid = self.ShopChoices[i]\n\tlocal c = self.Cards[cid]\n\tlocal base = \"/ui/DefaultGroup/ShopHud/Card\" .. tostring(i)\n\tif c ~= nil then\n\t\tself:SetText(base .. \"/Name\", c.name)\n\t\tself:SetText(base .. \"/Cost\", tostring(c.cost))\n\t\tself:SetText(base .. \"/Desc\", c.desc)\n\t\tself:SetText(base .. \"/Price\", string.format(\"%d\", 30) .. \" 골드\")\n\t\tlocal e = _EntityService:GetEntityByPath(base)\n\t\tif e ~= nil and e.SpriteGUIRendererComponent ~= nil then\n\t\t\tif self.ShopBought[i] == true then\n\t\t\t\te.SpriteGUIRendererComponent.Color = Color(0.2, 0.22, 0.26, 0.6)\n\t\t\telseif c.kind == \"Attack\" then\n\t\t\t\te.SpriteGUIRendererComponent.Color = Color(0.86, 0.42, 0.38, 1)\n\t\t\telseif c.kind == \"Skill\" then\n\t\t\t\te.SpriteGUIRendererComponent.Color = Color(0.42, 0.55, 0.85, 1)\n\t\t\telse\n\t\t\t\te.SpriteGUIRendererComponent.Color = Color(0.46, 0.68, 0.52, 1)\n\t\t\tend\n\t\tend\n\tend\nend\nlocal rr = self.Relics[self.ShopRelic]\nif rr ~= nil then\n\tself:SetText(\"/ui/DefaultGroup/ShopHud/Relic/Label\", rr.name .. \" — \" .. rr.desc)\n\tself:SetText(\"/ui/DefaultGroup/ShopHud/Relic/Price\", string.format(\"%d\", 60) .. \" 골드\")\n\tlocal re = _EntityService:GetEntityByPath(\"/ui/DefaultGroup/ShopHud/Relic\")\n\tif re ~= nil and re.SpriteGUIRendererComponent ~= nil then\n\t\tif self.ShopRelicBought == true then\n\t\t\tre.SpriteGUIRendererComponent.Color = Color(0.2, 0.22, 0.26, 0.6)\n\t\telse\n\t\t\tre.SpriteGUIRendererComponent.Color = Color(0.7, 0.55, 0.85, 1)\n\t\tend\n\tend\nend", "Scope": 2, "ExecSpace": 6, "Attributes": [], "Name": "RenderShop" }, + { + "Return": { + "Type": "void", + "DefaultValue": null, + "SyncDirection": 0, + "Attributes": [], + "Name": null + }, + "Arguments": [], + "Code": "if self.ShopRelicBought == true then\n\treturn\nend\nif self.Gold < 60 then\n\treturn\nend\nself.Gold = self.Gold - 60\nself:AddRelic(self.ShopRelic)\nself.ShopRelicBought = true\nself:RenderShop()\nself:RenderRun()", + "Scope": 2, + "ExecSpace": 6, + "Attributes": [], + "Name": "BuyRelic" + }, { "Return": { "Type": "void", diff --git a/data/relics.json b/data/relics.json new file mode 100644 index 0000000..8f986c4 --- /dev/null +++ b/data/relics.json @@ -0,0 +1,10 @@ +{ + "relics": { + "ironHeart": { "name": "강철 심장", "desc": "전투 시작 시 방어도 +6", "hook": "combatStart", "effect": "block", "value": 6 }, + "energyCore": { "name": "에너지 코어", "desc": "턴 시작 시 에너지 +1", "hook": "turnStart", "effect": "energy", "value": 1 }, + "vampire": { "name": "흡혈 송곳니", "desc": "공격 카드 사용 시 HP +1", "hook": "cardPlayed", "effect": "healOnAttack", "value": 1 }, + "goldIdol": { "name": "황금 우상", "desc": "전투 승리 시 골드 +10", "hook": "combatReward", "effect": "gold", "value": 10 } + }, + "startingRelic": "ironHeart", + "relicPool": ["energyCore", "vampire", "goldIdol"] +} diff --git a/tools/gen-slaydeck.mjs b/tools/gen-slaydeck.mjs index 397f0b7..0d8757f 100644 --- a/tools/gen-slaydeck.mjs +++ b/tools/gen-slaydeck.mjs @@ -26,6 +26,17 @@ for (const [id, n] of Object.entries(MAP.nodes)) { } const MAX_ROW = Math.max(...Object.values(MAP.nodes).map((n) => n.row)); +const RELICS = JSON.parse(readFileSync('data/relics.json', 'utf8')); +if (!RELICS.relics[RELICS.startingRelic]) throw new Error(`[gen-slaydeck] startingRelic 없음: ${RELICS.startingRelic}`); +for (const id of RELICS.relicPool) { + if (!RELICS.relics[id]) throw new Error(`[gen-slaydeck] relicPool에 없는 유물 id: ${id}`); +} +function luaRelicsTable(relics) { + const lines = Object.entries(relics).map(([id, r]) => + `\t${id} = { name = ${luaStr(r.name)}, desc = ${luaStr(r.desc)}, hook = ${luaStr(r.hook)}, effect = ${luaStr(r.effect)}, value = ${r.value} },`); + return `self.Relics = {\n${lines.join('\n')}\n}`; +} + function luaIntentsArray(intents) { return '{ ' + intents.map((it) => `{ kind = ${luaStr(it.kind)}, value = ${it.value} }`).join(', ') + ' }'; } @@ -501,6 +512,19 @@ function upsertUi() { ], })); } + combat.push(entity({ + id: guid('cmb', cmbN++), + path: '/ui/DefaultGroup/CombatHud/Relics', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 9, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 1000, y: 40 }, pos: { x: 0, y: 430 } }), + sprite({ color: TRANSPARENT }), + text({ value: '유물: 없음', fontSize: 22, bold: true, color: { r: 0.8, g: 0.7, b: 0.95, a: 1 }, alignment: 4 }), + ], + })); const result = entity({ id: guid('cmb', cmbN++), path: '/ui/DefaultGroup/CombatHud/Result', @@ -740,6 +764,45 @@ function upsertUi() { })); } } + shop.push(entity({ + id: guid('shp', shpN++), + path: '/ui/DefaultGroup/ShopHud/Relic', + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent', + displayOrder: 9, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 560, y: 76 }, pos: { x: 0, y: -190 } }), + sprite({ color: { r: 0.7, g: 0.55, b: 0.85, a: 1 }, type: 1, raycast: true }), + button(), + ], + })); + shop.push(entity({ + id: guid('shp', shpN++), + path: '/ui/DefaultGroup/ShopHud/Relic/Label', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 0, + components: [ + transform({ parentW: 560, parentH: 76, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 540, y: 40 }, pos: { x: 0, y: 12 } }), + sprite({ color: TRANSPARENT }), + text({ value: '유물', fontSize: 22, bold: true, color: { r: 1, g: 1, b: 1, a: 1 }, alignment: 4 }), + ], + })); + shop.push(entity({ + id: guid('shp', shpN++), + path: '/ui/DefaultGroup/ShopHud/Relic/Price', + modelId: 'uitext', + entryId: 'UIText', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + displayOrder: 1, + components: [ + transform({ parentW: 560, parentH: 76, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 540, y: 30 }, pos: { x: 0, y: -22 } }), + sprite({ color: TRANSPARENT }), + text({ value: '60 골드', fontSize: 20, bold: true, color: { r: 0.98, g: 0.85, b: 0.4, a: 1 }, alignment: 4 }), + ], + })); shop.push(entity({ id: guid('shp', shpN++), path: '/ui/DefaultGroup/ShopHud/Leave', @@ -871,6 +934,7 @@ function writeCodeblocks() { const GOLD_PER_WIN = 15; const CARD_PRICE = 30; const REST_HEAL = 30; + const RELIC_PRICE = 60; const combat = codeblock('SlayDeckController', 'SlayDeckController', [ prop('any', 'DrawPile'), prop('any', 'DiscardPile'), @@ -904,6 +968,11 @@ function writeCodeblocks() { prop('string', 'CurrentEnemyId', '""'), prop('any', 'ShopChoices'), prop('any', 'ShopBought'), + prop('any', 'Relics'), + prop('any', 'RunRelics'), + prop('any', 'RelicPool'), + prop('string', 'ShopRelic', '""'), + prop('boolean', 'ShopRelicBought', 'false'), ], [ method('OnBeginPlay', `self:StartRun()`), method('StartRun', `self.PlayerMaxHp = 80 @@ -913,12 +982,16 @@ self.Floor = 0 self.RunLength = ${MAX_ROW} self.RunDeck = { ${CARDS.starterDeck.map(luaStr).join(', ')} } self.RunActive = true +self.RunRelics = {} +${luaRelicsTable(RELICS.relics)} +self.RelicPool = { ${RELICS.relicPool.map(luaStr).join(', ')} } ${luaEnemiesTable(ENEMIES.enemies)} ${luaMapNodesTable(MAP.nodes)} ${luaStartArray(MAP.start)} self.CurrentNodeId = "" self.CurrentEnemyId = "" self:BindButtons() +self:AddRelic("${RELICS.startingRelic}") self:ShowMap()`), method('StartCombat', `self.MaxEnergy = 3 self.Turn = 0 @@ -944,7 +1017,9 @@ for i = 1, #self.RunDeck do end self:Shuffle(self.DrawPile) self:RenderCombat() -self:StartPlayerTurn()`), +self:StartPlayerTurn() +self:ApplyRelics("combatStart") +self:RenderCombat()`), method('Shuffle', `if list == nil then \treturn end @@ -994,12 +1069,17 @@ local shopLeave = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Leave if shopLeave ~= nil and shopLeave.ButtonComponent ~= nil then shopLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end) end +local shopRelic = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Relic") +if shopRelic ~= nil and shopRelic.ButtonComponent ~= nil then + shopRelic:ConnectEvent(ButtonClickEvent, function() self:BuyRelic() end) +end local restLeave = _EntityService:GetEntityByPath("/ui/DefaultGroup/RestHud/Leave") if restLeave ~= nil and restLeave.ButtonComponent ~= nil then restLeave:ConnectEvent(ButtonClickEvent, function() self:LeaveNode() end) end`), method('StartPlayerTurn', `self.Turn = self.Turn + 1 self.Energy = self.MaxEnergy +self:ApplyRelics("turnStart") self.PlayerBlock = 0 self:DrawCards(5) self:RenderHand(true) @@ -1131,6 +1211,7 @@ if c.kind == "Attack" then if c.damage ~= nil then self:DealDamageToEnemy(c.damage) end + self:ApplyRelics("cardPlayed") elseif c.kind == "Skill" then if c.block ~= nil then self.PlayerBlock = self.PlayerBlock + c.block @@ -1180,8 +1261,12 @@ self:RenderCombat()`), method('CheckCombatEnd', `if self.EnemyHp <= 0 then self.CombatOver = true self.Gold = self.Gold + ${GOLD_PER_WIN} + self:ApplyRelics("combatReward") self:RenderRun() local node = self.MapNodes[self.CurrentNodeId] + if node ~= nil and node.type == "elite" then + self:AddRelic(self.RelicPool[math.random(1, #self.RelicPool)]) + end if node ~= nil and node.type == "boss" then self:ShowResult("런 클리어!") self.RunActive = false @@ -1264,6 +1349,48 @@ if hud ~= nil then hud.Enable = false end self:ShowMap()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]), + method('ApplyRelics', `if self.RunRelics == nil then + return +end +for i = 1, #self.RunRelics do + local r = self.Relics[self.RunRelics[i]] + if r ~= nil and r.hook == hook then + if r.effect == "block" then + self.PlayerBlock = self.PlayerBlock + r.value + elseif r.effect == "energy" then + self.Energy = self.Energy + r.value + elseif r.effect == "healOnAttack" then + self.PlayerHp = self.PlayerHp + r.value + if self.PlayerHp > self.PlayerMaxHp then + self.PlayerHp = self.PlayerMaxHp + end + elseif r.effect == "gold" then + self.Gold = self.Gold + r.value + end + end +end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'hook' }]), + method('AddRelic', `if self.RunRelics == nil then + self.RunRelics = {} +end +table.insert(self.RunRelics, id) +self:RenderRelics()`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'id' }]), + method('RenderRelics', `local names = "" +if self.RunRelics ~= nil then + for i = 1, #self.RunRelics do + local r = self.Relics[self.RunRelics[i]] + if r ~= nil then + if names == "" then + names = r.name + else + names = names .. ", " .. r.name + end + end + end +end +if names == "" then + names = "없음" +end +self:SetText("/ui/DefaultGroup/CombatHud/Relics", "유물: " .. names)`), method('ShowMap', `self:RenderMap() local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/MapHud") if hud ~= nil then @@ -1330,6 +1457,8 @@ self.ShopBought = { false, false, false } for i = 1, 3 do self.ShopChoices[i] = pool[math.random(1, #pool)] end +self.ShopRelic = self.RelicPool[math.random(1, #self.RelicPool)] +self.ShopRelicBought = false self:RenderShop() local hud = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud") if hud ~= nil then @@ -1358,7 +1487,31 @@ for i = 1, 3 do end end end +end +local rr = self.Relics[self.ShopRelic] +if rr ~= nil then + self:SetText("/ui/DefaultGroup/ShopHud/Relic/Label", rr.name .. " — " .. rr.desc) + self:SetText("/ui/DefaultGroup/ShopHud/Relic/Price", string.format("%d", ${RELIC_PRICE}) .. " 골드") + local re = _EntityService:GetEntityByPath("/ui/DefaultGroup/ShopHud/Relic") + if re ~= nil and re.SpriteGUIRendererComponent ~= nil then + if self.ShopRelicBought == true then + re.SpriteGUIRendererComponent.Color = Color(0.2, 0.22, 0.26, 0.6) + else + re.SpriteGUIRendererComponent.Color = Color(0.7, 0.55, 0.85, 1) + end + end end`), + method('BuyRelic', `if self.ShopRelicBought == true then + return +end +if self.Gold < ${RELIC_PRICE} then + return +end +self.Gold = self.Gold - ${RELIC_PRICE} +self:AddRelic(self.ShopRelic) +self.ShopRelicBought = true +self:RenderShop() +self:RenderRun()`), method('BuyCard', `if self.ShopBought == nil or self.ShopBought[slot] == true then return end diff --git a/ui/DefaultGroup.ui b/ui/DefaultGroup.ui index f33c51c..f9cad9c 100644 --- a/ui/DefaultGroup.ui +++ b/ui/DefaultGroup.ui @@ -8493,6 +8493,194 @@ }, { "id": "0cb0000b-0000-4000-8000-00000cb0000b", + "path": "/ui/DefaultGroup/CombatHud/Relics", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "Relics", + "path": "/ui/DefaultGroup/CombatHud/Relics", + "nameEditable": true, + "enable": true, + "visible": true, + "localize": true, + "displayOrder": 9, + "pathConstraints": "////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UIText", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uitext", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": 500, + "y": 450 + }, + "OffsetMin": { + "x": -500, + "y": 410 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 1000, + "y": 40 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 0, + "y": 430 + }, + "Position": { + "x": 0, + "y": 430, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0, + "g": 0, + "b": 0, + "a": 0 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": false, + "Type": 1, + "Enable": true + }, + { + "@type": "MOD.Core.TextComponent", + "Alignment": 4, + "Bold": true, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "Font": 0, + "FontColor": { + "r": 0.8, + "g": 0.7, + "b": 0.95, + "a": 1 + }, + "FontSize": 22, + "MaxSize": 22, + "MinSize": 8, + "OutlineColor": { + "r": 0.08, + "g": 0.08, + "b": 0.08, + "a": 1 + }, + "OutlineDistance": { + "x": 1, + "y": -1 + }, + "OutlineWidth": 1, + "Overflow": 0, + "OverrideSorting": false, + "Padding": { + "left": 0, + "right": 0, + "top": 0, + "bottom": 0 + }, + "SizeFit": false, + "Text": "유물: 없음", + "UseOutLine": true, + "Enable": true + } + ], + "@version": 1 + } + }, + { + "id": "0cb0000c-0000-4000-8000-00000cb0000c", "path": "/ui/DefaultGroup/CombatHud/Result", "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", "jsonString": { @@ -17799,6 +17987,570 @@ }, { "id": "0ce00012-0000-4000-8000-00000ce00012", + "path": "/ui/DefaultGroup/ShopHud/Relic", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent", + "jsonString": { + "name": "Relic", + "path": "/ui/DefaultGroup/ShopHud/Relic", + "nameEditable": true, + "enable": true, + "visible": true, + "localize": true, + "displayOrder": 9, + "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": 280, + "y": -152 + }, + "OffsetMin": { + "x": -280, + "y": -228 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 560, + "y": 76 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 0, + "y": -190 + }, + "Position": { + "x": 0, + "y": -190, + "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.7, + "g": 0.55, + "b": 0.85, + "a": 1 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": true, + "Type": 1, + "Enable": true + }, + { + "@type": "MOD.Core.ButtonComponent", + "Colors": { + "NormalColor": { + "r": 1, + "g": 1, + "b": 1, + "a": 1 + }, + "HighlightedColor": { + "r": 0.9607843, + "g": 0.9607843, + "b": 0.9607843, + "a": 1 + }, + "PressedColor": { + "r": 0.784313738, + "g": 0.784313738, + "b": 0.784313738, + "a": 1 + }, + "SelectedColor": { + "r": 0.9607843, + "g": 0.9607843, + "b": 0.9607843, + "a": 1 + }, + "DisabledColor": { + "r": 0.784313738, + "g": 0.784313738, + "b": 0.784313738, + "a": 0.5019608 + }, + "ColorMultiplier": 1, + "FadeDuration": 0.1 + }, + "ImageRUIDs": { + "HighlightedSprite": null, + "PressedSprite": null, + "SelectedSprite": null, + "DisabledSprite": null + }, + "KeyCode": 0, + "OverrideSorting": false, + "Transition": 1, + "Enable": true + } + ], + "@version": 1 + } + }, + { + "id": "0ce00013-0000-4000-8000-00000ce00013", + "path": "/ui/DefaultGroup/ShopHud/Relic/Label", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "Label", + "path": "/ui/DefaultGroup/ShopHud/Relic/Label", + "nameEditable": true, + "enable": true, + "visible": true, + "localize": true, + "displayOrder": 0, + "pathConstraints": "/////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UIText", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uitext", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": 270, + "y": 32 + }, + "OffsetMin": { + "x": -270, + "y": -8 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 540, + "y": 40 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 0, + "y": 12 + }, + "Position": { + "x": 0, + "y": 12, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0, + "g": 0, + "b": 0, + "a": 0 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": false, + "Type": 1, + "Enable": true + }, + { + "@type": "MOD.Core.TextComponent", + "Alignment": 4, + "Bold": true, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "Font": 0, + "FontColor": { + "r": 1, + "g": 1, + "b": 1, + "a": 1 + }, + "FontSize": 22, + "MaxSize": 22, + "MinSize": 8, + "OutlineColor": { + "r": 0.08, + "g": 0.08, + "b": 0.08, + "a": 1 + }, + "OutlineDistance": { + "x": 1, + "y": -1 + }, + "OutlineWidth": 1, + "Overflow": 0, + "OverrideSorting": false, + "Padding": { + "left": 0, + "right": 0, + "top": 0, + "bottom": 0 + }, + "SizeFit": false, + "Text": "유물", + "UseOutLine": true, + "Enable": true + } + ], + "@version": 1 + } + }, + { + "id": "0ce00014-0000-4000-8000-00000ce00014", + "path": "/ui/DefaultGroup/ShopHud/Relic/Price", + "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent", + "jsonString": { + "name": "Price", + "path": "/ui/DefaultGroup/ShopHud/Relic/Price", + "nameEditable": true, + "enable": true, + "visible": true, + "localize": true, + "displayOrder": 1, + "pathConstraints": "/////", + "revision": 1, + "origin": { + "type": "Model", + "entry_id": "UIText", + "sub_entity_id": null, + "root_entity_id": null, + "replaced_model_id": null + }, + "modelId": "uitext", + "@components": [ + { + "@type": "MOD.Core.UITransformComponent", + "ActivePlatform": 255, + "AlignmentOption": 0, + "AnchorsMax": { + "x": 0.5, + "y": 0.5 + }, + "AnchorsMin": { + "x": 0.5, + "y": 0.5 + }, + "MobileOnly": false, + "OffsetMax": { + "x": 270, + "y": -7 + }, + "OffsetMin": { + "x": -270, + "y": -37 + }, + "Pivot": { + "x": 0.5, + "y": 0.5 + }, + "RectSize": { + "x": 540, + "y": 30 + }, + "UIMode": 1, + "UIScale": { + "x": 1, + "y": 1, + "z": 1 + }, + "UIVersion": 2, + "anchoredPosition": { + "x": 0, + "y": -22 + }, + "Position": { + "x": 0, + "y": -22, + "z": 0 + }, + "QuaternionRotation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "Scale": { + "x": 1, + "y": 1, + "z": 1 + }, + "Enable": true + }, + { + "@type": "MOD.Core.SpriteGUIRendererComponent", + "AnimClipPlayType": 0, + "EndFrameIndex": 2147483647, + "ImageRUID": { + "DataId": "" + }, + "LocalPosition": { + "x": 0, + "y": 0 + }, + "LocalScale": { + "x": 1, + "y": 1 + }, + "OverrideSorting": false, + "PlayRate": 1, + "PreserveSprite": 0, + "StartFrameIndex": 0, + "Color": { + "r": 0, + "g": 0, + "b": 0, + "a": 0 + }, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "FillAmount": 1, + "FillCenter": true, + "FillClockWise": true, + "FillMethod": 0, + "FillOrigin": 0, + "FlipX": false, + "FlipY": false, + "FrameColumn": 1, + "FrameRate": 0, + "FrameRow": 1, + "Outline": false, + "OutlineColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 1 + }, + "OutlineWidth": 3, + "RaycastTarget": false, + "Type": 1, + "Enable": true + }, + { + "@type": "MOD.Core.TextComponent", + "Alignment": 4, + "Bold": true, + "DropShadow": false, + "DropShadowAngle": 30, + "DropShadowColor": { + "r": 0, + "g": 0, + "b": 0, + "a": 0.72 + }, + "DropShadowDistance": 32, + "Font": 0, + "FontColor": { + "r": 0.98, + "g": 0.85, + "b": 0.4, + "a": 1 + }, + "FontSize": 20, + "MaxSize": 20, + "MinSize": 8, + "OutlineColor": { + "r": 0.08, + "g": 0.08, + "b": 0.08, + "a": 1 + }, + "OutlineDistance": { + "x": 1, + "y": -1 + }, + "OutlineWidth": 1, + "Overflow": 0, + "OverrideSorting": false, + "Padding": { + "left": 0, + "right": 0, + "top": 0, + "bottom": 0 + }, + "SizeFit": false, + "Text": "60 골드", + "UseOutLine": true, + "Enable": true + } + ], + "@version": 1 + } + }, + { + "id": "0ce00015-0000-4000-8000-00000ce00015", "path": "/ui/DefaultGroup/ShopHud/Leave", "componentNames": "MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent", "jsonString": {