pokerogue/test/moves/wish.test.ts
Bertie690 f7b87f3d1e
[Move Bug] Fully implemented Future Sight, Doom Desire; fixed Wish Double battle oversight (#5862)
* Mostly implemented Future Sight/Doom Desire

* Fixed a few docs

* Fixed com

* Update magic_guard.test.ts

* Update documentation

* Update documentation on arena-tag.ts

* Update arena-tag.ts docs

* Update arena-tag.ts

* Update turn-end-phase.ts

* Update move.ts documentation

* Fixed tpyo

* Update move.ts documentation

* Add assorted TODO test cases

* Refactored FS to use a positional tag manager

* Added strong typing to the manager, finished save load stufff

* Fixed locales + tests

* Fixed tests and documentation

* sh
Fixed tests for good

* Fixed MEP

* Reverted overrides changse

* Fixed issues with merging

* Fixed locales update & heal block test

* Fixed wish tests

* Fixed test typo

* Fixed wish test flaking out due to speed ties

* Fixed tests fr fr

* actually fixed tests bc i'm stupid

* Fixed tests for real

* Remove locales update

* Update arena-tag.ts

Co-authored-by: Dean <69436131+emdeann@users.noreply.github.com>

* Update move.ts

Co-authored-by: Dean <69436131+emdeann@users.noreply.github.com>

* Update arena-tag.ts

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Applied review suggestions and added a _wee_ bit more docs

* fixed wish condition

* Applied kev's reviews

* Minor nits

* Minor formatting change in `heal-block.test.ts`

---------

Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com>
Co-authored-by: Dean <69436131+emdeann@users.noreply.github.com>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2025-07-30 22:43:06 -06:00

184 lines
6.0 KiB
TypeScript

import { getPokemonNameWithAffix } from "#app/messages";
import { AbilityId } from "#enums/ability-id";
import { BattlerIndex } from "#enums/battler-index";
import { MoveId } from "#enums/move-id";
import { MoveResult } from "#enums/move-result";
import { PositionalTagType } from "#enums/positional-tag-type";
import { SpeciesId } from "#enums/species-id";
import { Stat } from "#enums/stat";
import { GameManager } from "#test/test-utils/game-manager";
import { toDmgValue } from "#utils/common";
import i18next from "i18next";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Move - Wish", () => {
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
.ability(AbilityId.BALL_FETCH)
.battleStyle("single")
.criticalHits(false)
.enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH)
.startingLevel(100)
.enemyLevel(100);
});
/**
* Expect that wish is active with the specified number of attacks.
* @param numAttacks - The number of wish instances that should be queued; default `1`
*/
function expectWishActive(numAttacks = 1) {
const wishes = game.scene.arena.positionalTagManager["tags"].filter(t => t.tagType === PositionalTagType.WISH);
expect(wishes).toHaveLength(numAttacks);
}
it("should heal the Pokemon in the current slot for 50% of the user's maximum HP", async () => {
await game.classicMode.startBattle([SpeciesId.ALOMOMOLA, SpeciesId.BLISSEY]);
const [alomomola, blissey] = game.scene.getPlayerParty();
alomomola.hp = 1;
blissey.hp = 1;
game.move.use(MoveId.WISH);
await game.toNextTurn();
expectWishActive();
game.doSwitchPokemon(1);
await game.toEndOfTurn();
expectWishActive(0);
expect(game.textInterceptor.logs).toContain(
i18next.t("arenaTag:wishTagOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(alomomola),
}),
);
expect(alomomola.hp).toBe(1);
expect(blissey.hp).toBe(toDmgValue(alomomola.getMaxHp() / 2) + 1);
});
it("should work if the user has full HP, but not if it already has an active Wish", async () => {
await game.classicMode.startBattle([SpeciesId.ALOMOMOLA, SpeciesId.BLISSEY]);
const alomomola = game.field.getPlayerPokemon();
alomomola.hp = 1;
game.move.use(MoveId.WISH);
await game.toNextTurn();
expectWishActive();
game.move.use(MoveId.WISH);
await game.toEndOfTurn();
expect(alomomola.hp).toBe(toDmgValue(alomomola.getMaxHp() / 2) + 1);
expect(alomomola.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
});
it("should function independently of Future Sight", async () => {
await game.classicMode.startBattle([SpeciesId.ALOMOMOLA, SpeciesId.BLISSEY]);
const [alomomola, blissey] = game.scene.getPlayerParty();
alomomola.hp = 1;
blissey.hp = 1;
game.move.use(MoveId.WISH);
await game.move.forceEnemyMove(MoveId.FUTURE_SIGHT);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.toNextTurn();
expectWishActive(1);
});
it("should work in double battles and trigger in order of creation", async () => {
game.override.battleStyle("double");
await game.classicMode.startBattle([SpeciesId.ALOMOMOLA, SpeciesId.BLISSEY]);
const [alomomola, blissey, karp1, karp2] = game.scene.getField();
alomomola.hp = 1;
blissey.hp = 1;
vi.spyOn(karp1, "getNameToRender").mockReturnValue("Karp 1");
vi.spyOn(karp2, "getNameToRender").mockReturnValue("Karp 2");
const oldOrder = game.field.getSpeedOrder();
game.move.use(MoveId.WISH, BattlerIndex.PLAYER);
game.move.use(MoveId.WISH, BattlerIndex.PLAYER_2);
await game.move.forceEnemyMove(MoveId.WISH);
await game.move.forceEnemyMove(MoveId.WISH);
// Ensure that the wishes are used deterministically in speed order (for speed ties)
await game.setTurnOrder(oldOrder.map(p => p.getBattlerIndex()));
await game.toNextTurn();
expectWishActive(4);
// Lower speed to change turn order
alomomola.setStatStage(Stat.SPD, 6);
blissey.setStatStage(Stat.SPD, -6);
const newOrder = game.field.getSpeedOrder();
expect(newOrder).not.toEqual(oldOrder);
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER);
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER_2);
await game.phaseInterceptor.to("PositionalTagPhase");
// all wishes have activated and added healing phases
expectWishActive(0);
const healPhases = game.scene.phaseManager.phaseQueue.filter(p => p.is("PokemonHealPhase"));
expect(healPhases).toHaveLength(4);
expect.soft(healPhases.map(php => php.getPokemon())).toEqual(oldOrder);
await game.toEndOfTurn();
expect(alomomola.hp).toBe(toDmgValue(alomomola.getMaxHp() / 2) + 1);
expect(blissey.hp).toBe(toDmgValue(blissey.getMaxHp() / 2) + 1);
});
it("should vanish and not play message if slot is empty", async () => {
game.override.battleStyle("double");
await game.classicMode.startBattle([SpeciesId.ALOMOMOLA, SpeciesId.BLISSEY]);
const [alomomola, blissey] = game.scene.getPlayerParty();
alomomola.hp = 1;
blissey.hp = 1;
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER);
game.move.use(MoveId.WISH, BattlerIndex.PLAYER_2);
await game.toNextTurn();
expectWishActive();
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER);
game.move.use(MoveId.MEMENTO, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2);
await game.toEndOfTurn();
// Wish went away without doing anything
expectWishActive(0);
expect(game.textInterceptor.logs).not.toContain(
i18next.t("arenaTag:wishTagOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(blissey),
}),
);
expect(alomomola.hp).toBe(1);
});
});