diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts deleted file mode 100644 index e8a7a238743..00000000000 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ /dev/null @@ -1,496 +0,0 @@ -import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate, } from "#app/data/trainer-config"; -import { ModifierTier } from "#app/modifier/modifier-tier"; -import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import { PartyMemberStrength } from "#enums/party-member-strength"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; -import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; -import { Species } from "#enums/species"; -import { TrainerType } from "#enums/trainer-type"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; -import { Abilities } from "#enums/abilities"; -import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; -import { Type } from "#app/data/type"; -import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; -import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { randSeedInt, randSeedShuffle } from "#app/utils"; -import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import { Mode } from "#app/ui/ui"; -import i18next from "i18next"; -import { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler"; -import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; -import { Ability } from "#app/data/ability"; -import { BerryModifier } from "#app/modifier/modifier"; -import { BerryType } from "#enums/berry-type"; -import { BattlerIndex } from "#app/battle"; -import { Moves } from "#enums/moves"; -import { EncounterAnim, EncounterBattleAnim } from "#app/data/battle-anims"; -import { MoveCategory } from "#app/data/move"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; - -/** the i18n namespace for the encounter */ -const namespace = "mysteryEncounter:clowningAround"; - -const RANDOM_ABILITY_POOL = [ - Abilities.STURDY, - Abilities.PICKUP, - Abilities.INTIMIDATE, - Abilities.GUTS, - Abilities.DROUGHT, - Abilities.DRIZZLE, - Abilities.SNOW_WARNING, - Abilities.SAND_STREAM, - Abilities.ELECTRIC_SURGE, - Abilities.PSYCHIC_SURGE, - Abilities.GRASSY_SURGE, - Abilities.MISTY_SURGE, - Abilities.MAGICIAN, - Abilities.SHEER_FORCE, - Abilities.PRANKSTER -]; - -/** - * Clowning Around encounter. - * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/69 | GitHub Issue #69} - * @see For biome requirements check {@linkcode mysteryEncountersByBiome} - */ -export const ClowningAroundEncounter: MysteryEncounter = - MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND) - .withEncounterTier(MysteryEncounterTier.ULTRA) - .withSceneWaveRangeRequirement(80, 180) - .withAnimations(EncounterAnim.SMOKESCREEN) - .withAutoHideIntroVisuals(false) - .withIntroSpriteConfigs([ - { - spriteKey: Species.MR_MIME.toString(), - fileRoot: "pokemon", - hasShadow: true, - repeat: true, - x: -25, - tint: 0.3, - y: -3, - yShadow: -3 - }, - { - spriteKey: Species.BLACEPHALON.toString(), - fileRoot: "pokemon/exp", - hasShadow: true, - repeat: true, - x: 25, - tint: 0.3, - y: -3, - yShadow: -3 - }, - { - spriteKey: "harlequin", - fileRoot: "trainer", - hasShadow: true, - x: 0, - y: 2, - yShadow: 2 - }, - ]) - .withIntroDialogue([ - { - text: `${namespace}.intro`, - }, - { - text: `${namespace}.intro_dialogue`, - speaker: `${namespace}.speaker` - }, - ]) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter; - - const clownTrainerType = TrainerType.HARLEQUIN; - const clownConfig = trainerConfigs[clownTrainerType].copy(); - const clownPartyTemplate = new TrainerPartyCompoundTemplate( - new TrainerPartyTemplate(1, PartyMemberStrength.STRONG), - new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER)); - clownConfig.setPartyTemplates(clownPartyTemplate); - clownConfig.setDoubleOnly(); - // @ts-ignore - clownConfig.partyTemplateFunc = null; // Overrides party template func if it exists - - // Generate random ability for Blacephalon from pool - const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)]; - encounter.setDialogueToken("ability", new Ability(ability, 3).name); - encounter.misc = { ability }; - - encounter.enemyPartyConfigs.push({ - trainerConfig: clownConfig, - pokemonConfigs: [ // Overrides first 2 pokemon to be Mr. Mime and Blacephalon - { - species: getPokemonSpecies(Species.MR_MIME), - isBoss: true, - moveSet: [Moves.TEETER_DANCE, Moves.ALLY_SWITCH, Moves.DAZZLING_GLEAM, Moves.PSYCHIC] - }, - { // Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter - species: getPokemonSpecies(Species.BLACEPHALON), - mysteryEncounterData: new MysteryEncounterPokemonData(undefined, ability, undefined, [randSeedInt(18), randSeedInt(18)]), - isBoss: true, - moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN] - }, - ], - doubleBattle: true - }); - - // Load animations/sfx for start of fight moves - loadCustomMovesForEncounter(scene, [Moves.ROLE_PLAY, Moves.TAUNT]); - - return true; - }) - .withTitle(`${namespace}.title`) - .withDescription(`${namespace}.description`) - .withQuery(`${namespace}.query`) - .withOption( - MysteryEncounterOptionBuilder - .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) - .withDialogue({ - buttonLabel: `${namespace}.option.1.label`, - buttonTooltip: `${namespace}.option.1.tooltip`, - selected: [ - { - text: `${namespace}.option.1.selected`, - speaker: `${namespace}.speaker` - }, - ], - }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter; - // Spawn battle - const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; - - setEncounterRewards(scene, { fillRemaining: true }); - - // TODO: when Magic Room and Wonder Room are implemented, add those to start of battle - encounter.startOfBattleEffects.push( - { // Mr. Mime copies the Blacephalon's random ability - sourceBattlerIndex: BattlerIndex.ENEMY, - targets: [BattlerIndex.ENEMY_2], - move: new PokemonMove(Moves.ROLE_PLAY), - ignorePp: true - }, - { - sourceBattlerIndex: BattlerIndex.ENEMY_2, - targets: [BattlerIndex.PLAYER], - move: new PokemonMove(Moves.TAUNT), - ignorePp: true - }, - { - sourceBattlerIndex: BattlerIndex.ENEMY_2, - targets: [BattlerIndex.PLAYER_2], - move: new PokemonMove(Moves.TAUNT), - ignorePp: true - }); - - await transitionMysteryEncounterIntroVisuals(scene); - await initBattleWithEnemyConfig(scene, config); - }) - .withPostOptionPhase(async (scene: BattleScene): Promise => { - // After the battle, offer the player the opportunity to permanently swap ability - const abilityWasSwapped = await handleSwapAbility(scene); - if (abilityWasSwapped) { - await showEncounterText(scene, `${namespace}.option.1.ability_gained`); - } - - // Play animations once ability swap is complete - // Trainer sprite that is shown at end of battle is not the same as mystery encounter intro visuals - scene.tweens.add({ - targets: scene.currentBattle.trainer, - x: "+=16", - y: "-=16", - alpha: 0, - ease: "Sine.easeInOut", - duration: 250 - }); - const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon()); - background.playWithoutTargets(scene, 230, 40, 2); - return true; - }) - .build() - ) - .withOption( - MysteryEncounterOptionBuilder - .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) - .withDialogue({ - buttonLabel: `${namespace}.option.2.label`, - buttonTooltip: `${namespace}.option.2.tooltip`, - selected: [ - { - text: `${namespace}.option.2.selected`, - speaker: `${namespace}.speaker` - }, - { - text: `${namespace}.option.2.selected_2`, - }, - { - text: `${namespace}.option.2.selected_3`, - speaker: `${namespace}.speaker` - }, - ], - }) - .withPreOptionPhase(async (scene: BattleScene) => { - // Swap player's items on pokemon with the most items - // Item comparisons look at whichever Pokemon has the greatest number of TRANSFERABLE, non-berry items - // So Vitamins, form change items, etc. are not included - const encounter = scene.currentBattle.mysteryEncounter; - - const party = scene.getParty(); - let mostHeldItemsPokemon = party[0]; - let count = mostHeldItemsPokemon.getHeldItems() - .filter(m => m.isTransferrable && !(m instanceof BerryModifier)) - .reduce((v, m) => v + m.stackCount, 0); - - party.forEach(pokemon => { - const nextCount = pokemon.getHeldItems() - .filter(m => m.isTransferrable && !(m instanceof BerryModifier)) - .reduce((v, m) => v + m.stackCount, 0); - if (nextCount > count) { - mostHeldItemsPokemon = pokemon; - count = nextCount; - } - }); - - encounter.setDialogueToken("switchPokemon", mostHeldItemsPokemon.getNameToRender()); - - const items = mostHeldItemsPokemon.getHeldItems(); - - // Shuffles Berries (if they have any) - let numBerries = 0; - items.filter(m => m instanceof BerryModifier) - .forEach(m => { - numBerries += m.stackCount; - scene.removeModifier(m); - }); - - generateItemsOfTier(scene, mostHeldItemsPokemon, numBerries, "Berries"); - - // Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm) - let numUltra = 0; - let numRogue = 0; - items.filter(m => m.isTransferrable && !(m instanceof BerryModifier)) - .forEach(m => { - const type = m.type.withTierFromPool(); - const tier = type.tier ?? ModifierTier.ULTRA; - if (type.id === "LUCKY_EGG" || tier === ModifierTier.ULTRA) { - numUltra += m.stackCount; - scene.removeModifier(m); - } else if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) { - numRogue += m.stackCount; - scene.removeModifier(m); - } - }); - - generateItemsOfTier(scene, mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA); - generateItemsOfTier(scene, mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE); - }) - .withOptionPhase(async (scene: BattleScene) => { - leaveEncounterWithoutBattle(scene, true); - }) - .withPostOptionPhase(async (scene: BattleScene) => { - // Play animations - const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon()); - background.playWithoutTargets(scene, 230, 40, 2); - await transitionMysteryEncounterIntroVisuals(scene); - }) - .build() - ) - .withOption( - MysteryEncounterOptionBuilder - .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) - .withDialogue({ - buttonLabel: `${namespace}.option.3.label`, - buttonTooltip: `${namespace}.option.3.tooltip`, - selected: [ - { - text: `${namespace}.option.3.selected`, - speaker: `${namespace}.speaker` - }, - { - text: `${namespace}.option.3.selected_2`, - }, - { - text: `${namespace}.option.3.selected_3`, - speaker: `${namespace}.speaker` - }, - ], - }) - .withPreOptionPhase(async (scene: BattleScene) => { - // Swap player's types on all party pokemon - // If a Pokemon had a single type prior, they will still have a single type after - for (const pokemon of scene.getParty()) { - const originalTypes = pokemon.getTypes(false, false, true); - - // If the Pokemon has non-status moves that don't match the Pokemon's type, prioritizes those as the new type - // Makes the "randomness" of the shuffle slightly less punishing - let priorityTypes = pokemon.moveset - .filter(move => move && !originalTypes.includes(move.getMove().type) && move.getMove().category !== MoveCategory.STATUS) - .map(move => move!.getMove().type); - if (priorityTypes?.length > 0) { - priorityTypes = [...new Set(priorityTypes)]; - randSeedShuffle(priorityTypes); - } - - let newTypes; - if (!originalTypes || originalTypes.length < 1) { - newTypes = priorityTypes?.length > 0 ? [priorityTypes.pop()] : [(randSeedInt(18) as Type)]; - } else { - newTypes = originalTypes.map(m => { - if (priorityTypes?.length > 0) { - const ret = priorityTypes.pop(); - randSeedShuffle(priorityTypes); - return ret; - } - - return randSeedInt(18) as Type; - }); - } - - if (!pokemon.mysteryEncounterData) { - pokemon.mysteryEncounterData = new MysteryEncounterPokemonData(undefined, undefined, undefined, newTypes); - } else { - pokemon.mysteryEncounterData.types = newTypes; - } - } - }) - .withOptionPhase(async (scene: BattleScene) => { - leaveEncounterWithoutBattle(scene, true); - }) - .withPostOptionPhase(async (scene: BattleScene) => { - // Play animations - const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon()); - background.playWithoutTargets(scene, 230, 40, 2); - await transitionMysteryEncounterIntroVisuals(scene); - }) - .build() - ) - .withOutroDialogue([ - { - text: `${namespace}.outro`, - }, - ]) - .build(); - -async function handleSwapAbility(scene: BattleScene) { - return new Promise(async resolve => { - await showEncounterDialogue(scene, `${namespace}.option.1.apply_ability_dialogue`, `${namespace}.speaker`); - await showEncounterText(scene, `${namespace}.option.1.apply_ability_message`); - - scene.ui.setMode(Mode.MESSAGE).then(() => { - displayYesNoOptions(scene, resolve); - }); - }); -} - -function displayYesNoOptions(scene: BattleScene, resolve) { - showEncounterText(scene, `${namespace}.option.1.ability_prompt`, 500, false); - const fullOptions = [ - { - label: i18next.t("menu:yes"), - handler: () => { - onYesAbilitySwap(scene, resolve); - return true; - } - }, - { - label: i18next.t("menu:no"), - handler: () => { - resolve(false); - return true; - } - } - ]; - - const config: OptionSelectConfig = { - options: fullOptions, - maxOptions: 7, - yOffset: 0 - }; - scene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true); -} - -function onYesAbilitySwap(scene: BattleScene, resolve) { - const onPokemonSelected = (pokemon: PlayerPokemon) => { - // Do ability swap - if (!pokemon.mysteryEncounterData) { - pokemon.mysteryEncounterData = new MysteryEncounterPokemonData(undefined, Abilities.AERILATE); - } - pokemon.mysteryEncounterData.ability = scene.currentBattle.mysteryEncounter.misc.ability; - scene.currentBattle.mysteryEncounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender()); - scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true)); - }; - - const onPokemonNotSelected = () => { - scene.ui.setMode(Mode.MESSAGE).then(() => { - displayYesNoOptions(scene, resolve); - }); - }; - - selectPokemonForOption(scene, onPokemonSelected, onPokemonNotSelected); -} - -function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItems: integer, tier: ModifierTier | "Berries") { - // These pools have to be defined at runtime so that modifierTypes exist - // Pools have instances of the modifier type equal to the max stacks that modifier can be applied to any one pokemon - // This is to prevent "over-generating" a random item of a certain type during item swaps - const ultraPool = [ - [modifierTypes.REVIVER_SEED, 1], - [modifierTypes.GOLDEN_PUNCH, 5], - [modifierTypes.ATTACK_TYPE_BOOSTER, 99], - [modifierTypes.QUICK_CLAW, 3], - [modifierTypes.WIDE_LENS, 3] - ]; - - const roguePool = [ - [modifierTypes.LEFTOVERS, 4], - [modifierTypes.SHELL_BELL, 4], - [modifierTypes.SOUL_DEW, 10], - [modifierTypes.SOOTHE_BELL, 3], - [modifierTypes.SCOPE_LENS, 1], - [modifierTypes.BATON, 1], - [modifierTypes.FOCUS_BAND, 5], - [modifierTypes.KINGS_ROCK, 3], - [modifierTypes.GRIP_CLAW, 5] - ]; - - const berryPool = [ - [BerryType.APICOT, 3], - [BerryType.ENIGMA, 2], - [BerryType.GANLON, 3], - [BerryType.LANSAT, 3], - [BerryType.LEPPA, 2], - [BerryType.LIECHI, 3], - [BerryType.LUM, 2], - [BerryType.PETAYA, 3], - [BerryType.SALAC, 2], - [BerryType.SITRUS, 2], - [BerryType.STARF, 3] - ]; - - let pool: any[]; - if (tier === "Berries") { - pool = berryPool; - } else { - pool = tier === ModifierTier.ULTRA ? ultraPool : roguePool; - } - - for (let i = 0; i < numItems; i++) { - const randIndex = randSeedInt(pool.length); - const newItemType = pool[randIndex]; - let newMod; - if (tier === "Berries") { - newMod = generateModifierType(scene, modifierTypes.BERRY, [newItemType[0]]) as PokemonHeldItemModifierType; - } else { - newMod = generateModifierType(scene, newItemType[0]) as PokemonHeldItemModifierType; - } - applyModifierTypeToPlayerPokemon(scene, pokemon, newMod); - // Decrement max stacks and remove from pool if at max - newItemType[1]--; - if (newItemType[1] <= 0) { - pool.splice(randIndex, 1); - } - } -} diff --git a/src/locales/en/mystery-encounters/clowning-around-dialogue.ts b/src/locales/en/mystery-encounters/clowning-around-dialogue.ts index dac5886c6d1..87dc0169b87 100644 --- a/src/locales/en/mystery-encounters/clowning-around-dialogue.ts +++ b/src/locales/en/mystery-encounters/clowning-around-dialogue.ts @@ -30,5 +30,5 @@ export const clowningAroundDialogue = { selected_3: "Flustered fool, fall for my flawless deception!", }, }, - outro: "The clown and his cohorts\n in a puff of smoke." + outro: "The clown and his cohorts\ndisappear in a puff of smoke." };