diff --git a/test/abilities/good_as_gold.test.ts b/test/abilities/good_as_gold.test.ts index 89e354b1f34..2490cd43fea 100644 --- a/test/abilities/good_as_gold.test.ts +++ b/test/abilities/good_as_gold.test.ts @@ -45,7 +45,7 @@ describe("Abilities - Good As Gold", () => { const player = game.scene.getPlayerPokemon()!; - game.move.select(MoveId.SPLASH, 0); + game.move.select(MoveId.SPLASH); await game.phaseInterceptor.to("BerryPhase"); @@ -54,12 +54,13 @@ describe("Abilities - Good As Gold", () => { }); it("should block memento and prevent the user from fainting", async () => { - game.override.enemyMoveset([MoveId.MEMENTO]); + game.override.enemyAbility(AbilityId.GOOD_AS_GOLD); await game.classicMode.startBattle([SpeciesId.MAGIKARP]); - game.move.select(MoveId.MEMENTO); + + game.move.use(MoveId.MEMENTO); await game.phaseInterceptor.to("BerryPhase"); - expect(game.scene.getPlayerPokemon()!.isFainted()).toBe(false); - expect(game.scene.getEnemyPokemon()?.getStatStage(Stat.ATK)).toBe(0); + expect(game.field.getPlayerPokemon().isFainted()).toBe(false); + expect(game.field.getEnemyPokemon().getStatStage(Stat.ATK)).toBe(0); }); it("should not block any status moves that target the field, one side, or all pokemon", async () => { diff --git a/test/abilities/ice_face.test.ts b/test/abilities/ice_face.test.ts index c42713d7e6c..6e261eb00e2 100644 --- a/test/abilities/ice_face.test.ts +++ b/test/abilities/ice_face.test.ts @@ -259,7 +259,7 @@ describe("Abilities - Ice Face", () => { const eiscue = game.scene.getEnemyPokemon()!; - expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined); + expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBeDefined(); expect(eiscue.formIndex).toBe(icefaceForm); expect(eiscue.hasAbility(AbilityId.ICE_FACE)).toBe(true); }); @@ -269,13 +269,9 @@ describe("Abilities - Ice Face", () => { await game.classicMode.startBattle([SpeciesId.MAGIKARP]); - game.move.select(MoveId.SIMPLE_BEAM); - - await game.phaseInterceptor.to(TurnInitPhase); - const eiscue = game.scene.getEnemyPokemon()!; - expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined); + expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBeDefined(); expect(eiscue.formIndex).toBe(icefaceForm); expect(game.scene.getPlayerPokemon()!.hasAbility(AbilityId.TRACE)).toBe(true); }); diff --git a/test/abilities/imposter.test.ts b/test/abilities/imposter.test.ts index 30491139877..976bc339dde 100644 --- a/test/abilities/imposter.test.ts +++ b/test/abilities/imposter.test.ts @@ -75,24 +75,31 @@ describe("Abilities - Imposter", () => { }); it("should copy in-battle overridden stats", async () => { - game.override.enemyMoveset([MoveId.POWER_SPLIT]); + game.override.ability(AbilityId.BALL_FETCH); + await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.DITTO]); - await game.classicMode.startBattle([SpeciesId.DITTO]); + const [karp, ditto] = game.scene.getPlayerField(); + const enemy = game.field.getEnemyPokemon(); + game.field.mockAbility(ditto, AbilityId.IMPOSTER); - const player = game.scene.getPlayerPokemon()!; - const enemy = game.scene.getEnemyPokemon()!; + // Turn 1: Use power split + const avgAtk = Math.floor((karp.getStat(Stat.ATK, false) + enemy.getStat(Stat.ATK, false)) / 2); + const avgSpAtk = Math.floor((karp.getStat(Stat.SPATK, false) + enemy.getStat(Stat.SPATK, false)) / 2); - const avgAtk = Math.floor((player.getStat(Stat.ATK, false) + enemy.getStat(Stat.ATK, false)) / 2); - const avgSpAtk = Math.floor((player.getStat(Stat.SPATK, false) + enemy.getStat(Stat.SPATK, false)) / 2); + game.move.use(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.POWER_SPLIT); + await game.toNextTurn(); - game.move.select(MoveId.TACKLE); - await game.phaseInterceptor.to(TurnEndPhase); - - expect(player.getStat(Stat.ATK, false)).toBe(avgAtk); expect(enemy.getStat(Stat.ATK, false)).toBe(avgAtk); - - expect(player.getStat(Stat.SPATK, false)).toBe(avgSpAtk); expect(enemy.getStat(Stat.SPATK, false)).toBe(avgSpAtk); + + // Turn 2: Switch in ditto, should copy enemy ability + game.doSwitchPokemon(1); + await game.move.forceEnemyMove(MoveId.SPLASH); + await game.toNextTurn(); + + expect(ditto.getStat(Stat.ATK, false)).toBe(avgAtk); + expect(ditto.getStat(Stat.SPATK, false)).toBe(avgSpAtk); }); it("should set each move's pp to a maximum of 5", async () => { @@ -101,31 +108,22 @@ describe("Abilities - Imposter", () => { await game.classicMode.startBattle([SpeciesId.DITTO]); const player = game.scene.getPlayerPokemon()!; - game.move.select(MoveId.TACKLE); - await game.phaseInterceptor.to(TurnEndPhase); - player.getMoveset().forEach(move => { // Should set correct maximum PP without touching `ppUp` - if (move) { - if (move.moveId === MoveId.SKETCH) { - expect(move.getMovePp()).toBe(1); - } else { - expect(move.getMovePp()).toBe(5); - } - expect(move.ppUp).toBe(0); + if (move.moveId === MoveId.SKETCH) { + expect(move.getMovePp()).toBe(1); + } else { + expect(move.getMovePp()).toBe(5); } + expect(move.ppUp).toBe(0); }); }); it("should activate its ability if it copies one that activates on summon", async () => { game.override.enemyAbility(AbilityId.INTIMIDATE); - await game.classicMode.startBattle([SpeciesId.DITTO]); - game.move.select(MoveId.TACKLE); - await game.phaseInterceptor.to("MoveEndPhase"); - - expect(game.scene.getEnemyPokemon()?.getStatStage(Stat.ATK)).toBe(-1); + expect(game.field.getEnemyPokemon().getStatStage(Stat.ATK)).toBe(-1); }); it("should persist transformed attributes across reloads", async () => { diff --git a/test/abilities/intimidate.test.ts b/test/abilities/intimidate.test.ts index 3dcd9bcd129..6790e2b98d3 100644 --- a/test/abilities/intimidate.test.ts +++ b/test/abilities/intimidate.test.ts @@ -3,7 +3,6 @@ import Phaser from "phaser"; import GameManager from "#test/testUtils/gameManager"; import { UiMode } from "#enums/ui-mode"; import { Stat } from "#enums/stat"; -import { getMovePosition } from "#test/testUtils/gameManagerUtils"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; @@ -114,7 +113,7 @@ describe("Abilities - Intimidate", () => { expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1); - game.move.select(getMovePosition(game.scene, 0, MoveId.SPLASH)); + game.move.select(MoveId.SPLASH); await game.toNextTurn(); enemyPokemon = game.scene.getEnemyPokemon()!; diff --git a/test/abilities/lightningrod.test.ts b/test/abilities/lightningrod.test.ts index 2dc29500454..028258a9d56 100644 --- a/test/abilities/lightningrod.test.ts +++ b/test/abilities/lightningrod.test.ts @@ -36,10 +36,8 @@ describe("Abilities - Lightningrod", () => { it("should redirect electric type moves", async () => { await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]); - const enemy1 = game.scene.getEnemyField()[0]; - const enemy2 = game.scene.getEnemyField()[1]; - - enemy2.summonData.ability = AbilityId.LIGHTNING_ROD; + const [enemy1, enemy2] = game.scene.getEnemyField(); + game.field.mockAbility(enemy2, AbilityId.LIGHTNING_ROD); game.move.select(MoveId.SHOCK_WAVE, BattlerIndex.PLAYER, BattlerIndex.ENEMY); game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2); @@ -52,10 +50,8 @@ describe("Abilities - Lightningrod", () => { game.override.moveset([MoveId.SPLASH, MoveId.AERIAL_ACE]); await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]); - const enemy1 = game.scene.getEnemyField()[0]; - const enemy2 = game.scene.getEnemyField()[1]; - - enemy2.summonData.ability = AbilityId.LIGHTNING_ROD; + const [enemy1, enemy2] = game.scene.getEnemyField(); + game.field.mockAbility(enemy2, AbilityId.LIGHTNING_ROD); game.move.select(MoveId.AERIAL_ACE, BattlerIndex.PLAYER, BattlerIndex.ENEMY); game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2); @@ -68,8 +64,7 @@ describe("Abilities - Lightningrod", () => { await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]); const enemy2 = game.scene.getEnemyField()[1]; - - enemy2.summonData.ability = AbilityId.LIGHTNING_ROD; + game.field.mockAbility(enemy2, AbilityId.LIGHTNING_ROD); game.move.select(MoveId.SHOCK_WAVE, BattlerIndex.PLAYER, BattlerIndex.ENEMY); game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2); @@ -81,31 +76,25 @@ describe("Abilities - Lightningrod", () => { it("should not redirect moves changed from electric type via ability", async () => { game.override.ability(AbilityId.NORMALIZE); - await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); - const enemy1 = game.scene.getEnemyField()[0]; - const enemy2 = game.scene.getEnemyField()[1]; - - enemy2.summonData.ability = AbilityId.LIGHTNING_ROD; + const [enemy1, enemy2] = game.scene.getEnemyField(); + game.field.mockAbility(enemy2, AbilityId.LIGHTNING_ROD); game.move.select(MoveId.SHOCK_WAVE, BattlerIndex.PLAYER, BattlerIndex.ENEMY); - game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2); await game.phaseInterceptor.to("BerryPhase"); expect(enemy1.isFullHp()).toBe(false); }); it("should redirect moves changed to electric type via ability", async () => { - game.override.ability(AbilityId.GALVANIZE).moveset(MoveId.TACKLE); + game.override.ability(AbilityId.GALVANIZE); await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]); - const enemy1 = game.scene.getEnemyField()[0]; - const enemy2 = game.scene.getEnemyField()[1]; + const [enemy1, enemy2] = game.scene.getEnemyField(); + game.field.mockAbility(enemy2, AbilityId.LIGHTNING_ROD); - enemy2.summonData.ability = AbilityId.LIGHTNING_ROD; - - game.move.select(MoveId.TACKLE, BattlerIndex.PLAYER, BattlerIndex.ENEMY); - game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2); + game.move.use(MoveId.TACKLE, BattlerIndex.PLAYER, BattlerIndex.ENEMY); await game.phaseInterceptor.to("BerryPhase"); expect(enemy1.isFullHp()).toBe(true); diff --git a/test/abilities/mold_breaker.test.ts b/test/abilities/mold_breaker.test.ts index 28a077e8908..3fac3448f4d 100644 --- a/test/abilities/mold_breaker.test.ts +++ b/test/abilities/mold_breaker.test.ts @@ -1,8 +1,6 @@ -import { BattlerIndex } from "#enums/battler-index"; -import { globalScene } from "#app/global-scene"; -import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; +import { AbilityId } from "#enums/ability-id"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -24,29 +22,28 @@ describe("Abilities - Mold Breaker", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .moveset([MoveId.SPLASH]) .ability(AbilityId.MOLD_BREAKER) .battleStyle("single") .criticalHits(false) .enemySpecies(SpeciesId.MAGIKARP) - .enemyAbility(AbilityId.BALL_FETCH) + .enemyAbility(AbilityId.STURDY) .enemyMoveset(MoveId.SPLASH); }); it("should turn off the ignore abilities arena variable after the user's move", async () => { - game.override - .enemyMoveset(MoveId.SPLASH) - .ability(AbilityId.MOLD_BREAKER) - .moveset([MoveId.ERUPTION]) - .startingLevel(100) - .enemyLevel(2); - await game.classicMode.startBattle([SpeciesId.MAGIKARP]); - const enemy = game.scene.getEnemyPokemon()!; + await game.classicMode.startBattle([SpeciesId.PINSIR]); - expect(enemy.isFainted()).toBe(false); - game.move.select(MoveId.SPLASH); - await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]); - await game.phaseInterceptor.to("MoveEndPhase", true); - expect(globalScene.arena.ignoreAbilities).toBe(false); + const player = game.field.getPlayerPokemon(); + const enemy = game.field.getEnemyPokemon(); + + game.move.use(MoveId.X_SCISSOR); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(game.scene.arena.ignoreAbilities).toBe(true); + expect(game.scene.arena.ignoringEffectSource).toBe(player.getBattlerIndex()); + + await game.toEndOfTurn(); + expect(game.scene.arena.ignoreAbilities).toBe(false); + expect(enemy.isFainted()).toBe(true); }); }); diff --git a/test/abilities/moxie.test.ts b/test/abilities/moxie.test.ts index a85ed081448..1c4acf139e4 100644 --- a/test/abilities/moxie.test.ts +++ b/test/abilities/moxie.test.ts @@ -65,8 +65,7 @@ describe("Abilities - Moxie", () => { secondPokemon.hp = 1; - game.move.select(moveToUse); - game.selectTarget(BattlerIndex.PLAYER_2); + game.move.select(moveToUse, BattlerIndex.PLAYER_2); await game.phaseInterceptor.to(TurnEndPhase); diff --git a/test/abilities/storm_drain.test.ts b/test/abilities/storm_drain.test.ts index 8eedf0c7ce8..6e56bf44fa7 100644 --- a/test/abilities/storm_drain.test.ts +++ b/test/abilities/storm_drain.test.ts @@ -96,16 +96,15 @@ describe("Abilities - Storm Drain", () => { }); it("should redirect moves changed to water type via ability", async () => { - game.override.ability(AbilityId.LIQUID_VOICE).moveset(MoveId.PSYCHIC_NOISE); - await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]); + game.override.ability(AbilityId.LIQUID_VOICE); + await game.classicMode.startBattle([SpeciesId.FEEBAS]); const enemy1 = game.scene.getEnemyField()[0]; const enemy2 = game.scene.getEnemyField()[1]; enemy2.summonData.ability = AbilityId.STORM_DRAIN; - game.move.select(MoveId.PSYCHIC_NOISE, BattlerIndex.PLAYER, BattlerIndex.ENEMY); - game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2); + game.move.use(MoveId.HYPER_VOICE, BattlerIndex.PLAYER); await game.phaseInterceptor.to("BerryPhase"); expect(enemy1.isFullHp()).toBe(true); diff --git a/test/arena/weather_strong_winds.test.ts b/test/arena/weather_strong_winds.test.ts index d0d256816eb..d98ba96fd85 100644 --- a/test/arena/weather_strong_winds.test.ts +++ b/test/arena/weather_strong_winds.test.ts @@ -86,7 +86,7 @@ describe("Weather - Strong Winds", () => { const enemy = game.scene.getEnemyPokemon()!; enemy.hp = 1; - game.move.select(MoveId.SPLASH); + game.move.use(MoveId.SPLASH); await game.phaseInterceptor.to("TurnEndPhase"); expect(game.scene.arena.weather?.weatherType).toBeUndefined(); diff --git a/test/items/dire_hit.test.ts b/test/items/dire_hit.test.ts index 25fe9c8b876..a484a0ad302 100644 --- a/test/items/dire_hit.test.ts +++ b/test/items/dire_hit.test.ts @@ -58,8 +58,7 @@ describe("Items - Dire Hit", () => { await game.classicMode.startBattle([SpeciesId.PIKACHU]); - game.move.select(MoveId.SPLASH); - + game.move.use(MoveId.SPLASH); await game.doKillOpponents(); await game.phaseInterceptor.to(BattleEndPhase); diff --git a/test/moves/aurora_veil.test.ts b/test/moves/aurora_veil.test.ts index b9ae79e4155..8faf3654401 100644 --- a/test/moves/aurora_veil.test.ts +++ b/test/moves/aurora_veil.test.ts @@ -4,7 +4,6 @@ import type Move from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import type Pokemon from "#app/field/pokemon"; -import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { NumberHolder } from "#app/utils/common"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; @@ -12,7 +11,7 @@ import { SpeciesId } from "#enums/species-id"; import { WeatherType } from "#enums/weather-type"; import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; let globalScene: BattleScene; @@ -52,10 +51,10 @@ describe("Moves - Aurora Veil", () => { game.move.select(moveToUse); - await game.phaseInterceptor.to(TurnEndPhase); + await game.toEndOfTurn(); const mockedDmg = getMockedMoveDamage( - game.scene.getEnemyPokemon()!, - game.scene.getPlayerPokemon()!, + game.field.getEnemyPokemon(), + game.field.getPlayerPokemon(), allMoves[moveToUse], ); @@ -71,10 +70,10 @@ describe("Moves - Aurora Veil", () => { game.move.select(moveToUse); game.move.select(moveToUse, 1); - await game.phaseInterceptor.to(TurnEndPhase); + await game.toEndOfTurn(); const mockedDmg = getMockedMoveDamage( - game.scene.getEnemyPokemon()!, - game.scene.getPlayerPokemon()!, + game.field.getEnemyPokemon(), + game.field.getPlayerPokemon(), allMoves[moveToUse], ); @@ -82,72 +81,48 @@ describe("Moves - Aurora Veil", () => { }); it("reduces damage of special attacks by half in a single battle", async () => { - const moveToUse = MoveId.ABSORB; await game.classicMode.startBattle([SpeciesId.SHUCKLE]); - game.move.select(moveToUse); + game.move.use(MoveId.ABSORB); - await game.phaseInterceptor.to(TurnEndPhase); + await game.toEndOfTurn(); const mockedDmg = getMockedMoveDamage( - game.scene.getEnemyPokemon()!, - game.scene.getPlayerPokemon()!, - allMoves[moveToUse], + game.field.getEnemyPokemon(), + game.field.getPlayerPokemon(), + allMoves[MoveId.ABSORB], ); - expect(mockedDmg).toBe(allMoves[moveToUse].power * singleBattleMultiplier); + expect(mockedDmg).toBe(allMoves[MoveId.ABSORB].power * singleBattleMultiplier); }); it("reduces damage of special attacks by a third in a double battle", async () => { game.override.battleStyle("double"); - - const moveToUse = MoveId.DAZZLING_GLEAM; - await game.classicMode.startBattle([SpeciesId.SHUCKLE, SpeciesId.SHUCKLE]); - - game.move.select(moveToUse); - game.move.select(moveToUse, 1); - - await game.phaseInterceptor.to(TurnEndPhase); - const mockedDmg = getMockedMoveDamage( - game.scene.getEnemyPokemon()!, - game.scene.getPlayerPokemon()!, - allMoves[moveToUse], - ); - - expect(mockedDmg).toBe(allMoves[moveToUse].power * doubleBattleMultiplier); - }); - - it("does not affect physical critical hits", async () => { - game.override.moveset([MoveId.WICKED_BLOW]); - const moveToUse = MoveId.WICKED_BLOW; await game.classicMode.startBattle([SpeciesId.SHUCKLE]); - game.move.select(moveToUse); - await game.phaseInterceptor.to(TurnEndPhase); - + game.move.use(MoveId.ABSORB); + await game.toEndOfTurn(); const mockedDmg = getMockedMoveDamage( - game.scene.getEnemyPokemon()!, - game.scene.getPlayerPokemon()!, - allMoves[moveToUse], + game.field.getEnemyPokemon(), + game.field.getPlayerPokemon(), + allMoves[MoveId.ABSORB], ); - expect(mockedDmg).toBe(allMoves[moveToUse].power); + + expect(mockedDmg).toBe(allMoves[MoveId.ABSORB].power * doubleBattleMultiplier); }); it("does not affect critical hits", async () => { - game.override.moveset([MoveId.FROST_BREATH]); - const moveToUse = MoveId.FROST_BREATH; - vi.spyOn(allMoves[MoveId.FROST_BREATH], "accuracy", "get").mockReturnValue(100); await game.classicMode.startBattle([SpeciesId.SHUCKLE]); - game.move.select(moveToUse); - await game.phaseInterceptor.to(TurnEndPhase); + game.move.use(MoveId.WICKED_BLOW); + await game.toEndOfTurn(); const mockedDmg = getMockedMoveDamage( - game.scene.getEnemyPokemon()!, - game.scene.getPlayerPokemon()!, - allMoves[moveToUse], + game.field.getEnemyPokemon(), + game.field.getPlayerPokemon(), + allMoves[MoveId.WICKED_BLOW], ); - expect(mockedDmg).toBe(allMoves[moveToUse].power); + expect(mockedDmg).toBe(allMoves[MoveId.WICKED_BLOW].power); }); }); diff --git a/test/moves/baddy_bad.test.ts b/test/moves/baddy_bad.test.ts index ffdf9f0309c..8709b6d3eac 100644 --- a/test/moves/baddy_bad.test.ts +++ b/test/moves/baddy_bad.test.ts @@ -33,7 +33,7 @@ describe("Moves - Baddy Bad", () => { game.override.enemyMoveset(MoveId.PROTECT); await game.classicMode.startBattle([SpeciesId.FEEBAS]); - game.move.select(MoveId.BADDY_BAD); + game.move.use(MoveId.BADDY_BAD); await game.phaseInterceptor.to("BerryPhase"); expect(game.scene.arena.tags.length).toBe(0); diff --git a/test/moves/gastro_acid.test.ts b/test/moves/gastro_acid.test.ts index 39167987809..baa4a4b863a 100644 --- a/test/moves/gastro_acid.test.ts +++ b/test/moves/gastro_acid.test.ts @@ -100,12 +100,12 @@ describe("Moves - Gastro Acid", () => { await game.toNextTurn(); expect(enemyPokemon.summonData.abilitySuppressed).toBe(true); - game.move.select(MoveId.WATER_GUN); + game.move.use(MoveId.WATER_GUN); await game.toNextTurn(); // water gun should've dealt damage due to suppressed Water Absorb expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); - game.move.select(MoveId.SPORE); + game.move.use(MoveId.SPORE); await game.toEndOfTurn(); // Comatose should block stauts effect diff --git a/test/moves/instruct.test.ts b/test/moves/instruct.test.ts index d12859301b6..19b7a941de7 100644 --- a/test/moves/instruct.test.ts +++ b/test/moves/instruct.test.ts @@ -349,7 +349,7 @@ describe("Moves - Instruct", () => { useMode: MoveUseMode.NORMAL, }); - game.move.select(MoveId.SPLASH); + game.move.use(MoveId.SPLASH); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.toEndOfTurn(); expect(game.field.getEnemyPokemon().getLastXMoves()[0].result).toBe(MoveResult.FAIL); diff --git a/test/moves/reflect_type.test.ts b/test/moves/reflect_type.test.ts index 0915069764c..e07c7b43ca4 100644 --- a/test/moves/reflect_type.test.ts +++ b/test/moves/reflect_type.test.ts @@ -30,30 +30,26 @@ describe("Moves - Reflect Type", () => { }); it("will make the user Normal/Grass if targetting a typeless Pokemon affected by Forest's Curse", async () => { - game.override - .moveset([MoveId.FORESTS_CURSE, MoveId.REFLECT_TYPE]) - .startingLevel(60) - .enemySpecies(SpeciesId.CHARMANDER) - .enemyMoveset([MoveId.BURN_UP, MoveId.SPLASH]); + game.override.startingLevel(60).enemySpecies(SpeciesId.CHARMANDER); await game.classicMode.startBattle([SpeciesId.FEEBAS]); - const playerPokemon = game.scene.getPlayerPokemon(); - const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.field.getPlayerPokemon(); + const enemyPokemon = game.field.getEnemyPokemon(); - game.move.select(MoveId.SPLASH); - await game.move.selectEnemyMove(MoveId.BURN_UP); + game.move.use(MoveId.SPLASH); + await game.move.forceEnemyMove(MoveId.BURN_UP); await game.toNextTurn(); - game.move.select(MoveId.FORESTS_CURSE); - await game.move.selectEnemyMove(MoveId.SPLASH); + game.move.use(MoveId.FORESTS_CURSE); + await game.move.forceEnemyMove(MoveId.SPLASH); await game.toNextTurn(); - expect(enemyPokemon?.getTypes().includes(PokemonType.UNKNOWN)).toBe(true); - expect(enemyPokemon?.getTypes().includes(PokemonType.GRASS)).toBe(true); + expect(enemyPokemon.getTypes().includes(PokemonType.UNKNOWN)).toBe(true); + expect(enemyPokemon.getTypes().includes(PokemonType.GRASS)).toBe(true); - game.move.select(MoveId.REFLECT_TYPE); - await game.move.selectEnemyMove(MoveId.SPLASH); + game.move.use(MoveId.REFLECT_TYPE); + await game.move.forceEnemyMove(MoveId.SPLASH); await game.phaseInterceptor.to("TurnEndPhase"); - expect(playerPokemon?.getTypes()[0]).toBe(PokemonType.NORMAL); - expect(playerPokemon?.getTypes().includes(PokemonType.GRASS)).toBe(true); + expect(playerPokemon.getTypes()[0]).toBe(PokemonType.NORMAL); + expect(playerPokemon.getTypes().includes(PokemonType.GRASS)).toBe(true); }); }); diff --git a/test/moves/spikes.test.ts b/test/moves/spikes.test.ts index 49492701d18..f6f603ef2c4 100644 --- a/test/moves/spikes.test.ts +++ b/test/moves/spikes.test.ts @@ -82,10 +82,10 @@ describe("Moves - Spikes", () => { it("should work when all targets fainted", async () => { game.override.enemySpecies(SpeciesId.DIGLETT).battleStyle("double").startingLevel(50); - await game.classicMode.startBattle([SpeciesId.RAYQUAZA, SpeciesId.ROWLET]); + await game.classicMode.startBattle([SpeciesId.RAYQUAZA]); - game.move.select(MoveId.EARTHQUAKE); - game.move.select(MoveId.SPIKES, 1); + game.move.use(MoveId.SPIKES); + await game.doKillOpponents(); await game.phaseInterceptor.to("TurnEndPhase"); expect(game.scene.arena.getTagOnSide(ArenaTrapTag, ArenaTagSide.ENEMY)).toBeDefined(); diff --git a/test/testUtils/gameManager.ts b/test/testUtils/gameManager.ts index b9f499d4e0c..f4ba31499d4 100644 --- a/test/testUtils/gameManager.ts +++ b/test/testUtils/gameManager.ts @@ -201,9 +201,8 @@ export default class GameManager { /** * Helper function to run to the final boss encounter as it's a bit tricky due to extra dialogue * Also handles Major/Minor bosses from endless modes - * @param game - The game manager - * @param species - * @param mode + * @param species - Array of {@linkcode SpeciesId}s to start the final battle with. + * @param mode - The {@linkcode GameModes} to spawn the final boss encounter in. */ async runToFinalBossEncounter(species: SpeciesId[], mode: GameModes) { console.log("===to final boss encounter==="); @@ -230,9 +229,9 @@ export default class GameManager { /** * Runs the game to a mystery encounter phase. - * @param encounterType if specified, will expect encounter to have been spawned - * @param species Optional array of species for party. - * @returns A promise that resolves when the EncounterPhase ends. + * @param encounterType - If specified, will expect encounter to be the given type. + * @param species - Optional array of species for party to start with. + * @returns A Promise that resolves when the EncounterPhase ends. */ async runToMysteryEncounter(encounterType?: MysteryEncounterType, species?: SpeciesId[]) { if (!isNullOrUndefined(encounterType)) { @@ -277,6 +276,7 @@ export default class GameManager { * Will trigger during the next {@linkcode SelectTargetPhase} * @param targetIndex - The {@linkcode BattlerIndex} of the attack target, or `undefined` for multi-target attacks * @param movePosition - The 0-indexed position of the move in the pokemon's moveset array + * @throws Immediately fails tests */ selectTarget(movePosition: number, targetIndex?: BattlerIndex) { this.onNextPrompt( @@ -292,7 +292,7 @@ export default class GameManager { handler.setCursor(targetIndex !== undefined ? targetIndex : BattlerIndex.ENEMY); } if (move.isMultiTarget() && targetIndex !== undefined) { - throw new Error(`targetIndex was passed to selectMove() but move ("${move.name}") is not targetted`); + expect.fail(`targetIndex was passed to selectMove() but move ("${move.name}") is not targetted`); } handler.processInput(Button.ACTION); }, diff --git a/test/testUtils/gameManagerUtils.ts b/test/testUtils/gameManagerUtils.ts index 57fd9b91d26..2e1714c90ad 100644 --- a/test/testUtils/gameManagerUtils.ts +++ b/test/testUtils/gameManagerUtils.ts @@ -10,7 +10,7 @@ import { getGameMode } from "#app/game-mode"; import { GameModes } from "#enums/game-modes"; import type { StarterMoveset } from "#app/system/game-data"; import type { Starter } from "#app/ui/starter-select-ui-handler"; -import { MoveId } from "#enums/move-id"; +import type { MoveId } from "#enums/move-id"; import type { SpeciesId } from "#enums/species-id"; /** Function to convert Blob to string */ @@ -98,15 +98,6 @@ export function waitUntil(truth): Promise { }); } -/** Get the index of `move` from the moveset of the pokemon on the player's field at location `pokemonIndex`. */ -export function getMovePosition(scene: BattleScene, pokemonIndex: 0 | 1, move: MoveId): number { - const playerPokemon = scene.getPlayerField()[pokemonIndex]; - const moveSet = playerPokemon.getMoveset(); - const index = moveSet.findIndex(m => m.moveId === move && m.ppUsed < m.getMovePp()); - console.log(`Move position for ${MoveId[move]} (=${move}):`, index); - return index; -} - /** * Useful for populating party, wave index, etc. without having to spin up and run through an entire EncounterPhase */ diff --git a/test/testUtils/helpers/moveHelper.ts b/test/testUtils/helpers/moveHelper.ts index ed1441a6a2f..b7b0fefa4db 100644 --- a/test/testUtils/helpers/moveHelper.ts +++ b/test/testUtils/helpers/moveHelper.ts @@ -1,4 +1,4 @@ -import type { BattlerIndex } from "#enums/battler-index"; +import { BattlerIndex } from "#enums/battler-index"; import { getMoveTargets } from "#app/data/moves/move-utils"; import type Pokemon from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; @@ -9,14 +9,13 @@ import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Command } from "#enums/command"; import { MoveId } from "#enums/move-id"; import { UiMode } from "#enums/ui-mode"; -import { getMovePosition } from "#test/testUtils/gameManagerUtils"; import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper"; -import { vi } from "vitest"; -import { coerceArray } from "#app/utils/common"; +import { expect, vi } from "vitest"; +import { coerceArray, toReadableString } from "#app/utils/common"; import { MoveUseMode } from "#enums/move-use-mode"; /** - * Helper to handle a Pokemon's move + * Helper to handle using a Pokemon's moves. */ export class MoveHelper extends GameManagerHelper { /** @@ -49,13 +48,31 @@ export class MoveHelper extends GameManagerHelper { } /** - * Select the move to be used by the given Pokemon(-index). Triggers during the next {@linkcode CommandPhase} - * @param move - the move to use - * @param pkmIndex - the pokemon index. Relevant for double-battles only (defaults to 0) - * @param targetIndex - The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves, or `null` if a manual call to `selectTarget()` is required + * Select a move _already in the player's moveset_ to be used during the next {@linkcode CommandPhase}. + * @param move - The {@linkcode MoveId} to use. + * @param pkmIndex - The {@linkcode BattlerIndex} of the player Pokemon using the move. Relevant for double battles only and defaults to {@linkcode BattlerIndex.PLAYER} if not specified. + * @param targetIndex - The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves; should be omitted for multi-target moves. + * If set to `null`, will forgo normal target selection entirely (useful for UI tests). + * @remarks + * Will fail the current test if the move being selected is not in the user's moveset. */ - public select(move: MoveId, pkmIndex: 0 | 1 = 0, targetIndex?: BattlerIndex | null) { - const movePosition = getMovePosition(this.game.scene, pkmIndex, move); + public select( + move: MoveId, + pkmIndex: BattlerIndex.PLAYER | BattlerIndex.PLAYER_2 = BattlerIndex.PLAYER, + targetIndex?: BattlerIndex | null, + ) { + const movePosition = this.getMovePosition(pkmIndex, move); + if (movePosition === -1) { + expect.fail( + `MoveHelper.selectWithTera called with move '${toReadableString(MoveId[move])}' not in moveset! +Battler Index: ${toReadableString(BattlerIndex[pkmIndex])}; +Moveset: [${this.game.scene + .getPlayerParty() + [pkmIndex].getMoveset() + .map(pm => toReadableString(MoveId[pm.moveId])) + .join(", ")}]`, + ); + } this.game.onNextPrompt("CommandPhase", UiMode.COMMAND, () => { this.game.scene.ui.setMode( @@ -77,14 +94,30 @@ export class MoveHelper extends GameManagerHelper { } /** - * Select the move to be used by the given Pokemon(-index), **which will also terastallize on this turn**. - * Triggers during the next {@linkcode CommandPhase} - * @param move - the move to use - * @param pkmIndex - the pokemon index. Relevant for double-battles only (defaults to 0) - * @param targetIndex - The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves, or `null` if a manual call to `selectTarget()` is required + * Select a move _already in the player's moveset_ to be used during the next {@linkcode CommandPhase}, **which will also terastallize on this turn**. + * @param move - The {@linkcode MoveId} to use. + * @param pkmIndex - The {@linkcode BattlerIndex} of the player Pokemon using the move. Relevant for double battles only and defaults to {@linkcode BattlerIndex.PLAYER} if not specified. + * @param targetIndex - The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves; should be omitted for multi-target moves. + * If set to `null`, will forgo normal target selection entirely (useful for UI tests) */ - public selectWithTera(move: MoveId, pkmIndex: 0 | 1 = 0, targetIndex?: BattlerIndex | null) { - const movePosition = getMovePosition(this.game.scene, pkmIndex, move); + public selectWithTera( + move: MoveId, + pkmIndex: BattlerIndex.PLAYER | BattlerIndex.PLAYER_2 = BattlerIndex.PLAYER, + targetIndex?: BattlerIndex | null, + ) { + const movePosition = this.getMovePosition(pkmIndex, move); + if (movePosition === -1) { + expect.fail( + `MoveHelper.selectWithTera called with move '${toReadableString(MoveId[move])}' not in moveset! +Battler Index: ${toReadableString(BattlerIndex[pkmIndex])}; +Moveset: [${this.game.scene + .getPlayerParty() + [pkmIndex].getMoveset() + .map(pm => toReadableString(MoveId[pm.moveId])) + .join(", ")}]`, + ); + } + this.game.scene.getPlayerParty()[pkmIndex].isTerastallized = false; this.game.onNextPrompt("CommandPhase", UiMode.COMMAND, () => { @@ -107,6 +140,15 @@ export class MoveHelper extends GameManagerHelper { } } + /** Helper function to get the index of the selected move in the selected part member's moveset. */ + private getMovePosition(pokemonIndex: BattlerIndex.PLAYER | BattlerIndex.PLAYER_2, move: MoveId): number { + const playerPokemon = this.game.scene.getPlayerField()[pokemonIndex]; + const moveset = playerPokemon.getMoveset(); + const index = moveset.findIndex(m => m.moveId === move && m.ppUsed < m.getMovePp()); + console.log(`Move position for ${MoveId[move]} (=${move}):`, index); + return index; + } + /** * Modifies a player pokemon's moveset to contain only the selected move and then * selects it to be used during the next {@linkcode CommandPhase}. @@ -116,14 +158,19 @@ export class MoveHelper extends GameManagerHelper { * Note: If you need to check for changes in the player's moveset as part of the test, it may be * best to use {@linkcode changeMoveset} and {@linkcode select} instead. * @param moveId - the move to use - * @param pkmIndex - the pokemon index. Relevant for double-battles only (defaults to 0) - * @param targetIndex - (optional) The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves, or `null` if a manual call to `selectTarget()` is required - * @param useTera - If `true`, the Pokemon also chooses to Terastallize. This does not require a Tera Orb. Default: `false`. + * @param pkmIndex - The {@linkcode BattlerIndex} of the player Pokemon using the move. Relevant for double battles only and defaults to {@linkcode BattlerIndex.PLAYER} if not specified. + * @param targetIndex - The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves; should be omitted for multi-target moves. + * @param useTera - If `true`, the Pokemon will attempt to Terastallize even without a Tera Orb; default `false`. */ - public use(moveId: MoveId, pkmIndex: 0 | 1 = 0, targetIndex?: BattlerIndex | null, useTera = false): void { + public use( + moveId: MoveId, + pkmIndex: BattlerIndex.PLAYER | BattlerIndex.PLAYER_2 = BattlerIndex.PLAYER, + targetIndex?: BattlerIndex, + useTera = false, + ): void { if ([Overrides.MOVESET_OVERRIDE].flat().length > 0) { vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([]); - console.warn("Warning: `use` overwrites the Pokemon's moveset and disables the player moveset override!"); + console.warn("Warning: `MoveHelper.use` overwriting player pokemon moveset and disabling moveset override!"); } const pokemon = this.game.scene.getPlayerField()[pkmIndex];