diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index df637aa4cfd..45987c928a1 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -145,7 +145,7 @@ export const FieryFalloutEncounter: IMysteryEncounter = .withOption( new MysteryEncounterOptionBuilder() .withOptionMode(EncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new TypeRequirement(Type.FIRE, 2)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically + .withPrimaryPokemonRequirement(new TypeRequirement(Type.FIRE, true,2)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically .withDialogue({ buttonLabel: `${namespace}_option_3_label`, buttonTooltip: `${namespace}_option_3_tooltip`, diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index 05cf76fae5f..42e4d17d518 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -57,7 +57,7 @@ export const SafariZoneEncounter: IMysteryEncounter = .withOptionPhase(async (scene: BattleScene) => { // Start safari encounter const encounter = scene.currentBattle.mysteryEncounter; - encounter.encounterVariant = MysteryEncounterVariant.SAFARI_BATTLE; + encounter.encounterVariant = MysteryEncounterVariant.REPEATED_ENCOUNTER; encounter.misc = { safariPokemonRemaining: 3 }; @@ -463,14 +463,15 @@ function tryChangeCatchStage(scene: BattleScene, change: number, chance?: number return true; } -async function doEndTurn(scene: BattleScene, cursorIndex: number, message?: string) { - const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon; - const isFlee = isPokemonFlee(pokemon, scene.currentBattle.mysteryEncounter.misc.fleeStage); +async function doEndTurn(scene: BattleScene, cursorIndex: number) { + const encounter = scene.currentBattle.mysteryEncounter; + const pokemon = encounter.misc.pokemon; + const isFlee = isPokemonFlee(pokemon, encounter.misc.fleeStage); if (isFlee) { // Pokemon flees! await doPokemonFlee(scene, pokemon); // Check how many safari pokemon left - if (scene.currentBattle.mysteryEncounter.misc.safariPokemonRemaining > 0) { + if (encounter.misc.safariPokemonRemaining > 0) { await summonSafariPokemon(scene); initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true }); } else { diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index 489bbd84d0a..1c6ad219799 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -27,7 +27,7 @@ export enum MysteryEncounterVariant { WILD_BATTLE, BOSS_BATTLE, NO_BATTLE, - SAFARI_BATTLE + REPEATED_ENCOUNTER } export enum MysteryEncounterTier { @@ -38,7 +38,7 @@ export enum MysteryEncounterTier { MASTER // Not currently used } -export class StartOfBattleEffect { +export interface StartOfBattleEffect { sourcePokemon?: Pokemon; sourceBattlerIndex?: BattlerIndex; targets: BattlerIndex[]; @@ -108,10 +108,6 @@ export default interface IMysteryEncounter { * You should never need to modify this */ seedOffset?: any; - /** - * Will be set by option select handlers automatically, and can be used to refer to which option was chosen by later phases - */ - startOfBattleEffectsComplete?: boolean; /** * Flags @@ -134,6 +130,10 @@ export default interface IMysteryEncounter { * Will be set to false after a shop is shown (so can't reroll same rarity items for free) */ lockEncounterRewardTiers?: boolean; + /** + * Will be set automatically, indicates special moves in startOfBattleEffects are complete (so will not repeat) + */ + startOfBattleEffectsComplete?: boolean; /** * Will be set by option select handlers automatically, and can be used to refer to which option was chosen by later phases */ @@ -473,6 +473,8 @@ export class MysteryEncounterBuilder implements Partial { /** * Defines any EncounterAnim animations that are intended to be used during the encounter + * EncounterAnims can be played at any point during an encounter or callback + * They just need to be specified here so that resources are loaded on encounter init * @param encounterAnimations * @returns */ diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 3c35263fae2..6e13bbb0874 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -555,8 +555,10 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase: return; } - if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.SAFARI_BATTLE) { - scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase)); + // If in repeated encounter variant, do nothing + // Variant must eventually be swapped in order to handle "true" end of the encounter + if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.REPEATED_ENCOUNTER) { + return; } else if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.NO_BATTLE) { scene.pushPhase(new EggLapsePhase(scene)); scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase)); diff --git a/src/overrides.ts b/src/overrides.ts index f7fa3c5fc86..e19a5bf20dd 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -117,9 +117,9 @@ export const EGG_GACHA_PULL_COUNT_OVERRIDE: number = 0; */ // 1 to 256, set to null to ignore -export const MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = 256; +export const MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = null; export const MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier = null; -export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = MysteryEncounterType.FIERY_FALLOUT; +export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = null; /** * MODIFIER / ITEM OVERRIDES diff --git a/src/test/mystery-encounter/encounterTestUtils.ts b/src/test/mystery-encounter/encounterTestUtils.ts index 65f6ae0edc9..1a602b7b239 100644 --- a/src/test/mystery-encounter/encounterTestUtils.ts +++ b/src/test/mystery-encounter/encounterTestUtils.ts @@ -1,17 +1,19 @@ import { Button } from "#app/enums/buttons"; -import { MessagePhase } from "#app/phases"; +import { MessagePhase, VictoryPhase } from "#app/phases"; import { MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phase"; import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler"; import { Mode } from "#app/ui/ui"; import GameManager from "../utils/gameManager"; +import MessageUiHandler from "#app/ui/message-ui-handler"; export async function runSelectMysteryEncounterOption(game: GameManager, optionNo: number) { + // Handle any eventual queued messages (e.g. weather phase, etc.) + game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => { + const uiHandler = game.scene.ui.getHandler(); + uiHandler.processInput(Button.ACTION); + }); + if (game.isCurrentPhase(MessagePhase)) { - // Handle eventual weather messages (e.g. a downpour started!) - game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => { - const uiHandler = game.scene.ui.getHandler(); - uiHandler.processInput(Button.ACTION); - }); await game.phaseInterceptor.run(MessagePhase); } @@ -20,35 +22,56 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN const uiHandler = game.scene.ui.getHandler(); uiHandler.processInput(Button.ACTION); }); - // select the desired option - game.onNextPrompt("MysteryEncounterPhase", Mode.MYSTERY_ENCOUNTER, () => { - const uiHandler = game.scene.ui.getHandler(); - uiHandler.unblockInput(); // input are blocked by 1s to prevent accidental input. Tests need to handle that - switch (optionNo) { - case 1: - // no movement needed. Default cursor position - break; - case 2: - uiHandler.processInput(Button.RIGHT); - break; - case 3: - uiHandler.processInput(Button.DOWN); - break; - case 4: - uiHandler.processInput(Button.RIGHT); - uiHandler.processInput(Button.DOWN); - break; - } - - uiHandler.processInput(Button.ACTION); - }); await game.phaseInterceptor.run(MysteryEncounterPhase); + // select the desired option + const uiHandler = game.scene.ui.getHandler(); + uiHandler.unblockInput(); // input are blocked by 1s to prevent accidental input. Tests need to handle that + + switch (optionNo) { + case 1: + // no movement needed. Default cursor position + break; + case 2: + uiHandler.processInput(Button.RIGHT); + break; + case 3: + uiHandler.processInput(Button.DOWN); + break; + case 4: + uiHandler.processInput(Button.RIGHT); + uiHandler.processInput(Button.DOWN); + break; + } + + uiHandler.processInput(Button.ACTION); + // run the selected options phase game.onNextPrompt("MysteryEncounterOptionSelectedPhase", Mode.MESSAGE, () => { const uiHandler = game.scene.ui.getHandler(); uiHandler.processInput(Button.ACTION); }); + + // If a battle is started, fast forward to end of the battle + game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { + game.scene.clearPhaseQueue(); + game.scene.clearPhaseQueueSplice(); + game.scene.unshiftPhase(new VictoryPhase(game.scene, 0)); + game.endPhase(); + }); + + // Handle end of battle trainer messages + game.onNextPrompt("TrainerVictoryPhase", Mode.MESSAGE, () => { + const uiHandler = game.scene.ui.getHandler(); + uiHandler.processInput(Button.ACTION); + }); + + // Handle egg hatch dialogue + game.onNextPrompt("EggLapsePhase", Mode.MESSAGE, () => { + const uiHandler = game.scene.ui.getHandler(); + uiHandler.processInput(Button.ACTION); + }); + await game.phaseInterceptor.to(MysteryEncounterRewardsPhase); } diff --git a/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts b/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts index 3b8017fa4c6..14dd43be8bb 100644 --- a/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts @@ -9,7 +9,6 @@ import { Moves } from "#app/enums/moves"; import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; import { Species } from "#app/enums/species"; import GameManager from "#app/test/utils/gameManager"; -import { workaround_reInitSceneWithOverrides } from "#app/test/utils/testUtils"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { runSelectMysteryEncounterOption } from "../encounterTestUtils"; @@ -30,8 +29,9 @@ describe("Lost at Sea - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); game.override.mysteryEncounterChance(100); - game.override.startingBiome(defaultBiome); game.override.startingWave(defaultWave); + game.override.startingBiome(defaultBiome); + vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( new Map([ [Biome.SEA, [MysteryEncounterType.LOST_AT_SEA]], @@ -45,7 +45,6 @@ describe("Lost at Sea - Mystery Encounter", () => { }); it("should have the correct properties", async () => { - await workaround_reInitSceneWithOverrides(game); await game.runToMysteryEncounter(defaultParty); expect(LostAtSeaEncounter.encounterType).toBe(MysteryEncounterType.LOST_AT_SEA); @@ -59,7 +58,6 @@ describe("Lost at Sea - Mystery Encounter", () => { it("should not spawn outside of sea biome", async () => { game.override.startingBiome(Biome.MOUNTAIN); - await workaround_reInitSceneWithOverrides(game); await game.runToMysteryEncounter(); expect(game.scene.currentBattle.mysteryEncounter.encounterType).not.toBe(MysteryEncounterType.LOST_AT_SEA); @@ -117,7 +115,6 @@ describe("Lost at Sea - Mystery Encounter", () => { it("should award exp to surfable PKM (Blastoise)", async () => { const laprasSpecies = getPokemonSpecies(Species.LAPRAS); - await workaround_reInitSceneWithOverrides(game); await game.runToMysteryEncounter(defaultParty); const party = game.scene.getParty(); const blastoise = party.find((pkm) => pkm.species.speciesId === Species.PIDGEOT); @@ -126,13 +123,12 @@ describe("Lost at Sea - Mystery Encounter", () => { await runSelectMysteryEncounterOption(game, 2); expect(blastoise.exp).toBe(expBefore + laprasSpecies.baseExp * defaultWave); - }); + }, 10000000); it("should leave encounter without battle", async () => { game.override.startingWave(33); const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); - await workaround_reInitSceneWithOverrides(game); await game.runToMysteryEncounter(defaultParty); await runSelectMysteryEncounterOption(game, 1); @@ -168,7 +164,6 @@ describe("Lost at Sea - Mystery Encounter", () => { const wave = 33; game.override.startingWave(wave); - await workaround_reInitSceneWithOverrides(game); await game.runToMysteryEncounter(defaultParty); const party = game.scene.getParty(); const pidgeot = party.find((pkm) => pkm.species.speciesId === Species.PIDGEOT); @@ -183,7 +178,7 @@ describe("Lost at Sea - Mystery Encounter", () => { game.override.startingWave(33); const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); - await workaround_reInitSceneWithOverrides(game); + // await workaround_reInitSceneWithOverrides(game); await game.runToMysteryEncounter(defaultParty); await runSelectMysteryEncounterOption(game, 2); @@ -215,7 +210,6 @@ describe("Lost at Sea - Mystery Encounter", () => { it("should damage all (allowed in battle) party PKM by 25%", async () => { game.override.startingWave(33); - await workaround_reInitSceneWithOverrides(game); await game.runToMysteryEncounter(defaultParty); const party = game.scene.getParty(); @@ -237,7 +231,6 @@ describe("Lost at Sea - Mystery Encounter", () => { game.override.startingWave(33); const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); - workaround_reInitSceneWithOverrides(game); await game.runToMysteryEncounter(defaultParty); await runSelectMysteryEncounterOption(game, 3); diff --git a/src/test/mystery-encounter/mystery-encounter-utils.test.ts b/src/test/mystery-encounter/mystery-encounter-utils.test.ts index 22582f8a8bc..2867bcc63bc 100644 --- a/src/test/mystery-encounter/mystery-encounter-utils.test.ts +++ b/src/test/mystery-encounter/mystery-encounter-utils.test.ts @@ -36,16 +36,12 @@ describe("Mystery Encounter Utils", () => { describe("getRandomPlayerPokemon", () => { it("gets a random pokemon from player party", () => { // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) - scene.waveSeed = "random"; - Phaser.Math.RND.sow([scene.waveSeed]); - scene.rngCounter = 0; + game.override.seed("random"); let result = getRandomPlayerPokemon(scene); expect(result.species.speciesId).toBe(Species.MANAPHY); - scene.waveSeed = "random2"; - Phaser.Math.RND.sow([scene.waveSeed]); - scene.rngCounter = 0; + game.override.seed("random2"); result = getRandomPlayerPokemon(scene); expect(result.species.speciesId).toBe(Species.ARCEUS); @@ -60,16 +56,12 @@ describe("Mystery Encounter Utils", () => { }); // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) - scene.waveSeed = "random"; - Phaser.Math.RND.sow([scene.waveSeed]); - scene.rngCounter = 0; + game.override.seed("random"); let result = getRandomPlayerPokemon(scene); expect(result.species.speciesId).toBe(Species.MANAPHY); - scene.waveSeed = "random2"; - Phaser.Math.RND.sow([scene.waveSeed]); - scene.rngCounter = 0; + game.override.seed("random2"); result = getRandomPlayerPokemon(scene); expect(result.species.speciesId).toBe(Species.ARCEUS); @@ -83,16 +75,12 @@ describe("Mystery Encounter Utils", () => { party[0].updateInfo(); // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) - scene.waveSeed = "random"; - Phaser.Math.RND.sow([scene.waveSeed]); - scene.rngCounter = 0; + game.override.seed("random"); let result = getRandomPlayerPokemon(scene, true); expect(result.species.speciesId).toBe(Species.MANAPHY); - scene.waveSeed = "random2"; - Phaser.Math.RND.sow([scene.waveSeed]); - scene.rngCounter = 0; + game.override.seed("random2"); result = getRandomPlayerPokemon(scene, true); expect(result.species.speciesId).toBe(Species.MANAPHY); @@ -106,16 +94,12 @@ describe("Mystery Encounter Utils", () => { party[0].updateInfo(); // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) - scene.waveSeed = "random"; - Phaser.Math.RND.sow([scene.waveSeed]); - scene.rngCounter = 0; + game.override.seed("random"); let result = getRandomPlayerPokemon(scene, true, false); expect(result.species.speciesId).toBe(Species.MANAPHY); - scene.waveSeed = "random2"; - Phaser.Math.RND.sow([scene.waveSeed]); - scene.rngCounter = 0; + game.override.seed("random2"); result = getRandomPlayerPokemon(scene, true, false); expect(result.species.speciesId).toBe(Species.MANAPHY); @@ -129,16 +113,12 @@ describe("Mystery Encounter Utils", () => { party[0].updateInfo(); // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) - scene.waveSeed = "random"; - Phaser.Math.RND.sow([scene.waveSeed]); - scene.rngCounter = 0; + game.override.seed("random"); let result = getRandomPlayerPokemon(scene, true, true); expect(result.species.speciesId).toBe(Species.ARCEUS); - scene.waveSeed = "random2"; - Phaser.Math.RND.sow([scene.waveSeed]); - scene.rngCounter = 0; + game.override.seed("random2"); result = getRandomPlayerPokemon(scene, true, true); expect(result.species.speciesId).toBe(Species.ARCEUS); diff --git a/src/test/mystery-encounter/mystery-encounter.test.ts b/src/test/mystery-encounter/mystery-encounter.test.ts index f4a083c06d4..8ed2e4756ca 100644 --- a/src/test/mystery-encounter/mystery-encounter.test.ts +++ b/src/test/mystery-encounter/mystery-encounter.test.ts @@ -1,5 +1,4 @@ -import { afterEach, beforeAll, beforeEach, expect, describe, it, vi } from "vitest"; -import * as overrides from "../../overrides"; +import { afterEach, beforeAll, beforeEach, expect, describe, it } from "vitest"; import GameManager from "#app/test/utils/gameManager"; import Phaser from "phaser"; import { Species } from "#enums/species"; @@ -22,16 +21,9 @@ describe("Mystery Encounters", () => { beforeEach(() => { game = new GameManager(phaserGame); - vi.spyOn(overrides, "MYSTERY_ENCOUNTER_RATE_OVERRIDE", "get").mockReturnValue(256); - vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(11); - vi.spyOn(overrides, "MYSTERY_ENCOUNTER_OVERRIDE", "get").mockReturnValue(MysteryEncounterType.MYSTERIOUS_CHALLENGERS); - - // Seed guarantees wild encounter to be replaced by ME - vi.spyOn(game.scene, "resetSeed").mockImplementation(() => { - game.scene.waveSeed = "test"; - Phaser.Math.RND.sow([game.scene.waveSeed]); - game.scene.rngCounter = 0; - }); + game.override.startingWave(11); + game.override.mysteryEncounterChance(100); + game.override.mysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS); }); it("Spawns a mystery encounter", async () => { diff --git a/src/test/phases/mystery-encounter-phase.test.ts b/src/test/phases/mystery-encounter-phase.test.ts index eef252bfff2..91e6a09bb99 100644 --- a/src/test/phases/mystery-encounter-phase.test.ts +++ b/src/test/phases/mystery-encounter-phase.test.ts @@ -1,14 +1,14 @@ -import {afterEach, beforeAll, beforeEach, expect, describe, it, vi} from "vitest"; -import * as overrides from "../../overrides"; +import {afterEach, beforeAll, beforeEach, expect, describe, it, vi } from "vitest"; import GameManager from "#app/test/utils/gameManager"; import Phaser from "phaser"; import {Species} from "#enums/species"; -import {MysteryEncounterOptionSelectedPhase, MysteryEncounterPhase} from "#app/phases/mystery-encounter-phase"; +import { MysteryEncounterOptionSelectedPhase, MysteryEncounterPhase } from "#app/phases/mystery-encounter-phase"; import {Mode} from "#app/ui/ui"; import {Button} from "#enums/buttons"; import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler"; import {MysteryEncounterType} from "#enums/mystery-encounter-type"; import {MysteryEncounterTier} from "#app/data/mystery-encounters/mystery-encounter"; +import MessageUiHandler from "#app/ui/message-ui-handler"; describe("Mystery Encounter Phases", () => { let phaserGame: Phaser.Game; @@ -26,16 +26,11 @@ describe("Mystery Encounter Phases", () => { beforeEach(() => { game = new GameManager(phaserGame); - vi.spyOn(overrides, "MYSTERY_ENCOUNTER_RATE_OVERRIDE", "get").mockReturnValue(256); - vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(11); - vi.spyOn(overrides, "MYSTERY_ENCOUNTER_OVERRIDE", "get").mockReturnValue(MysteryEncounterType.MYSTERIOUS_CHALLENGERS); - + game.override.startingWave(11); + game.override.mysteryEncounterChance(100); + game.override.mysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS); // Seed guarantees wild encounter to be replaced by ME - vi.spyOn(game.scene, "resetSeed").mockImplementation(() => { - game.scene.waveSeed = "test"; - Phaser.Math.RND.sow([ game.scene.waveSeed ]); - game.scene.rngCounter = 0; - }); + game.override.seed("test"); }); describe("MysteryEncounterPhase", () => { @@ -75,21 +70,25 @@ describe("Mystery Encounter Phases", () => { Species.VOLCARONA ]); - game.onNextPrompt("MysteryEncounterPhase", Mode.MYSTERY_ENCOUNTER, () => { - // Select option 1 for encounter - const handler = game.scene.ui.getHandler() as MysteryEncounterUiHandler; - handler.unblockInput(); + game.onNextPrompt("MysteryEncounterPhase", Mode.MESSAGE, () => { + const handler = game.scene.ui.getHandler() as MessageUiHandler; handler.processInput(Button.ACTION); - }, () => !game.isCurrentPhase(MysteryEncounterPhase)); + }); + await game.phaseInterceptor.run(MysteryEncounterPhase); - // After option selected - expect(game.scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterOptionSelectedPhase.name); + // Select option 1 for encounter + const handler = game.scene.ui.getHandler() as MysteryEncounterUiHandler; + handler.unblockInput(); + handler.processInput(Button.ACTION); + + // Waitfor required so that option select messages and preOptionPhase logic are handled + await vi.waitFor(() => expect(game.scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterOptionSelectedPhase.name)); expect(game.scene.ui.getMode()).toBe(Mode.MESSAGE); expect(dialogueSpy).toHaveBeenCalledTimes(1); expect(messageSpy).toHaveBeenCalledTimes(2); expect(dialogueSpy).toHaveBeenCalledWith("What's this?", "???", null, expect.any(Function)); - expect(messageSpy).toHaveBeenCalledWith("Mysterious challengers have appeared!", null, expect.any(Function), 300, true); + expect(messageSpy).toHaveBeenCalledWith("Mysterious challengers have appeared!", null, expect.any(Function), 750, true); expect(messageSpy).toHaveBeenCalledWith("The trainer steps forward...", null, expect.any(Function), 300, true); }); }); diff --git a/src/test/utils/TextInterceptor.ts b/src/test/utils/TextInterceptor.ts index 34b55aa30ac..a49f41f6be0 100644 --- a/src/test/utils/TextInterceptor.ts +++ b/src/test/utils/TextInterceptor.ts @@ -11,6 +11,11 @@ export default class TextInterceptor { this.logs.push(text); } + showDialogue(text: string, name: string, delay?: integer, callback?: Function, callbackDelay?: integer, promptDelay?: integer): void { + console.log(name, text); + this.logs.push(name, text); + } + getLatestMessage(): string { return this.logs.pop(); } diff --git a/src/test/utils/gameManager.ts b/src/test/utils/gameManager.ts index 6c750c60a92..f0c7d08c6b7 100644 --- a/src/test/utils/gameManager.ts +++ b/src/test/utils/gameManager.ts @@ -62,7 +62,7 @@ export default class GameManager { this.phaseInterceptor = new PhaseInterceptor(this.scene); this.textInterceptor = new TextInterceptor(this.scene); this.gameWrapper.setScene(this.scene); - this.override = new OverridesHelper(); + this.override = new OverridesHelper(this); } /** diff --git a/src/test/utils/mocks/mocksContainer/mockText.ts b/src/test/utils/mocks/mocksContainer/mockText.ts index 1dd440fde7c..2e6ed67f21f 100644 --- a/src/test/utils/mocks/mocksContainer/mockText.ts +++ b/src/test/utils/mocks/mocksContainer/mockText.ts @@ -17,6 +17,7 @@ export default class MockText { // Phaser.GameObjects.Text.prototype.updateText = () => null; // Phaser.Textures.TextureManager.prototype.addCanvas = () => {}; UI.prototype.showText = this.showText; + UI.prototype.showDialogue = this.showDialogue; // super(scene, x, y); // this.phaserText = new Phaser.GameObjects.Text(scene, x, y, content, styleOptions); } @@ -79,6 +80,13 @@ export default class MockText { } } + showDialogue(text, name, delay, callback, callbackDelay, promptDelay) { + this.scene.messageWrapper.showDialogue(text, name, delay, callback, callbackDelay, promptDelay); + if (callback) { + callback(); + } + } + setScale(scale) { // return this.phaserText.setScale(scale); } diff --git a/src/test/utils/overridesHelper.ts b/src/test/utils/overridesHelper.ts index dd45d972b50..85d458bf826 100644 --- a/src/test/utils/overridesHelper.ts +++ b/src/test/utils/overridesHelper.ts @@ -1,61 +1,90 @@ import { Weather, WeatherType } from "#app/data/weather"; import { Biome } from "#app/enums/biome"; import * as Overrides from "#app/overrides"; -import { vi } from "vitest"; +import { MockInstance, vi } from "vitest"; +import GameManager from "#test/utils/gameManager"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import * as overrides from "#app/overrides"; /** * Helper to handle overrides in tests */ export class OverridesHelper { - constructor() {} + game: GameManager; + constructor(game: GameManager) { + this.game = game; + } /** * Override the encounter chance for a mystery encounter. * @param percentage the encounter chance in % + * @returns spy instance */ - mysteryEncounterChance(percentage: number) { + mysteryEncounterChance(percentage: number): MockInstance { const maxRate: number = 256; // 100% const rate = maxRate * (percentage / 100); - vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_RATE_OVERRIDE", "get").mockReturnValue(rate); + const spy = vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_RATE_OVERRIDE", "get").mockReturnValue(rate); this.log(`Mystery encounter chance set to ${percentage}% (=${rate})!`); + return spy; + } + + /** + * Override the encounter that spawns for the scene + * @param encounterType + * @returns spy instance + */ + mysteryEncounter(encounterType: MysteryEncounterType): MockInstance { + const spy = vi.spyOn(overrides, "MYSTERY_ENCOUNTER_OVERRIDE", "get").mockReturnValue(encounterType); + this.log(`Mystery encounter override set to ${encounterType}!`); + return spy; } /** * Override the starting biome - * @warning The biome will not be overridden unless you call `workaround_reInitSceneWithOverrides()` (testUtils) + * @warning Any event listeners that are attached to [NewArenaEvent](events\battle-scene.ts) may need to be handled down the line * @param biome the biome to set */ startingBiome(biome: Biome) { - vi.spyOn(Overrides, "STARTING_BIOME_OVERRIDE", "get").mockReturnValue(biome); + this.game.scene.newArena(biome); this.log(`Starting biome set to ${Biome[biome]} (=${biome})!`); } /** * Override the starting wave (index) * @param wave the wave (index) to set. Classic: `1`-`200` + * @returns spy instance */ - startingWave(wave: number) { - vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(wave); + startingWave(wave: number): MockInstance { + const spy = vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(wave); this.log(`Starting wave set to ${wave}!`); + return spy; } /** * Override the weather (type) * @param type weather type to set + * @returns spy instance */ - weather(type: WeatherType) { - vi.spyOn(Overrides, "WEATHER_OVERRIDE", "get").mockReturnValue(type); + weather(type: WeatherType): MockInstance { + const spy = vi.spyOn(Overrides, "WEATHER_OVERRIDE", "get").mockReturnValue(type); this.log(`Weather set to ${Weather[type]} (=${type})!`); + return spy; } /** * Override the seed - * @warning The seed will not be overridden unless you call `workaround_reInitSceneWithOverrides()` (testUtils) * @param seed the seed to set + * @returns spy instance */ - seed(seed: string) { - vi.spyOn(Overrides, "SEED_OVERRIDE", "get").mockReturnValue(seed); + seed(seed: string): MockInstance { + const spy = vi.spyOn(this.game.scene, "resetSeed").mockImplementation(() => { + this.game.scene.waveSeed = seed; + Phaser.Math.RND.sow([seed]); + this.game.scene.rngCounter = 0; + }); + this.game.scene.resetSeed(); this.log(`Seed set to "${seed}"!`); + return spy; } private log(...params: any[]) { diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts index 71cf6b495ed..aa297e4944c 100644 --- a/src/test/utils/phaseInterceptor.ts +++ b/src/test/utils/phaseInterceptor.ts @@ -108,7 +108,7 @@ export default class PhaseInterceptor { ]; private endBySetMode = [ - TitlePhase, SelectGenderPhase, CommandPhase, SelectModifierPhase, PostMysteryEncounterPhase + TitlePhase, SelectGenderPhase, CommandPhase, SelectModifierPhase, MysteryEncounterPhase, PostMysteryEncounterPhase ]; /** diff --git a/src/test/utils/testUtils.ts b/src/test/utils/testUtils.ts index a8461b3a5db..b922fc9c61c 100644 --- a/src/test/utils/testUtils.ts +++ b/src/test/utils/testUtils.ts @@ -1,6 +1,5 @@ import i18next, { type ParseKeys } from "i18next"; import { vi } from "vitest"; -import GameManager from "./gameManager"; /** * Sets up the i18next mock. @@ -22,15 +21,3 @@ export function mockI18next() { export function arrayOfRange(start: integer, end: integer) { return Array.from({ length: end - start }, (_v, k) => k + start); } - -/** - * Woraround to reinitialize the game scene with overrides being set properly. - * By default the scene is initialized without all overrides even having a chance to be applied. - * @warning USE AT YOUR OWN RISK! Might be deleted in the future - * @param game The game manager - * @deprecated - */ -export async function workaround_reInitSceneWithOverrides(game: GameManager) { - await game.runToTitle(); - game.gameWrapper.setScene(game.scene); -}