pokerogue/test/abilities/dancer.test.ts
2025-06-16 06:42:22 -04:00

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);
});
("");
});