From 42f9143f73833ccf0a46dc2a54320731cab412c0 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 12 Jun 2026 07:29:01 +0900 Subject: [PATCH] =?UTF-8?q?feat(potions-relics):=20=EC=9C=A0=EB=AC=BC=2015?= =?UTF-8?q?=EC=A2=85=20=ED=9A=A8=EA=B3=BC=20=ED=9B=85=20(=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EA=B8=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HasRelic·PickNewRelic(미보유 추첨, 소진 시 골드+25) - ApplyRelics 확장: strength/draw/heal/healOnWin/healIfLow + combatEnd 훅 - AddRelic passive: 물약 슬롯 5칸(장인의 벨트)·최대 HP+7(건강의 반지) - CalcPlayerAttack: 아카베코(첫 공격+8)·펜닙(10번째 2배)·부츠(5 미만→5) - DealDamageToPlayer: 브론즈 체인메일 반사·점토 갑옷·백년의 부적 - 챔피언 벨트(취약 부여 시 약화+1), 정예·보스 유물 보상 개선 Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/deck/gen-slaydeck.mjs | 147 ++++++++++++++++++++++++++++++++++-- 1 file changed, 139 insertions(+), 8 deletions(-) diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index f50c8f8..c2846c8 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -32,10 +32,20 @@ for (const id of RELICS.relicPool) { } 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} },`); + `\t${id} = { name = ${luaStr(r.name)}, desc = ${luaStr(r.desc)}, hook = ${luaStr(r.hook)}, effect = ${luaStr(r.effect)}, value = ${r.value}, icon = ${luaStr(r.icon || '')} },`); return `self.Relics = {\n${lines.join('\n')}\n}`; } +const POTIONS = JSON.parse(readFileSync('data/potions.json', 'utf8')); +for (const [pid, p] of Object.entries(POTIONS.potions)) { + if (!p.name || !p.effect || p.value == null) throw new Error(`[gen-slaydeck] potion 필드 누락: ${pid}`); +} +function luaPotionsTable(potions) { + const lines = Object.entries(potions).map(([id, p]) => + `\t${id} = { name = ${luaStr(p.name)}, desc = ${luaStr(p.desc)}, effect = ${luaStr(p.effect)}, value = ${p.value}, icon = ${luaStr(p.icon || '')} },`); + return `self.Potions = {\n${lines.join('\n')}\n}`; +} + function luaIntentsArray(intents) { return '{ ' + intents.map((it) => { const fields = [`kind = ${luaStr(it.kind)}`, `value = ${it.value}`]; @@ -1942,6 +1952,15 @@ function writeCodeblocks() { prop('number', 'PlayerWeak', '0'), prop('number', 'PlayerVuln', '0'), prop('any', 'PlayerPowers'), + prop('any', 'Potions'), + prop('any', 'RunPotions'), + prop('number', 'PotionSlots', String(POTIONS.baseSlots)), + prop('string', 'ShopPotion', '""'), + prop('boolean', 'ShopPotionBought', 'false'), + prop('number', 'FightAttackCount', '0'), + prop('boolean', 'FirstHpLossDone', 'false'), + prop('number', 'ClayBlockNext', '0'), + prop('number', 'PotionMenuSlot', '0'), ], [ method('OnBeginPlay', `self:ShowMainMenu()`), method('HideGameHud', `self:SetEntityEnabled("/ui/DefaultGroup/Button_Attack", false) @@ -2040,6 +2059,9 @@ self.RunLength = ${ACT_COUNT} self.RunDeck = { ${CARDS.starterDeck.map(luaStr).join(', ')} } self.RunActive = true self.RunRelics = {} +self.RunPotions = {} +self.PotionSlots = ${POTIONS.baseSlots} +${luaPotionsTable(POTIONS.potions)} ${luaRelicsTable(RELICS.relics)} self.RelicPool = { ${RELICS.relicPool.map(luaStr).join(', ')} } ${luaEnemiesTable(ENEMIES.enemies)} @@ -2049,6 +2071,7 @@ self.CurrentNodeId = "" self.CurrentEnemyId = "" self:BindButtons() self:AddRelic("${RELICS.startingRelic}") +self:RenderPotions() self:ShowMap()`), method('StartCombat', `self:ShowState("combat") self:SetEntityEnabled("/ui/DefaultGroup/CombatHud/Result", false) @@ -2059,6 +2082,9 @@ self.PlayerStr = 0 self.PlayerWeak = 0 self.PlayerVuln = 0 self.PlayerPowers = {} +self.FightAttackCount = 0 +self.FirstHpLossDone = false +self.ClayBlockNext = 0 self.CombatOver = false self.DiscardPile = {} self.Hand = {} @@ -2256,6 +2282,10 @@ if self.PlayerPowers ~= nil then end end self.PlayerBlock = 0 +if self.ClayBlockNext > 0 then + self.PlayerBlock = self.PlayerBlock + self.ClayBlockNext + self.ClayBlockNext = 0 +end self:DrawCards(5) self:RenderHand(true) self:RenderCombat()`), @@ -2480,10 +2510,21 @@ end, 1 / 60)`, [ { Type: 'any', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'toPos' }, { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'duration' }, ]), - method('CalcPlayerAttack', `local dmg = base + self.PlayerStr + method('CalcPlayerAttack', `local base2 = base +self.FightAttackCount = self.FightAttackCount + 1 +if self.FightAttackCount == 1 and self:HasRelic("akabeko") then + base2 = base2 + 8 +end +local dmg = base2 + self.PlayerStr +if self:HasRelic("penNib") and self.FightAttackCount % 10 == 0 then + dmg = dmg * 2 +end if self.PlayerWeak > 0 then dmg = math.floor(dmg * 0.75) end +if dmg > 0 and dmg < 5 and self:HasRelic("boot") then + dmg = 5 +end if dmg < 0 then dmg = 0 end @@ -2531,7 +2572,12 @@ if c.weak ~= nil or c.vuln ~= nil then local tm = self.Monsters[self.TargetIndex] if tm ~= nil and tm.alive == true then if c.weak ~= nil then tm.weak = tm.weak + c.weak end - if c.vuln ~= nil then tm.vuln = tm.vuln + c.vuln end + if c.vuln ~= nil then + tm.vuln = tm.vuln + c.vuln + if self:HasRelic("championBelt") then + tm.weak = tm.weak + 1 + end + end end end table.remove(self.Hand, slot) @@ -2695,10 +2741,33 @@ if self.PlayerBlock > 0 then self.PlayerBlock = self.PlayerBlock - absorbed dmg = dmg - absorbed end -self.PlayerHp = self.PlayerHp - dmg +if dmg > 0 then + self.PlayerHp = self.PlayerHp - dmg + if self:HasRelic("bronzeScales") and attackerSlot ~= nil and attackerSlot > 0 then + local am = self.Monsters[attackerSlot] + if am ~= nil and am.alive == true then + am.hp = am.hp - 3 + if am.hp <= 0 then + am.hp = 0 + self:KillMonster(am.slot) + end + end + end + if self:HasRelic("selfFormingClay") then + self.ClayBlockNext = self.ClayBlockNext + 3 + end + if self:HasRelic("centennialPuzzle") and self.FirstHpLossDone == false then + self.FirstHpLossDone = true + self:DrawCards(3) + self:RenderHand(false) + end +end if self.PlayerHp < 0 then self.PlayerHp = 0 -end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }]), +end`, [ + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }, + { Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'attackerSlot' }, + ]), method('EnemyTurn', `self.TurnBusy = true self:EnemyActStep(1)`), method('EnemyActStep', `local idx = 0 @@ -2725,7 +2794,7 @@ _TimerService:SetTimerOnce(function() atk = math.floor(atk * 1.5) end local before = self.PlayerHp - self:DealDamageToPlayer(atk) + self:DealDamageToPlayer(atk, idx) self:ShowPlayerDmgPop(before - self.PlayerHp) elseif intent.kind == "Defend" then m.block = m.block + intent.value @@ -2760,12 +2829,31 @@ end if anyAlive == false then self.CombatOver = true self.Gold = self.Gold + ${GOLD_PER_WIN} + self:ApplyRelics("combatEnd") self:ApplyRelics("combatReward") + self:MaybeDropPotion() self:RenderRun() local node = self.MapNodes[self.CurrentNodeId] if node ~= nil and node.type == "elite" then self.Gold = self.Gold + 15 - self:AddRelic(self.RelicPool[math.random(1, #self.RelicPool)]) + local nid = self:PickNewRelic() + if nid ~= "" then + self:AddRelic(nid) + local nr = self.Relics[nid] + if nr ~= nil then + self:Toast("유물 획득: " .. nr.name) + end + end + end + if node ~= nil and node.type == "boss" then + local bid = self:PickNewRelic() + if bid ~= "" then + self:AddRelic(bid) + local br = self.Relics[bid] + if br ~= nil then + self:Toast("유물 획득: " .. br.name) + end + end end if node ~= nil and node.type == "boss" then if self.Floor < self.RunLength then @@ -2955,6 +3043,15 @@ if hud ~= nil then hud.Enable = false end self:ShowMap()`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'slot' }]), + method('HasRelic', `if self.RunRelics == nil then + return false +end +for i = 1, #self.RunRelics do + if self.RunRelics[i] == id then + return true + end +end +return false`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'id' }], 0, 'boolean'), method('ApplyRelics', `if self.RunRelics == nil then return end @@ -2965,11 +3062,23 @@ for i = 1, #self.RunRelics do self.PlayerBlock = self.PlayerBlock + r.value elseif r.effect == "energy" then self.Energy = self.Energy + r.value - elseif r.effect == "healOnAttack" then + elseif r.effect == "strength" then + self.PlayerStr = self.PlayerStr + r.value + elseif r.effect == "draw" then + self:DrawCards(r.value) + self:RenderHand(false) + elseif r.effect == "heal" or r.effect == "healOnAttack" or r.effect == "healOnWin" then self.PlayerHp = self.PlayerHp + r.value if self.PlayerHp > self.PlayerMaxHp then self.PlayerHp = self.PlayerMaxHp end + elseif r.effect == "healIfLow" then + if self.PlayerHp * 2 <= self.PlayerMaxHp then + self.PlayerHp = self.PlayerHp + r.value + if self.PlayerHp > self.PlayerMaxHp then + self.PlayerHp = self.PlayerMaxHp + end + end elseif r.effect == "gold" then self.Gold = self.Gold + r.value end @@ -2979,7 +3088,29 @@ end`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], N self.RunRelics = {} end table.insert(self.RunRelics, id) +local r = self.Relics[id] +if r ~= nil and r.hook == "passive" then + if r.effect == "potionSlots" then + self.PotionSlots = r.value + self:RenderPotions() + elseif r.effect == "maxHp" then + self.PlayerMaxHp = self.PlayerMaxHp + r.value + self.PlayerHp = self.PlayerHp + r.value + end +end self:RenderRelics()`, [{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'id' }]), + method('PickNewRelic', `local pool = {} +for i = 1, #self.RelicPool do + if self:HasRelic(self.RelicPool[i]) == false then + table.insert(pool, self.RelicPool[i]) + end +end +if #pool == 0 then + self.Gold = self.Gold + 25 + self:Toast("유물을 모두 모았습니다! 골드 +25") + return "" +end +return pool[math.random(1, #pool)]`, [], 0, 'string'), method('RenderRelics', `local names = "" if self.RunRelics ~= nil then for i = 1, #self.RunRelics do