From 642b18e747974509d040150a5a201eb42e63ecbe Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Fri, 30 Aug 2024 20:21:56 -0700 Subject: [PATCH] [Bug] Fix Pokemon not gaining HP when evolving (#3569) * Don't recalculate stats that already exist * add test to cover hp update after evo (#4) - add evolution phase to phase interceptor - add mock for video game object - add returning video mock on add.video() * add test to make sure pkm are not healed on evolve * Stop on `EndEvolutionPhase` to prevent game state leak in tests * Fix imports * Remove `.js` from import Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * Add docs to mock class --------- Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> --- src/field/pokemon.ts | 4 +- src/test/evolution.test.ts | 59 +++++++++++++++++++++ src/test/utils/mocks/mockTextureManager.ts | 3 ++ src/test/utils/mocks/mockVideoGameObject.ts | 13 +++++ src/test/utils/phaseInterceptor.ts | 4 ++ 5 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/test/utils/mocks/mockVideoGameObject.ts diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 6df486aab39..9fcbdd8dc6c 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -251,7 +251,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.shiny = false; } - this.calculateStats(); + if (!dataSource) { + this.calculateStats(); + } } diff --git a/src/test/evolution.test.ts b/src/test/evolution.test.ts index 41088c17bcb..f9123cf6d9a 100644 --- a/src/test/evolution.test.ts +++ b/src/test/evolution.test.ts @@ -1,9 +1,11 @@ import { pokemonEvolutions, SpeciesFormEvolution, SpeciesWildEvolutionDelay } from "#app/data/pokemon-evolutions"; import { Abilities } from "#app/enums/abilities"; +import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { SPLASH_ONLY } from "./utils/testUtils"; describe("Evolution", () => { let phaserGame: Phaser.Game; @@ -89,4 +91,61 @@ describe("Evolution", () => { expect(speciesFormEvo.wildDelay).toBe(SpeciesWildEvolutionDelay.NONE); }); + + it("should increase both HP and max HP when evolving", async () => { + game.override.moveset([Moves.SURF]) + .enemySpecies(Species.GOLEM) + .enemyMoveset(SPLASH_ONLY) + .startingWave(21) + .startingLevel(16) + .enemyLevel(50); + + await game.startBattle([Species.TOTODILE]); + + const totodile = game.scene.getPlayerPokemon()!; + const hpBefore = totodile.hp; + + expect(totodile.hp).toBe(totodile.getMaxHp()); + + const golem = game.scene.getEnemyPokemon()!; + golem.hp = 1; + + expect(golem.hp).toBe(1); + + game.move.select(Moves.SURF); + await game.phaseInterceptor.to("EndEvolutionPhase"); + + expect(totodile.hp).toBe(totodile.getMaxHp()); + expect(totodile.hp).toBeGreaterThan(hpBefore); + }, TIMEOUT); + + it("should not fully heal HP when evolving", async () => { + game.override.moveset([Moves.SURF]) + .enemySpecies(Species.GOLEM) + .enemyMoveset(SPLASH_ONLY) + .startingWave(21) + .startingLevel(13) + .enemyLevel(30); + + await game.startBattle([Species.CYNDAQUIL]); + + const cyndaquil = game.scene.getPlayerPokemon()!; + cyndaquil.hp = Math.floor(cyndaquil.getMaxHp() / 2); + const hpBefore = cyndaquil.hp; + const maxHpBefore = cyndaquil.getMaxHp(); + + expect(cyndaquil.hp).toBe(Math.floor(cyndaquil.getMaxHp() / 2)); + + const golem = game.scene.getEnemyPokemon()!; + golem.hp = 1; + + expect(golem.hp).toBe(1); + + game.move.select(Moves.SURF); + await game.phaseInterceptor.to("EndEvolutionPhase"); + + expect(cyndaquil.getMaxHp()).toBeGreaterThan(maxHpBefore); + expect(cyndaquil.hp).toBeGreaterThan(hpBefore); + expect(cyndaquil.hp).toBeLessThan(cyndaquil.getMaxHp()); + }, TIMEOUT); }); diff --git a/src/test/utils/mocks/mockTextureManager.ts b/src/test/utils/mocks/mockTextureManager.ts index b26d03441fe..ca8065bef97 100644 --- a/src/test/utils/mocks/mockTextureManager.ts +++ b/src/test/utils/mocks/mockTextureManager.ts @@ -7,6 +7,8 @@ import MockSprite from "#test/utils/mocks/mocksContainer/mockSprite"; import MockText from "#test/utils/mocks/mocksContainer/mockText"; import MockTexture from "#test/utils/mocks/mocksContainer/mockTexture"; import { MockGameObject } from "./mockGameObject"; +import { vi } from "vitest"; +import { MockVideoGameObject } from "./mockVideoGameObject"; /** * Stub class for Phaser.Textures.TextureManager @@ -34,6 +36,7 @@ export default class MockTextureManager { text: this.text.bind(this), bitmapText: this.text.bind(this), displayList: this.displayList, + video: vi.fn(() => new MockVideoGameObject()), }; } diff --git a/src/test/utils/mocks/mockVideoGameObject.ts b/src/test/utils/mocks/mockVideoGameObject.ts new file mode 100644 index 00000000000..96f03542bbc --- /dev/null +++ b/src/test/utils/mocks/mockVideoGameObject.ts @@ -0,0 +1,13 @@ +import { vi } from "vitest"; +import { MockGameObject } from "./mockGameObject"; + +/** Mocks video-related stuff */ +export class MockVideoGameObject implements MockGameObject { + constructor() {} + + public play = vi.fn(); + public stop = vi.fn(() => this); + public setOrigin = vi.fn(); + public setScale = vi.fn(); + public setVisible = vi.fn(); +} diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts index ca3d55137fa..de65405abff 100644 --- a/src/test/utils/phaseInterceptor.ts +++ b/src/test/utils/phaseInterceptor.ts @@ -6,7 +6,9 @@ import { CommandPhase } from "#app/phases/command-phase"; import { DamagePhase } from "#app/phases/damage-phase"; import { EggLapsePhase } from "#app/phases/egg-lapse-phase"; import { EncounterPhase } from "#app/phases/encounter-phase"; +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 { LoginPhase } from "#app/phases/login-phase"; import { MessagePhase } from "#app/phases/message-phase"; @@ -92,6 +94,8 @@ export default class PhaseInterceptor { [SwitchPhase, this.startPhase], [SwitchSummonPhase, this.startPhase], [PartyHealPhase, this.startPhase], + [EvolutionPhase, this.startPhase], + [EndEvolutionPhase, this.startPhase], ]; private endBySetMode = [