feat(job): 다단히트·방어무시·자가취약·파워 2종 (생성기)

- PlayCard Attack: hits 합산(힘·펜닙 타격마다 적용), pierce 전달, selfVuln
- DealDamageToTarget/PlayAttackFx pierce 시그니처 (물약 화염병 false)
- StartPlayerTurn 파워 루프: energyPerTurn·blockPerTurn (블록 리셋 후)
- 카드 class 직렬화(누락 시 fail-fast)·PlayerJob prop

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 13:34:11 +09:00
parent 15b342972d
commit 74e3a70a19

View File

@@ -68,6 +68,11 @@ function luaCardsTable(cards) {
if (c.vuln != null) fields.push(`vuln = ${c.vuln}`);
if (c.powerEffect != null) fields.push(`powerEffect = ${luaStr(c.powerEffect)}`);
if (c.value != null) fields.push(`value = ${c.value}`);
if (!c.class) throw new Error(`[gen-slaydeck] 카드 ${id}에 class 누락`);
fields.push(`class = ${luaStr(c.class)}`);
if (c.hits != null) fields.push(`hits = ${c.hits}`);
if (c.pierce === true) fields.push('pierce = true');
if (c.selfVuln != null) fields.push(`selfVuln = ${c.selfVuln}`);
if (c.image != null) fields.push(`image = ${luaStr(c.image)}`);
return `\t${id} = { ${fields.join(', ')} },`;
});
@@ -2237,6 +2242,7 @@ function writeCodeblocks() {
prop('number', 'Depth', '0'),
prop('any', 'VisitedNodes'),
prop('boolean', 'ChestOpened', 'false'),
prop('string', 'PlayerJob', '""'),
], [
method('OnBeginPlay', `self:ShowMainMenu()`),
method('HideGameHud', `self:SetEntityEnabled("/ui/DefaultGroup/Button_Attack", false)
@@ -2346,6 +2352,7 @@ self.RelicPool = { ${RELICS.relicPool.map(luaStr).join(', ')} }
${luaEnemiesTable(ENEMIES.enemies)}
self.CurrentNodeId = ""
self.CurrentEnemyId = ""
self.PlayerJob = ""
self:GenerateMap()
self:BindButtons()
self:AddRelic("${RELICS.startingRelic}")
@@ -2612,19 +2619,25 @@ end`),
method('StartPlayerTurn', `self.Turn = self.Turn + 1
self.Energy = self.MaxEnergy
self:ApplyRelics("turnStart")
if self.PlayerPowers ~= nil then
for i = 1, #self.PlayerPowers do
local pc = self.Cards[self.PlayerPowers[i]]
if pc ~= nil and pc.powerEffect == "strengthPerTurn" then
self.PlayerStr = self.PlayerStr + pc.value
end
end
end
self.PlayerBlock = 0
if self.ClayBlockNext > 0 then
self.PlayerBlock = self.PlayerBlock + self.ClayBlockNext
self.ClayBlockNext = 0
end
if self.PlayerPowers ~= nil then
for i = 1, #self.PlayerPowers do
local pc = self.Cards[self.PlayerPowers[i]]
if pc ~= nil then
if pc.powerEffect == "strengthPerTurn" then
self.PlayerStr = self.PlayerStr + pc.value
elseif pc.powerEffect == "energyPerTurn" then
self.Energy = self.Energy + pc.value
elseif pc.powerEffect == "blockPerTurn" then
self.PlayerBlock = self.PlayerBlock + pc.value
end
end
end
end
self:DrawCards(5)
self:RenderHand(true)
self:RenderCombat()`),
@@ -2889,7 +2902,12 @@ end
self.Energy = self.Energy - c.cost
if c.kind == "Attack" then
if c.damage ~= nil then
self:PlayAttackFx(self.TargetIndex, c.image, self:CalcPlayerAttack(c.damage))
local total = 0
local hitN = c.hits or 1
for h = 1, hitN do
total = total + self:CalcPlayerAttack(c.damage)
end
self:PlayAttackFx(self.TargetIndex, c.image, total, c.pierce == true)
end
if c.block ~= nil then
self.PlayerBlock = self.PlayerBlock + c.block
@@ -2907,6 +2925,9 @@ end
if c.strength ~= nil then
self.PlayerStr = self.PlayerStr + c.strength
end
if c.selfVuln ~= nil then
self.PlayerVuln = self.PlayerVuln + c.selfVuln
end
if c.weak ~= nil or c.vuln ~= nil then
local tm = self.Monsters[self.TargetIndex]
if tm ~= nil and tm.alive == true then
@@ -3014,7 +3035,7 @@ local dmg = amount
if m.vuln > 0 then
dmg = math.floor(dmg * 1.5)
end
if m.block > 0 then
if m.block > 0 and pierce ~= true then
local absorbed = math.min(m.block, dmg)
m.block = m.block - absorbed
dmg = dmg - absorbed
@@ -3023,10 +3044,13 @@ m.hp = m.hp - dmg
if m.hp <= 0 then
m.hp = 0
self:KillMonster(m.slot)
end`, [{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' }]),
end`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'amount' },
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'pierce' },
]),
method('PlayAttackFx', `local m = self.Monsters[targetIndex]
if m == nil or m.alive ~= true or m.entity == nil or not isvalid(m.entity) then
self:DealDamageToTarget(damage)
self:DealDamageToTarget(damage, pierce)
self:RenderCombat()
self:CheckCombatEnd()
return
@@ -3052,7 +3076,7 @@ _TimerService:SetTimerOnce(function()
if mt ~= nil and mt.alive == true and mt.vuln > 0 then
shown = math.floor(damage * 1.5)
end
self:DealDamageToTarget(damage)
self:DealDamageToTarget(damage, pierce)
self:ShowDmgPop(targetIndex, shown)
self:RenderCombat()
self:CheckCombatEnd()
@@ -3060,6 +3084,7 @@ end, 0.35)`, [
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'targetIndex' },
{ Type: 'string', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'image' },
{ Type: 'number', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'damage' },
{ Type: 'boolean', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: 'pierce' },
]),
method('KillMonster', `local m = self.Monsters[slot]
if m == nil then
@@ -3530,7 +3555,7 @@ end
if p.effect == "heal" then
self.PlayerHp = math.min(self.PlayerHp + p.value, self.PlayerMaxHp)
elseif p.effect == "damage" then
self:DealDamageToTarget(p.value)
self:DealDamageToTarget(p.value, false)
self:ShowDmgPop(self.TargetIndex, p.value)
elseif p.effect == "strength" then
self.PlayerStr = self.PlayerStr + p.value