feat(potions-relics): 유물 15종 효과 훅 (생성기)

- 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) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 07:29:01 +09:00
parent 239dd6caf3
commit 42f9143f73

View File

@@ -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