mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-21 00:52:47 +02:00
272 lines
9.9 KiB
TypeScript
272 lines
9.9 KiB
TypeScript
import { allMoves } from "#app/data/data-lists";
|
|
import { PokemonType } from "#enums/pokemon-type";
|
|
import type { PlayerPokemon } from "#app/field/pokemon";
|
|
import { MoveResult } from "#enums/move-result";
|
|
import { AbilityId } from "#enums/ability-id";
|
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
|
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 { BattleType } from "#enums/battle-type";
|
|
import { BattlerIndex } from "#enums/battler-index";
|
|
|
|
describe("Abilities - Protean/Libero", () => {
|
|
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")
|
|
.ability(AbilityId.PROTEAN)
|
|
.startingLevel(100)
|
|
.moveset([MoveId.CURSE, MoveId.DIG, MoveId.SPLASH])
|
|
.enemySpecies(SpeciesId.RATTATA)
|
|
.enemyMoveset(MoveId.SPLASH);
|
|
});
|
|
|
|
/**
|
|
* Assert that the protean/libero ability triggered to change the user's type to
|
|
* the type of its most recently used move.
|
|
* Takes into account type overrides from effects.
|
|
* @param pokemon - The {@linkcode PlayerPokemon} being checked.
|
|
* @remarks
|
|
* This will clear the given Pokemon's `abilitiesApplied` set after being called to allow for easier multi-turn testing.
|
|
*/
|
|
function expectTypeChange(pokemon: PlayerPokemon) {
|
|
expect(pokemon.waveData.abilitiesApplied).toContainEqual(expect.toBeOneOf([AbilityId.PROTEAN, AbilityId.LIBERO]));
|
|
const lastMove = allMoves[pokemon.getLastXMoves()[0].move]!;
|
|
|
|
const pokemonTypes = pokemon.getTypes().map(pt => PokemonType[pt]);
|
|
const moveType = PokemonType[pokemon.getMoveType(lastMove)];
|
|
expect(pokemonTypes).toEqual([moveType]);
|
|
pokemon.waveData.abilitiesApplied.clear();
|
|
}
|
|
|
|
/**
|
|
* Assert that the protean/libero ability did NOT trigger to change the user's type to
|
|
* the type of its most recently used move.
|
|
* Takes into account type overrides from effects.
|
|
* @param pokemon - The {@linkcode PlayerPokemon} being checked.
|
|
* @remarks
|
|
* This will clear the given Pokemon's `abilitiesApplied` set after being called to allow for easier multi-turn testing.
|
|
*/
|
|
function expectNoTypeChange(pokemon: PlayerPokemon) {
|
|
expect(pokemon.waveData.abilitiesApplied).not.toContainEqual(
|
|
expect.toBeOneOf([AbilityId.PROTEAN, AbilityId.LIBERO]),
|
|
);
|
|
const lastMove = allMoves[pokemon.getLastXMoves()[0].move]!;
|
|
|
|
const pokemonTypes = pokemon.getTypes().map(pt => PokemonType[pt]);
|
|
const moveType = PokemonType[pokemon.getMoveType(lastMove, true)];
|
|
expect(pokemonTypes).not.toEqual([moveType]);
|
|
pokemon.waveData.abilitiesApplied.clear();
|
|
}
|
|
|
|
it.each([
|
|
{ name: "Protean", ability: AbilityId.PROTEAN },
|
|
{ name: "Libero", ability: AbilityId.PROTEAN },
|
|
])("$name should change the user's type to the type of the move being used", async ({ ability }) => {
|
|
game.override.ability(ability);
|
|
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
|
|
|
const leadPokemon = game.field.getPlayerPokemon();
|
|
|
|
game.move.use(MoveId.SPLASH);
|
|
await game.toEndOfTurn();
|
|
|
|
expectTypeChange(leadPokemon);
|
|
});
|
|
|
|
// Test for Gen9+ functionality, we are using previous funcionality
|
|
it.skip("should apply only once per switch in", async () => {
|
|
game.override.moveset([MoveId.SPLASH, MoveId.AGILITY]);
|
|
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.BULBASAUR]);
|
|
|
|
const bulbasaur = game.field.getPlayerPokemon();
|
|
|
|
game.move.select(MoveId.SPLASH);
|
|
await game.toEndOfTurn();
|
|
|
|
expectTypeChange(bulbasaur);
|
|
|
|
game.move.select(MoveId.AGILITY);
|
|
await game.toEndOfTurn();
|
|
|
|
expectNoTypeChange(bulbasaur);
|
|
|
|
// switch out and back in
|
|
game.doSwitchPokemon(1);
|
|
await game.toNextTurn();
|
|
game.doSwitchPokemon(1);
|
|
await game.toNextTurn();
|
|
|
|
expect(bulbasaur.isOnField()).toBe(true);
|
|
|
|
game.move.select(MoveId.SPLASH);
|
|
await game.toEndOfTurn();
|
|
|
|
expectTypeChange(bulbasaur);
|
|
});
|
|
|
|
it.each<{ category: string; move?: MoveId; passive?: AbilityId; enemyMove?: MoveId }>([
|
|
{ category: "Variable type Moves'", move: MoveId.WEATHER_BALL, passive: AbilityId.DROUGHT },
|
|
{ category: "Type Change Abilities'", passive: AbilityId.REFRIGERATE },
|
|
{ category: "Move-calling Moves'", move: MoveId.NATURE_POWER, passive: AbilityId.PSYCHIC_SURGE },
|
|
{ category: "Ion Deluge's", enemyMove: MoveId.ION_DELUGE },
|
|
{ category: "Electrify's", enemyMove: MoveId.ELECTRIFY },
|
|
])(
|
|
"should respect $category final type",
|
|
async ({ move = MoveId.TACKLE, passive = AbilityId.NONE, enemyMove = MoveId.SPLASH }) => {
|
|
game.override.passiveAbility(passive);
|
|
await game.classicMode.startBattle([SpeciesId.LINOONE]); // Pure normal type for move overrides
|
|
|
|
const linoone = game.field.getPlayerPokemon();
|
|
|
|
game.move.use(move);
|
|
await game.move.forceEnemyMove(enemyMove);
|
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
|
await game.phaseInterceptor.to("BerryPhase"); // NB: berry phase = turn end tags stay = tests happy
|
|
|
|
expectTypeChange(linoone);
|
|
},
|
|
);
|
|
|
|
it.each<{ cause: string; move?: MoveId; passive?: AbilityId; enemyMove?: MoveId }>([
|
|
{ cause: "misses", move: MoveId.FOCUS_BLAST },
|
|
{ cause: "is protected against", enemyMove: MoveId.PROTECT },
|
|
{ cause: "is ineffective", move: MoveId.EARTHQUAKE },
|
|
{ cause: "matches only one of its types", move: MoveId.NIGHT_SLASH },
|
|
{ cause: "is blocked by terrain", move: MoveId.SHADOW_SNEAK, passive: AbilityId.PSYCHIC_SURGE },
|
|
])(
|
|
"should still trigger if the user's move $cause",
|
|
async ({ move = MoveId.TACKLE, passive = AbilityId.NONE, enemyMove = MoveId.SPLASH }) => {
|
|
game.override.passiveAbility(passive).enemySpecies(SpeciesId.SKARMORY);
|
|
await game.classicMode.startBattle([SpeciesId.MEOWSCARADA]);
|
|
|
|
// FOCUS MISS IS REAL CHAT
|
|
vi.spyOn(allMoves[MoveId.FOCUS_BLAST], "accuracy", "get").mockReturnValue(0);
|
|
|
|
const meow = game.field.getPlayerPokemon();
|
|
|
|
game.move.use(move);
|
|
await game.move.forceEnemyMove(enemyMove);
|
|
await game.toEndOfTurn();
|
|
|
|
expectTypeChange(meow);
|
|
},
|
|
);
|
|
|
|
it.each<{ cause: string; move?: MoveId; tera?: boolean; passive?: AbilityId }>([
|
|
{ cause: "user is terastallized to any type", tera: true },
|
|
{ cause: "user uses Struggle", move: MoveId.STRUGGLE },
|
|
{ cause: "the user's move is blocked by weather", move: MoveId.FIRE_BLAST, passive: AbilityId.PRIMORDIAL_SEA },
|
|
{ cause: "the user's move fails", move: MoveId.BURN_UP },
|
|
])("should not apply if $cause", async ({ move = MoveId.TACKLE, tera = false, passive = AbilityId.NONE }) => {
|
|
game.override.enemyPassiveAbility(passive);
|
|
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
|
|
|
const karp = game.field.getPlayerPokemon();
|
|
|
|
karp.teraType = PokemonType.STEEL;
|
|
|
|
game.move.use(move, BattlerIndex.PLAYER, undefined, tera);
|
|
await game.toEndOfTurn();
|
|
|
|
expectNoTypeChange(karp);
|
|
});
|
|
|
|
it("should not apply if user is already the move's type", async () => {
|
|
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
|
|
|
const karp = game.field.getPlayerPokemon();
|
|
|
|
game.move.use(MoveId.WATERFALL);
|
|
await game.toEndOfTurn();
|
|
|
|
expect(karp.waveData.abilitiesApplied.size).toBe(0);
|
|
expect(karp.getTypes()).toEqual([allMoves[MoveId.WATERFALL].type]);
|
|
});
|
|
|
|
it.each<{ moveName: string; move: MoveId }>([
|
|
{ moveName: "Roar", move: MoveId.ROAR },
|
|
{ moveName: "Whirlwind", move: MoveId.WHIRLWIND },
|
|
{ moveName: "Forest's Curse", move: MoveId.FORESTS_CURSE },
|
|
{ moveName: "Trick-or-Treat", move: MoveId.TRICK_OR_TREAT },
|
|
])("should still apply if the user's $moveName fails", async ({ move }) => {
|
|
game.override.battleType(BattleType.TRAINER).enemySpecies(SpeciesId.TREVENANT); // ghost/grass makes both moves fail
|
|
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
|
|
|
const leadPokemon = game.field.getPlayerPokemon();
|
|
|
|
game.move.use(move);
|
|
// KO all off-field opponents for Whirlwind and co.
|
|
for (const enemyMon of game.scene.getEnemyParty()) {
|
|
if (!enemyMon.isActive()) {
|
|
enemyMon.hp = 0;
|
|
}
|
|
}
|
|
await game.toEndOfTurn();
|
|
|
|
expectTypeChange(leadPokemon);
|
|
});
|
|
|
|
it("should trigger on the first turn of charging moves", async () => {
|
|
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
|
|
|
const karp = game.field.getPlayerPokemon();
|
|
|
|
game.move.select(MoveId.DIG);
|
|
await game.toEndOfTurn();
|
|
|
|
expectTypeChange(karp);
|
|
|
|
await game.toEndOfTurn();
|
|
expect(karp.waveData.abilitiesApplied).not.toContain(AbilityId.PROTEAN);
|
|
});
|
|
|
|
it("should cause the user to cast Ghost-type Curse on itself", async () => {
|
|
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
|
|
|
const karp = game.field.getPlayerPokemon();
|
|
expect(karp.isOfType(PokemonType.GHOST)).toBe(false);
|
|
|
|
game.move.select(MoveId.CURSE);
|
|
await game.toEndOfTurn();
|
|
|
|
expectTypeChange(karp);
|
|
expect(karp.getHpRatio(true)).toBeCloseTo(0.25);
|
|
expect(karp.getTag(BattlerTagType.CURSED)).toBeDefined();
|
|
});
|
|
|
|
it("should not trigger during Focus Punch's start-of-turn message or being interrupted", async () => {
|
|
game.override.moveset(MoveId.FOCUS_PUNCH).enemyMoveset(MoveId.ABSORB);
|
|
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
|
|
|
const karp = game.field.getPlayerPokemon();
|
|
expect(karp.isOfType(PokemonType.FIGHTING)).toBe(false);
|
|
|
|
game.move.select(MoveId.FOCUS_PUNCH);
|
|
|
|
await game.phaseInterceptor.to("MessagePhase");
|
|
expect(karp.isOfType(PokemonType.FIGHTING)).toBe(false);
|
|
|
|
await game.toEndOfTurn();
|
|
|
|
expectNoTypeChange(karp);
|
|
expect(karp.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
|
});
|
|
});
|