mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-21 00:52:47 +02:00
* Move game-mode to its own file Reduces circular imports to 325 * Move battler-index to own file Reduces circular deps to 314 * Move trainer-variant to own file Reduces circ deps to 313 * Move enums in pokemon to their own file * Move arena-tag-type to its own file * Move pokemon-moves to its own file * Move command to own file * Move learnMoveType to own file * Move form change item to own file * Move battlerTagLapseType to own file * Move anim enums to own shared file * Move enums out of challenges * Move species form change triggers to own file Reduces circ imports to 291 * Update test importing pokemon move * Replace move attribute imports with string names * Untangle circular deps from game data * Fix missing string call in switch summon phase * Apply kev's suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Ensure ChargeMove's is method calls super * Use InstanceType for proper narrowing * Apply kev's suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
253 lines
9.3 KiB
TypeScript
253 lines
9.3 KiB
TypeScript
import { BattlerTagType } from "#enums/battler-tag-type";
|
|
import { Challenges } from "#enums/challenges";
|
|
import { PokemonType } from "#enums/pokemon-type";
|
|
import { MoveResult } from "#enums/move-result";
|
|
import { AbilityId } from "#enums/ability-id";
|
|
import { MoveId } from "#enums/move-id";
|
|
import { SpeciesId } from "#enums/species-id";
|
|
import GameManager from "#test/testUtils/gameManager";
|
|
import Phaser from "phaser";
|
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { Status } from "#app/data/status-effect";
|
|
import { StatusEffect } from "#enums/status-effect";
|
|
import { globalScene } from "#app/global-scene";
|
|
import { BattlerIndex } from "#enums/battler-index";
|
|
import { BattleType } from "#enums/battle-type";
|
|
import { TrainerType } from "#enums/trainer-type";
|
|
|
|
describe("Moves - Whirlwind", () => {
|
|
let phaserGame: Phaser.Game;
|
|
let game: GameManager;
|
|
|
|
beforeAll(() => {
|
|
phaserGame = new Phaser.Game({
|
|
type: Phaser.HEADLESS,
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
game.phaseInterceptor.restoreOg();
|
|
});
|
|
|
|
beforeEach(() => {
|
|
game = new GameManager(phaserGame);
|
|
game.override
|
|
.battleStyle("single")
|
|
.moveset([MoveId.SPLASH])
|
|
.enemyAbility(AbilityId.BALL_FETCH)
|
|
.enemyMoveset([MoveId.SPLASH, MoveId.WHIRLWIND])
|
|
.enemySpecies(SpeciesId.PIDGEY);
|
|
});
|
|
|
|
it.each([
|
|
{ move: MoveId.FLY, name: "Fly" },
|
|
{ move: MoveId.BOUNCE, name: "Bounce" },
|
|
{ move: MoveId.SKY_DROP, name: "Sky Drop" },
|
|
])("should not hit a flying target: $name (=$move)", async ({ move }) => {
|
|
game.override.moveset([move]);
|
|
// Must have a pokemon in the back so that the move misses instead of fails.
|
|
await game.classicMode.startBattle([SpeciesId.STARAPTOR, SpeciesId.MAGIKARP]);
|
|
|
|
const staraptor = game.scene.getPlayerPokemon()!;
|
|
|
|
game.move.select(move);
|
|
await game.move.selectEnemyMove(MoveId.WHIRLWIND);
|
|
|
|
await game.phaseInterceptor.to("BerryPhase", false);
|
|
|
|
expect(staraptor.findTag(t => t.tagType === BattlerTagType.FLYING)).toBeDefined();
|
|
expect(game.scene.getEnemyPokemon()!.getLastXMoves(1)[0].result).toBe(MoveResult.MISS);
|
|
});
|
|
|
|
it("should force switches randomly", async () => {
|
|
await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER, SpeciesId.SQUIRTLE]);
|
|
|
|
const [bulbasaur, charmander, squirtle] = game.scene.getPlayerParty();
|
|
|
|
// Turn 1: Mock an RNG call that calls for switching to 1st backup Pokemon (Charmander)
|
|
vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((_range, min = 0) => {
|
|
return min;
|
|
});
|
|
game.move.select(MoveId.SPLASH);
|
|
await game.move.selectEnemyMove(MoveId.WHIRLWIND);
|
|
await game.toNextTurn();
|
|
|
|
expect(bulbasaur.isOnField()).toBe(false);
|
|
expect(charmander.isOnField()).toBe(true);
|
|
expect(squirtle.isOnField()).toBe(false);
|
|
|
|
// Turn 2: Mock an RNG call that calls for switching to 2nd backup Pokemon (Squirtle)
|
|
vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((_range, min = 0) => {
|
|
return min + 1;
|
|
});
|
|
game.move.select(MoveId.SPLASH);
|
|
await game.move.selectEnemyMove(MoveId.WHIRLWIND);
|
|
await game.toNextTurn();
|
|
|
|
expect(bulbasaur.isOnField()).toBe(false);
|
|
expect(charmander.isOnField()).toBe(false);
|
|
expect(squirtle.isOnField()).toBe(true);
|
|
});
|
|
|
|
it("should not force a switch to a challenge-ineligible Pokemon", async () => {
|
|
// Mono-Water challenge, Eevee is ineligible
|
|
game.challengeMode.addChallenge(Challenges.SINGLE_TYPE, PokemonType.WATER + 1, 0);
|
|
await game.challengeMode.startBattle([SpeciesId.LAPRAS, SpeciesId.EEVEE, SpeciesId.TOXAPEX, SpeciesId.PRIMARINA]);
|
|
|
|
const [lapras, eevee, toxapex, primarina] = game.scene.getPlayerParty();
|
|
|
|
// Turn 1: Mock an RNG call that would normally call for switching to Eevee, but it is ineligible
|
|
vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((_range, min = 0) => {
|
|
return min;
|
|
});
|
|
game.move.select(MoveId.SPLASH);
|
|
await game.move.selectEnemyMove(MoveId.WHIRLWIND);
|
|
await game.toNextTurn();
|
|
|
|
expect(lapras.isOnField()).toBe(false);
|
|
expect(eevee.isOnField()).toBe(false);
|
|
expect(toxapex.isOnField()).toBe(true);
|
|
expect(primarina.isOnField()).toBe(false);
|
|
});
|
|
|
|
it("should not force a switch to a fainted Pokemon", async () => {
|
|
await game.classicMode.startBattle([SpeciesId.LAPRAS, SpeciesId.EEVEE, SpeciesId.TOXAPEX, SpeciesId.PRIMARINA]);
|
|
|
|
const [lapras, eevee, toxapex, primarina] = game.scene.getPlayerParty();
|
|
|
|
// Turn 1: Eevee faints
|
|
eevee.hp = 0;
|
|
eevee.status = new Status(StatusEffect.FAINT);
|
|
expect(eevee.isFainted()).toBe(true);
|
|
game.move.select(MoveId.SPLASH);
|
|
await game.move.selectEnemyMove(MoveId.SPLASH);
|
|
await game.toNextTurn();
|
|
|
|
// Turn 2: Mock an RNG call that would normally call for switching to Eevee, but it is fainted
|
|
vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((_range, min = 0) => {
|
|
return min;
|
|
});
|
|
game.move.select(MoveId.SPLASH);
|
|
await game.move.selectEnemyMove(MoveId.WHIRLWIND);
|
|
await game.toNextTurn();
|
|
|
|
expect(lapras.isOnField()).toBe(false);
|
|
expect(eevee.isOnField()).toBe(false);
|
|
expect(toxapex.isOnField()).toBe(true);
|
|
expect(primarina.isOnField()).toBe(false);
|
|
});
|
|
|
|
it("should not force a switch if there are no available Pokemon to switch into", async () => {
|
|
await game.classicMode.startBattle([SpeciesId.LAPRAS, SpeciesId.EEVEE]);
|
|
|
|
const [lapras, eevee] = game.scene.getPlayerParty();
|
|
|
|
// Turn 1: Eevee faints
|
|
eevee.hp = 0;
|
|
eevee.status = new Status(StatusEffect.FAINT);
|
|
expect(eevee.isFainted()).toBe(true);
|
|
game.move.select(MoveId.SPLASH);
|
|
await game.move.selectEnemyMove(MoveId.SPLASH);
|
|
await game.toNextTurn();
|
|
|
|
// Turn 2: Mock an RNG call that would normally call for switching to Eevee, but it is fainted
|
|
vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((_range, min = 0) => {
|
|
return min;
|
|
});
|
|
game.move.select(MoveId.SPLASH);
|
|
await game.move.selectEnemyMove(MoveId.WHIRLWIND);
|
|
await game.toNextTurn();
|
|
|
|
expect(lapras.isOnField()).toBe(true);
|
|
expect(eevee.isOnField()).toBe(false);
|
|
});
|
|
|
|
it("should fail when player uses Whirlwind against an opponent with only one available Pokémon", async () => {
|
|
// Set up the battle scenario with the player knowing Whirlwind
|
|
game.override.startingWave(5).enemySpecies(SpeciesId.PIDGEY).moveset([MoveId.WHIRLWIND]);
|
|
await game.classicMode.startBattle();
|
|
|
|
const enemyParty = game.scene.getEnemyParty();
|
|
|
|
// Ensure the opponent has only one available Pokémon
|
|
if (enemyParty.length > 1) {
|
|
enemyParty.slice(1).forEach(p => {
|
|
p.hp = 0;
|
|
p.status = new Status(StatusEffect.FAINT);
|
|
});
|
|
}
|
|
const eligibleEnemy = enemyParty.filter(p => p.hp > 0 && p.isAllowedInBattle());
|
|
expect(eligibleEnemy.length).toBe(1);
|
|
|
|
// Spy on the queueMessage function
|
|
const queueSpy = vi.spyOn(globalScene.phaseManager, "queueMessage");
|
|
|
|
// Player uses Whirlwind; opponent uses Splash
|
|
game.move.select(MoveId.WHIRLWIND);
|
|
await game.move.selectEnemyMove(MoveId.SPLASH);
|
|
await game.toNextTurn();
|
|
|
|
// Verify that the failure message is displayed for Whirlwind
|
|
expect(queueSpy).toHaveBeenCalledWith(expect.stringContaining("But it failed"));
|
|
// Verify the opponent's Splash message
|
|
expect(queueSpy).toHaveBeenCalledWith(expect.stringContaining("But nothing happened!"));
|
|
});
|
|
|
|
it("should not pull in the other trainer's pokemon in a partner trainer battle", async () => {
|
|
game.override
|
|
.startingWave(2)
|
|
.battleType(BattleType.TRAINER)
|
|
.randomTrainer({
|
|
trainerType: TrainerType.BREEDER,
|
|
alwaysDouble: true,
|
|
})
|
|
.enemyMoveset([MoveId.SPLASH, MoveId.LUNAR_DANCE])
|
|
.moveset([MoveId.WHIRLWIND, MoveId.SPLASH]);
|
|
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.TOTODILE]);
|
|
|
|
// expect the enemy to have at least 4 pokemon, necessary for this check to even work
|
|
expect(game.scene.getEnemyParty().length, "enemy must have exactly 4 pokemon").toBe(4);
|
|
|
|
const user = game.scene.getPlayerPokemon()!;
|
|
|
|
console.log(user.getMoveset(false));
|
|
|
|
game.move.select(MoveId.SPLASH);
|
|
game.move.select(MoveId.SPLASH);
|
|
await game.move.selectEnemyMove(MoveId.MEMENTO);
|
|
await game.move.selectEnemyMove(MoveId.SPLASH);
|
|
await game.toNextTurn();
|
|
|
|
// Get the enemy pokemon id so we can check if is the same after switch.
|
|
const enemy_id = game.scene.getEnemyPokemon()!.id;
|
|
|
|
// Hit the enemy that fainted with whirlwind.
|
|
game.move.select(MoveId.WHIRLWIND, 0, BattlerIndex.ENEMY);
|
|
game.move.select(MoveId.SPLASH, 1);
|
|
|
|
await game.move.selectEnemyMove(MoveId.SPLASH);
|
|
await game.move.selectEnemyMove(MoveId.SPLASH);
|
|
|
|
await game.toNextTurn();
|
|
|
|
// Expect the enemy pokemon to not have switched out.
|
|
expect(game.scene.getEnemyPokemon()!.id).toBe(enemy_id);
|
|
});
|
|
|
|
it("should force a wild pokemon to flee", async () => {
|
|
game.override
|
|
.battleType(BattleType.WILD)
|
|
.moveset([MoveId.WHIRLWIND, MoveId.SPLASH])
|
|
.enemyMoveset(MoveId.SPLASH)
|
|
.ability(AbilityId.BALL_FETCH);
|
|
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
|
|
|
const user = game.scene.getPlayerPokemon()!;
|
|
|
|
game.move.select(MoveId.WHIRLWIND);
|
|
await game.phaseInterceptor.to("BerryPhase");
|
|
|
|
expect(user.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS);
|
|
});
|
|
});
|