feat: 워리어 카드와 공용 전투 효과 구현
This commit is contained in:
@@ -649,9 +649,10 @@ test("simulateCombat: damagePerAttackPlayedThisTurn scales Finisher", () => {
|
||||
starterDeck: ["Hit", "Finisher"],
|
||||
monsters: [{ name: "Dummy", maxHp: 12, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0);
|
||||
const stats = {};
|
||||
const r = simulateCombat(data, () => 0, stats);
|
||||
assert.equal(r.win, true);
|
||||
assert.equal(r.turns, 2);
|
||||
assert.ok((stats.Finisher?.damage || 0) >= 6);
|
||||
});
|
||||
|
||||
test("simulateCombat: damagePerOtherHandCard and damagePerSkillInHand are applied", () => {
|
||||
@@ -666,9 +667,11 @@ test("simulateCombat: damagePerOtherHandCard and damagePerSkillInHand are applie
|
||||
starterDeck: ["Skill1", "Skill2", "Blank", "Precise", "Flechettes"],
|
||||
monsters: [{ name: "Dummy", maxHp: 21, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0);
|
||||
const stats = {};
|
||||
const r = simulateCombat(data, () => 0, stats);
|
||||
assert.equal(r.win, true);
|
||||
assert.equal(r.turns, 5);
|
||||
assert.ok((stats.Precise?.damage || 0) >= 5);
|
||||
assert.ok((stats.Flechettes?.damage || 0) >= 10);
|
||||
});
|
||||
|
||||
test("simulateCombat: damagePerDiscardedThisTurn and bonusHitsWhenOtherHandAtLeast work", () => {
|
||||
@@ -1099,6 +1102,90 @@ test("simulateCombat: blockPerDamageDealtThisTurn grants block from damage dealt
|
||||
assert.equal(r.win, true);
|
||||
});
|
||||
|
||||
test("simulateCombat: damageFromCurrentBlock uses current block as attack damage", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
Guard: { name: "Guard", cost: 1, kind: "Skill", block: 5 },
|
||||
BodySlam: { name: "BodySlam", cost: 1, kind: "Attack", damageFromCurrentBlock: 1 },
|
||||
},
|
||||
starterDeck: ["Guard", "BodySlam"],
|
||||
monsters: [{ name: "Dummy", maxHp: 5, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0.999999);
|
||||
assert.equal(r.win, true);
|
||||
assert.equal(r.turns, 1);
|
||||
});
|
||||
|
||||
test("simulateCombat: damagePerOwnedNameMatch counts matching owned cards across combat piles", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
Strike1: { name: "타격", cost: 99, kind: "Attack", damage: 0 },
|
||||
Strike2: { name: "타격", cost: 99, kind: "Attack", damage: 0 },
|
||||
Perfected: { name: "완벽한 타격", cost: 0, kind: "Attack", damage: 6, damageNameMatch: "타격", damagePerOwnedNameMatch: 2 },
|
||||
},
|
||||
starterDeck: ["Strike1", "Strike2", "Perfected"],
|
||||
monsters: [{ name: "Dummy", maxHp: 12, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const stats = {};
|
||||
const r = simulateCombat(data, () => 0.999999, stats);
|
||||
assert.equal(r.win, true);
|
||||
assert.ok((stats.Perfected?.damage || 0) >= 12);
|
||||
});
|
||||
|
||||
test("simulateCombat: exhaustHandNonAttack exhausts only non-attacks and grants block per exhausted card", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
SecondWind: { name: "기사회생", cost: 0, kind: "Skill", exhaustHandNonAttack: true, blockPerExhaustedCard: 5 },
|
||||
Guard1: { name: "수비1", cost: 99, kind: "Skill", block: 0 },
|
||||
Guard2: { name: "수비2", cost: 99, kind: "Skill", block: 0 },
|
||||
Hit: { name: "타격", cost: 99, kind: "Attack", damage: 1 },
|
||||
},
|
||||
starterDeck: ["Guard1", "Guard2", "Hit", "SecondWind"],
|
||||
monsters: [{ name: "Dummy", maxHp: 9999, intents: [{ kind: "Attack", value: 10 }] }],
|
||||
};
|
||||
const stats = {};
|
||||
simulateCombat(data, () => 0.999999, stats);
|
||||
assert.ok((stats.SecondWind?.block || 0) >= 10);
|
||||
});
|
||||
|
||||
test("simulateCombat: drawOnExhaust draws when cards are exhausted", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
Embrace: { name: "어둠의 포옹", cost: 0, kind: "Power", drawOnExhaust: 1 },
|
||||
Burn: { name: "소각", cost: 0, kind: "Skill", exhaust: true },
|
||||
Hit: { name: "타격", cost: 0, kind: "Attack", damage: 6 },
|
||||
},
|
||||
starterDeck: ["Embrace", "Burn", "Hit"],
|
||||
monsters: [{ name: "Dummy", maxHp: 6, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0.999999);
|
||||
assert.equal(r.win, true);
|
||||
assert.equal(r.turns, 1);
|
||||
});
|
||||
|
||||
test("simulateCombat: keepBlock power preserves block across turns", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
Barricade: { name: "바리케이드", cost: 0, kind: "Power", powerEffect: "keepBlock", value: 0 },
|
||||
Guard: { name: "수비", cost: 0, kind: "Skill", block: 5 },
|
||||
Pass: { name: "대기", cost: 99, kind: "Skill", block: 0 },
|
||||
},
|
||||
starterDeck: ["Barricade", "Guard", "Pass"],
|
||||
monsters: [{ name: "Dummy", maxHp: 9999, intents: [{ kind: "Attack", value: 3 }, { kind: "Attack", value: 3 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0.999999);
|
||||
assert.equal(r.draw, true);
|
||||
assert.equal(r.playerHpRemaining, 80);
|
||||
});
|
||||
|
||||
test("chooseAction: damageFromCurrentBlock values attack using current block", () => {
|
||||
const cards = {
|
||||
Guard: { name: "Guard", cost: 1, kind: "Skill", block: 5 },
|
||||
BodySlam: { name: "BodySlam", cost: 1, kind: "Attack", damageFromCurrentBlock: 1 },
|
||||
};
|
||||
assert.equal(chooseAction(["BodySlam", "Guard"], cards, 1, { currentBlock: 6 }), 0);
|
||||
});
|
||||
|
||||
test("simulateCombat: cardPlayedRandomDamage hits a random enemy on card play", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
@@ -1124,6 +1211,95 @@ test("simulateCombat: rewardOnKill grants an extra reward screen when an attack
|
||||
assert.equal(r.bonusRewardScreens, 1);
|
||||
});
|
||||
|
||||
test("simulateCombat: maxHpOnKill increases max hp and heals when attack kills", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
Feed: { name: "포식", cost: 1, kind: "Attack", damage: 10, maxHpOnKill: 3 },
|
||||
},
|
||||
starterDeck: ["Feed"],
|
||||
monsters: [{ name: "Dummy", maxHp: 10, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
playerHp: 50,
|
||||
playerMaxHp: 80,
|
||||
};
|
||||
const r = simulateCombat(data, () => 0.999999);
|
||||
assert.equal(r.win, true);
|
||||
assert.equal(r.playerHpRemaining, 53);
|
||||
});
|
||||
|
||||
test("simulateCombat: drawNameMatchAutoPlay auto-plays matching drawn cards", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
Hellraiser: { name: "지옥검무", cost: 0, kind: "Power", drawNameMatchAutoPlay: "타격" },
|
||||
Strike: { name: "강타격", cost: 99, kind: "Attack", damage: 9 },
|
||||
Pass1: { name: "대기1", cost: 99, kind: "Skill" },
|
||||
Pass2: { name: "대기2", cost: 99, kind: "Skill" },
|
||||
Pass3: { name: "대기3", cost: 99, kind: "Skill" },
|
||||
Pass4: { name: "대기4", cost: 99, kind: "Skill" },
|
||||
},
|
||||
starterDeck: ["Hellraiser", "Pass1", "Pass2", "Pass3", "Pass4", "Strike"],
|
||||
monsters: [{ name: "Dummy", maxHp: 9, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0.999999);
|
||||
assert.equal(r.win, true);
|
||||
});
|
||||
|
||||
test("simulateCombat: addRandomCardCount can add same-class attack as zero-cost this turn", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
InfernalBlade: {
|
||||
name: "지옥검",
|
||||
cost: 0,
|
||||
kind: "Skill",
|
||||
addRandomCardCount: 1,
|
||||
addRandomCardKind: "Attack",
|
||||
addRandomCardSameClass: true,
|
||||
addedCardsCostZeroThisTurn: true,
|
||||
class: "warrior",
|
||||
},
|
||||
BigHit: { name: "큰 일격", cost: 2, kind: "Attack", damage: 12, class: "warrior" },
|
||||
OffClass: { name: "외부 공격", cost: 0, kind: "Attack", damage: 1, class: "rogue" },
|
||||
},
|
||||
starterDeck: ["InfernalBlade"],
|
||||
monsters: [{ name: "Dummy", maxHp: 12, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0.999999);
|
||||
assert.equal(r.win, true);
|
||||
});
|
||||
|
||||
test("simulateCombat: drawPerExhausted draws for each exhausted card", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
Stoke: { name: "화력 증폭", cost: 0, kind: "Skill", exhaustHandAll: true, drawPerExhausted: 1 },
|
||||
Filler1: { name: "채우기1", cost: 99, kind: "Skill" },
|
||||
Filler2: { name: "채우기2", cost: 99, kind: "Skill" },
|
||||
Hit: { name: "일격", cost: 0, kind: "Attack", damage: 8 },
|
||||
},
|
||||
starterDeck: ["Stoke", "Filler1", "Filler2", "Hit"],
|
||||
monsters: [{ name: "Dummy", maxHp: 8, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0.999999);
|
||||
assert.equal(r.win, true);
|
||||
});
|
||||
|
||||
test("simulateCombat: playTopDrawPileCountPerEnergy auto-plays top draw pile cards", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
Cascade: { name: "연쇄", cost: 0, kind: "Skill", useAllEnergy: true, playTopDrawPileCountPerEnergy: 1, innate: true },
|
||||
Filler1: { name: "준비1", cost: 99, kind: "Skill", innate: true },
|
||||
Filler2: { name: "준비2", cost: 99, kind: "Skill", innate: true },
|
||||
Filler3: { name: "준비3", cost: 99, kind: "Skill", innate: true },
|
||||
Filler4: { name: "준비4", cost: 99, kind: "Skill", innate: true },
|
||||
Hit1: { name: "타격1", cost: 99, kind: "Attack", damage: 6 },
|
||||
Hit2: { name: "타격2", cost: 99, kind: "Attack", damage: 6 },
|
||||
Hit3: { name: "타격3", cost: 99, kind: "Attack", damage: 6 },
|
||||
},
|
||||
starterDeck: ["Cascade", "Filler1", "Filler2", "Filler3", "Filler4", "Hit1", "Hit2", "Hit3"],
|
||||
monsters: [{ name: "Dummy", maxHp: 18, intents: [{ kind: "Attack", value: 0 }] }],
|
||||
};
|
||||
const r = simulateCombat(data, () => 0.999999);
|
||||
assert.equal(r.win, true);
|
||||
});
|
||||
|
||||
test("simulateCombat: intangible cards reduce incoming damage and persist across turns", () => {
|
||||
const data = {
|
||||
cards: {
|
||||
|
||||
Reference in New Issue
Block a user