clean up unit tests and utils

This commit is contained in:
ImperialSympathizer 2024-07-18 11:04:52 -04:00
parent c928445f5e
commit 2697a738ba
16 changed files with 166 additions and 145 deletions

View File

@ -145,7 +145,7 @@ export const FieryFalloutEncounter: IMysteryEncounter =
.withOption( .withOption(
new MysteryEncounterOptionBuilder() new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DISABLED_OR_SPECIAL) .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({ .withDialogue({
buttonLabel: `${namespace}_option_3_label`, buttonLabel: `${namespace}_option_3_label`,
buttonTooltip: `${namespace}_option_3_tooltip`, buttonTooltip: `${namespace}_option_3_tooltip`,

View File

@ -57,7 +57,7 @@ export const SafariZoneEncounter: IMysteryEncounter =
.withOptionPhase(async (scene: BattleScene) => { .withOptionPhase(async (scene: BattleScene) => {
// Start safari encounter // Start safari encounter
const encounter = scene.currentBattle.mysteryEncounter; const encounter = scene.currentBattle.mysteryEncounter;
encounter.encounterVariant = MysteryEncounterVariant.SAFARI_BATTLE; encounter.encounterVariant = MysteryEncounterVariant.REPEATED_ENCOUNTER;
encounter.misc = { encounter.misc = {
safariPokemonRemaining: 3 safariPokemonRemaining: 3
}; };
@ -463,14 +463,15 @@ function tryChangeCatchStage(scene: BattleScene, change: number, chance?: number
return true; return true;
} }
async function doEndTurn(scene: BattleScene, cursorIndex: number, message?: string) { async function doEndTurn(scene: BattleScene, cursorIndex: number) {
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon; const encounter = scene.currentBattle.mysteryEncounter;
const isFlee = isPokemonFlee(pokemon, scene.currentBattle.mysteryEncounter.misc.fleeStage); const pokemon = encounter.misc.pokemon;
const isFlee = isPokemonFlee(pokemon, encounter.misc.fleeStage);
if (isFlee) { if (isFlee) {
// Pokemon flees! // Pokemon flees!
await doPokemonFlee(scene, pokemon); await doPokemonFlee(scene, pokemon);
// Check how many safari pokemon left // Check how many safari pokemon left
if (scene.currentBattle.mysteryEncounter.misc.safariPokemonRemaining > 0) { if (encounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon(scene); await summonSafariPokemon(scene);
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true }); initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
} else { } else {

View File

@ -27,7 +27,7 @@ export enum MysteryEncounterVariant {
WILD_BATTLE, WILD_BATTLE,
BOSS_BATTLE, BOSS_BATTLE,
NO_BATTLE, NO_BATTLE,
SAFARI_BATTLE REPEATED_ENCOUNTER
} }
export enum MysteryEncounterTier { export enum MysteryEncounterTier {
@ -38,7 +38,7 @@ export enum MysteryEncounterTier {
MASTER // Not currently used MASTER // Not currently used
} }
export class StartOfBattleEffect { export interface StartOfBattleEffect {
sourcePokemon?: Pokemon; sourcePokemon?: Pokemon;
sourceBattlerIndex?: BattlerIndex; sourceBattlerIndex?: BattlerIndex;
targets: BattlerIndex[]; targets: BattlerIndex[];
@ -108,10 +108,6 @@ export default interface IMysteryEncounter {
* You should never need to modify this * You should never need to modify this
*/ */
seedOffset?: any; 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 * 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) * Will be set to false after a shop is shown (so can't reroll same rarity items for free)
*/ */
lockEncounterRewardTiers?: boolean; 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 * 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<IMysteryEncounter> {
/** /**
* Defines any EncounterAnim animations that are intended to be used during the encounter * 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 * @param encounterAnimations
* @returns * @returns
*/ */

View File

@ -555,8 +555,10 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase:
return; return;
} }
if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.SAFARI_BATTLE) { // If in repeated encounter variant, do nothing
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase)); // 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) { } else if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.NO_BATTLE) {
scene.pushPhase(new EggLapsePhase(scene)); scene.pushPhase(new EggLapsePhase(scene));
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase)); scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));

View File

@ -117,9 +117,9 @@ export const EGG_GACHA_PULL_COUNT_OVERRIDE: number = 0;
*/ */
// 1 to 256, set to null to ignore // 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_TIER_OVERRIDE: MysteryEncounterTier = null;
export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = MysteryEncounterType.FIERY_FALLOUT; export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = null;
/** /**
* MODIFIER / ITEM OVERRIDES * MODIFIER / ITEM OVERRIDES

View File

@ -1,17 +1,19 @@
import { Button } from "#app/enums/buttons"; 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 { MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phase";
import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler"; import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler";
import { Mode } from "#app/ui/ui"; import { Mode } from "#app/ui/ui";
import GameManager from "../utils/gameManager"; import GameManager from "../utils/gameManager";
import MessageUiHandler from "#app/ui/message-ui-handler";
export async function runSelectMysteryEncounterOption(game: GameManager, optionNo: number) { 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<MysteryEncounterUiHandler>();
uiHandler.processInput(Button.ACTION);
});
if (game.isCurrentPhase(MessagePhase)) { if (game.isCurrentPhase(MessagePhase)) {
// Handle eventual weather messages (e.g. a downpour started!)
game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => {
const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
uiHandler.processInput(Button.ACTION);
});
await game.phaseInterceptor.run(MessagePhase); await game.phaseInterceptor.run(MessagePhase);
} }
@ -20,35 +22,56 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN
const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>(); const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
uiHandler.processInput(Button.ACTION); uiHandler.processInput(Button.ACTION);
}); });
// select the desired option
game.onNextPrompt("MysteryEncounterPhase", Mode.MYSTERY_ENCOUNTER, () => {
const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
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); await game.phaseInterceptor.run(MysteryEncounterPhase);
// select the desired option
const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
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 // run the selected options phase
game.onNextPrompt("MysteryEncounterOptionSelectedPhase", Mode.MESSAGE, () => { game.onNextPrompt("MysteryEncounterOptionSelectedPhase", Mode.MESSAGE, () => {
const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>(); const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
uiHandler.processInput(Button.ACTION); 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<MessageUiHandler>();
uiHandler.processInput(Button.ACTION);
});
// Handle egg hatch dialogue
game.onNextPrompt("EggLapsePhase", Mode.MESSAGE, () => {
const uiHandler = game.scene.ui.getHandler<MessageUiHandler>();
uiHandler.processInput(Button.ACTION);
});
await game.phaseInterceptor.to(MysteryEncounterRewardsPhase); await game.phaseInterceptor.to(MysteryEncounterRewardsPhase);
} }

View File

@ -9,7 +9,6 @@ import { Moves } from "#app/enums/moves";
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
import { Species } from "#app/enums/species"; import { Species } from "#app/enums/species";
import GameManager from "#app/test/utils/gameManager"; 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 { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { runSelectMysteryEncounterOption } from "../encounterTestUtils"; import { runSelectMysteryEncounterOption } from "../encounterTestUtils";
@ -30,8 +29,9 @@ describe("Lost at Sea - Mystery Encounter", () => {
beforeEach(async () => { beforeEach(async () => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override.mysteryEncounterChance(100); game.override.mysteryEncounterChance(100);
game.override.startingBiome(defaultBiome);
game.override.startingWave(defaultWave); game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([ new Map<Biome, MysteryEncounterType[]>([
[Biome.SEA, [MysteryEncounterType.LOST_AT_SEA]], [Biome.SEA, [MysteryEncounterType.LOST_AT_SEA]],
@ -45,7 +45,6 @@ describe("Lost at Sea - Mystery Encounter", () => {
}); });
it("should have the correct properties", async () => { it("should have the correct properties", async () => {
await workaround_reInitSceneWithOverrides(game);
await game.runToMysteryEncounter(defaultParty); await game.runToMysteryEncounter(defaultParty);
expect(LostAtSeaEncounter.encounterType).toBe(MysteryEncounterType.LOST_AT_SEA); 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 () => { it("should not spawn outside of sea biome", async () => {
game.override.startingBiome(Biome.MOUNTAIN); game.override.startingBiome(Biome.MOUNTAIN);
await workaround_reInitSceneWithOverrides(game);
await game.runToMysteryEncounter(); await game.runToMysteryEncounter();
expect(game.scene.currentBattle.mysteryEncounter.encounterType).not.toBe(MysteryEncounterType.LOST_AT_SEA); 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 () => { it("should award exp to surfable PKM (Blastoise)", async () => {
const laprasSpecies = getPokemonSpecies(Species.LAPRAS); const laprasSpecies = getPokemonSpecies(Species.LAPRAS);
await workaround_reInitSceneWithOverrides(game);
await game.runToMysteryEncounter(defaultParty); await game.runToMysteryEncounter(defaultParty);
const party = game.scene.getParty(); const party = game.scene.getParty();
const blastoise = party.find((pkm) => pkm.species.speciesId === Species.PIDGEOT); const blastoise = party.find((pkm) => pkm.species.speciesId === Species.PIDGEOT);
@ -126,13 +123,12 @@ describe("Lost at Sea - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 2); await runSelectMysteryEncounterOption(game, 2);
expect(blastoise.exp).toBe(expBefore + laprasSpecies.baseExp * defaultWave); expect(blastoise.exp).toBe(expBefore + laprasSpecies.baseExp * defaultWave);
}); }, 10000000);
it("should leave encounter without battle", async () => { it("should leave encounter without battle", async () => {
game.override.startingWave(33); game.override.startingWave(33);
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await workaround_reInitSceneWithOverrides(game);
await game.runToMysteryEncounter(defaultParty); await game.runToMysteryEncounter(defaultParty);
await runSelectMysteryEncounterOption(game, 1); await runSelectMysteryEncounterOption(game, 1);
@ -168,7 +164,6 @@ describe("Lost at Sea - Mystery Encounter", () => {
const wave = 33; const wave = 33;
game.override.startingWave(wave); game.override.startingWave(wave);
await workaround_reInitSceneWithOverrides(game);
await game.runToMysteryEncounter(defaultParty); await game.runToMysteryEncounter(defaultParty);
const party = game.scene.getParty(); const party = game.scene.getParty();
const pidgeot = party.find((pkm) => pkm.species.speciesId === Species.PIDGEOT); const pidgeot = party.find((pkm) => pkm.species.speciesId === Species.PIDGEOT);
@ -183,7 +178,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
game.override.startingWave(33); game.override.startingWave(33);
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await workaround_reInitSceneWithOverrides(game); // await workaround_reInitSceneWithOverrides(game);
await game.runToMysteryEncounter(defaultParty); await game.runToMysteryEncounter(defaultParty);
await runSelectMysteryEncounterOption(game, 2); 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 () => { it("should damage all (allowed in battle) party PKM by 25%", async () => {
game.override.startingWave(33); game.override.startingWave(33);
await workaround_reInitSceneWithOverrides(game);
await game.runToMysteryEncounter(defaultParty); await game.runToMysteryEncounter(defaultParty);
const party = game.scene.getParty(); const party = game.scene.getParty();
@ -237,7 +231,6 @@ describe("Lost at Sea - Mystery Encounter", () => {
game.override.startingWave(33); game.override.startingWave(33);
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
workaround_reInitSceneWithOverrides(game);
await game.runToMysteryEncounter(defaultParty); await game.runToMysteryEncounter(defaultParty);
await runSelectMysteryEncounterOption(game, 3); await runSelectMysteryEncounterOption(game, 3);

View File

@ -36,16 +36,12 @@ describe("Mystery Encounter Utils", () => {
describe("getRandomPlayerPokemon", () => { describe("getRandomPlayerPokemon", () => {
it("gets a random pokemon from player party", () => { it("gets a random pokemon from player party", () => {
// Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal)
scene.waveSeed = "random"; game.override.seed("random");
Phaser.Math.RND.sow([scene.waveSeed]);
scene.rngCounter = 0;
let result = getRandomPlayerPokemon(scene); let result = getRandomPlayerPokemon(scene);
expect(result.species.speciesId).toBe(Species.MANAPHY); expect(result.species.speciesId).toBe(Species.MANAPHY);
scene.waveSeed = "random2"; game.override.seed("random2");
Phaser.Math.RND.sow([scene.waveSeed]);
scene.rngCounter = 0;
result = getRandomPlayerPokemon(scene); result = getRandomPlayerPokemon(scene);
expect(result.species.speciesId).toBe(Species.ARCEUS); 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) // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal)
scene.waveSeed = "random"; game.override.seed("random");
Phaser.Math.RND.sow([scene.waveSeed]);
scene.rngCounter = 0;
let result = getRandomPlayerPokemon(scene); let result = getRandomPlayerPokemon(scene);
expect(result.species.speciesId).toBe(Species.MANAPHY); expect(result.species.speciesId).toBe(Species.MANAPHY);
scene.waveSeed = "random2"; game.override.seed("random2");
Phaser.Math.RND.sow([scene.waveSeed]);
scene.rngCounter = 0;
result = getRandomPlayerPokemon(scene); result = getRandomPlayerPokemon(scene);
expect(result.species.speciesId).toBe(Species.ARCEUS); expect(result.species.speciesId).toBe(Species.ARCEUS);
@ -83,16 +75,12 @@ describe("Mystery Encounter Utils", () => {
party[0].updateInfo(); party[0].updateInfo();
// Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal)
scene.waveSeed = "random"; game.override.seed("random");
Phaser.Math.RND.sow([scene.waveSeed]);
scene.rngCounter = 0;
let result = getRandomPlayerPokemon(scene, true); let result = getRandomPlayerPokemon(scene, true);
expect(result.species.speciesId).toBe(Species.MANAPHY); expect(result.species.speciesId).toBe(Species.MANAPHY);
scene.waveSeed = "random2"; game.override.seed("random2");
Phaser.Math.RND.sow([scene.waveSeed]);
scene.rngCounter = 0;
result = getRandomPlayerPokemon(scene, true); result = getRandomPlayerPokemon(scene, true);
expect(result.species.speciesId).toBe(Species.MANAPHY); expect(result.species.speciesId).toBe(Species.MANAPHY);
@ -106,16 +94,12 @@ describe("Mystery Encounter Utils", () => {
party[0].updateInfo(); party[0].updateInfo();
// Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal)
scene.waveSeed = "random"; game.override.seed("random");
Phaser.Math.RND.sow([scene.waveSeed]);
scene.rngCounter = 0;
let result = getRandomPlayerPokemon(scene, true, false); let result = getRandomPlayerPokemon(scene, true, false);
expect(result.species.speciesId).toBe(Species.MANAPHY); expect(result.species.speciesId).toBe(Species.MANAPHY);
scene.waveSeed = "random2"; game.override.seed("random2");
Phaser.Math.RND.sow([scene.waveSeed]);
scene.rngCounter = 0;
result = getRandomPlayerPokemon(scene, true, false); result = getRandomPlayerPokemon(scene, true, false);
expect(result.species.speciesId).toBe(Species.MANAPHY); expect(result.species.speciesId).toBe(Species.MANAPHY);
@ -129,16 +113,12 @@ describe("Mystery Encounter Utils", () => {
party[0].updateInfo(); party[0].updateInfo();
// Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal)
scene.waveSeed = "random"; game.override.seed("random");
Phaser.Math.RND.sow([scene.waveSeed]);
scene.rngCounter = 0;
let result = getRandomPlayerPokemon(scene, true, true); let result = getRandomPlayerPokemon(scene, true, true);
expect(result.species.speciesId).toBe(Species.ARCEUS); expect(result.species.speciesId).toBe(Species.ARCEUS);
scene.waveSeed = "random2"; game.override.seed("random2");
Phaser.Math.RND.sow([scene.waveSeed]);
scene.rngCounter = 0;
result = getRandomPlayerPokemon(scene, true, true); result = getRandomPlayerPokemon(scene, true, true);
expect(result.species.speciesId).toBe(Species.ARCEUS); expect(result.species.speciesId).toBe(Species.ARCEUS);

View File

@ -1,5 +1,4 @@
import { afterEach, beforeAll, beforeEach, expect, describe, it, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, expect, describe, it } from "vitest";
import * as overrides from "../../overrides";
import GameManager from "#app/test/utils/gameManager"; import GameManager from "#app/test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
@ -22,16 +21,9 @@ describe("Mystery Encounters", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
vi.spyOn(overrides, "MYSTERY_ENCOUNTER_RATE_OVERRIDE", "get").mockReturnValue(256); game.override.startingWave(11);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(11); game.override.mysteryEncounterChance(100);
vi.spyOn(overrides, "MYSTERY_ENCOUNTER_OVERRIDE", "get").mockReturnValue(MysteryEncounterType.MYSTERIOUS_CHALLENGERS); 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;
});
}); });
it("Spawns a mystery encounter", async () => { it("Spawns a mystery encounter", async () => {

View File

@ -1,14 +1,14 @@
import {afterEach, beforeAll, beforeEach, expect, describe, it, vi} from "vitest"; import {afterEach, beforeAll, beforeEach, expect, describe, it, vi } from "vitest";
import * as overrides from "../../overrides";
import GameManager from "#app/test/utils/gameManager"; import GameManager from "#app/test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import {Species} from "#enums/species"; 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 {Mode} from "#app/ui/ui";
import {Button} from "#enums/buttons"; import {Button} from "#enums/buttons";
import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler"; import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler";
import {MysteryEncounterType} from "#enums/mystery-encounter-type"; import {MysteryEncounterType} from "#enums/mystery-encounter-type";
import {MysteryEncounterTier} from "#app/data/mystery-encounters/mystery-encounter"; import {MysteryEncounterTier} from "#app/data/mystery-encounters/mystery-encounter";
import MessageUiHandler from "#app/ui/message-ui-handler";
describe("Mystery Encounter Phases", () => { describe("Mystery Encounter Phases", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
@ -26,16 +26,11 @@ describe("Mystery Encounter Phases", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
vi.spyOn(overrides, "MYSTERY_ENCOUNTER_RATE_OVERRIDE", "get").mockReturnValue(256); game.override.startingWave(11);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(11); game.override.mysteryEncounterChance(100);
vi.spyOn(overrides, "MYSTERY_ENCOUNTER_OVERRIDE", "get").mockReturnValue(MysteryEncounterType.MYSTERIOUS_CHALLENGERS); game.override.mysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS);
// Seed guarantees wild encounter to be replaced by ME // Seed guarantees wild encounter to be replaced by ME
vi.spyOn(game.scene, "resetSeed").mockImplementation(() => { game.override.seed("test");
game.scene.waveSeed = "test";
Phaser.Math.RND.sow([ game.scene.waveSeed ]);
game.scene.rngCounter = 0;
});
}); });
describe("MysteryEncounterPhase", () => { describe("MysteryEncounterPhase", () => {
@ -75,21 +70,25 @@ describe("Mystery Encounter Phases", () => {
Species.VOLCARONA Species.VOLCARONA
]); ]);
game.onNextPrompt("MysteryEncounterPhase", Mode.MYSTERY_ENCOUNTER, () => { game.onNextPrompt("MysteryEncounterPhase", Mode.MESSAGE, () => {
// Select option 1 for encounter const handler = game.scene.ui.getHandler() as MessageUiHandler;
const handler = game.scene.ui.getHandler() as MysteryEncounterUiHandler;
handler.unblockInput();
handler.processInput(Button.ACTION); handler.processInput(Button.ACTION);
}, () => !game.isCurrentPhase(MysteryEncounterPhase)); });
await game.phaseInterceptor.run(MysteryEncounterPhase); await game.phaseInterceptor.run(MysteryEncounterPhase);
// After option selected // Select option 1 for encounter
expect(game.scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterOptionSelectedPhase.name); 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(game.scene.ui.getMode()).toBe(Mode.MESSAGE);
expect(dialogueSpy).toHaveBeenCalledTimes(1); expect(dialogueSpy).toHaveBeenCalledTimes(1);
expect(messageSpy).toHaveBeenCalledTimes(2); expect(messageSpy).toHaveBeenCalledTimes(2);
expect(dialogueSpy).toHaveBeenCalledWith("What's this?", "???", null, expect.any(Function)); 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); expect(messageSpy).toHaveBeenCalledWith("The trainer steps forward...", null, expect.any(Function), 300, true);
}); });
}); });

View File

@ -11,6 +11,11 @@ export default class TextInterceptor {
this.logs.push(text); 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 { getLatestMessage(): string {
return this.logs.pop(); return this.logs.pop();
} }

View File

@ -62,7 +62,7 @@ export default class GameManager {
this.phaseInterceptor = new PhaseInterceptor(this.scene); this.phaseInterceptor = new PhaseInterceptor(this.scene);
this.textInterceptor = new TextInterceptor(this.scene); this.textInterceptor = new TextInterceptor(this.scene);
this.gameWrapper.setScene(this.scene); this.gameWrapper.setScene(this.scene);
this.override = new OverridesHelper(); this.override = new OverridesHelper(this);
} }
/** /**

View File

@ -17,6 +17,7 @@ export default class MockText {
// Phaser.GameObjects.Text.prototype.updateText = () => null; // Phaser.GameObjects.Text.prototype.updateText = () => null;
// Phaser.Textures.TextureManager.prototype.addCanvas = () => {}; // Phaser.Textures.TextureManager.prototype.addCanvas = () => {};
UI.prototype.showText = this.showText; UI.prototype.showText = this.showText;
UI.prototype.showDialogue = this.showDialogue;
// super(scene, x, y); // super(scene, x, y);
// this.phaserText = new Phaser.GameObjects.Text(scene, x, y, content, styleOptions); // 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) { setScale(scale) {
// return this.phaserText.setScale(scale); // return this.phaserText.setScale(scale);
} }

View File

@ -1,61 +1,90 @@
import { Weather, WeatherType } from "#app/data/weather"; import { Weather, WeatherType } from "#app/data/weather";
import { Biome } from "#app/enums/biome"; import { Biome } from "#app/enums/biome";
import * as Overrides from "#app/overrides"; 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 * Helper to handle overrides in tests
*/ */
export class OverridesHelper { export class OverridesHelper {
constructor() {} game: GameManager;
constructor(game: GameManager) {
this.game = game;
}
/** /**
* Override the encounter chance for a mystery encounter. * Override the encounter chance for a mystery encounter.
* @param percentage the encounter chance in % * @param percentage the encounter chance in %
* @returns spy instance
*/ */
mysteryEncounterChance(percentage: number) { mysteryEncounterChance(percentage: number): MockInstance {
const maxRate: number = 256; // 100% const maxRate: number = 256; // 100%
const rate = maxRate * (percentage / 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})!`); 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 * 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 * @param biome the biome to set
*/ */
startingBiome(biome: Biome) { 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})!`); this.log(`Starting biome set to ${Biome[biome]} (=${biome})!`);
} }
/** /**
* Override the starting wave (index) * Override the starting wave (index)
* @param wave the wave (index) to set. Classic: `1`-`200` * @param wave the wave (index) to set. Classic: `1`-`200`
* @returns spy instance
*/ */
startingWave(wave: number) { startingWave(wave: number): MockInstance {
vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(wave); const spy = vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(wave);
this.log(`Starting wave set to ${wave}!`); this.log(`Starting wave set to ${wave}!`);
return spy;
} }
/** /**
* Override the weather (type) * Override the weather (type)
* @param type weather type to set * @param type weather type to set
* @returns spy instance
*/ */
weather(type: WeatherType) { weather(type: WeatherType): MockInstance {
vi.spyOn(Overrides, "WEATHER_OVERRIDE", "get").mockReturnValue(type); const spy = vi.spyOn(Overrides, "WEATHER_OVERRIDE", "get").mockReturnValue(type);
this.log(`Weather set to ${Weather[type]} (=${type})!`); this.log(`Weather set to ${Weather[type]} (=${type})!`);
return spy;
} }
/** /**
* Override the seed * Override the seed
* @warning The seed will not be overridden unless you call `workaround_reInitSceneWithOverrides()` (testUtils)
* @param seed the seed to set * @param seed the seed to set
* @returns spy instance
*/ */
seed(seed: string) { seed(seed: string): MockInstance {
vi.spyOn(Overrides, "SEED_OVERRIDE", "get").mockReturnValue(seed); 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}"!`); this.log(`Seed set to "${seed}"!`);
return spy;
} }
private log(...params: any[]) { private log(...params: any[]) {

View File

@ -108,7 +108,7 @@ export default class PhaseInterceptor {
]; ];
private endBySetMode = [ private endBySetMode = [
TitlePhase, SelectGenderPhase, CommandPhase, SelectModifierPhase, PostMysteryEncounterPhase TitlePhase, SelectGenderPhase, CommandPhase, SelectModifierPhase, MysteryEncounterPhase, PostMysteryEncounterPhase
]; ];
/** /**

View File

@ -1,6 +1,5 @@
import i18next, { type ParseKeys } from "i18next"; import i18next, { type ParseKeys } from "i18next";
import { vi } from "vitest"; import { vi } from "vitest";
import GameManager from "./gameManager";
/** /**
* Sets up the i18next mock. * Sets up the i18next mock.
@ -22,15 +21,3 @@ export function mockI18next() {
export function arrayOfRange(start: integer, end: integer) { export function arrayOfRange(start: integer, end: integer) {
return Array.from({ length: end - start }, (_v, k) => k + start); 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);
}