Skip to content

Commit cb40617

Browse files
committed
Add unit tests for Me First
1 parent bff88b6 commit cb40617

File tree

2 files changed

+158
-0
lines changed

2 files changed

+158
-0
lines changed

src/data/moves/move-attrs/me-first-attr.ts

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const invalidMeFirstMoves: Set<MoveId> = new Set([
4343
MoveId.CHATTER,
4444
MoveId.COPYCAT,
4545
MoveId.COUNTER,
46+
MoveId.COVET,
4647
MoveId.FOCUS_PUNCH,
4748
MoveId.ME_FIRST,
4849
MoveId.METAL_BURST,

test/moves/me-first.test.ts

+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { allMoves } from "#app/data/data-lists";
2+
import { Abilities } from "#enums/abilities";
3+
import { BattlerIndex } from "#enums/battler-index";
4+
import { MoveId } from "#enums/move-id";
5+
import { MoveResult } from "#enums/move-result";
6+
import { Species } from "#enums/species";
7+
import { Stat } from "#enums/stat";
8+
import { GameManager } from "#test/test-utils/gameManager";
9+
import Phaser from "phaser";
10+
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
11+
12+
describe("Moves - Me First", () => {
13+
let phaserGame: Phaser.Game;
14+
let game: GameManager;
15+
16+
beforeAll(() => {
17+
phaserGame = new Phaser.Game({
18+
type: Phaser.HEADLESS,
19+
});
20+
});
21+
22+
afterEach(() => {
23+
game.phaseInterceptor.restoreOg();
24+
});
25+
26+
beforeEach(() => {
27+
game = new GameManager(phaserGame);
28+
game.override
29+
.ability(Abilities.BALL_FETCH)
30+
.battleType("single")
31+
.disableCrits()
32+
.enemySpecies(Species.MAGIKARP)
33+
.enemyAbility(Abilities.BALL_FETCH)
34+
.enemyMoveset(MoveId.SWORDS_DANCE)
35+
.startingLevel(100)
36+
.enemyLevel(100);
37+
});
38+
39+
it("should copy the target's selected move", async () => {
40+
await game.classicMode.startBattle([Species.FEEBAS]);
41+
42+
const player = game.field.getPlayerPokemon();
43+
const enemy = game.field.getEnemyPokemon();
44+
45+
game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
46+
game.move.use(MoveId.ME_FIRST);
47+
48+
await game.toEndOfTurn();
49+
50+
expect(player.getLastXMoves()[0]?.move.id).toBe(MoveId.SWORDS_DANCE);
51+
expect(player.getStatStage(Stat.ATK)).toBe(2);
52+
expect(enemy.getStatStage(Stat.ATK)).toBe(2);
53+
});
54+
55+
it("should increase the power of copied attacks by 50%", async () => {
56+
await game.classicMode.startBattle([Species.FEEBAS]);
57+
58+
const tackle = allMoves.get(MoveId.TACKLE);
59+
vi.spyOn(tackle, "calculateBattlePower");
60+
61+
game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
62+
game.move.use(MoveId.ME_FIRST);
63+
await game.move.forceEnemyMove(MoveId.TACKLE);
64+
65+
await game.phaseInterceptor.to("MoveEndPhase");
66+
expect(tackle.calculateBattlePower).toHaveReturnedWith(60);
67+
68+
await game.phaseInterceptor.to("MoveEndPhase");
69+
expect(tackle.calculateBattlePower).toHaveReturnedWith(40);
70+
});
71+
72+
it.todo("should put the user in a frenzy if Outrage is copied", async () => {
73+
game.override.enemySpecies(Species.BASTIODON);
74+
await game.classicMode.startBattle([Species.BASTIODON]);
75+
76+
const outrage = allMoves.get(MoveId.OUTRAGE);
77+
vi.spyOn(outrage, "calculateBattlePower");
78+
79+
game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
80+
game.move.use(MoveId.ME_FIRST);
81+
await game.move.forceEnemyMove(MoveId.OUTRAGE);
82+
83+
// Player uses Me First - should call Outrage with increased power
84+
await game.phaseInterceptor.to("MoveEndPhase");
85+
expect(outrage.calculateBattlePower).toHaveLastReturnedWith(180);
86+
87+
// Enemy uses Outrage - should have base power
88+
await game.phaseInterceptor.to("MoveEndPhase");
89+
expect(outrage.calculateBattlePower).toHaveLastReturnedWith(120);
90+
91+
game.scene.getField(true).forEach((p) => expect(p.getMoveQueue()).toHaveLength(1));
92+
93+
await game.toNextTurn();
94+
95+
// Me First should not boost subsequent uses of Outrage
96+
await game.phaseInterceptor.to("MoveEndPhase");
97+
expect(outrage.calculateBattlePower).toHaveLastReturnedWith(120);
98+
99+
await game.phaseInterceptor.to("MoveEndPhase");
100+
expect(outrage.calculateBattlePower).toHaveLastReturnedWith(120);
101+
});
102+
103+
it("should fail if the target has already used their selected move for the turn", async () => {
104+
await game.classicMode.startBattle([Species.FEEBAS]);
105+
106+
const player = game.field.getPlayerPokemon();
107+
const enemy = game.field.getEnemyPokemon();
108+
109+
game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
110+
game.move.use(MoveId.ME_FIRST);
111+
112+
await game.toEndOfTurn();
113+
114+
expect(player.getLastXMoves()[0]?.result).toBe(MoveResult.FAIL);
115+
expect(player.getStatStage(Stat.ATK)).toBe(0);
116+
expect(enemy.getStatStage(Stat.ATK)).toBe(2);
117+
});
118+
119+
it.each([
120+
{ moveId: MoveId.BEAK_BLAST, moveName: "Beak Blast" },
121+
{ moveId: MoveId.BELCH, moveName: "Belch" },
122+
{ moveId: MoveId.CHATTER, moveName: "Chatter" },
123+
{ moveId: MoveId.COUNTER, moveName: "Counter" },
124+
{ moveId: MoveId.COVET, moveName: "Covet" },
125+
{ moveId: MoveId.FOCUS_PUNCH, moveName: "Focus Punch" },
126+
{ moveId: MoveId.METAL_BURST, moveName: "Metal Burst" },
127+
{ moveId: MoveId.MIRROR_COAT, moveName: "Mirror Coat" },
128+
{ moveId: MoveId.SHELL_TRAP, moveName: "Shell Trap" },
129+
{ moveId: MoveId.STRUGGLE, moveName: "Struggle" },
130+
{ moveId: MoveId.THIEF, moveName: "Thief" },
131+
132+
/**
133+
* {@linkcode https://bulbapedia.bulbagarden.net/wiki/Category:Moves_that_call_other_moves | Moves that call other moves}
134+
* also cause Me First to fail if the target selects them
135+
*/
136+
{ moveId: MoveId.ASSIST, moveName: "Assist" },
137+
{ moveId: MoveId.COPYCAT, moveName: "Copycat" },
138+
{ moveId: MoveId.ME_FIRST, moveName: "Me First" },
139+
{ moveId: MoveId.METRONOME, moveName: "Metronome" },
140+
{ moveId: MoveId.MIRROR_MOVE, moveName: "Mirror Move" },
141+
{ moveId: MoveId.NATURE_POWER, moveName: "Nature Power" },
142+
{ moveId: MoveId.SLEEP_TALK, moveName: "Sleep Talk" },
143+
{ moveId: MoveId.SNATCH, moveName: "Snatch" },
144+
])("should fail if the target selected $moveName for the turn", async ({ moveId }) => {
145+
await game.classicMode.startBattle([Species.FEEBAS]);
146+
147+
const player = game.field.getPlayerPokemon();
148+
149+
game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
150+
game.move.use(MoveId.ME_FIRST);
151+
await game.move.forceEnemyMove(moveId);
152+
153+
await game.phaseInterceptor.to("MoveEndPhase");
154+
155+
expect(player.getLastXMoves()[0]?.result).toBe(MoveResult.FAIL);
156+
});
157+
});

0 commit comments

Comments
 (0)