feat(cards): implement retain keyword

This commit is contained in:
2026-06-15 00:45:11 +09:00
parent 6427d23f50
commit b43ee02014
5 changed files with 38 additions and 8 deletions

View File

@@ -212,7 +212,7 @@ export function simulateCombat(data, rng, stats) {
else if (pc.powerEffect === 'energyPerTurn') energyBonus += pc.value;
else if (pc.powerEffect === 'blockPerTurn') pBlock += pc.value;
}
let energy = ENERGY + energyBonus; hand = []; draw(HAND_SIZE);
let energy = ENERGY + energyBonus; draw(HAND_SIZE);
while (true) {
const alive = aliveList();
if (alive.length === 0) break;
@@ -230,7 +230,13 @@ export function simulateCombat(data, rng, stats) {
let burn = 0;
for (const hid of hand) { const hc = cards[hid]; if (hc && hc.endTurnDamage) burn += hc.endTurnDamage; }
if (burn > 0) { pHp -= burn; if (pHp < 0) pHp = 0; }
discard.push(...hand); hand = [];
const kept = [];
for (const hid of hand) {
const hc = cards[hid];
if (hc?.retain === true) kept.push(hid);
else discard.push(hid);
}
hand = kept;
if (pHp <= 0) return { win: false, turns, playerHpRemaining: 0 };
// 플레이어 디버프 감소 — Lua EndPlayerTurn 동기화 (적 행동 전)
if (pWeak > 0) pWeak--;

View File

@@ -390,3 +390,18 @@ test("simulateCombat: sly discarded card resolves for free", () => {
assert.equal(r.win, true);
assert.equal(r.turns, 1);
});
test("simulateCombat: retain keeps card in hand across turns", () => {
const data = {
cards: {
Boost: { name: "Boost", cost: 3, kind: "Power", powerEffect: "energyPerTurn", value: 98 },
Hold: { name: "Hold", cost: 100, kind: "Attack", damage: 10, retain: true },
Blank: { name: "Blank", cost: 99, kind: "Skill", block: 0 },
},
starterDeck: ["Blank", "Blank", "Blank", "Blank", "Blank", "Boost", "Hold", "Blank", "Blank", "Blank"],
monsters: [{ name: "Dummy", maxHp: 10, intents: [{ kind: "Defend", value: 0 }] }],
};
const r = simulateCombat(data, () => 0.999999);
assert.equal(r.win, true);
assert.equal(r.turns, 2);
});

View File

@@ -152,6 +152,7 @@ function luaCardsTable(cards) {
if (c.discard != null) fields.push(`discard = ${c.discard}`);
if (c.discardAll === true) fields.push('discardAll = true');
if (c.sly === true) fields.push('sly = true');
if (c.retain === true) fields.push('retain = true');
if (c.aoe === true) fields.push('aoe = true');
if (c.unplayable === true) fields.push('unplayable = true');
if (c.curse === true) fields.push('curse = true');
@@ -3701,10 +3702,17 @@ if burn > 0 then
\tself:ShowPlayerDmgPop(burn)
\tself:RenderCombat()
end
local kept = {}
for i = 1, #self.Hand do
\ttable.insert(self.DiscardPile, self.Hand[i])
\tlocal cardId = self.Hand[i]
\tlocal c = self.Cards[cardId]
\tif c ~= nil and c.retain == true then
\t\ttable.insert(kept, cardId)
\telse
\t\ttable.insert(self.DiscardPile, cardId)
\tend
end
self.Hand = {}
self.Hand = kept
if self.PlayerWeak > 0 then self.PlayerWeak = self.PlayerWeak - 1 end
if self.PlayerVuln > 0 then self.PlayerVuln = self.PlayerVuln - 1 end
self:RenderHand(false)