pokerogue/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts
Sirz Benjie 408b66f913
[Misc][Refactor][GitHub] Ditch eslint for biome, and add a formatter (#5495)
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2025-03-09 14:13:25 -07:00

192 lines
7.1 KiB
TypeScript

import {
leaveEncounterWithoutBattle,
transitionMysteryEncounterIntroVisuals,
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import {
catchPokemon,
getRandomSpeciesByStarterCost,
getSpriteKeysFromPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters";
import { Species } from "#enums/species";
import { PokeballType } from "#enums/pokeball";
import type { EnemyPokemon } from "#app/field/pokemon";
import { PlayerPokemon } from "#app/field/pokemon";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import PokemonData from "#app/system/pokemon-data";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { Abilities } from "#enums/abilities";
import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounters/thePokemonSalesman";
const MAX_POKEMON_PRICE_MULTIPLIER = 4;
/** Odds of shiny magikarp will be 1/value */
const SHINY_MAGIKARP_WEIGHT = 100;
/**
* Pokemon Salesman encounter.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3799 | GitHub Issue #3799}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.THE_POKEMON_SALESMAN,
)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER)) // Some costs may not be as significant, this is the max you'd pay
.withAutoHideIntroVisuals(false)
.withIntroSpriteConfigs([
{
spriteKey: "pokemon_salesman",
fileRoot: "mystery-encounters",
hasShadow: true,
},
])
.withIntroDialogue([
{
text: `${namespace}:intro`,
},
{
text: `${namespace}:intro_dialogue`,
speaker: `${namespace}:speaker`,
},
])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
let species = getSalesmanSpeciesOffer();
let tries = 0;
// Reroll any species that don't have HAs
while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) {
species = getSalesmanSpeciesOffer();
tries++;
}
let pokemon: PlayerPokemon;
if (
randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 ||
isNullOrUndefined(species.abilityHidden) ||
species.abilityHidden === Abilities.NONE
) {
// If no HA mon found or you roll 1%, give shiny Magikarp with random variant
species = getPokemonSpecies(Species.MAGIKARP);
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex, undefined, true);
} else {
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex);
}
pokemon.generateAndPopulateMoveset();
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(pokemon);
encounter.spriteConfigs.push({
spriteKey: spriteKey,
fileRoot: fileRoot,
hasShadow: true,
repeat: true,
isPokemon: true,
isShiny: pokemon.shiny,
variant: pokemon.variant,
});
const starterTier = speciesStarterCosts[species.speciesId];
// Prices decrease by starter tier less than 5, but only reduces cost by half at max
let priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER * (Math.max(starterTier, 2.5) / 5);
if (pokemon.shiny) {
// Always max price for shiny (flip HA back to normal), and add special messaging
priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER;
pokemon.abilityIndex = 0;
encounter.dialogue.encounterOptionsDialogue!.description = `${namespace}:description_shiny`;
encounter.options[0].dialogue!.buttonTooltip = `${namespace}:option.1.tooltip_shiny`;
}
const price = globalScene.getWaveMoneyAmount(priceMultiplier);
encounter.setDialogueToken("purchasePokemon", pokemon.getNameToRender());
encounter.setDialogueToken("price", price.toString());
encounter.misc = {
price: price,
pokemon: pokemon,
};
pokemon.calculateStats();
return true;
})
.withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withHasDexProgress(true)
.withSceneMoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [
{
text: `${namespace}:option.1.selected_message`,
},
],
})
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const price = encounter.misc.price;
const purchasedPokemon = encounter.misc.pokemon as PlayerPokemon;
// Update money
updatePlayerMoney(-price, true, false);
// Show dialogue
await showEncounterDialogue(`${namespace}:option.1.selected_dialogue`, `${namespace}:speaker`);
await transitionMysteryEncounterIntroVisuals();
// "Catch" purchased pokemon
const data = new PokemonData(purchasedPokemon);
data.player = false;
await catchPokemon(data.toPokemon() as EnemyPokemon, null, PokeballType.POKEBALL, true, true);
leaveEncounterWithoutBattle(true);
})
.build(),
)
.withSimpleOption(
{
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [
{
text: `${namespace}:option.2.selected`,
},
],
},
async () => {
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true);
return true;
},
)
.build();
/**
* @returns A random species that has at most 5 starter cost and is not Mythical, Paradox, etc.
*/
export function getSalesmanSpeciesOffer(): PokemonSpecies {
return getPokemonSpecies(
getRandomSpeciesByStarterCost([0, 5], NON_LEGEND_PARADOX_POKEMON, undefined, false, false, false),
);
}