mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-12-16 14:55:22 +01:00
193 lines
7.2 KiB
TypeScript
193 lines
7.2 KiB
TypeScript
import { MAX_TERAS_PER_ARENA } from "#app/constants";
|
|
import { globalScene } from "#app/global-scene";
|
|
import { POKERUS_STARTER_COUNT, speciesStarterCosts } from "#balance/starters";
|
|
import { allSpecies } from "#data/data-lists";
|
|
import type { PokemonSpecies, PokemonSpeciesForm } from "#data/pokemon-species";
|
|
import { BattlerIndex } from "#enums/battler-index";
|
|
import { SpeciesId } from "#enums/species-id";
|
|
import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon";
|
|
import { randSeedItem } from "./common";
|
|
|
|
/**
|
|
* Gets the {@linkcode PokemonSpecies} object associated with the {@linkcode SpeciesId} enum given
|
|
* @param species - The {@linkcode SpeciesId} to fetch.
|
|
* If an array of `SpeciesId`s is passed (such as for named trainer spawn pools),
|
|
* one will be selected at random.
|
|
* @returns The associated {@linkcode PokemonSpecies} object
|
|
*/
|
|
export function getPokemonSpecies(species: SpeciesId | SpeciesId[]): PokemonSpecies {
|
|
if (Array.isArray(species)) {
|
|
// TODO: this RNG roll should not be handled by this function
|
|
species = species[Math.floor(Math.random() * species.length)];
|
|
}
|
|
if (species >= 2000) {
|
|
// the `!` is safe, `allSpecies` is static and contains all `SpeciesId`s
|
|
return allSpecies.find(s => s.speciesId === species)!;
|
|
}
|
|
return allSpecies[species - 1];
|
|
}
|
|
|
|
/**
|
|
* Method to get the daily list of starters with Pokerus.
|
|
* @returns A list of starters with Pokerus
|
|
*/
|
|
export function getPokerusStarters(): PokemonSpecies[] {
|
|
const pokerusStarters: PokemonSpecies[] = [];
|
|
const date = new Date();
|
|
date.setUTCHours(0, 0, 0, 0);
|
|
globalScene.executeWithSeedOffset(
|
|
() => {
|
|
while (pokerusStarters.length < POKERUS_STARTER_COUNT) {
|
|
const randomSpeciesId = Number.parseInt(randSeedItem(Object.keys(speciesStarterCosts)), 10);
|
|
const species = getPokemonSpecies(randomSpeciesId);
|
|
if (!pokerusStarters.includes(species)) {
|
|
pokerusStarters.push(species);
|
|
}
|
|
}
|
|
},
|
|
0,
|
|
date.getTime().toString(),
|
|
);
|
|
return pokerusStarters;
|
|
}
|
|
|
|
export function getFusedSpeciesName(speciesAName: string, speciesBName: string): string {
|
|
const fragAPattern = /([a-z]{2}.*?[aeiou(?:y$)\-']+)(.*?)$/i;
|
|
const fragBPattern = /([a-z]{2}.*?[aeiou(?:y$)\-'])(.*?)$/i;
|
|
|
|
const [speciesAPrefixMatch, speciesBPrefixMatch] = [speciesAName, speciesBName].map(n => /^(?:[^ ]+) /.exec(n));
|
|
const [speciesAPrefix, speciesBPrefix] = [speciesAPrefixMatch, speciesBPrefixMatch].map(m => (m ? m[0] : ""));
|
|
|
|
if (speciesAPrefix) {
|
|
speciesAName = speciesAName.slice(speciesAPrefix.length);
|
|
}
|
|
if (speciesBPrefix) {
|
|
speciesBName = speciesBName.slice(speciesBPrefix.length);
|
|
}
|
|
|
|
const [speciesASuffixMatch, speciesBSuffixMatch] = [speciesAName, speciesBName].map(n => / (?:[^ ]+)$/.exec(n));
|
|
const [speciesASuffix, speciesBSuffix] = [speciesASuffixMatch, speciesBSuffixMatch].map(m => (m ? m[0] : ""));
|
|
|
|
if (speciesASuffix) {
|
|
speciesAName = speciesAName.slice(0, -speciesASuffix.length);
|
|
}
|
|
if (speciesBSuffix) {
|
|
speciesBName = speciesBName.slice(0, -speciesBSuffix.length);
|
|
}
|
|
|
|
const splitNameA = speciesAName.split(/ /g);
|
|
const splitNameB = speciesBName.split(/ /g);
|
|
|
|
const fragAMatch = fragAPattern.exec(speciesAName);
|
|
const fragBMatch = fragBPattern.exec(speciesBName);
|
|
|
|
let fragA: string;
|
|
let fragB: string;
|
|
|
|
fragA = splitNameA.length === 1 ? (fragAMatch ? fragAMatch[1] : speciesAName) : splitNameA.at(-1)!;
|
|
|
|
if (splitNameB.length === 1) {
|
|
if (fragBMatch) {
|
|
const lastCharA = fragA.slice(fragA.length - 1);
|
|
const prevCharB = fragBMatch[1].slice(fragBMatch.length - 1);
|
|
fragB = (/[-']/.test(prevCharB) ? prevCharB : "") + fragBMatch[2] || prevCharB;
|
|
if (lastCharA === fragB[0]) {
|
|
if (/[aiu]/.test(lastCharA)) {
|
|
fragB = fragB.slice(1);
|
|
} else {
|
|
const newCharMatch = new RegExp(`[^${lastCharA}]`).exec(fragB);
|
|
if (newCharMatch?.index !== undefined && newCharMatch.index > 0) {
|
|
fragB = fragB.slice(newCharMatch.index);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
fragB = speciesBName;
|
|
}
|
|
} else {
|
|
fragB = splitNameB.at(-1)!;
|
|
}
|
|
|
|
if (splitNameA.length > 1) {
|
|
fragA = `${splitNameA.slice(0, splitNameA.length - 1).join(" ")} ${fragA}`;
|
|
}
|
|
|
|
fragB = `${fragB.slice(0, 1).toLowerCase()}${fragB.slice(1)}`;
|
|
|
|
return `${speciesAPrefix || speciesBPrefix}${fragA}${fragB}${speciesBSuffix || speciesASuffix}`;
|
|
}
|
|
|
|
export function getPokemonSpeciesForm(species: SpeciesId, formIndex: number): PokemonSpeciesForm {
|
|
const retSpecies: PokemonSpecies = getPokemonSpecies(species);
|
|
|
|
if (formIndex < retSpecies.forms.length) {
|
|
return retSpecies.forms[formIndex];
|
|
}
|
|
return retSpecies;
|
|
}
|
|
|
|
/**
|
|
* Return whether two battler indices are considered allies.
|
|
* To instead check with {@linkcode Pokemon} objects, use {@linkcode Pokemon.isOpponent}.
|
|
* @param a - First battler index
|
|
* @param b - Second battler index
|
|
* @returns Whether the two battler indices are allies. Always `false` if either index is `ATTACKER`.
|
|
*/
|
|
export function areAllies(a: BattlerIndex, b: BattlerIndex): boolean {
|
|
if (a === BattlerIndex.ATTACKER || b === BattlerIndex.ATTACKER) {
|
|
return false;
|
|
}
|
|
return (
|
|
(a === BattlerIndex.PLAYER || a === BattlerIndex.PLAYER_2)
|
|
=== (b === BattlerIndex.PLAYER || b === BattlerIndex.PLAYER_2)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Determine whether an enemy Pokémon will Terastallize the user
|
|
*
|
|
* Does not check if the Pokémon is allowed to Terastallize (e.g., if it's a mega)
|
|
* @param pokemon - The Pokémon to check
|
|
* @returns Whether the Pokémon will Terastallize
|
|
*
|
|
* @remarks
|
|
* Should really only be called with an enemy Pokémon, but will technically work with any Pokémon.
|
|
*
|
|
* @privateRemarks
|
|
* Assumes that Pokémon without a trainer will never tera, so this must be changed if
|
|
* a wild Pokémon is allowed to tera, e.g. for a Mystery Encounter.
|
|
*/
|
|
export function willTerastallize(pokemon: Pokemon): boolean {
|
|
// cast is safe, as if it is just a Pokémon, initialTeamIndex will be undefined triggering the null check
|
|
const initialTeamIndex = (pokemon as EnemyPokemon).initialTeamIndex;
|
|
return (
|
|
initialTeamIndex != null
|
|
&& pokemon.hasTrainer()
|
|
&& (globalScene.currentBattle?.trainer?.config.trainerAI.instantTeras.includes(initialTeamIndex) ?? false)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Determine whether the Pokémon's species is tera capable, and that the player has acquired the tera orb.
|
|
* @param pokemon - The Pokémon to check
|
|
* @returns Whether
|
|
*/
|
|
export function canSpeciesTera(pokemon: Pokemon): boolean {
|
|
const hasTeraMod = globalScene.findModifier(modifier => modifier.is("TerastallizeAccessModifier")) != null;
|
|
const isBlockedForm = pokemon.isMega() || pokemon.isMax() || pokemon.hasSpecies(SpeciesId.NECROZMA, "ultra");
|
|
return hasTeraMod && !isBlockedForm;
|
|
}
|
|
|
|
/**
|
|
* Same as {@linkcode canSpeciesTera}, but also checks that the player has not already used their tera in the arena.
|
|
*
|
|
* @remarks
|
|
* ⚠️ This does not account for tera commands that may be pending, so this should not be used during command selection!
|
|
* @param pokemon - The Pokémon to check
|
|
* @returns Whether the Pokémon can Terastallize
|
|
*/
|
|
export function canTerastallize(pokemon: PlayerPokemon): boolean {
|
|
const hasAvailableTeras = globalScene.arena.playerTerasUsed < MAX_TERAS_PER_ARENA;
|
|
return hasAvailableTeras && canSpeciesTera(pokemon);
|
|
}
|