diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index dec65e2c945..01994263688 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -39,8 +39,6 @@ export class EvolutionPhase extends Phase { this.pokemon = pokemon; this.evolution = evolution; this.lastLevel = lastLevel; - this.evolutionBgm = this.scene.playSoundWithoutBgm("evolution"); - this.preEvolvedPokemonName = getPokemonNameWithAffix(this.pokemon); } validate(): boolean { @@ -62,9 +60,9 @@ export class EvolutionPhase extends Phase { this.scene.fadeOutBgm(undefined, false); - const evolutionHandler = this.scene.ui.getHandler() as EvolutionSceneHandler; + this.evolutionHandler = this.scene.ui.getHandler() as EvolutionSceneHandler; - this.evolutionContainer = evolutionHandler.evolutionContainer; + this.evolutionContainer = this.evolutionHandler.evolutionContainer; this.evolutionBaseBg = this.scene.add.image(0, 0, "default_bg"); this.evolutionBaseBg.setOrigin(0, 0); @@ -117,14 +115,12 @@ export class EvolutionPhase extends Phase { sprite.pipelineData[k] = this.pokemon.getSprite().pipelineData[k]; }); }); - + this.preEvolvedPokemonName = getPokemonNameWithAffix(this.pokemon); this.doEvolution(); }); } doEvolution(): void { - this.evolutionHandler = this.scene.ui.getHandler() as EvolutionSceneHandler; - this.scene.ui.showText(i18next.t("menu:evolving", { pokemonName: this.preEvolvedPokemonName }), null, () => { this.pokemon.cry(); @@ -145,6 +141,7 @@ export class EvolutionPhase extends Phase { }); this.scene.time.delayedCall(1000, () => { + this.evolutionBgm = this.scene.playSoundWithoutBgm("evolution"); this.scene.tweens.add({ targets: this.evolutionBgOverlay, alpha: 1, diff --git a/src/test/phases/form-change-phase.test.ts b/src/test/phases/form-change-phase.test.ts new file mode 100644 index 00000000000..70ca429672f --- /dev/null +++ b/src/test/phases/form-change-phase.test.ts @@ -0,0 +1,60 @@ +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { Type } from "#app/data/type"; +import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { modifierTypes } from "#app/modifier/modifier-type"; + +describe("Form Change Phase", () => { + 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 + .moveset([ Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("Zacian should successfully change into Crowned form", async () => { + await game.classicMode.startBattle([ Species.ZACIAN ]); + + // Before the form change: Should be Hero form + const zacian = game.scene.getPlayerParty()[0]; + expect(zacian.getFormKey()).toBe("hero-of-many-battles"); + expect(zacian.getTypes()).toStrictEqual([ Type.FAIRY ]); + expect(zacian.calculateBaseStats()).toStrictEqual([ 92, 120, 115, 80, 115, 138 ]); + + // Give Zacian a Rusted Sword + const rustedSwordType = generateModifierType(game.scene, modifierTypes.RARE_FORM_CHANGE_ITEM)!; + const rustedSword = rustedSwordType.newModifier(zacian); + await game.scene.addModifier(rustedSword); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + // After the form change: Should be Crowned form + expect(game.phaseInterceptor.log.includes("FormChangePhase")).toBe(true); + expect(zacian.getFormKey()).toBe("crowned"); + expect(zacian.getTypes()).toStrictEqual([ Type.FAIRY, Type.STEEL ]); + expect(zacian.calculateBaseStats()).toStrictEqual([ 92, 150, 115, 80, 115, 148 ]); + }); +}); diff --git a/src/test/utils/helpers/classicModeHelper.ts b/src/test/utils/helpers/classicModeHelper.ts index 80d0b86de7b..41a21a52b72 100644 --- a/src/test/utils/helpers/classicModeHelper.ts +++ b/src/test/utils/helpers/classicModeHelper.ts @@ -35,7 +35,7 @@ export class ClassicModeHelper extends GameManagerHelper { selectStarterPhase.initBattle(starters); }); - await this.game.phaseInterceptor.run(EncounterPhase); + await this.game.phaseInterceptor.to(EncounterPhase); if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) { this.game.removeEnemyHeldItems(); } diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts index ec9309e2405..17b6c7a6c81 100644 --- a/src/test/utils/phaseInterceptor.ts +++ b/src/test/utils/phaseInterceptor.ts @@ -12,6 +12,7 @@ import { EndEvolutionPhase } from "#app/phases/end-evolution-phase"; import { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; import { EvolutionPhase } from "#app/phases/evolution-phase"; import { FaintPhase } from "#app/phases/faint-phase"; +import { FormChangePhase } from "#app/phases/form-change-phase"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { LevelCapPhase } from "#app/phases/level-cap-phase"; import { LoginPhase } from "#app/phases/login-phase"; @@ -67,7 +68,6 @@ type PhaseClass = | typeof LoginPhase | typeof TitlePhase | typeof SelectGenderPhase - | typeof EncounterPhase | typeof NewBiomeEncounterPhase | typeof SelectStarterPhase | typeof PostSummonPhase @@ -102,6 +102,7 @@ type PhaseClass = | typeof SwitchPhase | typeof SwitchSummonPhase | typeof PartyHealPhase + | typeof FormChangePhase | typeof EvolutionPhase | typeof EndEvolutionPhase | typeof LevelCapPhase @@ -114,13 +115,13 @@ type PhaseClass = | typeof PostMysteryEncounterPhase | typeof ModifierRewardPhase | typeof PartyExpPhase - | typeof ExpPhase; + | typeof ExpPhase + | typeof EncounterPhase; type PhaseString = | "LoginPhase" | "TitlePhase" | "SelectGenderPhase" - | "EncounterPhase" | "NewBiomeEncounterPhase" | "SelectStarterPhase" | "PostSummonPhase" @@ -155,6 +156,7 @@ type PhaseString = | "SwitchPhase" | "SwitchSummonPhase" | "PartyHealPhase" + | "FormChangePhase" | "EvolutionPhase" | "EndEvolutionPhase" | "LevelCapPhase" @@ -167,7 +169,8 @@ type PhaseString = | "PostMysteryEncounterPhase" | "ModifierRewardPhase" | "PartyExpPhase" - | "ExpPhase"; + | "ExpPhase" + | "EncounterPhase"; type PhaseInterceptorPhase = PhaseClass | PhaseString; @@ -187,12 +190,16 @@ export default class PhaseInterceptor { /** * List of phases with their corresponding start methods. + * + * CAUTION: If a phase and its subclasses (if any) both appear in this list, + * make sure that this list contains said phase AFTER all of its subclasses. + * This way, the phase's `prototype.start` is properly preserved during + * `initPhases()` so that its subclasses can use `super.start()` properly. */ private PHASES = [ [ LoginPhase, this.startPhase ], [ TitlePhase, this.startPhase ], [ SelectGenderPhase, this.startPhase ], - [ EncounterPhase, this.startPhase ], [ NewBiomeEncounterPhase, this.startPhase ], [ SelectStarterPhase, this.startPhase ], [ PostSummonPhase, this.startPhase ], @@ -227,6 +234,7 @@ export default class PhaseInterceptor { [ SwitchPhase, this.startPhase ], [ SwitchSummonPhase, this.startPhase ], [ PartyHealPhase, this.startPhase ], + [ FormChangePhase, this.startPhase ], [ EvolutionPhase, this.startPhase ], [ EndEvolutionPhase, this.startPhase ], [ LevelCapPhase, this.startPhase ], @@ -240,6 +248,7 @@ export default class PhaseInterceptor { [ ModifierRewardPhase, this.startPhase ], [ PartyExpPhase, this.startPhase ], [ ExpPhase, this.startPhase ], + [ EncounterPhase, this.startPhase ], ]; private endBySetMode = [