mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-12-14 13:55:20 +01:00
* Added more biome rules * Fixes * Added a few more rules * Added global phaser to biome * Fix tpyo * Updated biome to 2.1.4; improved docs on linting/localization; added vcs support Also added `.build` to gitignore cuz reasons * Fixed tpyo * dd * Applied linter fixes * Partially fixed some private property issues * Upgraded to Biome 2.2.0; added `operatorLinebreak` and a few new rules * Moved operator linebreaks before lines * Applied kev's suggestions * Update biome.jsonc Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * added like all the rules and then some * modify biome.jsonc * apply biome formatting * Reverted changes to balance folder * fixed stuff * Fixed biome stripping trailing globstars from everything * made `noInvertedElse` an error rule * Add & apply fixes for `useExplicitLengthCheck`, `useAtIndex` and `noNonNullAssertedOptionalChain` * Bumped biome to 2.2.3 * Fixed a few syntax errors * Removed trailing globstars since biome actually fixed their shit * Final clean up * foobarbaz * Fixed remaining issues * Fixed a few errors in SSUI * fixed rounding issue * Fixed test to not round funky * Fixed biome false positive for vitest hooks * Apply biome:all --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com>
256 lines
9.9 KiB
TypeScript
256 lines
9.9 KiB
TypeScript
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
|
import { timedEventManager } from "#app/global-event-manager";
|
|
import { globalScene } from "#app/global-scene";
|
|
import { NON_LEGEND_PARADOX_POKEMON, NON_LEGEND_ULTRA_BEASTS } from "#balance/special-species-groups";
|
|
import { speciesStarterCosts } from "#balance/starters";
|
|
import type { PokemonSpecies } from "#data/pokemon-species";
|
|
import { AbilityId } from "#enums/ability-id";
|
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
|
import { PokeballType } from "#enums/pokeball";
|
|
import { SpeciesId } from "#enums/species-id";
|
|
import type { EnemyPokemon } from "#field/pokemon";
|
|
import { PlayerPokemon } from "#field/pokemon";
|
|
import { showEncounterDialogue } from "#mystery-encounters/encounter-dialogue-utils";
|
|
import {
|
|
leaveEncounterWithoutBattle,
|
|
transitionMysteryEncounterIntroVisuals,
|
|
updatePlayerMoney,
|
|
} from "#mystery-encounters/encounter-phase-utils";
|
|
import {
|
|
catchPokemon,
|
|
getRandomSpeciesByStarterCost,
|
|
getSpriteKeysFromPokemon,
|
|
} from "#mystery-encounters/encounter-pokemon-utils";
|
|
import type { MysteryEncounter } from "#mystery-encounters/mystery-encounter";
|
|
import { MysteryEncounterBuilder } from "#mystery-encounters/mystery-encounter";
|
|
import { MysteryEncounterOptionBuilder } from "#mystery-encounters/mystery-encounter-option";
|
|
import { MoneyRequirement } from "#mystery-encounters/mystery-encounter-requirements";
|
|
import { PokemonData } from "#system/pokemon-data";
|
|
import { isNullOrUndefined, randSeedInt, randSeedItem } from "#utils/common";
|
|
import { getPokemonSpecies } from "#utils/pokemon-utils";
|
|
|
|
/** 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;
|
|
|
|
/** Odds of event sale will be value/100 */
|
|
const EVENT_THRESHOLD = 50;
|
|
|
|
/**
|
|
* 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}:introDialogue`,
|
|
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 === AbilityId.NONE) && tries < 5) {
|
|
species = getSalesmanSpeciesOffer();
|
|
tries++;
|
|
}
|
|
|
|
const r = randSeedInt(SHINY_MAGIKARP_WEIGHT);
|
|
|
|
const validEventEncounters = timedEventManager
|
|
.getEventEncounters()
|
|
.filter(
|
|
s =>
|
|
!getPokemonSpecies(s.species).legendary
|
|
&& !getPokemonSpecies(s.species).subLegendary
|
|
&& !getPokemonSpecies(s.species).mythical
|
|
&& !NON_LEGEND_PARADOX_POKEMON.includes(s.species)
|
|
&& !NON_LEGEND_ULTRA_BEASTS.includes(s.species),
|
|
);
|
|
|
|
let pokemon: PlayerPokemon;
|
|
/**
|
|
* Mon is determined as follows:
|
|
* If you roll the 1% for Shiny Magikarp, you get Magikarp with a random variant
|
|
* If an event with more than 1 valid event encounter species is active, you have 20% chance to get one of those
|
|
* If the rolled species has no HA, and there are valid event encounters, you will get one of those
|
|
* If the rolled species has no HA and there are no valid event encounters, you will get Shiny Magikarp
|
|
* Mons rolled from the event encounter pool get 3 extra shiny rolls
|
|
*/
|
|
if (
|
|
r === 0
|
|
|| ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === AbilityId.NONE)
|
|
&& validEventEncounters.length === 0)
|
|
) {
|
|
// If you roll 1%, give shiny Magikarp with random variant
|
|
species = getPokemonSpecies(SpeciesId.MAGIKARP);
|
|
pokemon = new PlayerPokemon(species, 5, 2, undefined, undefined, true);
|
|
} else if (
|
|
validEventEncounters.length > 0
|
|
&& (r <= EVENT_THRESHOLD || isNullOrUndefined(species.abilityHidden) || species.abilityHidden === AbilityId.NONE)
|
|
) {
|
|
tries = 0;
|
|
do {
|
|
// If you roll 20%, give event encounter with 3 extra shiny rolls and its HA, if it has one
|
|
const enc = randSeedItem(validEventEncounters);
|
|
species = getPokemonSpecies(enc.species);
|
|
pokemon = new PlayerPokemon(
|
|
species,
|
|
5,
|
|
species.abilityHidden === AbilityId.NONE ? undefined : 2,
|
|
enc.formIndex,
|
|
);
|
|
pokemon.trySetShinySeed();
|
|
pokemon.trySetShinySeed();
|
|
pokemon.trySetShinySeed();
|
|
if (pokemon.shiny || pokemon.abilityIndex === 2) {
|
|
break;
|
|
}
|
|
tries++;
|
|
} while (tries < 6);
|
|
if (!pokemon.shiny && pokemon.abilityIndex !== 2) {
|
|
// If, after 6 tries, you STILL somehow don't have an HA or shiny mon, pick from only the event mons that have an HA.
|
|
if (validEventEncounters.some(s => !!getPokemonSpecies(s.species).abilityHidden)) {
|
|
validEventEncounters.filter(s => !!getPokemonSpecies(s.species).abilityHidden);
|
|
const enc = randSeedItem(validEventEncounters);
|
|
species = getPokemonSpecies(enc.species);
|
|
pokemon = new PlayerPokemon(species, 5, 2, enc.formIndex);
|
|
pokemon.trySetShinySeed();
|
|
pokemon.trySetShinySeed();
|
|
pokemon.trySetShinySeed();
|
|
} else {
|
|
// If there's, and this would never happen, no eligible event encounters with a hidden ability, just do Magikarp
|
|
species = getPokemonSpecies(SpeciesId.MAGIKARP);
|
|
pokemon = new PlayerPokemon(species, 5, 2, undefined, undefined, true);
|
|
}
|
|
}
|
|
} else {
|
|
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex);
|
|
}
|
|
pokemon.generateAndPopulateMoveset();
|
|
|
|
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(pokemon);
|
|
encounter.spriteConfigs.push({
|
|
spriteKey,
|
|
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}:descriptionShiny`;
|
|
encounter.options[0].dialogue!.buttonTooltip = `${namespace}:option.1.tooltipShiny`;
|
|
}
|
|
const price = globalScene.getWaveMoneyAmount(priceMultiplier);
|
|
encounter.setDialogueToken("purchasePokemon", pokemon.getNameToRender());
|
|
encounter.setDialogueToken("price", price.toString());
|
|
encounter.misc = {
|
|
price,
|
|
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.selectedMessage`,
|
|
},
|
|
],
|
|
})
|
|
.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.selectedDialogue`, `${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),
|
|
);
|
|
}
|