add the strong stuff encounter and more unit tests

This commit is contained in:
ImperialSympathizer 2024-07-20 13:45:24 -04:00
parent f63b3938e4
commit d810af0b57
12 changed files with 512 additions and 56 deletions

View File

@ -54,7 +54,7 @@ export function doTrainerExclamation(scene: BattleScene) {
}
});
scene.playSound("GEN8- Exclaim.wav", { volume: 0.8 });
scene.playSound("GEN8- Exclaim.wav", { volume: 0.7 });
}
export interface EnemyPokemonConfig {

View File

@ -207,7 +207,7 @@ export function applyHealToPokemon(scene: BattleScene, pokemon: PlayerPokemon, h
export function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: number) {
pokemon.getSpeciesForm().baseStats = [...pokemon.getSpeciesForm().baseStats].map(v => {
const newVal = Math.floor(v + value);
return Math.min(newVal, 1);
return Math.max(newVal, 1);
});
pokemon.calculateStats();
pokemon.updateInfo();

View File

@ -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.THE_STRONG_STUFF;
export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = null;
/**
* MODIFIER / ITEM OVERRIDES

View File

@ -1107,18 +1107,19 @@ export class EncounterPhase extends BattlePhase {
if (showEncounterMessage) {
const introDialogue = this.scene.currentBattle.mysteryEncounter.dialogue.intro;
const FIRST_DIALOGUE_PROMPT_DELAY = 750;
let i = 0;
const showNextDialogue = () => {
const nextAction = i === introDialogue.length - 1 ? doShowEncounterOptions : showNextDialogue;
const dialogue = introDialogue[i];
const title = getEncounterText(this.scene, dialogue.speaker);
const text = getEncounterText(this.scene, dialogue.text);
if (title) {
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 0 ? 750 : 0);
} else {
this.scene.ui.showText(text, null, nextAction, i === 0 ? 750 : 0, true);
}
i++;
if (title) {
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 1 ? FIRST_DIALOGUE_PROMPT_DELAY : 0);
} else {
this.scene.ui.showText(text, null, nextAction, i === 1 ? FIRST_DIALOGUE_PROMPT_DELAY : 0, true);
}
};
if (introDialogue.length > 0) {

View File

@ -26,10 +26,9 @@ import { BattlerTagLapseType } from "#app/data/battler-tags";
* - Queuing of the MysteryEncounterOptionSelectedPhase
*/
export class MysteryEncounterPhase extends Phase {
private readonly FIRST_DIALOGUE_PROMPT_DELAY = 300;
optionSelectSettings: OptionSelectSettings;
private FIRST_DIALOGUE_PROMPT_DELAY = 300;
/**
*
* @param scene
@ -108,12 +107,12 @@ export class MysteryEncounterPhase extends Phase {
title = getEncounterText(this.scene, dialogue.speaker);
}
if (title) {
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 0 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0);
} else {
this.scene.ui.showText(text, null, nextAction, i === 0 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0, true);
}
i++;
if (title) {
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0);
} else {
this.scene.ui.showText(text, null, nextAction, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0, true);
}
};
showNextDialogue();
@ -420,6 +419,7 @@ export class MysteryEncounterRewardsPhase extends Phase {
* - Queuing of the next wave
*/
export class PostMysteryEncounterPhase extends Phase {
private readonly FIRST_DIALOGUE_PROMPT_DELAY = 750;
onPostOptionSelect: OptionPhaseCallback;
constructor(scene: BattleScene) {
@ -462,13 +462,13 @@ export class PostMysteryEncounterPhase extends Phase {
title = getEncounterText(this.scene, dialogue.speaker);
}
i++;
this.scene.ui.setMode(Mode.MESSAGE);
if (title) {
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 0 ? 750 : 0);
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0);
} else {
this.scene.ui.showText(text, null, nextAction, i === 0 ? 750 : 0, true);
this.scene.ui.showText(text, null, nextAction, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0, true);
}
i++;
};
showNextDialogue();

View File

@ -0,0 +1,240 @@
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
import { Biome } from "#app/enums/biome";
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
import { Species } from "#app/enums/species";
import GameManager from "#app/test/utils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { EncounterOptionMode } from "#app/data/mystery-encounters/mystery-encounter-option";
import { runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounterTestUtils";
import { SelectModifierPhase } from "#app/phases";
import BattleScene from "#app/battle-scene";
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
import { Mode } from "#app/ui/ui";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
import { DepartmentStoreSaleEncounter } from "#app/data/mystery-encounters/encounters/department-store-sale-encounter";
import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/mystery-encounters";
const namespace = "mysteryEncounter:departmentStoreSale";
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
const defaultBiome = Biome.PLAINS;
const defaultWave = 37;
describe("Department Store Sale - Mystery Encounter", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
let scene: BattleScene;
beforeAll(() => {
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
});
beforeEach(async () => {
game = new GameManager(phaserGame);
scene = game.scene;
game.override.mysteryEncounterChance(100);
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWave(true);
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
]);
CIVILIZATION_ENCOUNTER_BIOMES.forEach(biome => {
biomeMap.set(biome, [MysteryEncounterType.DEPARTMENT_STORE_SALE]);
});
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(biomeMap);
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
vi.clearAllMocks();
vi.resetAllMocks();
});
it("should have the correct properties", async () => {
game.override.mysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE);
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
expect(DepartmentStoreSaleEncounter.encounterType).toBe(MysteryEncounterType.DEPARTMENT_STORE_SALE);
expect(DepartmentStoreSaleEncounter.encounterTier).toBe(MysteryEncounterTier.COMMON);
expect(DepartmentStoreSaleEncounter.dialogue).toBeDefined();
expect(DepartmentStoreSaleEncounter.dialogue.intro).toStrictEqual([
{ text: `${namespace}:intro` },
{
speaker: `${namespace}:speaker`,
text: `${namespace}:intro_dialogue`,
}
]);
expect(DepartmentStoreSaleEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}:title`);
expect(DepartmentStoreSaleEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}:description`);
expect(DepartmentStoreSaleEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}:query`);
expect(DepartmentStoreSaleEncounter.options.length).toBe(4);
});
it("should not spawn outside of CIVILIZATION_ENCOUNTER_BIOMES", async () => {
game.override.startingBiome(Biome.VOLCANO);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.DEPARTMENT_STORE_SALE);
});
it("should not run below wave 10", async () => {
game.override.startingWave(9);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.DEPARTMENT_STORE_SALE);
});
it("should not run above wave 179", async () => {
game.override.startingWave(181);
await game.runToMysteryEncounter();
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
});
describe("Option 1 - TM Shop", () => {
it("should have the correct properties", () => {
const option = DepartmentStoreSaleEncounter.options[0];
expect(option.optionMode).toBe(EncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:1:label`,
buttonTooltip: `${namespace}:option:1:tooltip`,
});
});
it("should have shop with only TMs", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 1);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
expect(modifierSelectHandler.options.length).toEqual(4);
for (const option of modifierSelectHandler.options) {
expect(option.modifierTypeOption.type.id).toContain("TM_");
}
});
it("should leave encounter without battle", async () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 1);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
});
describe("Option 2 - Vitamin Shop", () => {
it("should have the correct properties", () => {
const option = DepartmentStoreSaleEncounter.options[1];
expect(option.optionMode).toBe(EncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:2:label`,
buttonTooltip: `${namespace}:option:2:tooltip`,
});
});
it("should have shop with only Vitamins", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 2);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
expect(modifierSelectHandler.options.length).toEqual(3);
for (const option of modifierSelectHandler.options) {
expect(option.modifierTypeOption.type.id.includes("PP_UP") ||
option.modifierTypeOption.type.id.includes("BASE_STAT_BOOSTER")).toBeTruthy();
}
});
it("should leave encounter without battle", async () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 2);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
});
describe("Option 3 - X Item Shop", () => {
it("should have the correct properties", () => {
const option = DepartmentStoreSaleEncounter.options[2];
expect(option.optionMode).toBe(EncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:3:label`,
buttonTooltip: `${namespace}:option:3:tooltip`,
});
});
it("should have shop with only X Items", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 3);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
expect(modifierSelectHandler.options.length).toEqual(5);
for (const option of modifierSelectHandler.options) {
expect(option.modifierTypeOption.type.id.includes("DIRE_HIT") ||
option.modifierTypeOption.type.id.includes("TEMP_STAT_BOOSTER")).toBeTruthy();
}
});
it("should leave encounter without battle", async () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 3);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
});
describe("Option 4 - Pokeball Shop", () => {
it("should have the correct properties", () => {
const option = DepartmentStoreSaleEncounter.options[3];
expect(option.optionMode).toBe(EncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:4:label`,
buttonTooltip: `${namespace}:option:4:tooltip`,
});
});
it("should have shop with only Pokeballs", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 4);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
expect(modifierSelectHandler.options.length).toEqual(4);
for (const option of modifierSelectHandler.options) {
expect(option.modifierTypeOption.type.id).toContain("BALL");
}
});
it("should leave encounter without battle", async () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 4);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
});
});

View File

@ -24,7 +24,7 @@ const namespace = "mysteryEncounter:fieryFallout";
/** Arcanine and Ninetails for 2 Fire types. Lapras, Gengar, Abra for burnable mon. */
const defaultParty = [Species.ARCANINE, Species.NINETALES, Species.LAPRAS, Species.GENGAR, Species.ABRA];
const defaultBiome = Biome.VOLCANO;
const defaultWave = 45;
const defaultWave = 56;
describe("Fiery Fallout - Mystery Encounter", () => {
let phaserGame: Phaser.Game;
@ -42,7 +42,6 @@ describe("Fiery Fallout - Mystery Encounter", () => {
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWave(true);
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([
@ -54,6 +53,8 @@ describe("Fiery Fallout - Mystery Encounter", () => {
afterEach(() => {
game.phaseInterceptor.restoreOg();
vi.clearAllMocks();
vi.resetAllMocks();
});
it("should have the correct properties", async () => {
@ -74,7 +75,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
game.override.startingBiome(Biome.MOUNTAIN);
await game.runToMysteryEncounter();
expect(scene.currentBattle.mysteryEncounter.encounterType).not.toBe(MysteryEncounterType.FIERY_FALLOUT);
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.FIERY_FALLOUT);
});
it("should not run below wave 41", async () => {
@ -82,7 +83,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
await game.runToMysteryEncounter();
expect(scene.currentBattle.mysteryEncounter.encounterType).not.toBe(MysteryEncounterType.FIERY_FALLOUT);
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.FIERY_FALLOUT);
});
it("should not run above wave 179", async () => {
@ -96,7 +97,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
it("should initialize fully ", async () => {
vi.spyOn(scene, "currentBattle", "get").mockReturnValue({ mysteryEncounter: FieryFalloutEncounter } as Battle);
const weatherSpy = vi.spyOn(scene.arena, "trySetWeather").mockReturnValue(true);
const moveInitSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets");
const moveInitSpy = vi.spyOn(BattleAnims, "initMoveAnim");
const moveLoadSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets");
const { onInit } = FieryFalloutEncounter;
@ -130,10 +131,6 @@ describe("Fiery Fallout - Mystery Encounter", () => {
});
describe("Option 1 - Fight 2 Volcarona", () => {
beforeEach(async () => {
game.override.mysteryEncounter(MysteryEncounterType.FIERY_FALLOUT);
});
it("should have the correct properties", () => {
const option1 = FieryFalloutEncounter.options[0];
expect(option1.optionMode).toBe(EncounterOptionMode.DEFAULT);
@ -180,14 +177,10 @@ describe("Fiery Fallout - Mystery Encounter", () => {
&& (m as PokemonHeldItemModifier).pokemonId === leadPokemonId, true) as PokemonHeldItemModifier[];
const charcoal = leadPokemonItems.find(i => i.type.name === "Charcoal");
expect(charcoal).toBeDefined;
}, 100000000);
});
});
describe("Option 2 - Suffer the weather", () => {
beforeEach(async () => {
game.override.mysteryEncounter(MysteryEncounterType.FIERY_FALLOUT);
});
it("should have the correct properties", () => {
const option1 = FieryFalloutEncounter.options[1];
expect(option1.optionMode).toBe(EncounterOptionMode.DEFAULT);
@ -235,10 +228,6 @@ describe("Fiery Fallout - Mystery Encounter", () => {
});
describe("Option 3 - use FIRE types", () => {
beforeEach(async () => {
game.override.mysteryEncounter(MysteryEncounterType.FIERY_FALLOUT);
});
it("should have the correct properties", () => {
const option1 = FieryFalloutEncounter.options[2];
expect(option1.optionMode).toBe(EncounterOptionMode.DISABLED_OR_SPECIAL);

View File

@ -32,7 +32,6 @@ describe("Lost at Sea - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWave(true);
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([
@ -44,6 +43,8 @@ describe("Lost at Sea - Mystery Encounter", () => {
afterEach(() => {
game.phaseInterceptor.restoreOg();
vi.clearAllMocks();
vi.resetAllMocks();
});
it("should have the correct properties", async () => {
@ -99,10 +100,6 @@ describe("Lost at Sea - Mystery Encounter", () => {
});
describe("Option 1 - Surf", () => {
beforeEach(async () => {
game.override.mysteryEncounter(MysteryEncounterType.LOST_AT_SEA);
});
it("should have the correct properties", () => {
const option1 = LostAtSeaEncounter.options[0];
expect(option1.optionMode).toBe(EncounterOptionMode.DISABLED_OR_DEFAULT);
@ -149,10 +146,6 @@ describe("Lost at Sea - Mystery Encounter", () => {
});
describe("Option 2 - Fly", () => {
beforeEach(async () => {
game.override.mysteryEncounter(MysteryEncounterType.LOST_AT_SEA);
});
it("should have the correct properties", () => {
const option2 = LostAtSeaEncounter.options[1];
@ -202,10 +195,6 @@ describe("Lost at Sea - Mystery Encounter", () => {
});
describe("Option 3 - Wander aimlessy", () => {
beforeEach(async () => {
game.override.mysteryEncounter(MysteryEncounterType.LOST_AT_SEA);
});
it("should have the correct properties", () => {
const option3 = LostAtSeaEncounter.options[2];

View File

@ -0,0 +1,231 @@
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
import { Biome } from "#app/enums/biome";
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
import { Species } from "#app/enums/species";
import GameManager from "#app/test/utils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import Battle from "#app/battle";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import * as BattleAnims from "#app/data/battle-anims";
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { EncounterOptionMode } from "#app/data/mystery-encounters/mystery-encounter-option";
import { runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
import { CommandPhase, MovePhase, SelectModifierPhase } from "#app/phases";
import { Moves } from "#enums/moves";
import BattleScene from "#app/battle-scene";
import * as Modifiers from "#app/modifier/modifier";
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter";
import { Nature } from "#app/data/nature";
import { BerryType } from "#enums/berry-type";
import { BattlerTagType } from "#enums/battler-tag-type";
import { PokemonMove } from "#app/field/pokemon";
import { Mode } from "#app/ui/ui";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
const namespace = "mysteryEncounter:theStrongStuff";
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
const defaultBiome = Biome.CAVE;
const defaultWave = 45;
describe("The Strong Stuff - Mystery Encounter", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
let scene: BattleScene;
beforeAll(() => {
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
});
beforeEach(async () => {
game = new GameManager(phaserGame);
scene = game.scene;
game.override.mysteryEncounterChance(100);
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([
[Biome.CAVE, [MysteryEncounterType.THE_STRONG_STUFF]],
[Biome.MOUNTAIN, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
])
);
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
vi.clearAllMocks();
vi.resetAllMocks();
});
it("should have the correct properties", async () => {
game.override.mysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF);
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
expect(TheStrongStuffEncounter.encounterType).toBe(MysteryEncounterType.THE_STRONG_STUFF);
expect(TheStrongStuffEncounter.encounterTier).toBe(MysteryEncounterTier.COMMON);
expect(TheStrongStuffEncounter.dialogue).toBeDefined();
expect(TheStrongStuffEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}:intro` }]);
expect(TheStrongStuffEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}:title`);
expect(TheStrongStuffEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}:description`);
expect(TheStrongStuffEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}:query`);
expect(TheStrongStuffEncounter.options.length).toBe(2);
});
it("should not spawn outside of CAVE biome", async () => {
game.override.startingBiome(Biome.MOUNTAIN);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.THE_STRONG_STUFF);
});
it("should not run below wave 10", async () => {
game.override.startingWave(9);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.THE_STRONG_STUFF);
});
it("should not run above wave 179", async () => {
game.override.startingWave(181);
await game.runToMysteryEncounter();
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
});
it("should initialize fully ", async () => {
vi.spyOn(scene, "currentBattle", "get").mockReturnValue({ mysteryEncounter: TheStrongStuffEncounter } as Battle);
const moveInitSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets");
const moveLoadSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets");
const { onInit } = TheStrongStuffEncounter;
expect(TheStrongStuffEncounter.onInit).toBeDefined();
const onInitResult = onInit(scene);
expect(TheStrongStuffEncounter.enemyPartyConfigs).toEqual([
{
levelAdditiveMultiplier: 1,
disableSwitch: true,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.SHUCKLE),
isBoss: true,
bossSegments: 5,
spriteScale: 1.5,
nature: Nature.BOLD,
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
modifierTypes: expect.any(Array),
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: expect.any(Function)
}
],
}
]);
await vi.waitFor(() => expect(moveInitSpy).toHaveBeenCalled());
await vi.waitFor(() => expect(moveLoadSpy).toHaveBeenCalled());
expect(onInitResult).toBe(true);
});
describe("Option 1 - Power Swap BSTs", () => {
it("should have the correct properties", () => {
const option1 = TheStrongStuffEncounter.options[0];
expect(option1.optionMode).toBe(EncounterOptionMode.DEFAULT);
expect(option1.dialogue).toBeDefined();
expect(option1.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:1:label`,
buttonTooltip: `${namespace}:option:1:tooltip`,
selected: [
{
text: `${namespace}:option:1:selected`,
},
],
});
});
it("should lower stats of highest BST and raise stats for rest of party", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
const bstsPrior = scene.getParty().map(p => p.getSpeciesForm().getBaseStatTotal());
await runSelectMysteryEncounterOption(game, 1);
const bstsAfter = scene.getParty().map(p => {
return p.getSpeciesForm().getBaseStatTotal();
});
expect(bstsAfter[0]).toEqual(bstsPrior[0] - 20 * 6);
expect(bstsAfter[1]).toEqual(bstsPrior[1] + 10 * 6);
expect(bstsAfter[2]).toEqual(bstsPrior[2] + 10 * 6);
});
it("should leave encounter without battle", async () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
await runSelectMysteryEncounterOption(game, 1);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
});
describe("Option 2 - battle the Shuckle", () => {
it("should have the correct properties", () => {
const option1 = TheStrongStuffEncounter.options[1];
expect(option1.optionMode).toBe(EncounterOptionMode.DEFAULT);
expect(option1.dialogue).toBeDefined();
expect(option1.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:2:label`,
buttonTooltip: `${namespace}:option:2:tooltip`,
selected: [
{
text: `${namespace}:option:2:selected`,
},
],
});
});
it("should start battle against Shuckle", async () => {
const phaseSpy = vi.spyOn(scene, "pushPhase");
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
await runSelectMysteryEncounterOption(game, 2, true);
const enemyField = scene.getEnemyField();
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(Species.SHUCKLE);
expect(enemyField[0].summonData.battleStats).toEqual([0, 2, 0, 2, 0, 0, 0]);
const shuckleItems = scene.getModifiers(Modifiers.BerryModifier, false);
expect(shuckleItems.length).toBe(4);
expect(shuckleItems.find(m => m.berryType === BerryType.SITRUS)?.stackCount).toBe(1);
expect(shuckleItems.find(m => m.berryType === BerryType.GANLON)?.stackCount).toBe(1);
expect(shuckleItems.find(m => m.berryType === BerryType.APICOT)?.stackCount).toBe(1);
expect(shuckleItems.find(m => m.berryType === BerryType.LUM)?.stackCount).toBe(2);
expect(enemyField[0].moveset).toEqual([new PokemonMove(Moves.INFESTATION), new PokemonMove(Moves.SALT_CURE), new PokemonMove(Moves.GASTRO_ACID), new PokemonMove(Moves.HEAL_ORDER)]);
// Should have used moves pre-battle
const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]);
expect(movePhases.length).toBe(2);
expect(movePhases.filter(p => (p as MovePhase).move.moveId === Moves.GASTRO_ACID).length).toBe(1);
expect(movePhases.filter(p => (p as MovePhase).move.moveId === Moves.STEALTH_ROCK).length).toBe(1);
});
it("should have Soul Dew in rewards", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
await runSelectMysteryEncounterOption(game, 2, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
expect(modifierSelectHandler.options.length).toEqual(3);
expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("SOUL_DEW");
});
});
});

View File

@ -243,7 +243,7 @@ describe("Mystery Encounter Utils", () => {
arceus.hp = 100;
expect(arceus.isAllowedInBattle()).toBe(true);
koPlayerPokemon(arceus);
koPlayerPokemon(scene, arceus);
expect(arceus.isAllowedInBattle()).toBe(false);
});
});

View File

@ -38,6 +38,7 @@ import {MysteryEncounterPhase} from "#app/phases/mystery-encounter-phases";
import { OverridesHelper } from "./overridesHelper";
import { expect } from "vitest";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { isNullOrUndefined } from "#app/utils";
/**
* Class to manage the game state and transitions between phases.
@ -151,6 +152,11 @@ export default class GameManager {
* @returns A promise that resolves when the EncounterPhase ends.
*/
async runToMysteryEncounter(encounterType?: MysteryEncounterType, species?: Species[]) {
if (!isNullOrUndefined(encounterType)) {
this.override.disableTrainerWave(true);
this.override.mysteryEncounter(encounterType);
}
await this.runToTitle();
this.onNextPrompt("TitlePhase", Mode.TITLE, () => {
@ -167,7 +173,7 @@ export default class GameManager {
}, () => this.isCurrentPhase(MysteryEncounterPhase), true);
await this.phaseInterceptor.run(EncounterPhase);
if (encounterType) {
if (!isNullOrUndefined(encounterType)) {
expect(this.scene.currentBattle?.mysteryEncounter?.encounterType).toBe(encounterType);
}
}

View File

@ -285,7 +285,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
this.cursor = cursor;
}
this.viewPartyIndex = this.optionsContainer.length - 1;
this.viewPartyIndex = this.optionsContainer.list?.length - 1;
if (!this.cursorObj) {
this.cursorObj = this.scene.add.image(0, 0, "cursor");
@ -294,11 +294,11 @@ export default class MysteryEncounterUiHandler extends UiHandler {
if (cursor === this.viewPartyIndex) {
this.cursorObj.setPosition(246, -17);
} else if (this.optionsContainer.length === 3) { // 2 Options
} else if (this.optionsContainer.list?.length === 3) { // 2 Options
this.cursorObj.setPosition(-10.5 + (cursor % 2 === 1 ? 100 : 0), 15);
} else if (this.optionsContainer.length === 4) { // 3 Options
} else if (this.optionsContainer.list?.length === 4) { // 3 Options
this.cursorObj.setPosition(-10.5 + (cursor % 2 === 1 ? 100 : 0), 7 + (cursor > 1 ? 16 : 0));
} else if (this.optionsContainer.length === 5) { // 4 Options
} else if (this.optionsContainer.list?.length === 5) { // 4 Options
this.cursorObj.setPosition(-10.5 + (cursor % 2 === 1 ? 100 : 0), 7 + (cursor > 1 ? 16 : 0));
}