mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-12-15 06:15:20 +01:00
* make IVs use Uint8Array * Add many typed array helpers * Move array utils to its own file * Add suppression comment * Adjust type of `getStats` * Adjust test mocks to use typed arrays * Adjust signatures of some phases to use ArrayLike<T> * Adjust signature of src/ui/containers/stats-container#updateIvs * Remove comment gap to try to satisfy typedoc * Ensure ivs are always set * fix: fun-and-games me to use typed array * Add new tests for array utilities * Update type of ivs in save-data.ts * Update part-timer-encounter.test.ts * Convert uses of StatusEffect[] to Uint8Array * Update ssui to use uint8array for ivs * Revert use of typed arrays * Move `nil` to @types/common * Make more arrays readonly * fix: remnant change to immune effects * Even more array improvements * Apply kev's suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * address Bertie's comments from code review * tests: remove undefined check for bigint array types * fixup abilities.ts --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
346 lines
12 KiB
TypeScript
346 lines
12 KiB
TypeScript
import { globalScene } from "#app/global-scene";
|
|
import { getPokemonNameWithAffix } from "#app/messages";
|
|
import type { SpeciesFormChange } from "#data/pokemon-forms";
|
|
import { AbilityId } from "#enums/ability-id";
|
|
import { Challenges } from "#enums/challenges";
|
|
import { FormChangeItem } from "#enums/form-change-item";
|
|
import { MoveId } from "#enums/move-id";
|
|
import { SpeciesFormKey } from "#enums/species-form-key";
|
|
import { StatusEffect } from "#enums/status-effect";
|
|
import type { TimeOfDay } from "#enums/time-of-day";
|
|
import { WeatherType } from "#enums/weather-type";
|
|
import type { Pokemon } from "#field/pokemon";
|
|
import type { PokemonFormChangeItemModifier } from "#modifiers/modifier";
|
|
import type { Constructor } from "#types/common";
|
|
import { coerceArray } from "#utils/array";
|
|
import { toCamelCase } from "#utils/strings";
|
|
import i18next from "i18next";
|
|
|
|
export abstract class SpeciesFormChangeTrigger {
|
|
public description = "";
|
|
|
|
canChange(_pokemon: Pokemon): boolean {
|
|
return true;
|
|
}
|
|
|
|
hasTriggerType(triggerType: Constructor<SpeciesFormChangeTrigger>): boolean {
|
|
return this instanceof triggerType;
|
|
}
|
|
}
|
|
|
|
export class SpeciesFormChangeManualTrigger extends SpeciesFormChangeTrigger {}
|
|
|
|
export class SpeciesFormChangeAbilityTrigger extends SpeciesFormChangeTrigger {
|
|
public description: string = i18next.t("pokemonEvolutions:forms.ability");
|
|
}
|
|
|
|
export class SpeciesFormChangeCompoundTrigger {
|
|
public description = "";
|
|
public triggers: SpeciesFormChangeTrigger[];
|
|
|
|
constructor(...triggers: SpeciesFormChangeTrigger[]) {
|
|
this.triggers = triggers;
|
|
this.description = this.triggers
|
|
.filter(trigger => trigger?.description?.length > 0)
|
|
.map(trigger => trigger.description)
|
|
.join(", ");
|
|
}
|
|
|
|
canChange(pokemon: Pokemon): boolean {
|
|
for (const trigger of this.triggers) {
|
|
if (!trigger.canChange(pokemon)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
hasTriggerType(triggerType: Constructor<SpeciesFormChangeTrigger>): boolean {
|
|
return !!this.triggers.find(t => t.hasTriggerType(triggerType));
|
|
}
|
|
}
|
|
|
|
export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger {
|
|
public item: FormChangeItem;
|
|
public active: boolean;
|
|
|
|
constructor(item: FormChangeItem, active = true) {
|
|
super();
|
|
this.item = item;
|
|
this.active = active;
|
|
this.description = this.active
|
|
? i18next.t("pokemonEvolutions:forms.item", {
|
|
item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`),
|
|
})
|
|
: i18next.t("pokemonEvolutions:forms.deactivateItem", {
|
|
item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`),
|
|
});
|
|
}
|
|
|
|
canChange(pokemon: Pokemon): boolean {
|
|
return !!globalScene.findModifier(r => {
|
|
// Assume that if m has the `formChangeItem` property, then it is a PokemonFormChangeItemModifier
|
|
const m = r as PokemonFormChangeItemModifier;
|
|
return (
|
|
"formChangeItem" in m
|
|
&& m.pokemonId === pokemon.id
|
|
&& m.formChangeItem === this.item
|
|
&& m.active === this.active
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
export class SpeciesFormChangeTimeOfDayTrigger extends SpeciesFormChangeTrigger {
|
|
public timesOfDay: TimeOfDay[];
|
|
|
|
constructor(...timesOfDay: TimeOfDay[]) {
|
|
super();
|
|
this.timesOfDay = timesOfDay;
|
|
this.description = i18next.t("pokemonEvolutions:orms.timeOfDay");
|
|
}
|
|
|
|
canChange(_pokemon: Pokemon): boolean {
|
|
return this.timesOfDay.indexOf(globalScene.arena.getTimeOfDay()) > -1;
|
|
}
|
|
}
|
|
export class SpeciesFormChangeActiveTrigger extends SpeciesFormChangeTrigger {
|
|
public active: boolean;
|
|
|
|
constructor(active = false) {
|
|
super();
|
|
this.active = active;
|
|
this.description = this.active
|
|
? i18next.t("pokemonEvolutions:forms.enter")
|
|
: i18next.t("pokemonEvolutions:forms.leave");
|
|
}
|
|
|
|
canChange(pokemon: Pokemon): boolean {
|
|
return pokemon.isActive(true) === this.active;
|
|
}
|
|
}
|
|
|
|
export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigger {
|
|
public readonly statusEffects: readonly StatusEffect[];
|
|
public invert: boolean;
|
|
|
|
constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) {
|
|
super();
|
|
this.statusEffects = coerceArray(statusEffects);
|
|
this.invert = invert;
|
|
// this.description = i18next.t("pokemonEvolutions:forms.statusEffect");
|
|
}
|
|
|
|
canChange(pokemon: Pokemon): boolean {
|
|
return this.statusEffects.indexOf(pokemon.status?.effect || StatusEffect.NONE) > -1 !== this.invert;
|
|
}
|
|
}
|
|
|
|
export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigger {
|
|
public move: MoveId;
|
|
public known: boolean;
|
|
|
|
constructor(move: MoveId, known = true) {
|
|
super();
|
|
this.move = move;
|
|
this.known = known;
|
|
const moveKey = toCamelCase(MoveId[this.move]);
|
|
this.description = known
|
|
? i18next.t("pokemonEvolutions:forms.moveLearned", {
|
|
move: i18next.t(`move:${moveKey}.name`),
|
|
})
|
|
: i18next.t("pokemonEvolutions:forms.moveForgotten", {
|
|
move: i18next.t(`move:${moveKey}.name`),
|
|
});
|
|
}
|
|
|
|
canChange(pokemon: Pokemon): boolean {
|
|
return pokemon.moveset.filter(m => m.moveId === this.move).length > 0 === this.known;
|
|
}
|
|
}
|
|
|
|
export abstract class SpeciesFormChangeMoveTrigger extends SpeciesFormChangeTrigger {
|
|
public movePredicate: (m: MoveId) => boolean;
|
|
public used: boolean;
|
|
|
|
constructor(move: MoveId | ((m: MoveId) => boolean), used = true) {
|
|
super();
|
|
this.movePredicate = typeof move === "function" ? move : (m: MoveId) => m === move;
|
|
this.used = used;
|
|
}
|
|
}
|
|
|
|
export class SpeciesFormChangePreMoveTrigger extends SpeciesFormChangeMoveTrigger {
|
|
description = i18next.t("pokemonEvolutions:forms.preMove");
|
|
canChange(pokemon: Pokemon): boolean {
|
|
const command = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()];
|
|
return !!command?.move && this.movePredicate(command.move.move) === this.used;
|
|
}
|
|
}
|
|
|
|
export class SpeciesFormChangePostMoveTrigger extends SpeciesFormChangeMoveTrigger {
|
|
description = i18next.t("pokemonEvolutions:forms.postMove");
|
|
canChange(pokemon: Pokemon): boolean {
|
|
return (
|
|
pokemon.summonData && pokemon.getLastXMoves(1).filter(m => this.movePredicate(m.move)).length > 0 === this.used
|
|
);
|
|
}
|
|
}
|
|
|
|
export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMoveTrigger {
|
|
override canChange(pokemon: Pokemon): boolean {
|
|
if (globalScene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) {
|
|
return false;
|
|
}
|
|
// Meloetta will not transform if it has the ability Sheer Force when using Relic Song
|
|
if (pokemon.hasAbility(AbilityId.SHEER_FORCE)) {
|
|
return false;
|
|
}
|
|
return super.canChange(pokemon);
|
|
}
|
|
}
|
|
|
|
export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger {
|
|
private formKey: string;
|
|
|
|
constructor(formKey: string) {
|
|
super();
|
|
this.formKey = formKey;
|
|
this.description = "";
|
|
}
|
|
|
|
canChange(pokemon: Pokemon): boolean {
|
|
return (
|
|
this.formKey
|
|
=== pokemon.species.forms[
|
|
globalScene.getSpeciesFormIndex(pokemon.species, pokemon.gender, pokemon.getNature(), true)
|
|
].formKey
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Class used for triggering form changes based on the user's Tera type.
|
|
* Used by Ogerpon and Terapagos.
|
|
*/
|
|
export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger {}
|
|
|
|
/**
|
|
* Class used for triggering form changes based on the user's lapsed Tera type.
|
|
* Used by Ogerpon and Terapagos.
|
|
*/
|
|
export class SpeciesFormChangeLapseTeraTrigger extends SpeciesFormChangeTrigger {}
|
|
|
|
/**
|
|
* Class used for triggering form changes based on weather.
|
|
* Used by Castform and Cherrim.
|
|
*/
|
|
export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger {
|
|
/** The ability that triggers the form change */
|
|
public ability: AbilityId;
|
|
/** The list of weathers that trigger the form change */
|
|
public readonly weathers: readonly WeatherType[];
|
|
|
|
constructor(ability: AbilityId, weathers: readonly WeatherType[]) {
|
|
super();
|
|
this.ability = ability;
|
|
this.weathers = weathers;
|
|
this.description = i18next.t("pokemonEvolutions:forms.weather");
|
|
}
|
|
|
|
/**
|
|
* Checks if the Pokemon has the required ability and is in the correct weather while
|
|
* the weather or ability is also not suppressed.
|
|
* @param pokemon - The pokemon that is trying to do the form change
|
|
* @returns `true` if the Pokemon can change forms, `false` otherwise
|
|
*/
|
|
canChange(pokemon: Pokemon): boolean {
|
|
const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE;
|
|
const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed();
|
|
const isAbilitySuppressed = pokemon.summonData.abilitySuppressed;
|
|
|
|
return (
|
|
!isAbilitySuppressed
|
|
&& !isWeatherSuppressed
|
|
&& pokemon.hasAbility(this.ability)
|
|
&& this.weathers.includes(currentWeather)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Class used for reverting to the original form when the weather runs out
|
|
* or when the user loses the ability/is suppressed.
|
|
* Used by Castform and Cherrim.
|
|
*/
|
|
export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChangeTrigger {
|
|
/** The ability that triggers the form change*/
|
|
public ability: AbilityId;
|
|
/** The list of weathers that will also trigger a form change to original form */
|
|
public readonly weathers: readonly WeatherType[];
|
|
|
|
constructor(ability: AbilityId, weathers: readonly WeatherType[]) {
|
|
super();
|
|
this.ability = ability;
|
|
this.weathers = weathers;
|
|
this.description = i18next.t("pokemonEvolutions:forms.weatherRevert");
|
|
}
|
|
|
|
/**
|
|
* Checks if the Pokemon has the required ability and the weather is one that will revert
|
|
* the Pokemon to its original form or the weather or ability is suppressed
|
|
* @param pokemon the pokemon that is trying to do the form change
|
|
* @returns `true` if the Pokemon will revert to its original form, `false` otherwise
|
|
*/
|
|
canChange(pokemon: Pokemon): boolean {
|
|
if (pokemon.hasAbility(this.ability, false, true)) {
|
|
const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE;
|
|
const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed();
|
|
const isAbilitySuppressed = pokemon.summonData.abilitySuppressed;
|
|
const summonDataAbility = pokemon.summonData.ability;
|
|
const isAbilityChanged = summonDataAbility !== this.ability && summonDataAbility !== AbilityId.NONE;
|
|
|
|
if (this.weathers.includes(currentWeather) || isWeatherSuppressed || isAbilitySuppressed || isAbilityChanged) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: SpeciesFormChange, preName: string): string {
|
|
const formKey = formChange.formKey;
|
|
const isMega = formKey.indexOf(SpeciesFormKey.MEGA) > -1;
|
|
const isGmax = formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1;
|
|
const isEmax = formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1;
|
|
const isRevert = !isMega && formChange.formKey === pokemon.species.forms[0].formKey;
|
|
if (isMega) {
|
|
return i18next.t("battlePokemonForm:megaChange", {
|
|
preName,
|
|
pokemonName: pokemon.name,
|
|
});
|
|
}
|
|
if (isGmax) {
|
|
return i18next.t("battlePokemonForm:gigantamaxChange", {
|
|
preName,
|
|
pokemonName: pokemon.name,
|
|
});
|
|
}
|
|
if (isEmax) {
|
|
return i18next.t("battlePokemonForm:eternamaxChange", {
|
|
preName,
|
|
pokemonName: pokemon.name,
|
|
});
|
|
}
|
|
if (isRevert) {
|
|
return i18next.t("battlePokemonForm:revertChange", {
|
|
pokemonName: getPokemonNameWithAffix(pokemon),
|
|
});
|
|
}
|
|
if (pokemon.getAbility().id === AbilityId.DISGUISE) {
|
|
return i18next.t("battlePokemonForm:disguiseChange");
|
|
}
|
|
return i18next.t("battlePokemonForm:formChange", { preName });
|
|
}
|