mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-21 00:52:47 +02:00
* [Refactor] Create utility function `makeArray` This replaces the `if(!Array.isArray(var)) { var = [var] }` pattern * Replace `if` with ternary, rename to `coerceArray` * Add TSDocs * Improve type inferencing * Replace missed `Array.isArray` checks * Apply Biome * Re-apply changes to phase manager * Re-apply to `SpeciesFormChangeStatusEffectTrigger` constructor Apply to new instances in test mocks
1216 lines
43 KiB
TypeScript
1216 lines
43 KiB
TypeScript
import { globalScene } from "#app/global-scene";
|
|
import { allAbilities } from "../data-lists";
|
|
import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
|
|
import { Nature } from "#enums/nature";
|
|
import { pokemonFormChanges } from "#app/data/pokemon-forms";
|
|
import { SpeciesFormChangeItemTrigger } from "../pokemon-forms/form-change-triggers";
|
|
import { FormChangeItem } from "#enums/form-change-item";
|
|
import { StatusEffect } from "#enums/status-effect";
|
|
import { PokemonType } from "#enums/pokemon-type";
|
|
import { WeatherType } from "#enums/weather-type";
|
|
import type { PlayerPokemon } from "#app/field/pokemon";
|
|
import { AttackTypeBoosterModifier } from "#app/modifier/modifier";
|
|
import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
|
|
import { coerceArray, isNullOrUndefined } from "#app/utils/common";
|
|
import type { AbilityId } from "#enums/ability-id";
|
|
import { MoveId } from "#enums/move-id";
|
|
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
|
import { SpeciesId } from "#enums/species-id";
|
|
import { SpeciesFormKey } from "#enums/species-form-key";
|
|
import { TimeOfDay } from "#enums/time-of-day";
|
|
|
|
export interface EncounterRequirement {
|
|
meetsRequirement(): boolean; // Boolean to see if a requirement is met
|
|
getDialogueToken(pokemon?: PlayerPokemon): [string, string];
|
|
}
|
|
|
|
export abstract class EncounterSceneRequirement implements EncounterRequirement {
|
|
/**
|
|
* Returns whether the EncounterSceneRequirement's... requirements, are met by the given scene
|
|
*/
|
|
abstract meetsRequirement(): boolean;
|
|
/**
|
|
* Returns a dialogue token key/value pair for a given Requirement.
|
|
* Should be overridden by child Requirement classes.
|
|
* @param pokemon
|
|
*/
|
|
abstract getDialogueToken(pokemon?: PlayerPokemon): [string, string];
|
|
}
|
|
|
|
/**
|
|
* Combination of multiple {@linkcode EncounterSceneRequirement | EncounterSceneRequirements} (OR/AND possible. See {@linkcode isAnd})
|
|
*/
|
|
export class CombinationSceneRequirement extends EncounterSceneRequirement {
|
|
/** If `true`, all requirements must be met (AND). If `false`, any requirement must be met (OR) */
|
|
private isAnd: boolean;
|
|
requirements: EncounterSceneRequirement[];
|
|
|
|
public static Some(...requirements: EncounterSceneRequirement[]): CombinationSceneRequirement {
|
|
return new CombinationSceneRequirement(false, ...requirements);
|
|
}
|
|
|
|
public static Every(...requirements: EncounterSceneRequirement[]): CombinationSceneRequirement {
|
|
return new CombinationSceneRequirement(true, ...requirements);
|
|
}
|
|
|
|
private constructor(isAnd: boolean, ...requirements: EncounterSceneRequirement[]) {
|
|
super();
|
|
this.isAnd = isAnd;
|
|
this.requirements = requirements;
|
|
}
|
|
|
|
/**
|
|
* Checks if all/any requirements are met (depends on {@linkcode isAnd})
|
|
* @returns true if all/any requirements are met (depends on {@linkcode isAnd})
|
|
*/
|
|
override meetsRequirement(): boolean {
|
|
return this.isAnd
|
|
? this.requirements.every(req => req.meetsRequirement())
|
|
: this.requirements.some(req => req.meetsRequirement());
|
|
}
|
|
|
|
/**
|
|
* Retrieves a dialogue token key/value pair for the given {@linkcode EncounterSceneRequirement | requirements}.
|
|
* @param pokemon The {@linkcode PlayerPokemon} to check against
|
|
* @returns A dialogue token key/value pair
|
|
* @throws An {@linkcode Error} if {@linkcode isAnd} is `true` (not supported)
|
|
*/
|
|
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
|
if (this.isAnd) {
|
|
throw new Error("Not implemented (Sorry)");
|
|
}
|
|
for (const req of this.requirements) {
|
|
if (req.meetsRequirement()) {
|
|
return req.getDialogueToken(pokemon);
|
|
}
|
|
}
|
|
|
|
return this.requirements[0].getDialogueToken(pokemon);
|
|
}
|
|
}
|
|
|
|
export abstract class EncounterPokemonRequirement implements EncounterRequirement {
|
|
public minNumberOfPokemon: number;
|
|
public invertQuery: boolean;
|
|
|
|
/**
|
|
* Returns whether the EncounterPokemonRequirement's... requirements, are met by the given scene
|
|
*/
|
|
abstract meetsRequirement(): boolean;
|
|
|
|
/**
|
|
* Returns all party members that are compatible with this requirement. For non pokemon related requirements, the entire party is returned.
|
|
* @param partyPokemon
|
|
*/
|
|
abstract queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[];
|
|
|
|
/**
|
|
* Returns a dialogue token key/value pair for a given Requirement.
|
|
* Should be overridden by child Requirement classes.
|
|
* @param pokemon
|
|
*/
|
|
abstract getDialogueToken(pokemon?: PlayerPokemon): [string, string];
|
|
}
|
|
|
|
/**
|
|
* Combination of multiple {@linkcode EncounterPokemonRequirement | EncounterPokemonRequirements} (OR/AND possible. See {@linkcode isAnd})
|
|
*/
|
|
export class CombinationPokemonRequirement extends EncounterPokemonRequirement {
|
|
/** If `true`, all requirements must be met (AND). If `false`, any requirement must be met (OR) */
|
|
private isAnd: boolean;
|
|
private requirements: EncounterPokemonRequirement[];
|
|
|
|
public static Some(...requirements: EncounterPokemonRequirement[]): CombinationPokemonRequirement {
|
|
return new CombinationPokemonRequirement(false, ...requirements);
|
|
}
|
|
|
|
public static Every(...requirements: EncounterPokemonRequirement[]): CombinationPokemonRequirement {
|
|
return new CombinationPokemonRequirement(true, ...requirements);
|
|
}
|
|
|
|
private constructor(isAnd: boolean, ...requirements: EncounterPokemonRequirement[]) {
|
|
super();
|
|
this.isAnd = isAnd;
|
|
this.invertQuery = false;
|
|
this.minNumberOfPokemon = 1;
|
|
this.requirements = requirements;
|
|
}
|
|
|
|
/**
|
|
* Checks if all/any requirements are met (depends on {@linkcode isAnd})
|
|
* @returns true if all/any requirements are met (depends on {@linkcode isAnd})
|
|
*/
|
|
override meetsRequirement(): boolean {
|
|
return this.isAnd
|
|
? this.requirements.every(req => req.meetsRequirement())
|
|
: this.requirements.some(req => req.meetsRequirement());
|
|
}
|
|
|
|
/**
|
|
* Queries the players party for all party members that are compatible with all/any requirements (depends on {@linkcode isAnd})
|
|
* @param partyPokemon The party of {@linkcode PlayerPokemon}
|
|
* @returns All party members that are compatible with all/any requirements (depends on {@linkcode isAnd})
|
|
*/
|
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
|
if (this.isAnd) {
|
|
return this.requirements.reduce((relevantPokemon, req) => req.queryParty(relevantPokemon), partyPokemon);
|
|
}
|
|
const matchingRequirement = this.requirements.find(req => req.queryParty(partyPokemon).length > 0);
|
|
return matchingRequirement ? matchingRequirement.queryParty(partyPokemon) : [];
|
|
}
|
|
|
|
/**
|
|
* Retrieves a dialogue token key/value pair for the given {@linkcode EncounterPokemonRequirement | requirements}.
|
|
* @param pokemon The {@linkcode PlayerPokemon} to check against
|
|
* @returns A dialogue token key/value pair
|
|
* @throws An {@linkcode Error} if {@linkcode isAnd} is `true` (not supported)
|
|
*/
|
|
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
|
if (this.isAnd) {
|
|
throw new Error("Not implemented (Sorry)");
|
|
}
|
|
for (const req of this.requirements) {
|
|
if (req.meetsRequirement()) {
|
|
return req.getDialogueToken(pokemon);
|
|
}
|
|
}
|
|
|
|
return this.requirements[0].getDialogueToken(pokemon);
|
|
}
|
|
}
|
|
|
|
export class PreviousEncounterRequirement extends EncounterSceneRequirement {
|
|
previousEncounterRequirement: MysteryEncounterType;
|
|
|
|
/**
|
|
* Used for specifying an encounter that must be seen before this encounter can spawn
|
|
* @param previousEncounterRequirement
|
|
*/
|
|
constructor(previousEncounterRequirement: MysteryEncounterType) {
|
|
super();
|
|
this.previousEncounterRequirement = previousEncounterRequirement;
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
return globalScene.mysteryEncounterSaveData.encounteredEvents.some(
|
|
e => e.type === this.previousEncounterRequirement,
|
|
);
|
|
}
|
|
|
|
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
|
|
return [
|
|
"previousEncounter",
|
|
globalScene.mysteryEncounterSaveData.encounteredEvents
|
|
.find(e => e.type === this.previousEncounterRequirement)?.[0]
|
|
.toString() ?? "",
|
|
];
|
|
}
|
|
}
|
|
|
|
export class WaveRangeRequirement extends EncounterSceneRequirement {
|
|
waveRange: [number, number];
|
|
|
|
/**
|
|
* Used for specifying a unique wave or wave range requirement
|
|
* If minWaveIndex and maxWaveIndex are equivalent, will check for exact wave number
|
|
* @param waveRange [min, max]
|
|
*/
|
|
constructor(waveRange: [number, number]) {
|
|
super();
|
|
this.waveRange = waveRange;
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
if (!isNullOrUndefined(this.waveRange) && this.waveRange[0] <= this.waveRange[1]) {
|
|
const waveIndex = globalScene.currentBattle.waveIndex;
|
|
if (
|
|
(waveIndex >= 0 && this.waveRange[0] >= 0 && this.waveRange[0] > waveIndex) ||
|
|
(this.waveRange[1] >= 0 && this.waveRange[1] < waveIndex)
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
|
|
return ["waveIndex", globalScene.currentBattle.waveIndex.toString()];
|
|
}
|
|
}
|
|
|
|
export class WaveModulusRequirement extends EncounterSceneRequirement {
|
|
waveModuli: number[];
|
|
modulusValue: number;
|
|
|
|
/**
|
|
* Used for specifying a modulus requirement on the wave index
|
|
* For example, can be used to require the wave index to end with 1, 2, or 3
|
|
* @param waveModuli The allowed modulus results
|
|
* @param modulusValue The modulus calculation value
|
|
*
|
|
* Example:
|
|
* new WaveModulusRequirement([1, 2, 3], 10) will check for 1st/2nd/3rd waves that are immediately after a multiple of 10 wave
|
|
* So waves 21, 32, 53 all return true. 58, 14, 99 return false.
|
|
*/
|
|
constructor(waveModuli: number[], modulusValue: number) {
|
|
super();
|
|
this.waveModuli = waveModuli;
|
|
this.modulusValue = modulusValue;
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
return this.waveModuli.includes(globalScene.currentBattle.waveIndex % this.modulusValue);
|
|
}
|
|
|
|
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
|
|
return ["waveIndex", globalScene.currentBattle.waveIndex.toString()];
|
|
}
|
|
}
|
|
|
|
export class TimeOfDayRequirement extends EncounterSceneRequirement {
|
|
requiredTimeOfDay: TimeOfDay[];
|
|
|
|
constructor(timeOfDay: TimeOfDay | TimeOfDay[]) {
|
|
super();
|
|
this.requiredTimeOfDay = coerceArray(timeOfDay);
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
const timeOfDay = globalScene.arena?.getTimeOfDay();
|
|
return !(
|
|
!isNullOrUndefined(timeOfDay) &&
|
|
this.requiredTimeOfDay?.length > 0 &&
|
|
!this.requiredTimeOfDay.includes(timeOfDay)
|
|
);
|
|
}
|
|
|
|
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
|
|
return ["timeOfDay", TimeOfDay[globalScene.arena.getTimeOfDay()].toLocaleLowerCase()];
|
|
}
|
|
}
|
|
|
|
export class WeatherRequirement extends EncounterSceneRequirement {
|
|
requiredWeather: WeatherType[];
|
|
|
|
constructor(weather: WeatherType | WeatherType[]) {
|
|
super();
|
|
this.requiredWeather = coerceArray(weather);
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
const currentWeather = globalScene.arena.weather?.weatherType;
|
|
return !(
|
|
!isNullOrUndefined(currentWeather) &&
|
|
this.requiredWeather?.length > 0 &&
|
|
!this.requiredWeather.includes(currentWeather!)
|
|
);
|
|
}
|
|
|
|
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
|
|
const currentWeather = globalScene.arena.weather?.weatherType;
|
|
let token = "";
|
|
if (!isNullOrUndefined(currentWeather)) {
|
|
token = WeatherType[currentWeather].replace("_", " ").toLocaleLowerCase();
|
|
}
|
|
return ["weather", token];
|
|
}
|
|
}
|
|
|
|
export class PartySizeRequirement extends EncounterSceneRequirement {
|
|
partySizeRange: [number, number];
|
|
excludeDisallowedPokemon: boolean;
|
|
|
|
/**
|
|
* Used for specifying a party size requirement
|
|
* If min and max are equivalent, will check for exact size
|
|
* @param partySizeRange
|
|
* @param excludeDisallowedPokemon
|
|
*/
|
|
constructor(partySizeRange: [number, number], excludeDisallowedPokemon: boolean) {
|
|
super();
|
|
this.partySizeRange = partySizeRange;
|
|
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
if (!isNullOrUndefined(this.partySizeRange) && this.partySizeRange[0] <= this.partySizeRange[1]) {
|
|
const partySize = this.excludeDisallowedPokemon
|
|
? globalScene.getPokemonAllowedInBattle().length
|
|
: globalScene.getPlayerParty().length;
|
|
if (
|
|
(partySize >= 0 && this.partySizeRange[0] >= 0 && this.partySizeRange[0] > partySize) ||
|
|
(this.partySizeRange[1] >= 0 && this.partySizeRange[1] < partySize)
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
|
|
return ["partySize", globalScene.getPlayerParty().length.toString()];
|
|
}
|
|
}
|
|
|
|
export class PersistentModifierRequirement extends EncounterSceneRequirement {
|
|
requiredHeldItemModifiers: string[];
|
|
minNumberOfItems: number;
|
|
|
|
constructor(heldItem: string | string[], minNumberOfItems = 1) {
|
|
super();
|
|
this.minNumberOfItems = minNumberOfItems;
|
|
this.requiredHeldItemModifiers = coerceArray(heldItem);
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
const partyPokemon = globalScene.getPlayerParty();
|
|
if (isNullOrUndefined(partyPokemon) || this.requiredHeldItemModifiers?.length < 0) {
|
|
return false;
|
|
}
|
|
let modifierCount = 0;
|
|
for (const modifier of this.requiredHeldItemModifiers) {
|
|
const matchingMods = globalScene.findModifiers(m => m.constructor.name === modifier);
|
|
if (matchingMods?.length > 0) {
|
|
for (const matchingMod of matchingMods) {
|
|
modifierCount += matchingMod.stackCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
return modifierCount >= this.minNumberOfItems;
|
|
}
|
|
|
|
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
|
|
return ["requiredItem", this.requiredHeldItemModifiers[0]];
|
|
}
|
|
}
|
|
|
|
export class MoneyRequirement extends EncounterSceneRequirement {
|
|
requiredMoney: number; // Static value
|
|
scalingMultiplier: number; // Calculates required money based off wave index
|
|
|
|
constructor(requiredMoney: number, scalingMultiplier?: number) {
|
|
super();
|
|
this.requiredMoney = requiredMoney ?? 0;
|
|
this.scalingMultiplier = scalingMultiplier ?? 0;
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
const money = globalScene.money;
|
|
if (isNullOrUndefined(money)) {
|
|
return false;
|
|
}
|
|
|
|
if (this.scalingMultiplier > 0) {
|
|
this.requiredMoney = globalScene.getWaveMoneyAmount(this.scalingMultiplier);
|
|
}
|
|
return !(this.requiredMoney > 0 && this.requiredMoney > money);
|
|
}
|
|
|
|
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
|
|
const value =
|
|
this.scalingMultiplier > 0
|
|
? globalScene.getWaveMoneyAmount(this.scalingMultiplier).toString()
|
|
: this.requiredMoney.toString();
|
|
return ["money", value];
|
|
}
|
|
}
|
|
|
|
export class SpeciesRequirement extends EncounterPokemonRequirement {
|
|
requiredSpecies: SpeciesId[];
|
|
minNumberOfPokemon: number;
|
|
invertQuery: boolean;
|
|
|
|
constructor(species: SpeciesId | SpeciesId[], minNumberOfPokemon = 1, invertQuery = false) {
|
|
super();
|
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
|
this.invertQuery = invertQuery;
|
|
this.requiredSpecies = coerceArray(species);
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
const partyPokemon = globalScene.getPlayerParty();
|
|
if (isNullOrUndefined(partyPokemon) || this.requiredSpecies?.length < 0) {
|
|
return false;
|
|
}
|
|
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
|
}
|
|
|
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
|
if (!this.invertQuery) {
|
|
return partyPokemon.filter(
|
|
pokemon => this.requiredSpecies.filter(species => pokemon.species.speciesId === species).length > 0,
|
|
);
|
|
}
|
|
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed speciess
|
|
return partyPokemon.filter(
|
|
pokemon => this.requiredSpecies.filter(species => pokemon.species.speciesId === species).length === 0,
|
|
);
|
|
}
|
|
|
|
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
|
if (pokemon?.species.speciesId && this.requiredSpecies.includes(pokemon.species.speciesId)) {
|
|
return ["species", SpeciesId[pokemon.species.speciesId]];
|
|
}
|
|
return ["species", ""];
|
|
}
|
|
}
|
|
|
|
export class NatureRequirement extends EncounterPokemonRequirement {
|
|
requiredNature: Nature[];
|
|
minNumberOfPokemon: number;
|
|
invertQuery: boolean;
|
|
|
|
constructor(nature: Nature | Nature[], minNumberOfPokemon = 1, invertQuery = false) {
|
|
super();
|
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
|
this.invertQuery = invertQuery;
|
|
this.requiredNature = coerceArray(nature);
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
const partyPokemon = globalScene.getPlayerParty();
|
|
if (isNullOrUndefined(partyPokemon) || this.requiredNature?.length < 0) {
|
|
return false;
|
|
}
|
|
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
|
}
|
|
|
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
|
if (!this.invertQuery) {
|
|
return partyPokemon.filter(pokemon => this.requiredNature.filter(nature => pokemon.nature === nature).length > 0);
|
|
}
|
|
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed natures
|
|
return partyPokemon.filter(pokemon => this.requiredNature.filter(nature => pokemon.nature === nature).length === 0);
|
|
}
|
|
|
|
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
|
if (!isNullOrUndefined(pokemon?.nature) && this.requiredNature.includes(pokemon.nature)) {
|
|
return ["nature", Nature[pokemon.nature]];
|
|
}
|
|
return ["nature", ""];
|
|
}
|
|
}
|
|
|
|
export class TypeRequirement extends EncounterPokemonRequirement {
|
|
requiredType: PokemonType[];
|
|
excludeFainted: boolean;
|
|
minNumberOfPokemon: number;
|
|
invertQuery: boolean;
|
|
|
|
constructor(type: PokemonType | PokemonType[], excludeFainted = true, minNumberOfPokemon = 1, invertQuery = false) {
|
|
super();
|
|
this.excludeFainted = excludeFainted;
|
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
|
this.invertQuery = invertQuery;
|
|
this.requiredType = coerceArray(type);
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
let partyPokemon = globalScene.getPlayerParty();
|
|
|
|
if (isNullOrUndefined(partyPokemon)) {
|
|
return false;
|
|
}
|
|
|
|
if (this.excludeFainted) {
|
|
partyPokemon = partyPokemon.filter(pokemon => !pokemon.isFainted());
|
|
}
|
|
|
|
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
|
}
|
|
|
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
|
if (!this.invertQuery) {
|
|
return partyPokemon.filter(
|
|
pokemon => this.requiredType.filter(type => pokemon.getTypes().includes(type)).length > 0,
|
|
);
|
|
}
|
|
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed types
|
|
return partyPokemon.filter(
|
|
pokemon => this.requiredType.filter(type => pokemon.getTypes().includes(type)).length === 0,
|
|
);
|
|
}
|
|
|
|
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
|
const includedTypes = this.requiredType.filter(ty => pokemon?.getTypes().includes(ty));
|
|
if (includedTypes.length > 0) {
|
|
return ["type", PokemonType[includedTypes[0]]];
|
|
}
|
|
return ["type", ""];
|
|
}
|
|
}
|
|
|
|
export class MoveRequirement extends EncounterPokemonRequirement {
|
|
requiredMoves: MoveId[] = [];
|
|
minNumberOfPokemon: number;
|
|
invertQuery: boolean;
|
|
excludeDisallowedPokemon: boolean;
|
|
|
|
constructor(
|
|
moves: MoveId | MoveId[],
|
|
excludeDisallowedPokemon: boolean,
|
|
minNumberOfPokemon = 1,
|
|
invertQuery = false,
|
|
) {
|
|
super();
|
|
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
|
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
|
this.invertQuery = invertQuery;
|
|
this.requiredMoves = coerceArray(moves);
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
const partyPokemon = globalScene.getPlayerParty();
|
|
if (isNullOrUndefined(partyPokemon) || this.requiredMoves?.length < 0) {
|
|
return false;
|
|
}
|
|
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
|
}
|
|
|
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
|
if (!this.invertQuery) {
|
|
// get the Pokemon with at least one move in the required moves list
|
|
return partyPokemon.filter(
|
|
pokemon =>
|
|
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) &&
|
|
pokemon.moveset.some(move => move.moveId && this.requiredMoves.includes(move.moveId)),
|
|
);
|
|
}
|
|
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed moves
|
|
return partyPokemon.filter(
|
|
pokemon =>
|
|
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) &&
|
|
!pokemon.moveset.some(move => move.moveId && this.requiredMoves.includes(move.moveId)),
|
|
);
|
|
}
|
|
|
|
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
|
const includedMoves = pokemon?.moveset.filter(move => move.moveId && this.requiredMoves.includes(move.moveId));
|
|
if (includedMoves && includedMoves.length > 0 && includedMoves[0]) {
|
|
return ["move", includedMoves[0].getName()];
|
|
}
|
|
return ["move", ""];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find out if Pokemon in the party are able to learn one of many specific moves by TM.
|
|
* NOTE: Egg moves are not included as learnable.
|
|
* NOTE: If the Pokemon already knows the move, this requirement will fail, since it's not technically learnable.
|
|
*/
|
|
export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
|
|
requiredMoves: MoveId[];
|
|
minNumberOfPokemon: number;
|
|
invertQuery: boolean;
|
|
|
|
constructor(learnableMove: MoveId | MoveId[], minNumberOfPokemon = 1, invertQuery = false) {
|
|
super();
|
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
|
this.invertQuery = invertQuery;
|
|
this.requiredMoves = coerceArray(learnableMove);
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
const partyPokemon = globalScene.getPlayerParty();
|
|
if (isNullOrUndefined(partyPokemon) || this.requiredMoves?.length < 0) {
|
|
return false;
|
|
}
|
|
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
|
}
|
|
|
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
|
if (!this.invertQuery) {
|
|
return partyPokemon.filter(
|
|
pokemon =>
|
|
this.requiredMoves.filter(learnableMove =>
|
|
pokemon.compatibleTms.filter(tm => !pokemon.moveset.find(m => m.moveId === tm)).includes(learnableMove),
|
|
).length > 0,
|
|
);
|
|
}
|
|
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed learnableMoves
|
|
return partyPokemon.filter(
|
|
pokemon =>
|
|
this.requiredMoves.filter(learnableMove =>
|
|
pokemon.compatibleTms.filter(tm => !pokemon.moveset.find(m => m.moveId === tm)).includes(learnableMove),
|
|
).length === 0,
|
|
);
|
|
}
|
|
|
|
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
|
const includedCompatMoves = this.requiredMoves.filter(reqMove =>
|
|
pokemon?.compatibleTms.filter(tm => !pokemon.moveset.find(m => m.moveId === tm)).includes(reqMove),
|
|
);
|
|
if (includedCompatMoves.length > 0) {
|
|
return ["compatibleMove", MoveId[includedCompatMoves[0]]];
|
|
}
|
|
return ["compatibleMove", ""];
|
|
}
|
|
}
|
|
|
|
export class AbilityRequirement extends EncounterPokemonRequirement {
|
|
requiredAbilities: AbilityId[];
|
|
minNumberOfPokemon: number;
|
|
invertQuery: boolean;
|
|
excludeDisallowedPokemon: boolean;
|
|
|
|
constructor(
|
|
abilities: AbilityId | AbilityId[],
|
|
excludeDisallowedPokemon: boolean,
|
|
minNumberOfPokemon = 1,
|
|
invertQuery = false,
|
|
) {
|
|
super();
|
|
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
|
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
|
this.invertQuery = invertQuery;
|
|
this.requiredAbilities = coerceArray(abilities);
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
const partyPokemon = globalScene.getPlayerParty();
|
|
if (isNullOrUndefined(partyPokemon) || this.requiredAbilities?.length < 0) {
|
|
return false;
|
|
}
|
|
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
|
}
|
|
|
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
|
if (!this.invertQuery) {
|
|
return partyPokemon.filter(
|
|
pokemon =>
|
|
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) &&
|
|
this.requiredAbilities.some(ability => pokemon.hasAbility(ability, false)),
|
|
);
|
|
}
|
|
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed abilities
|
|
return partyPokemon.filter(
|
|
pokemon =>
|
|
(!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) &&
|
|
this.requiredAbilities.filter(ability => pokemon.hasAbility(ability, false)).length === 0,
|
|
);
|
|
}
|
|
|
|
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
|
const matchingAbility = this.requiredAbilities.find(a => pokemon?.hasAbility(a, false));
|
|
if (!isNullOrUndefined(matchingAbility)) {
|
|
return ["ability", allAbilities[matchingAbility].name];
|
|
}
|
|
return ["ability", ""];
|
|
}
|
|
}
|
|
|
|
export class StatusEffectRequirement extends EncounterPokemonRequirement {
|
|
requiredStatusEffect: StatusEffect[];
|
|
minNumberOfPokemon: number;
|
|
invertQuery: boolean;
|
|
|
|
constructor(statusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon = 1, invertQuery = false) {
|
|
super();
|
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
|
this.invertQuery = invertQuery;
|
|
this.requiredStatusEffect = coerceArray(statusEffect);
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
const partyPokemon = globalScene.getPlayerParty();
|
|
if (isNullOrUndefined(partyPokemon) || this.requiredStatusEffect?.length < 0) {
|
|
return false;
|
|
}
|
|
const x = this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
|
console.log(x);
|
|
return x;
|
|
}
|
|
|
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
|
if (!this.invertQuery) {
|
|
return partyPokemon.filter(pokemon => {
|
|
return this.requiredStatusEffect.some(statusEffect => {
|
|
if (statusEffect === StatusEffect.NONE) {
|
|
// StatusEffect.NONE also checks for null or undefined status
|
|
return (
|
|
isNullOrUndefined(pokemon.status) ||
|
|
isNullOrUndefined(pokemon.status.effect) ||
|
|
pokemon.status.effect === statusEffect
|
|
);
|
|
}
|
|
return pokemon.status?.effect === statusEffect;
|
|
});
|
|
});
|
|
}
|
|
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed StatusEffects
|
|
return partyPokemon.filter(pokemon => {
|
|
return !this.requiredStatusEffect.some(statusEffect => {
|
|
if (statusEffect === StatusEffect.NONE) {
|
|
// StatusEffect.NONE also checks for null or undefined status
|
|
return (
|
|
isNullOrUndefined(pokemon.status) ||
|
|
isNullOrUndefined(pokemon.status.effect) ||
|
|
pokemon.status.effect === statusEffect
|
|
);
|
|
}
|
|
return pokemon.status?.effect === statusEffect;
|
|
});
|
|
});
|
|
}
|
|
|
|
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
|
const reqStatus = this.requiredStatusEffect.filter(a => {
|
|
if (a === StatusEffect.NONE) {
|
|
return (
|
|
isNullOrUndefined(pokemon?.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === a
|
|
);
|
|
}
|
|
return pokemon!.status?.effect === a;
|
|
});
|
|
if (reqStatus.length > 0) {
|
|
return ["status", StatusEffect[reqStatus[0]]];
|
|
}
|
|
return ["status", ""];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds if there are pokemon that can form change with a given item.
|
|
* Notice that we mean specific items, like Charizardite, not the Mega Bracelet.
|
|
* If you want to trigger the event based on the form change enabler, use PersistentModifierRequirement.
|
|
*/
|
|
export class CanFormChangeWithItemRequirement extends EncounterPokemonRequirement {
|
|
requiredFormChangeItem: FormChangeItem[];
|
|
minNumberOfPokemon: number;
|
|
invertQuery: boolean;
|
|
|
|
constructor(formChangeItem: FormChangeItem | FormChangeItem[], minNumberOfPokemon = 1, invertQuery = false) {
|
|
super();
|
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
|
this.invertQuery = invertQuery;
|
|
this.requiredFormChangeItem = coerceArray(formChangeItem);
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
const partyPokemon = globalScene.getPlayerParty();
|
|
if (isNullOrUndefined(partyPokemon) || this.requiredFormChangeItem?.length < 0) {
|
|
return false;
|
|
}
|
|
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
|
}
|
|
|
|
filterByForm(pokemon, formChangeItem) {
|
|
return (
|
|
pokemonFormChanges.hasOwnProperty(pokemon.species.speciesId) &&
|
|
// Get all form changes for this species with an item trigger, including any compound triggers
|
|
pokemonFormChanges[pokemon.species.speciesId]
|
|
.filter(fc => fc.trigger.hasTriggerType(SpeciesFormChangeItemTrigger))
|
|
// Returns true if any form changes match this item
|
|
.flatMap(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger)
|
|
.flatMap(fc => fc.item)
|
|
.includes(formChangeItem)
|
|
);
|
|
}
|
|
|
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
|
if (!this.invertQuery) {
|
|
return partyPokemon.filter(
|
|
pokemon =>
|
|
this.requiredFormChangeItem.filter(formChangeItem => this.filterByForm(pokemon, formChangeItem)).length > 0,
|
|
);
|
|
}
|
|
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed formChangeItems
|
|
return partyPokemon.filter(
|
|
pokemon =>
|
|
this.requiredFormChangeItem.filter(formChangeItem => this.filterByForm(pokemon, formChangeItem)).length === 0,
|
|
);
|
|
}
|
|
|
|
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
|
const requiredItems = this.requiredFormChangeItem.filter(formChangeItem =>
|
|
this.filterByForm(pokemon, formChangeItem),
|
|
);
|
|
if (requiredItems.length > 0) {
|
|
return ["formChangeItem", FormChangeItem[requiredItems[0]]];
|
|
}
|
|
return ["formChangeItem", ""];
|
|
}
|
|
}
|
|
|
|
export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
|
|
requiredEvolutionItem: EvolutionItem[];
|
|
minNumberOfPokemon: number;
|
|
invertQuery: boolean;
|
|
|
|
constructor(evolutionItems: EvolutionItem | EvolutionItem[], minNumberOfPokemon = 1, invertQuery = false) {
|
|
super();
|
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
|
this.invertQuery = invertQuery;
|
|
this.requiredEvolutionItem = coerceArray(evolutionItems);
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
const partyPokemon = globalScene.getPlayerParty();
|
|
if (isNullOrUndefined(partyPokemon) || this.requiredEvolutionItem?.length < 0) {
|
|
return false;
|
|
}
|
|
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
|
}
|
|
|
|
filterByEvo(pokemon, evolutionItem) {
|
|
if (
|
|
pokemonEvolutions.hasOwnProperty(pokemon.species.speciesId) &&
|
|
pokemonEvolutions[pokemon.species.speciesId].filter(
|
|
e => e.item === evolutionItem && (!e.condition || e.condition.predicate(pokemon)),
|
|
).length &&
|
|
pokemon.getFormKey() !== SpeciesFormKey.GIGANTAMAX
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return (
|
|
pokemon.isFusion() &&
|
|
pokemonEvolutions.hasOwnProperty(pokemon.fusionSpecies.speciesId) &&
|
|
pokemonEvolutions[pokemon.fusionSpecies.speciesId].filter(
|
|
e => e.item === evolutionItem && (!e.condition || e.condition.predicate(pokemon)),
|
|
).length &&
|
|
pokemon.getFusionFormKey() !== SpeciesFormKey.GIGANTAMAX
|
|
);
|
|
}
|
|
|
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
|
if (!this.invertQuery) {
|
|
return partyPokemon.filter(
|
|
pokemon =>
|
|
this.requiredEvolutionItem.filter(evolutionItem => this.filterByEvo(pokemon, evolutionItem)).length > 0,
|
|
);
|
|
}
|
|
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed evolutionItemss
|
|
return partyPokemon.filter(
|
|
pokemon =>
|
|
this.requiredEvolutionItem.filter(evolutionItems => this.filterByEvo(pokemon, evolutionItems)).length === 0,
|
|
);
|
|
}
|
|
|
|
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
|
const requiredItems = this.requiredEvolutionItem.filter(evoItem => this.filterByEvo(pokemon, evoItem));
|
|
if (requiredItems.length > 0) {
|
|
return ["evolutionItem", EvolutionItem[requiredItems[0]]];
|
|
}
|
|
return ["evolutionItem", ""];
|
|
}
|
|
}
|
|
|
|
export class HeldItemRequirement extends EncounterPokemonRequirement {
|
|
requiredHeldItemModifiers: string[];
|
|
minNumberOfPokemon: number;
|
|
invertQuery: boolean;
|
|
requireTransferable: boolean;
|
|
|
|
constructor(heldItem: string | string[], minNumberOfPokemon = 1, invertQuery = false, requireTransferable = true) {
|
|
super();
|
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
|
this.invertQuery = invertQuery;
|
|
this.requiredHeldItemModifiers = coerceArray(heldItem);
|
|
this.requireTransferable = requireTransferable;
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
const partyPokemon = globalScene.getPlayerParty();
|
|
if (isNullOrUndefined(partyPokemon)) {
|
|
return false;
|
|
}
|
|
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
|
}
|
|
|
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
|
if (!this.invertQuery) {
|
|
return partyPokemon.filter(pokemon =>
|
|
this.requiredHeldItemModifiers.some(heldItem => {
|
|
return pokemon.getHeldItems().some(it => {
|
|
return it.constructor.name === heldItem && (!this.requireTransferable || it.isTransferable);
|
|
});
|
|
}),
|
|
);
|
|
}
|
|
// for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers
|
|
// E.g. functions as a blacklist
|
|
return partyPokemon.filter(
|
|
pokemon =>
|
|
pokemon.getHeldItems().filter(it => {
|
|
return (
|
|
!this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem) &&
|
|
(!this.requireTransferable || it.isTransferable)
|
|
);
|
|
}).length > 0,
|
|
);
|
|
}
|
|
|
|
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
|
const requiredItems = pokemon?.getHeldItems().filter(it => {
|
|
return (
|
|
this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem) &&
|
|
(!this.requireTransferable || it.isTransferable)
|
|
);
|
|
});
|
|
if (requiredItems && requiredItems.length > 0) {
|
|
return ["heldItem", requiredItems[0].type.name];
|
|
}
|
|
return ["heldItem", ""];
|
|
}
|
|
}
|
|
|
|
export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRequirement {
|
|
requiredHeldItemTypes: PokemonType[];
|
|
minNumberOfPokemon: number;
|
|
invertQuery: boolean;
|
|
requireTransferable: boolean;
|
|
|
|
constructor(
|
|
heldItemTypes: PokemonType | PokemonType[],
|
|
minNumberOfPokemon = 1,
|
|
invertQuery = false,
|
|
requireTransferable = true,
|
|
) {
|
|
super();
|
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
|
this.invertQuery = invertQuery;
|
|
this.requiredHeldItemTypes = coerceArray(heldItemTypes);
|
|
this.requireTransferable = requireTransferable;
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
const partyPokemon = globalScene.getPlayerParty();
|
|
if (isNullOrUndefined(partyPokemon)) {
|
|
return false;
|
|
}
|
|
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
|
}
|
|
|
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
|
if (!this.invertQuery) {
|
|
return partyPokemon.filter(pokemon =>
|
|
this.requiredHeldItemTypes.some(heldItemType => {
|
|
return pokemon.getHeldItems().some(it => {
|
|
return (
|
|
it instanceof AttackTypeBoosterModifier &&
|
|
(it.type as AttackTypeBoosterModifierType).moveType === heldItemType &&
|
|
(!this.requireTransferable || it.isTransferable)
|
|
);
|
|
});
|
|
}),
|
|
);
|
|
}
|
|
// for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers
|
|
// E.g. functions as a blacklist
|
|
return partyPokemon.filter(
|
|
pokemon =>
|
|
pokemon.getHeldItems().filter(it => {
|
|
return !this.requiredHeldItemTypes.some(
|
|
heldItemType =>
|
|
it instanceof AttackTypeBoosterModifier &&
|
|
(it.type as AttackTypeBoosterModifierType).moveType === heldItemType &&
|
|
(!this.requireTransferable || it.isTransferable),
|
|
);
|
|
}).length > 0,
|
|
);
|
|
}
|
|
|
|
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
|
const requiredItems = pokemon?.getHeldItems().filter(it => {
|
|
return (
|
|
this.requiredHeldItemTypes.some(
|
|
heldItemType =>
|
|
it instanceof AttackTypeBoosterModifier &&
|
|
(it.type as AttackTypeBoosterModifierType).moveType === heldItemType,
|
|
) &&
|
|
(!this.requireTransferable || it.isTransferable)
|
|
);
|
|
});
|
|
if (requiredItems && requiredItems.length > 0) {
|
|
return ["heldItem", requiredItems[0].type.name];
|
|
}
|
|
return ["heldItem", ""];
|
|
}
|
|
}
|
|
|
|
export class LevelRequirement extends EncounterPokemonRequirement {
|
|
requiredLevelRange: [number, number];
|
|
minNumberOfPokemon: number;
|
|
invertQuery: boolean;
|
|
|
|
constructor(requiredLevelRange: [number, number], minNumberOfPokemon = 1, invertQuery = false) {
|
|
super();
|
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
|
this.invertQuery = invertQuery;
|
|
this.requiredLevelRange = requiredLevelRange;
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
// Party Pokemon inside required level range
|
|
if (!isNullOrUndefined(this.requiredLevelRange) && this.requiredLevelRange[0] <= this.requiredLevelRange[1]) {
|
|
const partyPokemon = globalScene.getPlayerParty();
|
|
const pokemonInRange = this.queryParty(partyPokemon);
|
|
if (pokemonInRange.length < this.minNumberOfPokemon) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
|
if (!this.invertQuery) {
|
|
return partyPokemon.filter(
|
|
pokemon => pokemon.level >= this.requiredLevelRange[0] && pokemon.level <= this.requiredLevelRange[1],
|
|
);
|
|
}
|
|
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredLevelRanges
|
|
return partyPokemon.filter(
|
|
pokemon => pokemon.level < this.requiredLevelRange[0] || pokemon.level > this.requiredLevelRange[1],
|
|
);
|
|
}
|
|
|
|
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
|
return ["level", pokemon?.level.toString() ?? ""];
|
|
}
|
|
}
|
|
|
|
export class FriendshipRequirement extends EncounterPokemonRequirement {
|
|
requiredFriendshipRange: [number, number];
|
|
minNumberOfPokemon: number;
|
|
invertQuery: boolean;
|
|
|
|
constructor(requiredFriendshipRange: [number, number], minNumberOfPokemon = 1, invertQuery = false) {
|
|
super();
|
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
|
this.invertQuery = invertQuery;
|
|
this.requiredFriendshipRange = requiredFriendshipRange;
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
// Party Pokemon inside required friendship range
|
|
if (
|
|
!isNullOrUndefined(this.requiredFriendshipRange) &&
|
|
this.requiredFriendshipRange[0] <= this.requiredFriendshipRange[1]
|
|
) {
|
|
const partyPokemon = globalScene.getPlayerParty();
|
|
const pokemonInRange = this.queryParty(partyPokemon);
|
|
if (pokemonInRange.length < this.minNumberOfPokemon) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
|
if (!this.invertQuery) {
|
|
return partyPokemon.filter(
|
|
pokemon =>
|
|
pokemon.friendship >= this.requiredFriendshipRange[0] &&
|
|
pokemon.friendship <= this.requiredFriendshipRange[1],
|
|
);
|
|
}
|
|
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredFriendshipRanges
|
|
return partyPokemon.filter(
|
|
pokemon =>
|
|
pokemon.friendship < this.requiredFriendshipRange[0] || pokemon.friendship > this.requiredFriendshipRange[1],
|
|
);
|
|
}
|
|
|
|
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
|
return ["friendship", pokemon?.friendship.toString() ?? ""];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* .1 -> 10% hp
|
|
* .5 -> 50% hp
|
|
* 1 -> 100% hp
|
|
*/
|
|
export class HealthRatioRequirement extends EncounterPokemonRequirement {
|
|
requiredHealthRange: [number, number];
|
|
minNumberOfPokemon: number;
|
|
invertQuery: boolean;
|
|
|
|
constructor(requiredHealthRange: [number, number], minNumberOfPokemon = 1, invertQuery = false) {
|
|
super();
|
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
|
this.invertQuery = invertQuery;
|
|
this.requiredHealthRange = requiredHealthRange;
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
// Party Pokemon's health inside required health range
|
|
if (!isNullOrUndefined(this.requiredHealthRange) && this.requiredHealthRange[0] <= this.requiredHealthRange[1]) {
|
|
const partyPokemon = globalScene.getPlayerParty();
|
|
const pokemonInRange = this.queryParty(partyPokemon);
|
|
if (pokemonInRange.length < this.minNumberOfPokemon) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
|
if (!this.invertQuery) {
|
|
return partyPokemon.filter(pokemon => {
|
|
return (
|
|
pokemon.getHpRatio() >= this.requiredHealthRange[0] && pokemon.getHpRatio() <= this.requiredHealthRange[1]
|
|
);
|
|
});
|
|
}
|
|
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredHealthRanges
|
|
return partyPokemon.filter(
|
|
pokemon =>
|
|
pokemon.getHpRatio() < this.requiredHealthRange[0] || pokemon.getHpRatio() > this.requiredHealthRange[1],
|
|
);
|
|
}
|
|
|
|
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
|
const hpRatio = pokemon?.getHpRatio();
|
|
if (!isNullOrUndefined(hpRatio)) {
|
|
return ["healthRatio", Math.floor(hpRatio * 100).toString() + "%"];
|
|
}
|
|
return ["healthRatio", ""];
|
|
}
|
|
}
|
|
|
|
export class WeightRequirement extends EncounterPokemonRequirement {
|
|
requiredWeightRange: [number, number];
|
|
minNumberOfPokemon: number;
|
|
invertQuery: boolean;
|
|
|
|
constructor(requiredWeightRange: [number, number], minNumberOfPokemon = 1, invertQuery = false) {
|
|
super();
|
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
|
this.invertQuery = invertQuery;
|
|
this.requiredWeightRange = requiredWeightRange;
|
|
}
|
|
|
|
override meetsRequirement(): boolean {
|
|
// Party Pokemon's weight inside required weight range
|
|
if (!isNullOrUndefined(this.requiredWeightRange) && this.requiredWeightRange[0] <= this.requiredWeightRange[1]) {
|
|
const partyPokemon = globalScene.getPlayerParty();
|
|
const pokemonInRange = this.queryParty(partyPokemon);
|
|
if (pokemonInRange.length < this.minNumberOfPokemon) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
|
if (!this.invertQuery) {
|
|
return partyPokemon.filter(
|
|
pokemon =>
|
|
pokemon.getWeight() >= this.requiredWeightRange[0] && pokemon.getWeight() <= this.requiredWeightRange[1],
|
|
);
|
|
}
|
|
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredWeightRanges
|
|
return partyPokemon.filter(
|
|
pokemon => pokemon.getWeight() < this.requiredWeightRange[0] || pokemon.getWeight() > this.requiredWeightRange[1],
|
|
);
|
|
}
|
|
|
|
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
|
return ["weight", pokemon?.getWeight().toString() ?? ""];
|
|
}
|
|
}
|