mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-21 00:52:47 +02:00
549 lines
22 KiB
TypeScript
549 lines
22 KiB
TypeScript
import { BattlerIndex } from "#enums/battler-index";
|
|
import type Pokemon from "#app/field/pokemon";
|
|
import { MoveResult } from "#enums/move-result";
|
|
import { MovePhase } from "#app/phases/move-phase";
|
|
import { AbilityId } from "#enums/ability-id";
|
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
|
import { MoveUseMode } from "#enums/move-use-mode";
|
|
import { MoveId } from "#enums/move-id";
|
|
import { SpeciesId } from "#enums/species-id";
|
|
import { Stat } from "#enums/stat";
|
|
import { StatusEffect } from "#enums/status-effect";
|
|
import GameManager from "#test/testUtils/gameManager";
|
|
import Phaser from "phaser";
|
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { toDmgValue } from "#app/utils/common";
|
|
import { allMoves, allAbilities } from "#app/data/data-lists";
|
|
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
|
|
|
|
describe("Abilities - Dancer", () => {
|
|
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")
|
|
.disableCrits()
|
|
.ability(AbilityId.DANCER)
|
|
.enemySpecies(SpeciesId.SHUCKLE)
|
|
.enemyAbility(AbilityId.BALL_FETCH)
|
|
.enemyMoveset(MoveId.SPLASH)
|
|
.enemyLevel(100)
|
|
.startingLevel(100);
|
|
});
|
|
|
|
/**
|
|
* Check that the specified {@linkcode Pokemon} is using the specified move
|
|
* in the current {@linkcode MovePhase} against the specified targets.
|
|
*/
|
|
function checkCurrentMoveUser(
|
|
pokemon: Pokemon,
|
|
move: MoveId,
|
|
targets?: BattlerIndex[],
|
|
useMode: MoveUseMode = MoveUseMode.INDIRECT,
|
|
) {
|
|
const currentPhase = game.scene.phaseManager.getCurrentPhase() as MovePhase;
|
|
expect(currentPhase).not.toBeNull();
|
|
expect(currentPhase).toBeInstanceOf(MovePhase);
|
|
expect(currentPhase.pokemon).toBe(pokemon);
|
|
expect(currentPhase.move.moveId).toBe(move);
|
|
if (targets) {
|
|
expect(currentPhase.targets).toHaveLength(targets.length);
|
|
expect(currentPhase.targets).toEqual(expect.arrayContaining(targets));
|
|
}
|
|
expect(currentPhase.useMode).toBe(useMode);
|
|
}
|
|
|
|
async function toNextMove() {
|
|
await game.phaseInterceptor.to("MoveEndPhase");
|
|
await game.phaseInterceptor.to("MovePhase", false);
|
|
}
|
|
|
|
// Reference Link: https://bulbapedia.bulbagarden.net/wiki/Dancer_(Ability).
|
|
|
|
it("should copy dance moves without consuming extra PP", async () => {
|
|
game.override.enemyAbility(AbilityId.DANCER);
|
|
await game.classicMode.startBattle([SpeciesId.ORICORIO]);
|
|
|
|
const oricorio = game.field.getPlayerPokemon();
|
|
const shuckle = game.field.getEnemyPokemon();
|
|
|
|
game.move.changeMoveset(oricorio, [MoveId.VICTORY_DANCE, MoveId.SWORDS_DANCE]);
|
|
game.move.changeMoveset(shuckle, [MoveId.VICTORY_DANCE, MoveId.SWORDS_DANCE]);
|
|
|
|
game.move.select(MoveId.SWORDS_DANCE);
|
|
await game.move.selectEnemyMove(MoveId.VICTORY_DANCE);
|
|
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
await game.phaseInterceptor.to("BerryPhase");
|
|
|
|
// shpuldn't use PP if copied move is also in moveset
|
|
expect(oricorio.moveset.map(m => m.ppUsed)).toEqual([0, 1]);
|
|
expect(shuckle.moveset.map(m => m.ppUsed)).toEqual([1, 0]);
|
|
|
|
// effects were applied correctly
|
|
expect(oricorio.getStatStage(Stat.ATK)).toBe(3);
|
|
expect(shuckle.getStatStage(Stat.ATK)).toBe(3);
|
|
|
|
// moves showed up in history
|
|
expect(oricorio.getLastXMoves(-1)).toHaveLength(2);
|
|
expect(oricorio.getLastXMoves(-1)).toEqual([
|
|
expect.objectContaining({ move: MoveId.VICTORY_DANCE, useMode: MoveUseMode.INDIRECT }),
|
|
expect.objectContaining({ move: MoveId.SWORDS_DANCE, useMode: MoveUseMode.NORMAL }),
|
|
]);
|
|
expect(shuckle.getLastXMoves(-1)).toHaveLength(2);
|
|
expect(shuckle.getLastXMoves(-1)).toEqual([
|
|
expect.objectContaining({ move: MoveId.VICTORY_DANCE, useMode: MoveUseMode.NORMAL }),
|
|
expect.objectContaining({ move: MoveId.SWORDS_DANCE, useMode: MoveUseMode.INDIRECT }),
|
|
]);
|
|
});
|
|
|
|
it("should redirect copied move if ally target faints", async () => {
|
|
game.override.battleStyle("double").startingLevel(500);
|
|
await game.classicMode.startBattle([SpeciesId.ORICORIO, SpeciesId.FEEBAS]);
|
|
|
|
const [oricorio, feebas] = game.scene.getPlayerField();
|
|
|
|
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER);
|
|
game.move.use(MoveId.REVELATION_DANCE, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2);
|
|
await game.setTurnOrder([BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
|
|
|
|
await game.phaseInterceptor.to("MovePhase", false); // feebas rev dance
|
|
checkCurrentMoveUser(feebas, MoveId.REVELATION_DANCE, [BattlerIndex.ENEMY_2], MoveUseMode.NORMAL);
|
|
await toNextMove();
|
|
|
|
// attack should redirect
|
|
const [shuckle1, shuckle2] = game.scene.getEnemyField();
|
|
expect(shuckle2.isFainted()).toBe(true);
|
|
checkCurrentMoveUser(oricorio, MoveId.REVELATION_DANCE, [BattlerIndex.ENEMY]);
|
|
|
|
await game.toEndOfTurn();
|
|
|
|
expect(shuckle1.isFainted()).toBe(true);
|
|
});
|
|
|
|
it("should redirect copied move if source enemy faints", async () => {
|
|
game.override.battleStyle("double");
|
|
await game.classicMode.startBattle([SpeciesId.ORICORIO]);
|
|
|
|
const oricorio = game.field.getPlayerPokemon();
|
|
const [shuckle1, shuckle2] = game.scene.getEnemyField();
|
|
shuckle1.hp = 1;
|
|
vi.spyOn(shuckle2, "getAbility").mockReturnValue(allAbilities[AbilityId.ROUGH_SKIN]);
|
|
|
|
// Enemy 1 hits enemy 2 and gets pwned
|
|
game.move.use(MoveId.SPLASH);
|
|
await game.move.forceEnemyMove(MoveId.AQUA_STEP, BattlerIndex.ENEMY_2);
|
|
await game.move.forceEnemyMove(MoveId.SPLASH);
|
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER]);
|
|
|
|
await game.phaseInterceptor.to("MovePhase"); // shuckle aqua steps its ally and kills itself
|
|
await toNextMove(); // Oricorio copies
|
|
expect(shuckle1.isFainted()).toBe(true);
|
|
|
|
// attack should redirect to other shuckle
|
|
checkCurrentMoveUser(oricorio, MoveId.AQUA_STEP, [BattlerIndex.ENEMY_2]);
|
|
|
|
await game.toEndOfTurn();
|
|
expect(oricorio.isFullHp()).toBe(false);
|
|
});
|
|
|
|
it("should target correctly in double battles", async () => {
|
|
game.override.battleStyle("double");
|
|
await game.classicMode.startBattle([SpeciesId.ORICORIO, SpeciesId.FEEBAS]);
|
|
|
|
const [oricorio, feebas] = game.scene.getPlayerField();
|
|
|
|
// oricorio feather dances feebas, everyone else dances like crazy
|
|
game.move.use(MoveId.FEATHER_DANCE, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2);
|
|
game.move.use(MoveId.REVELATION_DANCE, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2);
|
|
await game.move.forceEnemyMove(MoveId.FIERY_DANCE, BattlerIndex.PLAYER);
|
|
await game.move.forceEnemyMove(MoveId.SWORDS_DANCE);
|
|
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
|
|
|
|
await game.phaseInterceptor.to("MovePhase", false); // oricorio feather dance
|
|
checkCurrentMoveUser(oricorio, MoveId.FEATHER_DANCE, [BattlerIndex.PLAYER_2], MoveUseMode.NORMAL);
|
|
await toNextMove(); // feebas copies feather dance against oricorio
|
|
checkCurrentMoveUser(feebas, MoveId.FEATHER_DANCE, [BattlerIndex.PLAYER]);
|
|
|
|
await toNextMove(); // feebas uses rev dance on shuckle #2
|
|
checkCurrentMoveUser(feebas, MoveId.REVELATION_DANCE, [BattlerIndex.ENEMY_2], MoveUseMode.NORMAL);
|
|
await toNextMove(); // oricorio copies rev dance against same target
|
|
checkCurrentMoveUser(oricorio, MoveId.REVELATION_DANCE, [BattlerIndex.ENEMY_2]);
|
|
|
|
await toNextMove(); // shuckle 1 uses fiery dance
|
|
await toNextMove(); // oricorio copies fiery dance against it
|
|
checkCurrentMoveUser(oricorio, MoveId.FIERY_DANCE, [BattlerIndex.ENEMY]);
|
|
await toNextMove(); // feebas copies fiery dance
|
|
checkCurrentMoveUser(feebas, MoveId.FIERY_DANCE, [BattlerIndex.ENEMY]);
|
|
|
|
await toNextMove(); // shuckle 2 uses swords dance
|
|
await toNextMove(); // oricorio copies swords dance
|
|
checkCurrentMoveUser(oricorio, MoveId.SWORDS_DANCE, [BattlerIndex.PLAYER]);
|
|
await toNextMove(); // feebas copies swords dance
|
|
checkCurrentMoveUser(feebas, MoveId.SWORDS_DANCE, [BattlerIndex.PLAYER_2]);
|
|
|
|
await game.toEndOfTurn();
|
|
});
|
|
|
|
it("should display ability flyouts right before move use", async () => {
|
|
game.override.battleStyle("double").enemyAbility(AbilityId.DANCER);
|
|
await game.classicMode.startBattle([SpeciesId.ORICORIO, SpeciesId.FEEBAS]);
|
|
|
|
// TODO: uncomment once dynamic spd order added
|
|
// game.scene
|
|
// .getField()
|
|
// .forEach((pkmn, i) => pkmn.setStat(Stat.SPD, 5 - i));
|
|
|
|
game.move.use(MoveId.SWORDS_DANCE, BattlerIndex.PLAYER);
|
|
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
|
await game.phaseInterceptor.to("DancerPhase", false);
|
|
game.phaseInterceptor.clearLogs();
|
|
await game.toEndOfTurn();
|
|
|
|
const showAbPhases: number[] = [];
|
|
const hideAbPhases: number[] = [];
|
|
const movePhases: number[] = [];
|
|
const moveEndPhases: number[] = [];
|
|
|
|
const log = game.phaseInterceptor.log;
|
|
for (const [index, phase] of log.entries()) {
|
|
switch (phase) {
|
|
case "ShowAbilityPhase":
|
|
showAbPhases.push(index);
|
|
break;
|
|
case "HideAbilityPhase":
|
|
hideAbPhases.push(index);
|
|
break;
|
|
case "MovePhase":
|
|
movePhases.push(index);
|
|
break;
|
|
case "MoveEndPhase":
|
|
case "DancerPhase": // also count the initial dancer phase (which occurs right after the initial usage anyhow)
|
|
moveEndPhases.push(index);
|
|
break;
|
|
}
|
|
}
|
|
|
|
expect(showAbPhases).toHaveLength(3);
|
|
expect(hideAbPhases).toHaveLength(3);
|
|
|
|
// Each dancer's ShowAbilityPhase must be immediately preceded by a MoveEndPhase and HideAbilityPhase,
|
|
// and followed by a MovePhase.
|
|
// We do not check the move phases directly as other pokemon may have moved themselves.
|
|
for (const i of showAbPhases) {
|
|
expect(moveEndPhases).toContain(i - 2);
|
|
expect(hideAbPhases).toContain(i - 1);
|
|
expect(movePhases).toContain(i + 1);
|
|
}
|
|
});
|
|
|
|
// TODO: Enable once abilities start proccing in speed order
|
|
it.todo("should respect speed order during doubles", async () => {
|
|
game.override
|
|
.battleStyle("double")
|
|
.enemyAbility(AbilityId.DANCER)
|
|
.moveset([MoveId.QUIVER_DANCE, MoveId.SPLASH])
|
|
.enemyMoveset(MoveId.SPLASH);
|
|
await game.classicMode.startBattle([SpeciesId.ORICORIO, SpeciesId.FEEBAS]);
|
|
|
|
// Set the mons in reverse speed order - P1, P2, E1, E2
|
|
// Used in place of `setTurnOrder` as the latter only applies for turn start phase
|
|
game.scene.getField().forEach((pkmn, i) => pkmn.setStat(Stat.SPD, 5 - i));
|
|
const orderSpy = vi.spyOn(MovePhase.prototype, "start");
|
|
const showAbSpy = vi.spyOn(ShowAbilityPhase.prototype, "start");
|
|
|
|
game.move.select(MoveId.QUIVER_DANCE, BattlerIndex.PLAYER);
|
|
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
|
await game.toEndOfTurn();
|
|
|
|
const [oricorio, feebas, shuckle1, shuckle2] = game.scene.getField();
|
|
|
|
const expectedOrder = [
|
|
// Oricorio quiver dance, then copies
|
|
oricorio,
|
|
feebas,
|
|
shuckle1,
|
|
shuckle2,
|
|
];
|
|
|
|
const order = (orderSpy.mock.contexts as MovePhase[]).map(mp => mp.pokemon);
|
|
const abOrder = (showAbSpy.mock.contexts as ShowAbilityPhase[]).map(sap => sap.getPokemon());
|
|
expect(order).toEqual(expectedOrder);
|
|
expect(abOrder).toEqual(expectedOrder);
|
|
});
|
|
|
|
// TODO: Currently this is bugged as counter moves don't work at all
|
|
it.todo("should count as last attack recieved for counter moves", async () => {
|
|
game.override.battleStyle("double");
|
|
|
|
await game.classicMode.startBattle([SpeciesId.ORICORIO, SpeciesId.SNIVY]);
|
|
|
|
const [oricorio, _snivy] = game.scene.getPlayerField();
|
|
const [shuckle1, shuckle2] = game.scene.getEnemyField();
|
|
|
|
const enemyDmgSpy = vi.spyOn(shuckle2, "damageAndUpdate");
|
|
|
|
// snivy attacks enemy 2, prompting oricorio to do the same
|
|
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER);
|
|
game.move.use(MoveId.REVELATION_DANCE, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2);
|
|
await game.killPokemon(shuckle1);
|
|
await game.move.forceEnemyMove(MoveId.METAL_BURST);
|
|
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2]);
|
|
|
|
// ORDER:
|
|
// oricorio splash
|
|
// shuckle 1 splash
|
|
// snivy rev dance vs shuckle 2
|
|
// oricorio copies rev dance vs shuckle 2
|
|
// shuckle 2 metal burst vs Oricorio
|
|
|
|
await game.toEndOfTurn();
|
|
expect(shuckle2.getLastXMoves(-1)[0].move).toBe(MoveId.METAL_BURST);
|
|
expect(shuckle2.getLastXMoves(-1)[0].targets).toEqual([BattlerIndex.PLAYER]);
|
|
|
|
expect(enemyDmgSpy).toHaveBeenCalledTimes(2);
|
|
const lastDmgTaken = enemyDmgSpy.mock.lastCall?.[0]!;
|
|
expect(oricorio.getInverseHp()).toBe(toDmgValue(lastDmgTaken * 1.5));
|
|
});
|
|
|
|
it.each<{ name: string; move?: MoveId; enemyMove: MoveId }>([
|
|
{ name: "protected moves", enemyMove: MoveId.FIERY_DANCE, move: MoveId.PROTECT },
|
|
{ name: "missed moves", enemyMove: MoveId.AQUA_STEP },
|
|
{ name: "ineffective moves", enemyMove: MoveId.REVELATION_DANCE }, // ground type
|
|
// TODO: These currently don't work as the moves are still considered "successful"
|
|
// if all targets are unaffected
|
|
// { name: "failed Teeter Dance", enemyMove: Moves.TEETER_DANCE },
|
|
// { name: "capped stat-boosting moves", enemyMove: Moves.FEATHER_DANCE },
|
|
// { name: "capped stat-lowering moves", enemyMove: Moves.QUIVER_DANCE },
|
|
])("should not trigger on $name", async ({ move = MoveId.SPLASH, enemyMove }) => {
|
|
game.override.enemySpecies(SpeciesId.GROUDON);
|
|
// force aqua step to whiff
|
|
vi.spyOn(allMoves[MoveId.AQUA_STEP], "accuracy", "get").mockReturnValue(0);
|
|
await game.classicMode.startBattle([SpeciesId.ORICORIO]);
|
|
|
|
const oricorio = game.field.getPlayerPokemon();
|
|
|
|
oricorio.setStatStage(Stat.ATK, -6);
|
|
oricorio.setStatStage(Stat.SPATK, +6);
|
|
oricorio.setStatStage(Stat.SPDEF, +6);
|
|
oricorio.setStatStage(Stat.SPD, +6);
|
|
oricorio.addTag(BattlerTagType.CONFUSED, 12, MoveId.CONFUSE_RAY, game.field.getEnemyPokemon().id);
|
|
|
|
game.move.use(move);
|
|
await game.move.forceEnemyMove(enemyMove);
|
|
await game.toNextTurn();
|
|
|
|
expect(oricorio.getLastXMoves(-1)).toEqual([
|
|
expect.objectContaining({ move, result: MoveResult.SUCCESS, useMode: MoveUseMode.NORMAL }),
|
|
]);
|
|
expect(oricorio.waveData.abilityRevealed).toBe(false);
|
|
});
|
|
|
|
it("should trigger confusion self-damage, even when protected against", async () => {
|
|
game.override.confusionActivation(false); // disable confusion unless forced by mocks
|
|
await game.classicMode.startBattle([SpeciesId.ORICORIO]);
|
|
|
|
const oricorio = game.field.getPlayerPokemon();
|
|
|
|
// get confused
|
|
game.move.use(MoveId.SPLASH);
|
|
await game.move.forceEnemyMove(MoveId.CONFUSE_RAY);
|
|
await game.toNextTurn();
|
|
|
|
expect(oricorio.getTag(BattlerTagType.CONFUSED)).toBeDefined();
|
|
|
|
// Protect, then copy swords dance
|
|
game.move.use(MoveId.PROTECT);
|
|
await game.move.forceEnemyMove(MoveId.SWORDS_DANCE);
|
|
|
|
await game.phaseInterceptor.to("MovePhase"); // protect
|
|
await game.phaseInterceptor.to("MoveEndPhase"); // Swords dance
|
|
await game.move.forceConfusionActivation(true); // force confusion proc during swords dance copy
|
|
await game.toEndOfTurn();
|
|
|
|
// took damage from confusion instead of using move; player remains confused
|
|
expect(oricorio.hp).toBeLessThan(oricorio.getMaxHp());
|
|
expect(oricorio.getTag(BattlerTagType.CONFUSED)).toBeDefined();
|
|
expect(game.field.getEnemyPokemon()?.getTag(BattlerTagType.CONFUSED)).toBeUndefined();
|
|
});
|
|
|
|
it("should respect full paralysis", async () => {
|
|
game.override.statusEffect(StatusEffect.PARALYSIS).statusActivation(true);
|
|
await game.classicMode.startBattle([SpeciesId.ORICORIO]);
|
|
|
|
const oricorio = game.field.getPlayerPokemon();
|
|
expect(oricorio.status).not.toBeNull();
|
|
|
|
// attempt to copy swords dance and get para'd
|
|
game.move.use(MoveId.SPLASH);
|
|
await game.move.forceEnemyMove(MoveId.SWORDS_DANCE);
|
|
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
await game.toNextTurn();
|
|
|
|
expect(oricorio.getLastXMoves(-1)[0]).toMatchObject({ move: MoveId.NONE });
|
|
expect(oricorio.status).toBeTruthy();
|
|
expect(oricorio.getStatStage(Stat.ATK)).toBe(0);
|
|
});
|
|
|
|
it.each<{ name: string; status: StatusEffect }>([
|
|
{ name: "Sleep", status: StatusEffect.SLEEP },
|
|
{ name: "Freeze", status: StatusEffect.FREEZE },
|
|
])("should cure $name when copying move", async ({ status }) => {
|
|
game.override.statusEffect(status).statusActivation(true);
|
|
await game.classicMode.startBattle([SpeciesId.ORICORIO]);
|
|
|
|
const oricorio = game.field.getPlayerPokemon();
|
|
expect(oricorio.status?.effect).toBe(status);
|
|
|
|
game.move.use(MoveId.ACROBATICS);
|
|
await game.move.forceEnemyMove(MoveId.SWORDS_DANCE);
|
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
|
|
|
await game.phaseInterceptor.to("MoveEffectPhase"); // enemy SD
|
|
await game.phaseInterceptor.to("MovePhase"); // player dancer attempt (curing happens inside MP)
|
|
expect(oricorio.status).toBeNull();
|
|
|
|
await game.toEndOfTurn();
|
|
expect(oricorio.getStatStage(Stat.ATK)).toBe(2);
|
|
expect(game.field.getEnemyPokemon().isFullHp()).toBe(false);
|
|
});
|
|
|
|
// TODO: This more or less requires an overhaul of Frenzy moves
|
|
it.todo("should not lock user into Petal Dance or reduce its duration", async () => {
|
|
game.override.enemyMoveset(MoveId.PETAL_DANCE);
|
|
await game.classicMode.startBattle([SpeciesId.ORICORIO]);
|
|
|
|
// Mock RNG to make frenzy always last for max duration
|
|
const oricorio = game.field.getPlayerPokemon();
|
|
vi.spyOn(oricorio, "randBattleSeedIntRange").mockImplementation((_, max) => max);
|
|
|
|
game.move.changeMoveset(oricorio, [MoveId.SPLASH, MoveId.PETAL_DANCE]);
|
|
|
|
const shuckle = game.field.getEnemyPokemon();
|
|
|
|
// Enemy uses petal dance and we copy
|
|
game.move.select(MoveId.SPLASH);
|
|
await game.toEndOfTurn();
|
|
|
|
// used petal dance without being locked into move
|
|
expect(oricorio.getLastXMoves(-1)[0]).toMatchObject({ move: MoveId.PETAL_DANCE, useMode: MoveUseMode.INDIRECT });
|
|
expect(oricorio.getMoveQueue()).toHaveLength(0);
|
|
expect(oricorio.getTag(BattlerTagType.FRENZY)).toBeUndefined();
|
|
expect(shuckle.turnData.attacksReceived).toHaveLength(1);
|
|
await game.toNextTurn();
|
|
|
|
// Use petal dance ourselves and copy enemy one
|
|
game.move.select(MoveId.PETAL_DANCE);
|
|
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
await game.phaseInterceptor.to("MoveEndPhase");
|
|
const prevQueueLength = oricorio.getMoveQueue().length;
|
|
await game.toEndOfTurn();
|
|
|
|
// locked into Petal Dance for 2 more turns (not 1)
|
|
expect(oricorio.getMoveQueue()).toHaveLength(prevQueueLength);
|
|
expect(oricorio.getTag(BattlerTagType.FRENZY)).toBeDefined();
|
|
expect(oricorio.getMoveset().find(m => m.moveId === MoveId.PETAL_DANCE)?.ppUsed).toBe(1);
|
|
});
|
|
|
|
it("should lapse Truant and respect its disables", async () => {
|
|
game.override.passiveAbility(AbilityId.TRUANT).moveset(MoveId.SPLASH).enemyMoveset(MoveId.SWORDS_DANCE);
|
|
await game.classicMode.startBattle([SpeciesId.ORICORIO]);
|
|
|
|
const oricorio = game.field.getPlayerPokemon();
|
|
|
|
// turn 1: Splash --> truanted Dancer SD
|
|
game.move.select(MoveId.SPLASH);
|
|
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
await game.toNextTurn();
|
|
|
|
expect(oricorio.getLastXMoves(2)).toEqual([
|
|
expect.objectContaining({
|
|
move: MoveId.NONE,
|
|
result: MoveResult.FAIL,
|
|
useMode: MoveUseMode.INDIRECT,
|
|
}),
|
|
expect.objectContaining({
|
|
move: MoveId.SPLASH,
|
|
result: MoveResult.SUCCESS,
|
|
useMode: MoveUseMode.NORMAL,
|
|
}),
|
|
]);
|
|
expect(oricorio.getStatStage(Stat.ATK)).toBe(0);
|
|
|
|
// Turn 2: Dancer SD --> truanted Splash
|
|
game.move.select(MoveId.SPLASH);
|
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
|
await game.toEndOfTurn();
|
|
|
|
expect(oricorio.getLastXMoves(2)).toEqual([
|
|
expect.objectContaining({
|
|
move: MoveId.NONE,
|
|
result: MoveResult.FAIL,
|
|
useMode: MoveUseMode.NORMAL,
|
|
}),
|
|
expect.objectContaining({
|
|
move: MoveId.SWORDS_DANCE,
|
|
result: MoveResult.SUCCESS,
|
|
useMode: MoveUseMode.INDIRECT,
|
|
}),
|
|
]);
|
|
expect(oricorio.getStatStage(Stat.ATK)).toBe(2);
|
|
});
|
|
|
|
it.each<{ name: string; move: MoveId }>([
|
|
{ name: "Mirror Move", move: MoveId.MIRROR_MOVE },
|
|
{ name: "Copycat", move: MoveId.COPYCAT },
|
|
])("should not count as last move used for $name", async ({ move }) => {
|
|
await game.classicMode.startBattle([SpeciesId.ORICORIO]);
|
|
|
|
const shuckle = game.field.getEnemyPokemon();
|
|
|
|
// select splash first so we have a clear indicator of what move got copied
|
|
game.move.use(MoveId.SPLASH);
|
|
await game.move.forceEnemyMove(MoveId.SWORDS_DANCE);
|
|
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
await game.toNextTurn();
|
|
|
|
game.move.use(MoveId.SPLASH);
|
|
await game.move.forceEnemyMove(move);
|
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
|
await game.toEndOfTurn();
|
|
|
|
expect(shuckle.getLastXMoves()[0]).toMatchObject({ move: MoveId.SPLASH, useMode: MoveUseMode.FOLLOW_UP });
|
|
});
|
|
|
|
it("should not count as the last move used for Instruct", async () => {
|
|
game.override.battleStyle("double");
|
|
await game.classicMode.startBattle([SpeciesId.ORICORIO, SpeciesId.FEEBAS]);
|
|
|
|
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER);
|
|
game.move.use(MoveId.FIERY_DANCE, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2);
|
|
await game.move.forceEnemyMove(MoveId.SPLASH);
|
|
await game.move.forceEnemyMove(MoveId.INSTRUCT, BattlerIndex.PLAYER);
|
|
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2, BattlerIndex.ENEMY]);
|
|
|
|
await game.phaseInterceptor.to("MovePhase"); // Oricorio uses splash
|
|
|
|
await game.phaseInterceptor.to("MovePhase"); // Feebas uses fiery dance
|
|
await game.phaseInterceptor.to("MovePhase"); // Oricorio copies fiery dance
|
|
|
|
await game.phaseInterceptor.to("MovePhase"); // shuckle 2 instructs oricorio
|
|
await toNextMove(); // instructed move used
|
|
|
|
checkCurrentMoveUser(game.field.getPlayerPokemon(), MoveId.SPLASH, [BattlerIndex.PLAYER], MoveUseMode.NORMAL);
|
|
});
|
|
|
|
("");
|
|
});
|