pokerogue/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts
ImperialSympathizer acb2b66be4
[Feature] Add Mystery Encounters to the game (#3938)
* add .github/workflows/mystery-event.yml

* update mystery-event.yml

* mystery encounters: resolve review comments:

Lost at Sea:
-fix typo in handlePokemonGuidingYouPhase function

Mysterious Chest:
- remove obsolete commented code

mystery-encounter.ts
- remove unused `onDone` field from MysteryEncounterBuilder

* fix typo in CanLearnMoveRequirementOptions

* remove redundance from Pokemon.isAllowedInBattle()

* chore: jsdoc formatting

* fix lost-at-sea tests

* add fallback for biomeMysteryEncounters if empty

* lost-at-sea-encounter: fix and extend tests

* move "battle:fainted" into `koPlayerPokemon`

* add retries to quick-draw tests

* fix lost-at-sea-encounter tests

* clean up battle animation logic

* Update and rename mystery-event.yml to mystery-events.yml

* Update mystery-events.yml

* Fix typo

* Update mystery-events.yml

Fix debug runs

* clean up unit tests and utils

* attach github issues to all encounter jsdocs

* start dialogue refactor

* update sleeping snorlax encounter

* migrate encounters dialogue to new format

* cleanup and add jsdocs

* finish fiery fallout encounter

* fix unit test breaks

* add skeleton tests to fiery fallout

* commit latest test changes

* finish unit tests for fiery fallout

* bug fix for empty modifier shop

* stash working changes

* stash changes

* Update src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* Update src/test/utils/overridesHelper.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* Update src/test/utils/overridesHelper.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* Update src/test/utils/overridesHelper.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* Update src/test/utils/overridesHelper.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* Update src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* Update src/data/battle-anims.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* nit updates and cleanup

* Update src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* add jsdocs and more cleanup

* add more jsdoc

* add the strong stuff encounter

* add the strong stuff encounter and more unit tests

* cleanup container length checks in ME ui

* add retries to tests

* add retries to tests

* fix trainer wave disable override

* add shuckle juice modifier

* add dialogue bug fixes

* add dialogue bug fixes

* add pokemon salesman encounter and affects pokedex UI display

* add unit tests for pokemon salesman

* temp stash

* add offer you can't refuse

* add unit tests for offer you can't refuse encounter

* remove unnecessary prompt handlers

* add tests for disabled encounter options

* add delibird-y encounter

* add delibird-y encounter

* add absolute avarice encounter

* finish absolute avarice encounter

* add unit tests and enhancements for item overrides in tests

* fix unit test

* cleanup absolute avarice PR

* small bug fixes with latest sync from main

* update visuals loading for safari and stat trainer visuals

* update visuals loading for safari and stat trainer visuals

* update a trainer's test encounter and add unit tests

* add Trash to Treasure encounter

* clean up trash to treasure encounter

* clean up trash to treasure encounter

* add berries abound encounter

* start clowning around encounter

* first implementation pass at clowning around

* add unit tests for clowning around

* add unit tests for clowning around

* clean up ME unit tests

* clean up unit tests

* update unit tests

* add part timer and dancing lessons encounters

* add unit tests for Dancing Lessons and Part-Timer

* reordered biome list and adjusted redirection for project and labels

* Add Weird Dream encounter and slight reworks to Berries Abound/Fight or Flight

* adjusting yml to match new labels

* fix yml whoopsie

* Expanded 'Weird Dream' banlist and fixed a bug with the BST bump range

* adds Winstrate Challenge mystery encounter

* small cleanup for winstrates

* add unit tests for Winstrate Challenge

* fix pokemon not returning after winstrate battle

* commit latest beta merge updates

* fix ME null checks and unit tests with beta update

* fix ME null checks and unit tests with beta update

* MEs to pokerogue beta branch

* test dialogue changes

* test patch fix

* test patch fix

* test patch fix

* adds teleporting hijinks encounter

* add unit tests for Teleporting Hijinks

* small change to teleporting hijinks dialogue

* migrate ME translations to json

* add retries to berries-abound.Option1: should reward the player with X berries based on wave

* add missing ME dialogue back in

* revert template changes

* add ME unique trainer dialogue to both dialogue jsons

* fix hanging comma in json

* fix broken imports

* resolve lint issues

* fix flaky test

* balance tweaks to a few MEs, updates to bug superfan

* add unit tests for Bug-Type Superfan and clean up dialogue

* Adds Fun and Games mystery encounter

* add unit tests for Fun and Games encounter

* update jsdoc

* small ME balance changes

* small ME balance changes

* Adds Uncommon Breed ME and misc. ME bug fixes

* Update getFinalSessionData() to collect Mystery Encounter data

* adds GTS encounter

* various ME bug fixes and balance changes

* latest ME bug fixes

* clean up GTS Encounter and add unit tests

* small cleanup to MEs branch

* add BGM music names for ME music

* bug fixes and balance changes for MEs

* ME data schema updates

* balance changes and bug fixes to MEs

* balance changes and bug fixes to MEs

* update tests for MEs

* add jsdoc to party exp function

* dialogue updates and test fixes for MEs

* dialogue updates and test fixes for MEs

* PR suggestions and fixees

* stash PR feedback and bugfixes

* fix all tests for MEs and cleanup

* PR feedback

* update flaky ME test

* update tests, bug fix MEs, and sprite assets

* remove unintentional console log

* re-enable stubbed function for Phaser text styling

* handle undefined introVisuals properly

* PR feedback from NightKev

* disable Uncommon Breed tests

* locales updates and bug fixes for safari zone

* more PR feedback and update field trip with Rarer Candy

* fix unit test

* Change how reroll button gets disabled in Modifier Shop Phase

* update continue button text logic

* Update src/ui/modifier-select-ui-handler.ts

Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>

* fix money formatting and some nits

* more nits

* more nits

* update ME tsdocs with links

* update ME tsdocs with links

---------

Co-authored-by: Felix Staud <felix.staud@headwire.com>
Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>
Co-authored-by: ImperialSympathizer <imperialsympathizer@gmail.com>
Co-authored-by: InnocentGameDev <asdargmng@gmail.com>
Co-authored-by: Mumble <171087428+frutescens@users.noreply.github.com>
2024-09-14 03:05:58 +01:00

260 lines
11 KiB
TypeScript

import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
import { HUMAN_TRANSITABLE_BIOMES } 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 { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils";
import BattleScene from "#app/battle-scene";
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { AnOfferYouCantRefuseEncounter } from "#app/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves";
import { ShinyRateBoosterModifier } from "#app/modifier/modifier";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
const namespace = "mysteryEncounter:offerYouCantRefuse";
/** Gyarados for Indimidate */
const defaultParty = [Species.GYARADOS, Species.GENGAR, Species.ABRA];
const defaultBiome = Biome.CAVE;
const defaultWave = 45;
describe("An Offer You Can't Refuse - 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.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves();
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
]);
HUMAN_TRANSITABLE_BIOMES.forEach(biome => {
biomeMap.set(biome, [MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE]);
});
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(biomeMap);
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
vi.clearAllMocks();
vi.resetAllMocks();
});
it("should have the correct properties", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, defaultParty);
expect(AnOfferYouCantRefuseEncounter.encounterType).toBe(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE);
expect(AnOfferYouCantRefuseEncounter.encounterTier).toBe(MysteryEncounterTier.GREAT);
expect(AnOfferYouCantRefuseEncounter.dialogue).toBeDefined();
expect(AnOfferYouCantRefuseEncounter.dialogue.intro).toStrictEqual([
{ text: `${namespace}.intro` },
{ speaker: `${namespace}.speaker`, text: `${namespace}.intro_dialogue` }
]);
expect(AnOfferYouCantRefuseEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
expect(AnOfferYouCantRefuseEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
expect(AnOfferYouCantRefuseEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
expect(AnOfferYouCantRefuseEncounter.options.length).toBe(3);
});
it("should not spawn outside of HUMAN_TRANSITABLE_BIOMES", async () => {
game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT);
game.override.startingBiome(Biome.VOLCANO);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE);
});
it("should not run below wave 10", async () => {
game.override.startingWave(9);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE);
});
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 () => {
initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = AnOfferYouCantRefuseEncounter;
const { onInit } = AnOfferYouCantRefuseEncounter;
expect(AnOfferYouCantRefuseEncounter.onInit).toBeDefined();
AnOfferYouCantRefuseEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit!(scene);
expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.strongestPokemon).toBeDefined();
expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.price).toBeDefined();
expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.option2PrimaryAbility).toBe("Intimidate");
expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.moveOrAbility).toBe("Intimidate");
expect(AnOfferYouCantRefuseEncounter.misc.pokemon instanceof PlayerPokemon).toBeTruthy();
expect(AnOfferYouCantRefuseEncounter.misc?.price?.toString()).toBe(AnOfferYouCantRefuseEncounter.dialogueTokens?.price);
expect(onInitResult).toBe(true);
});
describe("Option 1 - Sell your Pokemon for money and a Shiny Charm", () => {
it("should have the correct properties", () => {
const option = AnOfferYouCantRefuseEncounter.options[0];
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
speaker: `${namespace}.speaker`,
text: `${namespace}.option.1.selected`,
},
],
});
});
it("Should update the player's money properly", async () => {
const initialMoney = 20000;
scene.money = initialMoney;
const updateMoneySpy = vi.spyOn(EncounterPhaseUtils, "updatePlayerMoney");
await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, defaultParty);
await runMysteryEncounterToEnd(game, 1);
const price = scene.currentBattle.mysteryEncounter!.misc.price;
expect(updateMoneySpy).toHaveBeenCalledWith(scene, price);
expect(scene.money).toBe(initialMoney + price);
});
it("Should give the player a Shiny Charm", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, defaultParty);
await runMysteryEncounterToEnd(game, 1);
const itemModifier = scene.findModifier(m => m instanceof ShinyRateBoosterModifier) as ShinyRateBoosterModifier;
expect(itemModifier).toBeDefined();
expect(itemModifier?.stackCount).toBe(1);
});
it("Should remove the Pokemon from the party", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, defaultParty);
const initialPartySize = scene.getParty().length;
const pokemonName = scene.currentBattle.mysteryEncounter!.misc.pokemon.name;
await runMysteryEncounterToEnd(game, 1);
expect(scene.getParty().length).toBe(initialPartySize - 1);
expect(scene.getParty().find(p => p.name === pokemonName)).toBeUndefined();
});
it("should leave encounter without battle", async () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, defaultParty);
await runMysteryEncounterToEnd(game, 1);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
});
describe("Option 2 - Extort the Kid", () => {
it("should have the correct properties", () => {
const option = AnOfferYouCantRefuseEncounter.options[1];
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL);
expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
disabledButtonTooltip: `${namespace}.option.2.tooltip_disabled`,
selected: [
{
speaker: `${namespace}.speaker`,
text: `${namespace}.option.2.selected`,
},
],
});
});
it("should award EXP to a pokemon with an ability in EXTORTION_ABILITIES", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, defaultParty);
const party = scene.getParty();
const gyarados = party.find((pkm) => pkm.species.speciesId === Species.GYARADOS)!;
const expBefore = gyarados.exp;
await runMysteryEncounterToEnd(game, 2);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(gyarados.exp).toBe(expBefore + Math.floor(getPokemonSpecies(Species.LIEPARD).baseExp * defaultWave / 5 + 1));
});
it("should award EXP to a pokemon with a move in EXTORTION_MOVES", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, [Species.ABRA]);
const party = scene.getParty();
const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA)!;
abra.moveset = [new PokemonMove(Moves.BEAT_UP)];
const expBefore = abra.exp;
await runMysteryEncounterToEnd(game, 2);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(abra.exp).toBe(expBefore + Math.floor(getPokemonSpecies(Species.LIEPARD).baseExp * defaultWave / 5 + 1));
});
it("Should update the player's money properly", async () => {
const initialMoney = 20000;
scene.money = initialMoney;
const updateMoneySpy = vi.spyOn(EncounterPhaseUtils, "updatePlayerMoney");
await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, defaultParty);
await runMysteryEncounterToEnd(game, 2);
const price = scene.currentBattle.mysteryEncounter!.misc.price;
expect(updateMoneySpy).toHaveBeenCalledWith(scene, price);
expect(scene.money).toBe(initialMoney + price);
});
it("should leave encounter without battle", async () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, defaultParty);
await runMysteryEncounterToEnd(game, 2);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
});
describe("Option 3 - Leave", () => {
it("should leave encounter without battle", async () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, defaultParty);
await runMysteryEncounterToEnd(game, 3);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
});
});