diff --git a/src/battle.ts b/src/battle.ts index ab47eac7993..9076eba7d0a 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -206,7 +206,7 @@ export default class Battle { getBgmOverride(scene: BattleScene): string { const battlers = this.enemyParty.slice(0, this.getBattlerCount()); if (this.battleType === BattleType.TRAINER || this.mysteryEncounter?.encounterVariant === MysteryEncounterVariant.TRAINER_BATTLE) { - if (!this.started && this.trainer.config.encounterBgm && this.trainer.getEncounterMessages()?.length) { + if (!this.started && this.trainer?.config?.encounterBgm && this.trainer?.getEncounterMessages()?.length) { return `encounter_${this.trainer.getEncounterBgm()}`; } if (scene.musicPreference === 0) { diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 23ccba9c32e..698b374497b 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -71,7 +71,7 @@ const excludedBosses = [ /** * Dark Deal encounter. * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/61 | GitHub Issue #61} - * @see For biome requirements check [mysteryEncountersByBiome](../mystery-encounters.ts) + * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ export const DarkDealEncounter: IMysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DARK_DEAL) diff --git a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts index 2e8a5ff67bd..131dc68aa14 100644 --- a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts +++ b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts @@ -18,7 +18,7 @@ const namespace = "mysteryEncounter:departmentStoreSale"; /** * Department Store Sale encounter. * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/33 | GitHub Issue #33} - * @see For biome requirements check [mysteryEncountersByBiome](../mystery-encounters.ts) + * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ export const DepartmentStoreSaleEncounter: IMysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DEPARTMENT_STORE_SALE) diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts index 45ac52eb161..21db15ac7e0 100644 --- a/src/data/mystery-encounters/encounters/field-trip-encounter.ts +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -24,7 +24,7 @@ const namespace = "mysteryEncounter:fieldTrip"; /** * Field Trip encounter. * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/17 | GitHub Issue #17} - * @see For biome requirements check [mysteryEncountersByBiome](../mystery-encounters.ts) + * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ export const FieldTripEncounter: IMysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIELD_TRIP) diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 2a7c26995d3..ae7d26feece 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -31,7 +31,7 @@ const DAMAGE_PERCENTAGE: number = 20; /** * Fiery Fallout encounter. * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/88 | GitHub Issue #88} - * @see For biome requirements check [mysteryEncountersByBiome](../mystery-encounters.ts) + * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ export const FieryFalloutEncounter: IMysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIERY_FALLOUT) @@ -127,8 +127,13 @@ export const FieryFalloutEncounter: IMysteryEncounter = async (scene: BattleScene) => { // Pick battle const encounter = scene.currentBattle.mysteryEncounter; - const charcoal = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FIRE]); - setEncounterRewards(scene, { guaranteedModifierTypeOptions: [charcoal], fillRemaining: true }); + setEncounterRewards(scene, + { fillRemaining: true }, + null, + () => { + giveLeadPokemonCharcoal(scene); + }); + encounter.startOfBattleEffects.push( { sourceBattlerIndex: BattlerIndex.ENEMY, @@ -196,8 +201,8 @@ export const FieryFalloutEncounter: IMysteryEncounter = .withOption( new MysteryEncounterOptionBuilder() .withOptionMode(EncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new TypeRequirement(Type.FIRE, true,1)) // Will set option3PrimaryName dialogue token automatically - .withSecondaryPokemonRequirement(new TypeRequirement(Type.FIRE, true,1)) // Will set option3SecondaryName dialogue token automatically + .withPrimaryPokemonRequirement(new TypeRequirement(Type.STEEL, true,1)) // Will set option3PrimaryName dialogue token automatically + .withSecondaryPokemonRequirement(new TypeRequirement(Type.STEEL, true,1)) // Will set option3SecondaryName dialogue token automatically .withDialogue({ buttonLabel: `${namespace}:option:3:label`, buttonTooltip: `${namespace}:option:3:tooltip`, @@ -215,8 +220,12 @@ export const FieryFalloutEncounter: IMysteryEncounter = // Fire types help calm the Volcarona const encounter = scene.currentBattle.mysteryEncounter; transitionMysteryEncounterIntroVisuals(scene); - const charcoal = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FIRE]); - setEncounterRewards(scene, { guaranteedModifierTypeOptions: [charcoal], fillRemaining: true }); + setEncounterRewards(scene, + { fillRemaining: true }, + null, + () => { + giveLeadPokemonCharcoal(scene); + }); const primary = encounter.options[2].primaryPokemon; const secondary = encounter.options[2].secondaryPokemon[0]; @@ -227,3 +236,15 @@ export const FieryFalloutEncounter: IMysteryEncounter = .build() ) .build(); + +function giveLeadPokemonCharcoal(scene: BattleScene) { + // Give first party pokemon Charcoal for free at end of battle + const leadPokemon = scene.getParty()?.[0]; + if (leadPokemon) { + const charcoal = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FIRE]); + scene.addModifier(charcoal.type.newModifier(leadPokemon), true); + scene.updateModifiers(); + scene.currentBattle.mysteryEncounter.setDialogueToken("leadPokemon", leadPokemon.name); + queueEncounterMessage(scene, `${namespace}:found_charcoal`); + } +} diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index ca64ba30552..2d142f46d4e 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -35,7 +35,7 @@ const namespace = "mysteryEncounter:fightOrFlight"; /** * Fight or Flight encounter. * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/24 | GitHub Issue #24} - * @see For biome requirements check [mysteryEncountersByBiome](../mystery-encounters.ts) + * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ export const FightOrFlightEncounter: IMysteryEncounter = MysteryEncounterBuilder.withEncounterType( diff --git a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts index fb7d1bcbbb5..3418f977ccd 100644 --- a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts +++ b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts @@ -21,7 +21,7 @@ const namespace = "mysteryEncounter:lostAtSea"; /** * Lost at sea encounter. * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/9 | GitHub Issue #9} - * @see For biome requirements check [mysteryEncountersByBiome](../mystery-encounters.ts) + * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.LOST_AT_SEA) .withEncounterTier(MysteryEncounterTier.COMMON) diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index b1b61434511..382dc1f212e 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -26,7 +26,7 @@ const namespace = "mysteryEncounter:mysteriousChallengers"; /** * Mysterious Challengers encounter. * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/41 | GitHub Issue #41} - * @see For biome requirements check [mysteryEncountersByBiome](../mystery-encounters.ts) + * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ export const MysteriousChallengersEncounter: IMysteryEncounter = MysteryEncounterBuilder.withEncounterType( diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index a93df6cb264..620d5380754 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -14,7 +14,7 @@ const namespace = "mysteryEncounter:mysteriousChest"; /** * Mysterious Chest encounter. * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/32 | GitHub Issue #32} - * @see For biome requirements check [mysteryEncountersByBiome](../mystery-encounters.ts) + * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ export const MysteriousChestEncounter: IMysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHEST) diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index 164ac112596..e5c5bd39b4d 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -24,7 +24,7 @@ const TRAINER_THROW_ANIMATION_TIMES = [512, 184, 768]; /** * Safari Zone encounter. * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/39 | GitHub Issue #39} - * @see For biome requirements check [mysteryEncountersByBiome](../mystery-encounters.ts) + * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ export const SafariZoneEncounter: IMysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SAFARI_ZONE) diff --git a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts index 082eb9a1872..de20a1e11b0 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -17,7 +17,7 @@ const namespace = "mysteryEncounter:shadyVitaminDealer"; /** * Shady Vitamin Dealer encounter. * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/34 | GitHub Issue #34} - * @see For biome requirements check [mysteryEncountersByBiome](../mystery-encounters.ts) + * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ export const ShadyVitaminDealerEncounter: IMysteryEncounter = MysteryEncounterBuilder.withEncounterType( diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index 2efae68c9eb..c905e0bd222 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -21,7 +21,7 @@ const namespace = "mysteryEncounter:slumberingSnorlax"; /** * Sleeping Snorlax encounter. * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/103 | GitHub Issue #103} - * @see For biome requirements check [mysteryEncountersByBiome](../mystery-encounters.ts) + * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ export const SlumberingSnorlaxEncounter: IMysteryEncounter = MysteryEncounterBuilder.withEncounterType( diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index e417bcc7ef4..2800a0befd1 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -24,7 +24,7 @@ const namespace = "mysteryEncounter:trainingSession"; /** * Training Session encounter. * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/43 | GitHub Issue #43} - * @see For biome requirements check [mysteryEncountersByBiome](../mystery-encounters.ts) + * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ export const TrainingSessionEncounter: IMysteryEncounter = MysteryEncounterBuilder.withEncounterType( diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index 289800d0b52..2162f30a994 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -71,8 +71,6 @@ export default interface IMysteryEncounter { onInit?: (scene: BattleScene) => boolean; /** Event when battlefield visuals have finished sliding in and the encounter dialogue begins */ onVisualsStart?: (scene: BattleScene) => boolean; - /** Event right before MysteryEncounterPhase begins. Use for unshifting any phases before the actual encounter */ - onPreMysteryEncounterPhase?: (scene: BattleScene) => boolean; /** Will provide the player party EXP before rewards are displayed for that wave */ doEncounterExp?: (scene: BattleScene) => boolean; /** Will provide the player a rewards shop for that wave */ @@ -392,7 +390,6 @@ export class MysteryEncounterBuilder implements Partial { doEncounterRewards?: (scene: BattleScene) => boolean; onInit?: (scene: BattleScene) => boolean; onVisualsStart?: (scene: BattleScene) => boolean; - onPreMysteryEncounterPhase?: (scene: BattleScene) => boolean; hideBattleIntroMessage?: boolean; hideIntroVisuals?: boolean; @@ -625,18 +622,6 @@ export class MysteryEncounterBuilder implements Partial { return Object.assign(this, { onInit: onInit }); } - /** - * Event callback right before MysteryEncounterPhase begins. - * Use for unshifting any last-minute phases before the actual encounter, as all phases are cleared and reset at that point. - * Example: set the weather before encounter begins - * - * @param onVisualsStart - synchronous callback function to perform immediately before MysteryEncounterPhase begins - * @returns - */ - withOnPreMysteryEncounterPhase(onPreMysteryEncounterPhase: (scene: BattleScene) => boolean): this & Required> { - return Object.assign(this, { onPreMysteryEncounterPhase: onPreMysteryEncounterPhase }); - } - /** * Can be used to perform some extra logic (usually animations) when the enemy field is finished sliding in * diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index d4720ce3e47..c07307b0597 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -50,7 +50,7 @@ export function doTrainerExclamation(scene: BattleScene) { } }); - scene.playSound("GEN8- Exclaim.wav"); + scene.playSound("GEN8- Exclaim.wav", { volume: 0.8 }); } export interface EnemyPokemonConfig { @@ -417,10 +417,10 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p * Can have shop displayed or skipped * @param scene - Battle Scene * @param customShopRewards - adds a shop phase with the specified rewards / reward tiers - * @param nonShopRewards - will add a non-shop reward phase for each specified item/modifier (can happen in addition to a shop) + * @param nonShopPlayerItemRewards - will add a non-shop reward phase for each specified item/modifier (can happen in addition to a shop) * @param preRewardsCallback - can execute an arbitrary callback before the new phases if necessary (useful for updating items/party/injecting new phases before MysteryEncounterRewardsPhase) */ -export function setEncounterRewards(scene: BattleScene, customShopRewards?: CustomModifierSettings, nonShopRewards?: ModifierTypeFunc[], preRewardsCallback?: Function) { +export function setEncounterRewards(scene: BattleScene, customShopRewards?: CustomModifierSettings, nonShopPlayerItemRewards?: ModifierTypeFunc[], preRewardsCallback?: Function) { scene.currentBattle.mysteryEncounter.doEncounterRewards = (scene: BattleScene) => { if (preRewardsCallback) { preRewardsCallback(); @@ -432,8 +432,8 @@ export function setEncounterRewards(scene: BattleScene, customShopRewards?: Cust scene.tryRemovePhase(p => p instanceof SelectModifierPhase); } - if (nonShopRewards?.length > 0) { - nonShopRewards.forEach((reward) => { + if (nonShopPlayerItemRewards?.length > 0) { + nonShopPlayerItemRewards.forEach((reward) => { scene.unshiftPhase(new ModifierRewardPhase(scene, reward)); }); } else { @@ -679,7 +679,7 @@ export function handleEncounterStartOfBattleEffects(scene: BattleScene) { } else { source = scene.getEnemyField()[0]; } - scene.pushPhase(new MovePhase(scene, source, effect.targets, effect.move, effect.followUp, effect.followUp)); + scene.pushPhase(new MovePhase(scene, source, effect.targets, effect.move, effect.followUp, effect.ignorePp)); }); // Pseudo turn end phase to reset flinch states, Endure, etc. diff --git a/src/locales/en/mystery-encounters/fiery-fallout-dialogue.ts b/src/locales/en/mystery-encounters/fiery-fallout-dialogue.ts index 6f93cb239b1..2fbf5a15bda 100644 --- a/src/locales/en/mystery-encounters/fiery-fallout-dialogue.ts +++ b/src/locales/en/mystery-encounters/fiery-fallout-dialogue.ts @@ -8,7 +8,7 @@ export const fieryFalloutDialogue = { label: "Find the source", tooltip: "(?) Discover the source\n(-) Hard Battle", selected: `You push through the storm, and find two Volcarona in the middle of a mating dance! - $They don't take kindly to the interruption and attack!`, + $They don't take kindly to the interruption and attack!` }, 2: { label: "Hunker down", @@ -24,5 +24,7 @@ export const fieryFalloutDialogue = { selected: `Your {{option3PrimaryName}} and {{option3SecondaryName}} guide you to where two Volcarona are in the middle of a mating dance! $Thankfully, your Pokémon are able to calm them,\nand they depart without issue.`, }, - } + }, + found_charcoal: `After the weather clears,\nyour {{leadPokemon}} spots something on the ground. + $@s{item_fanfare}{{leadPokemon}} gained a Charcoal!` }; diff --git a/src/phases.ts b/src/phases.ts index 2e6c8224de2..bbf3115e671 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1101,11 +1101,6 @@ export class EncounterPhase extends BattlePhase { this.scene.ui.clearText(); this.scene.ui.getMessageHandler().hideNameText(); - // Can add any additional unshift phases here before MysteryEncounterPhase begins (and all phase queues are cleared) - if (this.scene.currentBattle.mysteryEncounter.onPreMysteryEncounterPhase) { - this.scene.currentBattle.mysteryEncounter.onPreMysteryEncounterPhase(this.scene); - } - this.scene.unshiftPhase(new MysteryEncounterPhase(this.scene)); this.end(); }; diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index 1ac00cbea8e..25f6b140140 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -184,7 +184,7 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase { pokemon.lapseTags(BattlerTagLapseType.TURN_END); }); - this.end(); + super.end(); } } diff --git a/src/test/mystery-encounter/encounterTestUtils.ts b/src/test/mystery-encounter/encounterTestUtils.ts index 6caff2a8693..82975092a0f 100644 --- a/src/test/mystery-encounter/encounterTestUtils.ts +++ b/src/test/mystery-encounter/encounterTestUtils.ts @@ -1,15 +1,16 @@ import { Button } from "#app/enums/buttons"; -import { MessagePhase, VictoryPhase } from "#app/phases"; +import { CommandPhase, MessagePhase, VictoryPhase } from "#app/phases"; import { MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases"; 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"; +import { Status, StatusEffect } from "#app/data/status-effect"; -export async function runSelectMysteryEncounterOption(game: GameManager, optionNo: number) { +export async function runSelectMysteryEncounterOption(game: GameManager, optionNo: number, isBattle: boolean = false) { // Handle any eventual queued messages (e.g. weather phase, etc.) game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => { - const uiHandler = game.scene.ui.getHandler(); + const uiHandler = game.scene.ui.getHandler(); uiHandler.processInput(Button.ACTION); }); @@ -23,7 +24,7 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN uiHandler.processInput(Button.ACTION); }); - await game.phaseInterceptor.run(MysteryEncounterPhase); + await game.phaseInterceptor.to(MysteryEncounterPhase, true); // select the desired option const uiHandler = game.scene.ui.getHandler(); @@ -73,5 +74,23 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN uiHandler.processInput(Button.ACTION); }); - await game.phaseInterceptor.to(MysteryEncounterRewardsPhase); + if (isBattle) { + await game.phaseInterceptor.to(CommandPhase); + } else { + await game.phaseInterceptor.to(MysteryEncounterRewardsPhase); + } +} + +export async function skipBattleRunMysteryEncounterRewardsPhase(game: GameManager) { + game.scene.clearPhaseQueue(); + game.scene.clearPhaseQueueSplice(); + game.scene.getEnemyParty().forEach(p => { + p.hp = 0; + p.status = new Status(StatusEffect.FAINT); + game.scene.field.remove(p); + }); + game.scene.unshiftPhase(new VictoryPhase(game.scene, 0)); + game.endPhase(); + game.phaseInterceptor.superEndPhase(); + await game.phaseInterceptor.to(MysteryEncounterRewardsPhase, true); } diff --git a/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts b/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts index 0c53066e5ff..a88a31968e2 100644 --- a/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts @@ -5,6 +5,16 @@ import { Species } from "#app/enums/species"; import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter"; +import Battle from "#app/battle"; +import { Gender } from "#app/data/gender"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; +import * as BattleAnims from "#app/data/battle-anims"; +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 { PokemonHeldItemModifier } from "#app/modifier/modifier"; const namespace = "mysteryEncounter:fieryFallout"; /** Arcanine and Ninetails for 2 Fire types. Lapras for burnable mon. */ @@ -15,6 +25,7 @@ const defaultWave = 45; describe("Fiery Fallout - Mystery Encounter", () => { let phaserGame: Phaser.Game; let game: GameManager; + let scene: BattleScene; beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS }); @@ -22,13 +33,14 @@ describe("Fiery Fallout - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); + scene = game.scene; game.override.mysteryEncounterChance(100); game.override.startingWave(defaultWave); game.override.startingBiome(defaultBiome); vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( new Map([ - [Biome.SEA, [MysteryEncounterType.FIERY_FALLOUT]], + [Biome.VOLCANO, [MysteryEncounterType.FIERY_FALLOUT]], [Biome.MOUNTAIN, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]], ]) ); @@ -54,7 +66,7 @@ describe("Fiery Fallout - Mystery Encounter", () => { game.override.startingBiome(Biome.MOUNTAIN); await game.runToMysteryEncounter(); - expect(game.scene.currentBattle.mysteryEncounter.encounterType).not.toBe(MysteryEncounterType.LOST_AT_SEA); + expect(scene.currentBattle.mysteryEncounter.encounterType).not.toBe(MysteryEncounterType.LOST_AT_SEA); }); it("should not run below wave 41", async () => { @@ -62,7 +74,7 @@ describe("Fiery Fallout - Mystery Encounter", () => { await game.runToMysteryEncounter(); - expect(game.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 () => { @@ -70,164 +82,186 @@ describe("Fiery Fallout - Mystery Encounter", () => { await game.runToMysteryEncounter(); - expect(game.scene.currentBattle.mysteryEncounter).toBeUndefined(); + expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); }); - // it("should set the correct dialog tokens during initialization", () => { - // vi.spyOn(game.scene, "currentBattle", "get").mockReturnValue({ mysteryEncounter: FieryFalloutEncounter } as Battle); - // - // const { onInit } = FieryFalloutEncounter; - // - // expect(FieryFalloutEncounter.onInit).toBeDefined(); - // - // const onInitResult = onInit(game.scene); - // - // expect(FieryFalloutEncounter.dialogueTokens?.damagePercentage).toBe("25"); - // expect(FieryFalloutEncounter.dialogueTokens?.option1RequiredMove).toBe(Moves[Moves.SURF]); - // expect(FieryFalloutEncounter.dialogueTokens?.option2RequiredMove).toBe(Moves[Moves.FLY]); - // expect(onInitResult).toBe(true); - // }); + 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 moveLoadSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets"); - // describe("Option 1 - Fight 2 Volcarona", () => { - // it("should have the correct properties", () => { - // const option1 = LostAtSeaEncounter.options[0]; - // expect(option1.optionMode).toBe(EncounterOptionMode.DISABLED_OR_DEFAULT); - // expect(option1.dialogue).toBeDefined(); - // expect(option1.dialogue).toStrictEqual({ - // buttonLabel: `${namespace}:option:1:label`, - // disabledButtonLabel: `${namespace}:option:1:label_disabled`, - // buttonTooltip: `${namespace}:option:1:tooltip`, - // disabledButtonTooltip: `${namespace}:option:1:tooltip_disabled`, - // selected: [ - // { - // text: `${namespace}:option:1:selected`, - // }, - // ], - // }); - // }); - // - // it("should award exp to surfable PKM (Blastoise)", async () => { - // const laprasSpecies = getPokemonSpecies(Species.LAPRAS); - // - // await game.runToMysteryEncounter(defaultParty); - // const party = game.scene.getParty(); - // const blastoise = party.find((pkm) => pkm.species.speciesId === Species.PIDGEOT); - // const expBefore = blastoise.exp; - // - // await runSelectMysteryEncounterOption(game, 2); - // - // expect(blastoise.exp).toBe(expBefore + Math.floor(laprasSpecies.baseExp * defaultWave / 5 + 1)); - // }); - // - // it("should leave encounter without battle", async () => { - // game.override.startingWave(33); - // const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); - // - // await game.runToMysteryEncounter(defaultParty); - // await runSelectMysteryEncounterOption(game, 1); - // - // expect(leaveEncounterWithoutBattleSpy).toBeCalled(); - // }); - // - // it("should be disabled if no surfable PKM is in party", async () => { - // // TODO - // }); - // }); + const { onInit } = FieryFalloutEncounter; - // describe("Option 2 - Suffer the weather", () => { - // it("should have the correct properties", () => { - // const option2 = LostAtSeaEncounter.options[1]; - // - // expect(option2.optionMode).toBe(EncounterOptionMode.DISABLED_OR_DEFAULT); - // expect(option2.dialogue).toBeDefined(); - // expect(option2.dialogue).toStrictEqual({ - // buttonLabel: `${namespace}:option:2:label`, - // disabledButtonLabel: `${namespace}:option:2:label_disabled`, - // buttonTooltip: `${namespace}:option:2:tooltip`, - // disabledButtonTooltip: `${namespace}:option:2:tooltip_disabled`, - // selected: [ - // { - // text: `${namespace}:option:2:selected`, - // }, - // ], - // }); - // }); - // - // it("should award exp to flyable PKM (Pidgeot)", async () => { - // const laprasBaseExp = 187; - // const wave = 33; - // game.override.startingWave(wave); - // - // await game.runToMysteryEncounter(defaultParty); - // const party = game.scene.getParty(); - // const pidgeot = party.find((pkm) => pkm.species.speciesId === Species.PIDGEOT); - // const expBefore = pidgeot.exp; - // - // await runSelectMysteryEncounterOption(game, 2); - // - // expect(pidgeot.exp).toBe(expBefore + Math.floor(laprasBaseExp * defaultWave / 5 + 1)); - // }); - // - // it("should leave encounter without battle", async () => { - // game.override.startingWave(33); - // const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); - // - // await game.runToMysteryEncounter(defaultParty); - // await runSelectMysteryEncounterOption(game, 2); - // - // expect(leaveEncounterWithoutBattleSpy).toBeCalled(); - // }); - // - // it("should be disabled if no flyable PKM is in party", async () => { - // // TODO - // }); - // }); + expect(FieryFalloutEncounter.onInit).toBeDefined(); - // describe("Option 3 - use FIRE types", () => { - // it("should have the correct properties", () => { - // const option3 = LostAtSeaEncounter.options[2]; - // - // expect(option3.optionMode).toBe(EncounterOptionMode.DEFAULT); - // expect(option3.dialogue).toBeDefined(); - // expect(option3.dialogue).toStrictEqual({ - // buttonLabel: `${namespace}:option:3:label`, - // buttonTooltip: `${namespace}:option:3:tooltip`, - // selected: [ - // { - // text: `${namespace}:option:3:selected`, - // }, - // ], - // }); - // }); - // - // it("should damage all (allowed in battle) party PKM by 25%", async () => { - // game.override.startingWave(33); - // - // await game.runToMysteryEncounter(defaultParty); - // - // const party = game.scene.getParty(); - // const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA); - // vi.spyOn(abra, "isAllowedInBattle").mockReturnValue(false); - // - // await runSelectMysteryEncounterOption(game, 3); - // - // const allowedPkm = party.filter((pkm) => pkm.isAllowedInBattle()); - // const notAllowedPkm = party.filter((pkm) => !pkm.isAllowedInBattle()); - // allowedPkm.forEach((pkm) => - // expect(pkm.hp, `${pkm.name} should have receivd 25% damage: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp() - Math.floor(pkm.getMaxHp() * 0.25)) - // ); - // - // notAllowedPkm.forEach((pkm) => expect(pkm.hp, `${pkm.name} should be full hp: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp())); - // }); - // - // it("should leave encounter without battle", async () => { - // game.override.startingWave(33); - // const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); - // - // await game.runToMysteryEncounter(defaultParty); - // await runSelectMysteryEncounterOption(game, 3); - // - // expect(leaveEncounterWithoutBattleSpy).toBeCalled(); - // }); - // }); + const onInitResult = onInit(scene); + + expect(FieryFalloutEncounter.enemyPartyConfigs).toEqual([ + { + pokemonConfigs: [ + { + species: getPokemonSpecies(Species.VOLCARONA), + isBoss: false, + gender: Gender.MALE + }, + { + species: getPokemonSpecies(Species.VOLCARONA), + isBoss: false, + gender: Gender.FEMALE + } + ], + doubleBattle: true, + disableSwitch: true + } + ]); + expect(weatherSpy).toHaveBeenCalledTimes(1); + await vi.waitFor(() => expect(moveInitSpy).toHaveBeenCalled()); + await vi.waitFor(() => expect(moveLoadSpy).toHaveBeenCalled()); + expect(onInitResult).toBe(true); + }); + + describe("Option 1 - Fight 2 Volcarona", () => { + it("should have the correct properties", () => { + const option1 = FieryFalloutEncounter.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 start battle against 2 Volcarona", async () => { + const phaseSpy = vi.spyOn(scene, "pushPhase"); + + await game.runToMysteryEncounter(defaultParty); + await runSelectMysteryEncounterOption(game, 1, true); + + const enemyField = scene.getEnemyField(); + expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); + expect(enemyField.length).toBe(2); + expect(enemyField[0].species.speciesId).toBe(Species.VOLCARONA); + expect(enemyField[1].species.speciesId).toBe(Species.VOLCARONA); + expect(enemyField[0].gender).not.toEqual(enemyField[1].gender); // Should be opposite gender + + const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]); + expect(movePhases.length).toBe(4); + expect(movePhases.filter(p => (p as MovePhase).move.moveId === Moves.FIRE_SPIN).length).toBe(2); // Fire spin used twice before battle + expect(movePhases.filter(p => (p as MovePhase).move.moveId === Moves.QUIVER_DANCE).length).toBe(2); // Quiver Dance used twice before battle + }); + + it("should give charcoal to lead pokemon", async () => { + await game.runToMysteryEncounter(defaultParty); + await runSelectMysteryEncounterOption(game, 1, true); + await skipBattleRunMysteryEncounterRewardsPhase(game); + await game.phaseInterceptor.to(SelectModifierPhase, false); + expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name); + + const leadPokemonId = scene.getParty()?.[0].id; + const leadPokemonItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier + && (m as PokemonHeldItemModifier).pokemonId === leadPokemonId, true) as PokemonHeldItemModifier[]; + const charcoal = leadPokemonItems.find(i => i.type.name === "Charcoal"); + expect(charcoal).toBeDefined; + }); + }); + + describe("Option 2 - Suffer the weather", () => { + it("should have the correct properties", () => { + const option1 = FieryFalloutEncounter.options[0]; + 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 damage all (allowed in battle) party PKM by 25%", async () => { + game.override.startingWave(33); + + await game.runToMysteryEncounter(defaultParty); + + const party = scene.getParty(); + const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA); + vi.spyOn(abra, "isAllowedInBattle").mockReturnValue(false); + + await runSelectMysteryEncounterOption(game, 3); + + const allowedPkm = party.filter((pkm) => pkm.isAllowedInBattle()); + const notAllowedPkm = party.filter((pkm) => !pkm.isAllowedInBattle()); + allowedPkm.forEach((pkm) => + expect(pkm.hp, `${pkm.name} should have receivd 25% damage: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp() - Math.floor(pkm.getMaxHp() * 0.25)) + ); + + notAllowedPkm.forEach((pkm) => expect(pkm.hp, `${pkm.name} should be full hp: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp())); + }); + + it("should leave encounter without battle", async () => { + game.override.startingWave(33); + const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); + + await game.runToMysteryEncounter(defaultParty); + await runSelectMysteryEncounterOption(game, 3); + + expect(leaveEncounterWithoutBattleSpy).toBeCalled(); + }); + }); + + describe("Option 3 - use FIRE types", () => { + it("should have the correct properties", () => { + const option1 = FieryFalloutEncounter.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 damage all (allowed in battle) party PKM by 25%", async () => { + game.override.startingWave(33); + + await game.runToMysteryEncounter(defaultParty); + + const party = scene.getParty(); + const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA); + vi.spyOn(abra, "isAllowedInBattle").mockReturnValue(false); + + await runSelectMysteryEncounterOption(game, 3); + + const allowedPkm = party.filter((pkm) => pkm.isAllowedInBattle()); + const notAllowedPkm = party.filter((pkm) => !pkm.isAllowedInBattle()); + allowedPkm.forEach((pkm) => + expect(pkm.hp, `${pkm.name} should have receivd 25% damage: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp() - Math.floor(pkm.getMaxHp() * 0.25)) + ); + + notAllowedPkm.forEach((pkm) => expect(pkm.hp, `${pkm.name} should be full hp: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp())); + }); + + it("should leave encounter without battle", async () => { + game.override.startingWave(33); + const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); + + await game.runToMysteryEncounter(defaultParty); + await runSelectMysteryEncounterOption(game, 3); + + expect(leaveEncounterWithoutBattleSpy).toBeCalled(); + }); + }); }); diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts index 39278c673d4..7114d63afa0 100644 --- a/src/test/utils/phaseInterceptor.ts +++ b/src/test/utils/phaseInterceptor.ts @@ -104,7 +104,8 @@ export default class PhaseInterceptor { [MysteryEncounterBattlePhase, this.startPhase], [MysteryEncounterRewardsPhase, this.startPhase], [PostMysteryEncounterPhase, this.startPhase], - [LearnMovePhase, this.startPhase] + [LearnMovePhase, this.startPhase], + // [CommonAnimPhase, this.startPhase] ]; private endBySetMode = [