Port the status and field move effects
Toxic, Leech Seed, Nightmare and Curse hurt in ResidualDamage's order. Protect, Detect, Endure, Destiny Bond, Spite, Heal Bell, Disable, Encore, Attract, Mist, Focus Energy, Haze, the healing moves and Rest, Mean Look, Pain Split, Belly Drum, Psych Up, Safeguard, Perish Song, Spikes, Rapid Spin, AncientPower and Future Sight now run, with Future Sight, Perish Song, Safeguard and Encore handled between turns and Spikes on entry. A trainer's party struct now follows its Pokémon where the original updates it, and the enemy's moves spend PP there or in the wild Pokémon's kept PP, so a caught Pokémon keeps the PP it used. Part of #28 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c53edb77e5
commit
8f5b464efc
6 files changed
+1036
-52
No files matched your search
+325
-4
@@ -65,7 +65,37 @@ const DOUBLESLAP = 3,
|
||||
MIND_READER = 170,
|
||||
ROLLOUT = 205,
|
||||
FURY_CUTTER = 210,
|
||||
PURSUIT = 228;
|
||||
PURSUIT = 228,
|
||||
SWORDS_DANCE = 0x0e,
|
||||
GROWL = 0x2d,
|
||||
DISABLE = 0x32,
|
||||
MIST = 0x36,
|
||||
LEECH_SEED = 0x49,
|
||||
POISONPOWDER = 0x4d,
|
||||
TOXIC = 0x5c,
|
||||
RECOVER = 0x69,
|
||||
HAZE = 0x72,
|
||||
FOCUS_ENERGY = 0x74,
|
||||
REST = 0x9c,
|
||||
NIGHTMARE = 0xab,
|
||||
CURSE = 0xae,
|
||||
SPITE = 0xb4,
|
||||
PROTECT = 0xb6,
|
||||
BELLY_DRUM = 0xbb,
|
||||
SPIKES = 0xbf,
|
||||
DESTINY_BOND = 0xc2,
|
||||
PERISH_SONG = 0xc3,
|
||||
ENDURE = 0xcb,
|
||||
MEAN_LOOK = 0xd4,
|
||||
ATTRACT = 0xd5,
|
||||
HEAL_BELL = 0xd7,
|
||||
SAFEGUARD = 0xdb,
|
||||
PAIN_SPLIT = 0xdc,
|
||||
ENCORE = 0xe3,
|
||||
RAPID_SPIN = 0xe5,
|
||||
PSYCH_UP = 0xf4,
|
||||
ANCIENTPOWER = 0xf6,
|
||||
FUTURE_SIGHT = 0xf8;
|
||||
// Species and items
|
||||
const PIDGEY = 16,
|
||||
RATTATA = 19,
|
||||
@@ -86,7 +116,13 @@ const PIDGEY = 16,
|
||||
ONIX = 95,
|
||||
SCYTHER = 123,
|
||||
TAUROS = 128,
|
||||
HOUNDOUR = 228;
|
||||
HOUNDOUR = 228,
|
||||
BULBASAUR = 1,
|
||||
GENGAR = 0x5e,
|
||||
NIDORAN_F = 0x1d,
|
||||
NIDORAN_M = 0x20,
|
||||
STARMIE = 0x79,
|
||||
FORRETRESS = 0xcd;
|
||||
const AMULET_COIN = 0x5b,
|
||||
LEFTOVERS = 0x92,
|
||||
FOCUS_BAND = 0x77,
|
||||
@@ -180,8 +216,8 @@ describe.skipIf(!existsSync(romPath))("move effects with the local ROM", () => {
|
||||
) => {
|
||||
for (let seed = 1; seed < 500; seed++) {
|
||||
const battle = make(seed);
|
||||
const { texts } = firstTurn(battle);
|
||||
if (test(texts, battle)) return { seed, battle, texts };
|
||||
const { texts, steps } = firstTurn(battle);
|
||||
if (test(texts, battle)) return { seed, battle, texts, steps };
|
||||
}
|
||||
throw new Error("No seed found.");
|
||||
};
|
||||
@@ -551,6 +587,291 @@ describe.skipIf(!existsSync(romPath))("move effects with the local ROM", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("status and field moves", () => {
|
||||
/** Play turns choosing these move slots, returning the texts. */
|
||||
const play = (battle: Battle, moves: number[]) => {
|
||||
const steps = battleSteps(battle);
|
||||
for (const move of moves) {
|
||||
if (steps.done || steps.prompt?.kind !== "action") break;
|
||||
steps.answer({ action: "fight", move });
|
||||
}
|
||||
return steps;
|
||||
};
|
||||
it("badly poisons with Toxic, one more sixteenth each turn", () => {
|
||||
const enemy = mon(SNORLAX, 60, [SPLASH]);
|
||||
const { battle, steps } = findSeed(
|
||||
(seed) => duel([mon(GENGAR, 40, [TOXIC, SPLASH])], [enemy], { seed }),
|
||||
(texts) => texts.some((text) => text.includes("badly poisoned")),
|
||||
);
|
||||
const sixteenth = (enemy.stats[0] >> 2) >> 2;
|
||||
expect(battle.enemy.mon.hp).toBe(enemy.stats[0] - sixteenth);
|
||||
steps.answer({ action: "fight", move: 1 });
|
||||
expect(battle.enemy.mon.hp).toBe(enemy.stats[0] - sixteenth * 3);
|
||||
steps.answer({ action: "fight", move: 1 });
|
||||
expect(battle.enemy.mon.hp).toBe(enemy.stats[0] - sixteenth * 6);
|
||||
expect(battle.enemy.toxicCount).toBe(3);
|
||||
});
|
||||
it("seeds the target, draining an eighth to the seeder each turn, but not a Grass type", () => {
|
||||
const { battle, texts } = findSeed(
|
||||
(seed) => {
|
||||
const player = mon(BULBASAUR, 60, [LEECH_SEED]);
|
||||
player.hp = 10;
|
||||
return duel([player], [mon(SNORLAX, 30, [SPLASH])], { seed });
|
||||
},
|
||||
(texts) => texts.some((text) => text.includes("was seeded")),
|
||||
);
|
||||
const eighth = (battle.enemy.mon.maxHp >> 2) >> 1;
|
||||
expect(battle.enemy.mon.maxHp - battle.enemy.mon.hp).toBe(eighth);
|
||||
expect(battle.player.mon.hp).toBe(10 + eighth);
|
||||
expect(texts).toContain("LEECH SEED saps Enemy SNORLAX!");
|
||||
const grass = firstTurn(duel([mon(BULBASAUR, 40, [LEECH_SEED])], [mon(BULBASAUR, 40, [SPLASH])]));
|
||||
expect(grass.texts.join(" ")).toContain("doesn't affect");
|
||||
});
|
||||
it("gives a sleeping target a nightmare that costs a quarter each turn", () => {
|
||||
const enemy = mon(SNORLAX, 60, [SPLASH], { status: 5 });
|
||||
const battle = duel([mon(GENGAR, 40, [NIGHTMARE])], [enemy]);
|
||||
const { texts } = firstTurn(battle);
|
||||
expect(texts.join(" ")).toContain("started to have a NIGHTMARE");
|
||||
expect(battle.enemy.mon.hp).toBe(enemy.stats[0] - (enemy.stats[0] >> 2));
|
||||
const awake = firstTurn(duel([mon(GENGAR, 40, [NIGHTMARE])], [mon(SNORLAX, 60, [SPLASH])]));
|
||||
expect(awake.texts).toContain("But it failed!");
|
||||
});
|
||||
it("curses differently for Ghost types", () => {
|
||||
const ghost = mon(GENGAR, 40, [CURSE]);
|
||||
const enemy = mon(SNORLAX, 60, [SPLASH]);
|
||||
const battle = duel([ghost], [enemy]);
|
||||
const { texts } = firstTurn(battle);
|
||||
expect(texts.join(" ")).toContain("put a CURSE");
|
||||
expect(ghost.hp).toBe(ghost.stats[0] - (ghost.stats[0] >> 1));
|
||||
expect(battle.enemy.mon.hp).toBe(enemy.stats[0] - (enemy.stats[0] >> 2));
|
||||
const other = duel([mon(SNORLAX, 40, [CURSE])], [mon(SNORLAX, 60, [SPLASH])]);
|
||||
const curse = firstTurn(other);
|
||||
expect(other.player.levels.slice(0, 3)).toEqual([8, 8, 6]);
|
||||
const used = curse.texts.indexOf("SNORLAX used CURSE!");
|
||||
expect(curse.texts.slice(used + 1, used + 4)).toEqual(["SNORLAX's SPEED fell!", "SNORLAX's ATTACK went up!", "SNORLAX's DEFENSE went up!"]);
|
||||
});
|
||||
it("takes the attacker down with Destiny Bond", () => {
|
||||
// Starmie moves first, so its Destiny Bond holds through Snorlax's hit.
|
||||
const player = mon(STARMIE, 20, [DESTINY_BOND]);
|
||||
const battle = duel([player], [mon(SNORLAX, 60, [TACKLE])]);
|
||||
const { texts } = playBattle(battle);
|
||||
expect(texts.join(" ")).toContain("took down with it");
|
||||
expect(battle.enemy.mon.hp).toBe(0);
|
||||
});
|
||||
it("protects against a move, with less chance on each use in a row", () => {
|
||||
const battle = duel([mon(STARMIE, 40, [PROTECT])], [mon(SNORLAX, 30, [TACKLE])]);
|
||||
const { texts } = firstTurn(battle);
|
||||
expect(texts).toContain("STARMIE PROTECTED itself!");
|
||||
expect(texts).toContain("STARMIE's PROTECTING itself!");
|
||||
let failures = 0;
|
||||
for (let seed = 1; seed < 40; seed++) {
|
||||
const steps = play(duel([mon(STARMIE, 40, [PROTECT])], [mon(SNORLAX, 30, [TACKLE])], { seed }), [0, 0, 0]);
|
||||
failures += steps.texts.filter((text) => text === "But it failed!").length;
|
||||
}
|
||||
expect(failures).toBeGreaterThan(0);
|
||||
});
|
||||
it("endures a knockout hit with 1 HP", () => {
|
||||
const player = mon(RATTATA, 10, [ENDURE]);
|
||||
const battle = duel([player], [mon(SNORLAX, 60, [TACKLE])]);
|
||||
const { texts } = firstTurn(battle);
|
||||
expect(texts.join(" ")).toContain("braced itself");
|
||||
expect(texts.join(" ")).toContain("ENDURED the hit");
|
||||
expect(battle.player.mon.hp).toBe(1);
|
||||
});
|
||||
it("spites the last move's PP, in the trainer's party struct too", () => {
|
||||
const enemy = mon(SNORLAX, 60, [TACKLE]);
|
||||
const battle = duel([mon(GENGAR, 40, [SPITE])], [enemy]);
|
||||
const { texts } = turns(battle, [0, 0]);
|
||||
const spite = texts.find((text) => text.includes("was reduced by"));
|
||||
const amount = Number(/reduced by (\d)/.exec(spite!)![1]);
|
||||
expect(amount).toBeGreaterThanOrEqual(2);
|
||||
// Two Tackles and the Spite in between, in the party struct too.
|
||||
expect(battle.enemy.mon.pp[0]).toBe(assets.pokemon.move(TACKLE).pp - 2 - amount);
|
||||
expect(enemy.pp[0] & 63).toBe(assets.pokemon.move(TACKLE).pp - 2 - amount);
|
||||
});
|
||||
it("cures the whole party with Heal Bell", () => {
|
||||
const party = [mon(CYNDAQUIL, 40, [HEAL_BELL], { status: PSN }), mon(PIDGEY, 10, [TACKLE], { status: 1 << C.PAR })];
|
||||
const battle = duel(party, [mon(SNORLAX, 60, [SPLASH])]);
|
||||
const { texts } = firstTurn(battle);
|
||||
expect(texts.join(" ")).toContain("A bell chimed!");
|
||||
expect(party.map((member) => member.status)).toEqual([0, 0]);
|
||||
});
|
||||
it("disables the target's last move, leaving it to Struggle", () => {
|
||||
const battle = duel([mon(GENGAR, 60, [DISABLE])], [mon(SNORLAX, 20, [TACKLE])]);
|
||||
// Disable fails before Snorlax has moved; the Tackle it already chose
|
||||
// is then stopped, and Struggle follows.
|
||||
const { texts } = turns(battle, [0, 0, 0]);
|
||||
expect(texts).toContain("Enemy SNORLAX's TACKLE was DISABLED!");
|
||||
expect(texts).toContain("Enemy SNORLAX's TACKLE is DISABLED!");
|
||||
expect(texts).toContain("Enemy SNORLAX used STRUGGLE!");
|
||||
});
|
||||
it("encores the target into repeating its last move", () => {
|
||||
const enemy = mon(SNORLAX, 30, [SPLASH, TACKLE]);
|
||||
const battle = duel([mon(STARMIE, 60, [ENCORE, SWORDS_DANCE])], [enemy]);
|
||||
const steps = battleSteps(battle);
|
||||
steps.answer({ action: "fight", move: 1 });
|
||||
const lastMove = battle.enemy.lastMove;
|
||||
steps.answer({ action: "fight", move: 0 });
|
||||
expect(steps.texts.join(" ")).toContain("got an ENCORE");
|
||||
expect(battle.enemy.has(5, C.SUBSTATUS_ENCORED)).toBe(true);
|
||||
steps.answer({ action: "fight", move: 1 });
|
||||
const used = steps.texts.filter((text) => text.startsWith("Enemy SNORLAX used")).slice(-1)[0];
|
||||
expect(used).toBe(`Enemy SNORLAX used ${assets.pokemon.moveName(lastMove)}!`);
|
||||
});
|
||||
it("keeps the player's encored move whatever FIGHT chooses", () => {
|
||||
const battle = duel([mon(SNORLAX, 30, [TACKLE, SPLASH])], [mon(STARMIE, 60, [ENCORE])]);
|
||||
// Starmie's first Encore fails, as Snorlax has not moved yet.
|
||||
const steps = battleSteps(battle);
|
||||
steps.answer({ action: "fight", move: 1 }).answer({ action: "fight", move: 1 });
|
||||
expect(battle.player.has(5, C.SUBSTATUS_ENCORED)).toBe(true);
|
||||
expect(battle.fightKeepsMove).toBe(true);
|
||||
steps.answer({ action: "fight", move: 0 });
|
||||
expect(steps.texts.filter((text) => text === "SNORLAX used SPLASH!")).toHaveLength(3);
|
||||
expect(steps.texts).not.toContain("SNORLAX used TACKLE!");
|
||||
});
|
||||
it("makes the opposite gender fall in love, which may stop it", () => {
|
||||
const { battle } = findSeed(
|
||||
(seed) => duel([mon(NIDORAN_M, 40, [ATTRACT])], [mon(NIDORAN_F, 40, [TACKLE])], { seed }),
|
||||
(texts) => texts.some((text) => text.includes("fell in love")),
|
||||
);
|
||||
expect(battle.enemy.has(1, C.SUBSTATUS_IN_LOVE)).toBe(true);
|
||||
const same = firstTurn(duel([mon(NIDORAN_M, 40, [ATTRACT])], [mon(NIDORAN_M, 40, [SPLASH])]));
|
||||
expect(same.texts).toContain("But it failed!");
|
||||
const immobile = findSeed(
|
||||
(seed) => duel([mon(NIDORAN_M, 40, [ATTRACT, SPLASH])], [mon(NIDORAN_F, 40, [TACKLE])], { seed }),
|
||||
(texts) => texts.some((text) => text.includes("infatuation kept it from attacking")),
|
||||
);
|
||||
expect(immobile.texts.join(" ")).toContain("is in love with");
|
||||
});
|
||||
it("shrouds the user in Mist against stat drops, and pumps it up with Focus Energy", () => {
|
||||
const battle = duel([mon(STARMIE, 60, [MIST, FOCUS_ENERGY])], [mon(SNORLAX, 30, [GROWL])]);
|
||||
const steps = battleSteps(battle);
|
||||
steps.answer({ action: "fight", move: 0 });
|
||||
expect(steps.texts.join(" ")).toContain("shrouded in MIST");
|
||||
expect(steps.texts.join(" ")).toContain("protected by MIST");
|
||||
expect(battle.player.levels[C.ATTACK]).toBe(7);
|
||||
steps.answer({ action: "fight", move: 1 });
|
||||
expect(steps.texts.join(" ")).toContain("getting pumped");
|
||||
expect(battle.player.has(4, C.SUBSTATUS_FOCUS_ENERGY)).toBe(true);
|
||||
});
|
||||
it("eliminates all stat changes with Haze", () => {
|
||||
const battle = duel([mon(STARMIE, 60, [SWORDS_DANCE, HAZE])], [mon(SNORLAX, 30, [GROWL])]);
|
||||
const steps = battleSteps(battle);
|
||||
steps.answer({ action: "fight", move: 0 });
|
||||
steps.answer({ action: "fight", move: 1 });
|
||||
expect(steps.texts.join(" ")).toContain("All stat changes were eliminated!");
|
||||
expect(battle.player.levels).toEqual([7, 7, 7, 7, 7, 7, 7]);
|
||||
});
|
||||
it("recovers half its HP, or all of it with Rest and two turns of sleep", () => {
|
||||
const player = mon(STARMIE, 60, [RECOVER, REST]);
|
||||
player.hp = 20;
|
||||
const battle = duel([player], [mon(SNORLAX, 10, [SPLASH])]);
|
||||
const steps = battleSteps(battle);
|
||||
steps.answer({ action: "fight", move: 0 });
|
||||
expect(battle.player.mon.hp).toBe(20 + (player.stats[0] >> 1));
|
||||
steps.answer({ action: "fight", move: 1 });
|
||||
expect(steps.texts.join(" ")).toContain("STARMIE went to sleep!");
|
||||
expect(battle.player.mon.hp).toBe(player.stats[0]);
|
||||
expect(battle.player.mon.status).toBe(C.REST_SLEEP_TURNS + 1);
|
||||
const full = firstTurn(duel([mon(STARMIE, 60, [RECOVER])], [mon(SNORLAX, 10, [SPLASH])]));
|
||||
expect(full.texts).toContain("STARMIE's HP is full!");
|
||||
});
|
||||
it("stops the player running after Mean Look", () => {
|
||||
const battle = duel([mon(STARMIE, 60, [SPLASH])], { wild: { species: GENGAR, level: 5 } });
|
||||
const internal = battle as unknown as { enemy: { mon: { moves: number[] } } };
|
||||
void internal;
|
||||
const steps = battleSteps(battle);
|
||||
battle.enemy.set(5, C.SUBSTATUS_CANT_RUN);
|
||||
steps.answer({ action: "run" });
|
||||
expect(steps.texts).toContain("Can't escape!");
|
||||
});
|
||||
it("shares pain to the average HP", () => {
|
||||
const player = mon(GENGAR, 40, [PAIN_SPLIT]);
|
||||
player.hp = 10;
|
||||
const enemy = mon(SNORLAX, 60, [SPLASH]);
|
||||
const battle = duel([player], [enemy]);
|
||||
firstTurn(battle);
|
||||
const average = (10 + enemy.stats[0]) >> 1;
|
||||
expect(battle.enemy.mon.hp).toBe(average);
|
||||
expect(battle.player.mon.hp).toBe(Math.min(average, player.stats[0]));
|
||||
});
|
||||
it("drums up the highest Attack for half the HP, keeping two stages when it fails", () => {
|
||||
const drummer = mon(SNORLAX, 40, [BELLY_DRUM]);
|
||||
const battle = duel([drummer], [mon(PIDGEY, 10, [SPLASH])]);
|
||||
firstTurn(battle);
|
||||
expect(battle.player.levels[C.ATTACK]).toBe(13);
|
||||
expect(drummer.hp).toBe(drummer.stats[0] - (drummer.stats[0] >> 1));
|
||||
const weak = mon(SNORLAX, 40, [BELLY_DRUM]);
|
||||
weak.hp = 10;
|
||||
const failed = duel([weak], [mon(PIDGEY, 10, [SPLASH])]);
|
||||
expect(firstTurn(failed).texts).toContain("But it failed!");
|
||||
expect(failed.player.levels[C.ATTACK]).toBe(9);
|
||||
});
|
||||
it("copies the target's stat changes with Psych Up", () => {
|
||||
const battle = duel([mon(STARMIE, 60, [PSYCH_UP])], [mon(SNORLAX, 30, [SWORDS_DANCE])]);
|
||||
const steps = battleSteps(battle);
|
||||
steps.answer({ action: "fight", move: 0 });
|
||||
expect(steps.texts).toContain("But it failed!");
|
||||
steps.answer({ action: "fight", move: 0 });
|
||||
expect(steps.texts.join(" ")).toContain("copied the stat changes of Enemy SNORLAX");
|
||||
expect(battle.player.levels[C.ATTACK]).toBe(9);
|
||||
});
|
||||
it("guards against status with Safeguard for five turns", () => {
|
||||
const battle = duel([mon(STARMIE, 60, [SAFEGUARD, SPLASH])], [mon(SNORLAX, 30, [POISONPOWDER])]);
|
||||
const steps = battleSteps(battle);
|
||||
steps.answer({ action: "fight", move: 0 });
|
||||
expect(steps.texts.join(" ")).toContain("covered by a veil");
|
||||
expect(steps.texts.join(" ")).toContain("protected by SAFEGUARD");
|
||||
for (let turn = 0; turn < 4; turn++) steps.answer({ action: "fight", move: 1 });
|
||||
expect(steps.texts.join(" ")).toContain("SAFEGUARD faded");
|
||||
});
|
||||
it("counts Perish Song down until both faint", () => {
|
||||
const battle = duel([mon(STARMIE, 60, [PERISH_SONG, SPLASH]), mon(PIDGEY, 10, [TACKLE])], [mon(SNORLAX, 60, [SPLASH])]);
|
||||
const steps = battleSteps(battle);
|
||||
steps.answer({ action: "fight", move: 0 });
|
||||
for (let turn = 0; turn < 3 && steps.prompt?.kind === "action"; turn++)
|
||||
steps.answer({ action: "fight", move: 1 });
|
||||
const counts = steps.texts.filter((text) => text.includes("PERISH count is"));
|
||||
expect(counts.slice(0, 2)).toEqual(["STARMIE's PERISH count is 3!", "Enemy SNORLAX's PERISH count is 3!"]);
|
||||
expect(steps.texts.join(" ")).toContain("PERISH count is 0!");
|
||||
expect(battle.enemy.mon.hp).toBe(0);
|
||||
});
|
||||
it("hurts the next Pokémon sent onto Spikes, which Rapid Spin blows away", () => {
|
||||
const lead = mon(PIDGEY, 5, [TACKLE]);
|
||||
lead.hp = 1;
|
||||
const next = mon(SNORLAX, 30, [SPLASH]);
|
||||
const battle = duel([mon(FORRETRESS, 60, [SPIKES, TACKLE])], [lead, next]);
|
||||
const steps = battleSteps(battle);
|
||||
steps.answer({ action: "fight", move: 0 });
|
||||
expect(steps.texts.join(" ")).toContain("SPIKES scattered all around Enemy PIDGEY");
|
||||
steps.answer({ action: "fight", move: 1 });
|
||||
expect(steps.texts.join(" ")).toContain("hurt by SPIKES");
|
||||
expect(battle.enemy.mon.maxHp - battle.enemy.mon.hp).toBe((next.stats[0] >> 2) >> 1);
|
||||
const spinner = duel([mon(FORRETRESS, 60, [RAPID_SPIN])], [mon(FORRETRESS, 60, [SPIKES])]);
|
||||
const spun = turns(spinner, [0, 0]);
|
||||
expect(spun.texts.join(" ")).toContain("blew away SPIKES");
|
||||
});
|
||||
it("may raise every stat with AncientPower", () => {
|
||||
const { battle } = findSeed(
|
||||
(seed) => duel([mon(ONIX, 60, [ANCIENTPOWER])], [mon(SNORLAX, 60, [SPLASH])], { seed }),
|
||||
(texts) => texts.some((text) => text.includes("SPCL.DEF went up")),
|
||||
);
|
||||
expect(battle.player.levels.slice(0, 5)).toEqual([8, 8, 8, 8, 8]);
|
||||
});
|
||||
it("hits two turns later with Future Sight", () => {
|
||||
const battle = duel([mon(STARMIE, 60, [FUTURE_SIGHT, SPLASH])], [mon(SNORLAX, 60, [SPLASH])]);
|
||||
const steps = battleSteps(battle);
|
||||
steps.answer({ action: "fight", move: 0 });
|
||||
expect(steps.texts.join(" ")).toContain("foresaw an attack");
|
||||
const full = battle.enemy.mon.hp;
|
||||
steps.answer({ action: "fight", move: 0 });
|
||||
expect(steps.texts).toContain("But it failed!");
|
||||
steps.answer({ action: "fight", move: 1 });
|
||||
expect(steps.texts.at(-1)).toBe("Enemy SNORLAX was hit by FUTURE SIGHT!");
|
||||
expect(battle.enemy.mon.hp).toBeLessThan(full);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Counter and Mirror Coat", () => {
|
||||
it("returns twice a physical hit taken first", () => {
|
||||
const player = mon(SNORLAX, 40, [COUNTER]);
|
||||
|
||||
Reference in new issue
Block a user