mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-04 15:32:18 +02:00
Merge remote-tracking branch 'upstream/beta' into no-select-move-missing
This commit is contained in:
commit
71ef800582
@ -1,6 +1,19 @@
|
|||||||
/** @type {import('dependency-cruiser').IConfiguration} */
|
/** @type {import('dependency-cruiser').IConfiguration} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
forbidden: [
|
forbidden: [
|
||||||
|
{
|
||||||
|
name: "no-non-type-@type-exports",
|
||||||
|
severity: "error",
|
||||||
|
comment:
|
||||||
|
"Files in @types should not export anything but types and interfaces. " +
|
||||||
|
"The folder is intended to house imports that are removed at runtime, " +
|
||||||
|
"and thus should not contain anything with a bearing on runtime code.",
|
||||||
|
from: {},
|
||||||
|
to: {
|
||||||
|
path: "(^|/)src/@types",
|
||||||
|
dependencyTypesNot: ["type-only"],
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "only-type-imports",
|
name: "only-type-imports",
|
||||||
severity: "error",
|
severity: "error",
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import type { AbAttr } from "#app/data/abilities/ability";
|
|
||||||
import type Move from "#app/data/moves/move";
|
import type Move from "#app/data/moves/move";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import type { BattleStat } from "#enums/stat";
|
import type { BattleStat } from "#enums/stat";
|
||||||
import type { AbAttrConstructorMap } from "#app/data/abilities/ability";
|
import type { AbAttrConstructorMap } from "#app/data/abilities/ability";
|
||||||
|
|
||||||
// Intentionally re-export all types from the ability attributes module
|
// intentionally re-export all types from abilities to have this be the centralized place to import ability types
|
||||||
export type * from "#app/data/abilities/ability";
|
export type * from "#app/data/abilities/ability";
|
||||||
|
|
||||||
export type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean, ...args: any[]) => void;
|
// biome-ignore lint/correctness/noUnusedImports: Used in a tsdoc comment
|
||||||
export type AbAttrSuccessFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean, ...args: any[]) => boolean;
|
import type { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
|
|
||||||
export type AbAttrCondition = (pokemon: Pokemon) => boolean;
|
export type AbAttrCondition = (pokemon: Pokemon) => boolean;
|
||||||
export type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
|
export type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
|
||||||
export type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
|
export type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
|
||||||
@ -25,3 +25,22 @@ export type AbAttrString = keyof AbAttrConstructorMap;
|
|||||||
export type AbAttrMap = {
|
export type AbAttrMap = {
|
||||||
[K in keyof AbAttrConstructorMap]: InstanceType<AbAttrConstructorMap[K]>;
|
[K in keyof AbAttrConstructorMap]: InstanceType<AbAttrConstructorMap[K]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subset of ability attribute classes that may be passed to {@linkcode applyAbAttrs} method
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Our AbAttr classes violate Liskov Substitution Principle.
|
||||||
|
*
|
||||||
|
* AbAttrs that are not in this have subclasses with apply methods requiring different parameters than
|
||||||
|
* the base apply method.
|
||||||
|
*
|
||||||
|
* Such attributes may not be passed to the {@linkcode applyAbAttrs} method
|
||||||
|
*/
|
||||||
|
export type CallableAbAttrString =
|
||||||
|
| Exclude<AbAttrString, "PreDefendAbAttr" | "PreAttackAbAttr">
|
||||||
|
| "PreApplyBattlerTagAbAttr";
|
||||||
|
|
||||||
|
export type AbAttrParamMap = {
|
||||||
|
[K in keyof AbAttrMap]: Parameters<AbAttrMap[K]["apply"]>[0];
|
||||||
|
};
|
||||||
|
34
src/@types/type-helpers.ts
Normal file
34
src/@types/type-helpers.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* A collection of custom utility types that aid in type checking and ensuring strict type conformity
|
||||||
|
*/
|
||||||
|
|
||||||
|
// biome-ignore lint/correctness/noUnusedImports: Used in a tsdoc comment
|
||||||
|
import type { AbAttr } from "./ability-types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exactly matches the type of the argument, preventing adding additional properties.
|
||||||
|
*
|
||||||
|
* ⚠️ Should never be used with `extends`, as this will nullify the exactness of the type.
|
||||||
|
*
|
||||||
|
* As an example, used to ensure that the parameters of {@linkcode AbAttr.canApply} and {@linkcode AbAttr.getTriggerMessage} are compatible with
|
||||||
|
* the type of the apply method
|
||||||
|
*
|
||||||
|
* @typeParam T - The type to match exactly
|
||||||
|
*/
|
||||||
|
export type Exact<T> = {
|
||||||
|
[K in keyof T]: T[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type hint that indicates that the type is intended to be closed to a specific shape.
|
||||||
|
* Does not actually do anything special, is really just an alias for X.
|
||||||
|
*/
|
||||||
|
export type Closed<X> = X;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove `readonly` from all properties of the provided type
|
||||||
|
* @typeParam T - The type to make mutable
|
||||||
|
*/
|
||||||
|
export type Mutable<T> = {
|
||||||
|
-readonly [P in keyof T]: T[P];
|
||||||
|
};
|
@ -67,7 +67,7 @@ import { modifierTypes } from "./data/data-lists";
|
|||||||
import { getModifierPoolForType } from "./utils/modifier-utils";
|
import { getModifierPoolForType } from "./utils/modifier-utils";
|
||||||
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
||||||
import AbilityBar from "#app/ui/ability-bar";
|
import AbilityBar from "#app/ui/ability-bar";
|
||||||
import { applyAbAttrs, applyPostBattleInitAbAttrs, applyPostItemLostAbAttrs } from "./data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "./data/abilities/apply-ab-attrs";
|
||||||
import { allAbilities } from "./data/data-lists";
|
import { allAbilities } from "./data/data-lists";
|
||||||
import type { FixedBattleConfig } from "#app/battle";
|
import type { FixedBattleConfig } from "#app/battle";
|
||||||
import Battle from "#app/battle";
|
import Battle from "#app/battle";
|
||||||
@ -894,9 +894,19 @@ export default class BattleScene extends SceneBase {
|
|||||||
return activeOnly ? this.infoToggles.filter(t => t?.isActive()) : this.infoToggles;
|
return activeOnly ? this.infoToggles.filter(t => t?.isActive()) : this.infoToggles;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPokemonById(pokemonId: number): Pokemon | null {
|
/**
|
||||||
const findInParty = (party: Pokemon[]) => party.find(p => p.id === pokemonId);
|
* Return the {@linkcode Pokemon} associated with a given ID.
|
||||||
return (findInParty(this.getPlayerParty()) || findInParty(this.getEnemyParty())) ?? null;
|
* @param pokemonId - The ID whose Pokemon will be retrieved.
|
||||||
|
* @returns The {@linkcode Pokemon} associated with the given id.
|
||||||
|
* Returns `null` if the ID is `undefined` or not present in either party.
|
||||||
|
*/
|
||||||
|
getPokemonById(pokemonId: number | undefined): Pokemon | null {
|
||||||
|
if (isNullOrUndefined(pokemonId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const party = (this.getPlayerParty() as Pokemon[]).concat(this.getEnemyParty());
|
||||||
|
return party.find(p => p.id === pokemonId) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
addPlayerPokemon(
|
addPlayerPokemon(
|
||||||
@ -1256,7 +1266,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
const doubleChance = new NumberHolder(newWaveIndex % 10 === 0 ? 32 : 8);
|
const doubleChance = new NumberHolder(newWaveIndex % 10 === 0 ? 32 : 8);
|
||||||
this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance);
|
this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance);
|
||||||
for (const p of playerField) {
|
for (const p of playerField) {
|
||||||
applyAbAttrs("DoubleBattleChanceAbAttr", p, null, false, doubleChance);
|
applyAbAttrs("DoubleBattleChanceAbAttr", { pokemon: p, chance: doubleChance });
|
||||||
}
|
}
|
||||||
return Math.max(doubleChance.value, 1);
|
return Math.max(doubleChance.value, 1);
|
||||||
}
|
}
|
||||||
@ -1461,7 +1471,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
for (const pokemon of this.getPlayerParty()) {
|
for (const pokemon of this.getPlayerParty()) {
|
||||||
pokemon.resetBattleAndWaveData();
|
pokemon.resetBattleAndWaveData();
|
||||||
pokemon.resetTera();
|
pokemon.resetTera();
|
||||||
applyPostBattleInitAbAttrs("PostBattleInitAbAttr", pokemon);
|
applyAbAttrs("PostBattleInitAbAttr", { pokemon });
|
||||||
if (
|
if (
|
||||||
pokemon.hasSpecies(SpeciesId.TERAPAGOS) ||
|
pokemon.hasSpecies(SpeciesId.TERAPAGOS) ||
|
||||||
(this.gameMode.isClassic && this.currentBattle.waveIndex > 180 && this.currentBattle.waveIndex <= 190)
|
(this.gameMode.isClassic && this.currentBattle.waveIndex > 180 && this.currentBattle.waveIndex <= 190)
|
||||||
@ -2743,7 +2753,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
|
|
||||||
if (source && source.isPlayer() !== target.isPlayer()) {
|
if (source && source.isPlayer() !== target.isPlayer()) {
|
||||||
applyAbAttrs("BlockItemTheftAbAttr", source, cancelled);
|
applyAbAttrs("BlockItemTheftAbAttr", { pokemon: source, cancelled });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
@ -2783,13 +2793,13 @@ export default class BattleScene extends SceneBase {
|
|||||||
if (target.isPlayer()) {
|
if (target.isPlayer()) {
|
||||||
this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant);
|
this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant);
|
||||||
if (source && itemLost) {
|
if (source && itemLost) {
|
||||||
applyPostItemLostAbAttrs("PostItemLostAbAttr", source, false);
|
applyAbAttrs("PostItemLostAbAttr", { pokemon: source });
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
this.addEnemyModifier(newItemModifier, ignoreUpdate, instant);
|
this.addEnemyModifier(newItemModifier, ignoreUpdate, instant);
|
||||||
if (source && itemLost) {
|
if (source && itemLost) {
|
||||||
applyPostItemLostAbAttrs("PostItemLostAbAttr", source, false);
|
applyAbAttrs("PostItemLostAbAttr", { pokemon: source });
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -2812,7 +2822,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
|
|
||||||
if (source && source.isPlayer() !== target.isPlayer()) {
|
if (source && source.isPlayer() !== target.isPlayer()) {
|
||||||
applyAbAttrs("BlockItemTheftAbAttr", source, cancelled);
|
applyAbAttrs("BlockItemTheftAbAttr", { pokemon: source, cancelled });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,63 +1,14 @@
|
|||||||
import type { AbAttrApplyFunc, AbAttrMap, AbAttrString, AbAttrSuccessFunc } from "#app/@types/ability-types";
|
import type { AbAttrParamMap } from "#app/@types/ability-types";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type { AbAttrBaseParams, AbAttrString, CallableAbAttrString } from "#app/@types/ability-types";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type { BooleanHolder, NumberHolder } from "#app/utils/common";
|
|
||||||
import type { BattlerIndex } from "#enums/battler-index";
|
|
||||||
import type { HitResult } from "#enums/hit-result";
|
|
||||||
import type { BattleStat, Stat } from "#enums/stat";
|
|
||||||
import type { StatusEffect } from "#enums/status-effect";
|
|
||||||
import type { WeatherType } from "#enums/weather-type";
|
|
||||||
import type { BattlerTag } from "../battler-tags";
|
|
||||||
import type Move from "../moves/move";
|
|
||||||
import type { PokemonMove } from "../moves/pokemon-move";
|
|
||||||
import type { TerrainType } from "../terrain";
|
|
||||||
import type { Weather } from "../weather";
|
|
||||||
import type {
|
|
||||||
PostBattleInitAbAttr,
|
|
||||||
PreDefendAbAttr,
|
|
||||||
PostDefendAbAttr,
|
|
||||||
PostMoveUsedAbAttr,
|
|
||||||
StatMultiplierAbAttr,
|
|
||||||
AllyStatMultiplierAbAttr,
|
|
||||||
PostSetStatusAbAttr,
|
|
||||||
PostDamageAbAttr,
|
|
||||||
FieldMultiplyStatAbAttr,
|
|
||||||
PreAttackAbAttr,
|
|
||||||
ExecutedMoveAbAttr,
|
|
||||||
PostAttackAbAttr,
|
|
||||||
PostKnockOutAbAttr,
|
|
||||||
PostVictoryAbAttr,
|
|
||||||
PostSummonAbAttr,
|
|
||||||
PreSummonAbAttr,
|
|
||||||
PreSwitchOutAbAttr,
|
|
||||||
PreLeaveFieldAbAttr,
|
|
||||||
PreStatStageChangeAbAttr,
|
|
||||||
PostStatStageChangeAbAttr,
|
|
||||||
PreSetStatusAbAttr,
|
|
||||||
PreApplyBattlerTagAbAttr,
|
|
||||||
PreWeatherEffectAbAttr,
|
|
||||||
PreWeatherDamageAbAttr,
|
|
||||||
PostTurnAbAttr,
|
|
||||||
PostWeatherChangeAbAttr,
|
|
||||||
PostWeatherLapseAbAttr,
|
|
||||||
PostTerrainChangeAbAttr,
|
|
||||||
CheckTrappedAbAttr,
|
|
||||||
PostBattleAbAttr,
|
|
||||||
PostFaintAbAttr,
|
|
||||||
PostItemLostAbAttr,
|
|
||||||
} from "./ability";
|
|
||||||
|
|
||||||
function applySingleAbAttrs<T extends AbAttrString>(
|
function applySingleAbAttrs<T extends AbAttrString>(
|
||||||
pokemon: Pokemon,
|
|
||||||
passive: boolean,
|
|
||||||
attrType: T,
|
attrType: T,
|
||||||
applyFunc: AbAttrApplyFunc<AbAttrMap[T]>,
|
params: AbAttrParamMap[T],
|
||||||
successFunc: AbAttrSuccessFunc<AbAttrMap[T]>,
|
|
||||||
args: any[],
|
|
||||||
gainedMidTurn = false,
|
gainedMidTurn = false,
|
||||||
simulated = false,
|
|
||||||
messages: string[] = [],
|
messages: string[] = [],
|
||||||
) {
|
) {
|
||||||
|
const { simulated = false, passive = false, pokemon } = params;
|
||||||
if (!pokemon?.canApplyAbility(passive) || (passive && pokemon.getPassiveAbility().id === pokemon.getAbility().id)) {
|
if (!pokemon?.canApplyAbility(passive) || (passive && pokemon.getPassiveAbility().id === pokemon.getAbility().id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -75,7 +26,11 @@ function applySingleAbAttrs<T extends AbAttrString>(
|
|||||||
for (const attr of ability.getAttrs(attrType)) {
|
for (const attr of ability.getAttrs(attrType)) {
|
||||||
const condition = attr.getCondition();
|
const condition = attr.getCondition();
|
||||||
let abShown = false;
|
let abShown = false;
|
||||||
if ((condition && !condition(pokemon)) || !successFunc(attr, passive)) {
|
// We require an `as any` cast to suppress an error about the `params` type not being assignable to
|
||||||
|
// the type of the argument expected by `attr.canApply()`. This is OK, because we know that
|
||||||
|
// `attr` is an instance of the `attrType` class provided to the method, and typescript _will_ check
|
||||||
|
// that the `params` object has the correct properties for that class at the callsites.
|
||||||
|
if ((condition && !condition(pokemon)) || !attr.canApply(params as any)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,15 +40,16 @@ function applySingleAbAttrs<T extends AbAttrString>(
|
|||||||
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true);
|
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true);
|
||||||
abShown = true;
|
abShown = true;
|
||||||
}
|
}
|
||||||
const message = attr.getTriggerMessage(pokemon, ability.name, args);
|
|
||||||
|
const message = attr.getTriggerMessage(params as any, ability.name);
|
||||||
if (message) {
|
if (message) {
|
||||||
if (!simulated) {
|
if (!simulated) {
|
||||||
globalScene.phaseManager.queueMessage(message);
|
globalScene.phaseManager.queueMessage(message);
|
||||||
}
|
}
|
||||||
messages.push(message);
|
messages.push(message);
|
||||||
}
|
}
|
||||||
|
// The `as any` cast here uses the same reasoning as above.
|
||||||
applyFunc(attr, passive);
|
attr.apply(params as any);
|
||||||
|
|
||||||
if (abShown) {
|
if (abShown) {
|
||||||
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false);
|
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false);
|
||||||
@ -107,726 +63,60 @@ function applySingleAbAttrs<T extends AbAttrString>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyAbAttrsInternal<T extends AbAttrString>(
|
function applyAbAttrsInternal<T extends CallableAbAttrString>(
|
||||||
attrType: T,
|
attrType: T,
|
||||||
pokemon: Pokemon | null,
|
params: AbAttrParamMap[T],
|
||||||
applyFunc: AbAttrApplyFunc<AbAttrMap[T]>,
|
|
||||||
successFunc: AbAttrSuccessFunc<AbAttrMap[T]>,
|
|
||||||
args: any[],
|
|
||||||
simulated = false,
|
|
||||||
messages: string[] = [],
|
messages: string[] = [],
|
||||||
gainedMidTurn = false,
|
gainedMidTurn = false,
|
||||||
) {
|
) {
|
||||||
for (const passive of [false, true]) {
|
// If the pokemon is not defined, no ability attributes to be applied.
|
||||||
if (pokemon) {
|
// TODO: Evaluate whether this check is even necessary anymore
|
||||||
applySingleAbAttrs(pokemon, passive, attrType, applyFunc, successFunc, args, gainedMidTurn, simulated, messages);
|
if (!params.pokemon) {
|
||||||
globalScene.phaseManager.clearPhaseQueueSplice();
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (params.passive !== undefined) {
|
||||||
|
applySingleAbAttrs(attrType, params, gainedMidTurn, messages);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const passive of [false, true]) {
|
||||||
|
params.passive = passive;
|
||||||
|
applySingleAbAttrs(attrType, params, gainedMidTurn, messages);
|
||||||
|
globalScene.phaseManager.clearPhaseQueueSplice();
|
||||||
|
}
|
||||||
|
// We need to restore passive to its original state in the case that it was undefined on entry
|
||||||
|
// this is necessary in case this method is called with an object that is reused.
|
||||||
|
params.passive = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyAbAttrs<T extends AbAttrString>(
|
/**
|
||||||
|
* @param attrType - The type of the ability attribute to apply. (note: may not be any attribute that extends PostSummonAbAttr)
|
||||||
|
* @param params - The parameters to pass to the ability attribute's apply method
|
||||||
|
* @param messages - An optional array to which ability trigger messges will be added
|
||||||
|
*/
|
||||||
|
export function applyAbAttrs<T extends CallableAbAttrString>(
|
||||||
attrType: T,
|
attrType: T,
|
||||||
pokemon: Pokemon,
|
params: AbAttrParamMap[T],
|
||||||
cancelled: BooleanHolder | null,
|
messages?: string[],
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
): void {
|
||||||
applyAbAttrsInternal<T>(
|
applyAbAttrsInternal(attrType, params, messages);
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
// @ts-expect-error: TODO: fix the error on `cancelled`
|
|
||||||
(attr, passive) => attr.apply(pokemon, passive, simulated, cancelled, args),
|
|
||||||
(attr, passive) => attr.canApply(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Improve the type signatures of the following methods / refactor the apply methods
|
// TODO: Improve the type signatures of the following methods / refactor the apply methods
|
||||||
|
|
||||||
export function applyPostBattleInitAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostBattleInitAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PostBattleInitAbAttr).applyPostBattleInit(pokemon, passive, simulated, args),
|
|
||||||
(attr, passive) => (attr as PostBattleInitAbAttr).canApplyPostBattleInit(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPreDefendAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PreDefendAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
attacker: Pokemon,
|
|
||||||
move: Move | null,
|
|
||||||
cancelled: BooleanHolder | null,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreDefendAbAttr).applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreDefendAbAttr).canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostDefendAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostDefendAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
attacker: Pokemon,
|
|
||||||
move: Move,
|
|
||||||
hitResult: HitResult | null,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostDefendAbAttr).applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostDefendAbAttr).canApplyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostMoveUsedAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostMoveUsedAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
move: PokemonMove,
|
|
||||||
source: Pokemon,
|
|
||||||
targets: BattlerIndex[],
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, _passive) => (attr as PostMoveUsedAbAttr).applyPostMoveUsed(pokemon, move, source, targets, simulated, args),
|
|
||||||
(attr, _passive) =>
|
|
||||||
(attr as PostMoveUsedAbAttr).canApplyPostMoveUsed(pokemon, move, source, targets, simulated, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyStatMultiplierAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends StatMultiplierAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
stat: BattleStat,
|
|
||||||
statValue: NumberHolder,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as StatMultiplierAbAttr).applyStatStage(pokemon, passive, simulated, stat, statValue, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as StatMultiplierAbAttr).canApplyStatStage(pokemon, passive, simulated, stat, statValue, args),
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies an ally's Stat multiplier attribute
|
|
||||||
* @param attrType - {@linkcode AllyStatMultiplierAbAttr} should always be AllyStatMultiplierAbAttr for the time being
|
|
||||||
* @param pokemon - The {@linkcode Pokemon} with the ability
|
|
||||||
* @param stat - The type of the checked {@linkcode Stat}
|
|
||||||
* @param statValue - {@linkcode NumberHolder} containing the value of the checked stat
|
|
||||||
* @param checkedPokemon - The {@linkcode Pokemon} with the checked stat
|
|
||||||
* @param ignoreAbility - Whether or not the ability should be ignored by the pokemon or its move.
|
|
||||||
* @param args - unused
|
|
||||||
*/
|
|
||||||
export function applyAllyStatMultiplierAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends AllyStatMultiplierAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
stat: BattleStat,
|
|
||||||
statValue: NumberHolder,
|
|
||||||
simulated = false,
|
|
||||||
checkedPokemon: Pokemon,
|
|
||||||
ignoreAbility: boolean,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as AllyStatMultiplierAbAttr).applyAllyStat(
|
|
||||||
pokemon,
|
|
||||||
passive,
|
|
||||||
simulated,
|
|
||||||
stat,
|
|
||||||
statValue,
|
|
||||||
checkedPokemon,
|
|
||||||
ignoreAbility,
|
|
||||||
args,
|
|
||||||
),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as AllyStatMultiplierAbAttr).canApplyAllyStat(
|
|
||||||
pokemon,
|
|
||||||
passive,
|
|
||||||
simulated,
|
|
||||||
stat,
|
|
||||||
statValue,
|
|
||||||
checkedPokemon,
|
|
||||||
ignoreAbility,
|
|
||||||
args,
|
|
||||||
),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostSetStatusAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostSetStatusAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
effect: StatusEffect,
|
|
||||||
sourcePokemon?: Pokemon | null,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostSetStatusAbAttr).applyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostSetStatusAbAttr).canApplyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostDamageAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostDamageAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
damage: number,
|
|
||||||
_passive: boolean,
|
|
||||||
simulated = false,
|
|
||||||
args: any[],
|
|
||||||
source?: Pokemon,
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PostDamageAbAttr).applyPostDamage(pokemon, damage, passive, simulated, args, source),
|
|
||||||
(attr, passive) => (attr as PostDamageAbAttr).canApplyPostDamage(pokemon, damage, passive, simulated, args, source),
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Applies a field Stat multiplier attribute
|
|
||||||
* @param attrType {@linkcode FieldMultiplyStatAbAttr} should always be FieldMultiplyBattleStatAbAttr for the time being
|
|
||||||
* @param pokemon {@linkcode Pokemon} the Pokemon applying this ability
|
|
||||||
* @param stat {@linkcode Stat} the type of the checked stat
|
|
||||||
* @param statValue {@linkcode NumberHolder} the value of the checked stat
|
|
||||||
* @param checkedPokemon {@linkcode Pokemon} the Pokemon with the checked stat
|
|
||||||
* @param hasApplied {@linkcode BooleanHolder} whether or not a FieldMultiplyBattleStatAbAttr has already affected this stat
|
|
||||||
* @param args unused
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function applyFieldStatMultiplierAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends FieldMultiplyStatAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
stat: Stat,
|
|
||||||
statValue: NumberHolder,
|
|
||||||
checkedPokemon: Pokemon,
|
|
||||||
hasApplied: BooleanHolder,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as FieldMultiplyStatAbAttr).applyFieldStat(
|
|
||||||
pokemon,
|
|
||||||
passive,
|
|
||||||
simulated,
|
|
||||||
stat,
|
|
||||||
statValue,
|
|
||||||
checkedPokemon,
|
|
||||||
hasApplied,
|
|
||||||
args,
|
|
||||||
),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as FieldMultiplyStatAbAttr).canApplyFieldStat(
|
|
||||||
pokemon,
|
|
||||||
passive,
|
|
||||||
simulated,
|
|
||||||
stat,
|
|
||||||
statValue,
|
|
||||||
checkedPokemon,
|
|
||||||
hasApplied,
|
|
||||||
args,
|
|
||||||
),
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPreAttackAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PreAttackAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
defender: Pokemon | null,
|
|
||||||
move: Move,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PreAttackAbAttr).applyPreAttack(pokemon, passive, simulated, defender, move, args),
|
|
||||||
(attr, passive) => (attr as PreAttackAbAttr).canApplyPreAttack(pokemon, passive, simulated, defender, move, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyExecutedMoveAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends ExecutedMoveAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
attr => (attr as ExecutedMoveAbAttr).applyExecutedMove(pokemon, simulated),
|
|
||||||
attr => (attr as ExecutedMoveAbAttr).canApplyExecutedMove(pokemon, simulated),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostAttackAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostAttackAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
defender: Pokemon,
|
|
||||||
move: Move,
|
|
||||||
hitResult: HitResult | null,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostAttackAbAttr).applyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostAttackAbAttr).canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostKnockOutAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostKnockOutAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
knockedOut: Pokemon,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PostKnockOutAbAttr).applyPostKnockOut(pokemon, passive, simulated, knockedOut, args),
|
|
||||||
(attr, passive) => (attr as PostKnockOutAbAttr).canApplyPostKnockOut(pokemon, passive, simulated, knockedOut, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostVictoryAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostVictoryAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PostVictoryAbAttr).applyPostVictory(pokemon, passive, simulated, args),
|
|
||||||
(attr, passive) => (attr as PostVictoryAbAttr).canApplyPostVictory(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostSummonAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostSummonAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
passive = false,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applySingleAbAttrs(
|
|
||||||
pokemon,
|
|
||||||
passive,
|
|
||||||
attrType,
|
|
||||||
(attr, passive) => (attr as PostSummonAbAttr).applyPostSummon(pokemon, passive, simulated, args),
|
|
||||||
(attr, passive) => (attr as PostSummonAbAttr).canApplyPostSummon(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
false,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPreSummonAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PreSummonAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PreSummonAbAttr).applyPreSummon(pokemon, passive, args),
|
|
||||||
(attr, passive) => (attr as PreSummonAbAttr).canApplyPreSummon(pokemon, passive, args),
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPreSwitchOutAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PreSwitchOutAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PreSwitchOutAbAttr).applyPreSwitchOut(pokemon, passive, simulated, args),
|
|
||||||
(attr, passive) => (attr as PreSwitchOutAbAttr).canApplyPreSwitchOut(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPreLeaveFieldAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PreLeaveFieldAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PreLeaveFieldAbAttr).applyPreLeaveField(pokemon, passive, simulated, args),
|
|
||||||
(attr, passive) => (attr as PreLeaveFieldAbAttr).canApplyPreLeaveField(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPreStatStageChangeAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PreStatStageChangeAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon | null,
|
|
||||||
stat: BattleStat,
|
|
||||||
cancelled: BooleanHolder,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreStatStageChangeAbAttr).applyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreStatStageChangeAbAttr).canApplyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostStatStageChangeAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostStatStageChangeAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
stats: BattleStat[],
|
|
||||||
stages: number,
|
|
||||||
selfTarget: boolean,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, _passive) =>
|
|
||||||
(attr as PostStatStageChangeAbAttr).applyPostStatStageChange(pokemon, simulated, stats, stages, selfTarget, args),
|
|
||||||
(attr, _passive) =>
|
|
||||||
(attr as PostStatStageChangeAbAttr).canApplyPostStatStageChange(
|
|
||||||
pokemon,
|
|
||||||
simulated,
|
|
||||||
stats,
|
|
||||||
stages,
|
|
||||||
selfTarget,
|
|
||||||
args,
|
|
||||||
),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPreSetStatusAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PreSetStatusAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
effect: StatusEffect | undefined,
|
|
||||||
cancelled: BooleanHolder,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreSetStatusAbAttr).applyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreSetStatusAbAttr).canApplyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPreApplyBattlerTagAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PreApplyBattlerTagAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
tag: BattlerTag,
|
|
||||||
cancelled: BooleanHolder,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreApplyBattlerTagAbAttr).applyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreApplyBattlerTagAbAttr).canApplyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPreWeatherEffectAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PreWeatherEffectAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
weather: Weather | null,
|
|
||||||
cancelled: BooleanHolder,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreWeatherDamageAbAttr).applyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PreWeatherDamageAbAttr).canApplyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostTurnAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostTurnAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PostTurnAbAttr).applyPostTurn(pokemon, passive, simulated, args),
|
|
||||||
(attr, passive) => (attr as PostTurnAbAttr).canApplyPostTurn(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostWeatherChangeAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostWeatherChangeAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
weather: WeatherType,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostWeatherChangeAbAttr).applyPostWeatherChange(pokemon, passive, simulated, weather, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostWeatherChangeAbAttr).canApplyPostWeatherChange(pokemon, passive, simulated, weather, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostWeatherLapseAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostWeatherLapseAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
weather: Weather | null,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostWeatherLapseAbAttr).applyPostWeatherLapse(pokemon, passive, simulated, weather, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostWeatherLapseAbAttr).canApplyPostWeatherLapse(pokemon, passive, simulated, weather, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostTerrainChangeAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostTerrainChangeAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
terrain: TerrainType,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostTerrainChangeAbAttr).applyPostTerrainChange(pokemon, passive, simulated, terrain, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostTerrainChangeAbAttr).canApplyPostTerrainChange(pokemon, passive, simulated, terrain, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyCheckTrappedAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends CheckTrappedAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
trapped: BooleanHolder,
|
|
||||||
otherPokemon: Pokemon,
|
|
||||||
messages: string[],
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as CheckTrappedAbAttr).applyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as CheckTrappedAbAttr).canApplyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
messages,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostBattleAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostBattleAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) => (attr as PostBattleAbAttr).applyPostBattle(pokemon, passive, simulated, args),
|
|
||||||
(attr, passive) => (attr as PostBattleAbAttr).canApplyPostBattle(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostFaintAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostFaintAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
attacker?: Pokemon,
|
|
||||||
move?: Move,
|
|
||||||
hitResult?: HitResult,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostFaintAbAttr).applyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args),
|
|
||||||
(attr, passive) =>
|
|
||||||
(attr as PostFaintAbAttr).canApplyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args),
|
|
||||||
args,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function applyPostItemLostAbAttrs<K extends AbAttrString>(
|
|
||||||
attrType: AbAttrMap[K] extends PostItemLostAbAttr ? K : never,
|
|
||||||
pokemon: Pokemon,
|
|
||||||
simulated = false,
|
|
||||||
...args: any[]
|
|
||||||
): void {
|
|
||||||
applyAbAttrsInternal(
|
|
||||||
attrType,
|
|
||||||
pokemon,
|
|
||||||
(attr, _passive) => (attr as PostItemLostAbAttr).applyPostItemLost(pokemon, simulated, args),
|
|
||||||
(attr, _passive) => (attr as PostItemLostAbAttr).canApplyPostItemLost(pokemon, simulated, args),
|
|
||||||
args,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies abilities when they become active mid-turn (ability switch)
|
* Applies abilities when they become active mid-turn (ability switch)
|
||||||
*
|
*
|
||||||
* Ignores passives as they don't change and shouldn't be reapplied when main abilities change
|
* Ignores passives as they don't change and shouldn't be reapplied when main abilities change
|
||||||
*/
|
*/
|
||||||
export function applyOnGainAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void {
|
export function applyOnGainAbAttrs(params: AbAttrBaseParams): void {
|
||||||
applySingleAbAttrs(
|
applySingleAbAttrs("PostSummonAbAttr", params, true);
|
||||||
pokemon,
|
|
||||||
passive,
|
|
||||||
"PostSummonAbAttr",
|
|
||||||
(attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args),
|
|
||||||
(attr, passive) => attr.canApplyPostSummon(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
true,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies ability attributes which activate when the ability is lost or suppressed (i.e. primal weather)
|
* Applies ability attributes which activate when the ability is lost or suppressed (i.e. primal weather)
|
||||||
*/
|
*/
|
||||||
export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void {
|
export function applyOnLoseAbAttrs(params: AbAttrBaseParams): void {
|
||||||
applySingleAbAttrs(
|
applySingleAbAttrs("PreLeaveFieldAbAttr", params, true);
|
||||||
pokemon,
|
|
||||||
passive,
|
|
||||||
"PreLeaveFieldAbAttr",
|
|
||||||
(attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [...args, true]),
|
|
||||||
(attr, passive) => attr.canApplyPreLeaveField(pokemon, passive, simulated, [...args, true]),
|
|
||||||
args,
|
|
||||||
true,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
|
|
||||||
applySingleAbAttrs(
|
applySingleAbAttrs("IllusionBreakAbAttr", params, true);
|
||||||
pokemon,
|
|
||||||
passive,
|
|
||||||
"IllusionBreakAbAttr",
|
|
||||||
(attr, passive) => attr.apply(pokemon, passive, simulated, null, args),
|
|
||||||
(attr, passive) => attr.canApply(pokemon, passive, simulated, args),
|
|
||||||
args,
|
|
||||||
true,
|
|
||||||
simulated,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -72,10 +72,11 @@ export abstract class ArenaTag {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function that retrieves the source Pokemon
|
* Helper function that retrieves the source Pokemon
|
||||||
* @returns The source {@linkcode Pokemon} or `null` if none is found
|
* @returns - The source {@linkcode Pokemon} for this tag.
|
||||||
|
* Returns `null` if `this.sourceId` is `undefined`
|
||||||
*/
|
*/
|
||||||
public getSourcePokemon(): Pokemon | null {
|
public getSourcePokemon(): Pokemon | null {
|
||||||
return this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
|
return globalScene.getPokemonById(this.sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -107,19 +108,22 @@ export class MistTag extends ArenaTag {
|
|||||||
onAdd(arena: Arena, quiet = false): void {
|
onAdd(arena: Arena, quiet = false): void {
|
||||||
super.onAdd(arena);
|
super.onAdd(arena);
|
||||||
|
|
||||||
if (this.sourceId) {
|
// We assume `quiet=true` means "just add the bloody tag no questions asked"
|
||||||
const source = globalScene.getPokemonById(this.sourceId);
|
if (quiet) {
|
||||||
|
return;
|
||||||
if (!quiet && source) {
|
|
||||||
globalScene.phaseManager.queueMessage(
|
|
||||||
i18next.t("arenaTag:mistOnAdd", {
|
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else if (!quiet) {
|
|
||||||
console.warn("Failed to get source for MistTag onAdd");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for MistTag on add message; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
globalScene.phaseManager.queueMessage(
|
||||||
|
i18next.t("arenaTag:mistOnAdd", {
|
||||||
|
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -137,7 +141,7 @@ export class MistTag extends ArenaTag {
|
|||||||
if (attacker) {
|
if (attacker) {
|
||||||
const bypassed = new BooleanHolder(false);
|
const bypassed = new BooleanHolder(false);
|
||||||
// TODO: Allow this to be simulated
|
// TODO: Allow this to be simulated
|
||||||
applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
|
applyAbAttrs("InfiltratorAbAttr", { pokemon: attacker, simulated: false, bypassed });
|
||||||
if (bypassed.value) {
|
if (bypassed.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -202,7 +206,7 @@ export class WeakenMoveScreenTag extends ArenaTag {
|
|||||||
): boolean {
|
): boolean {
|
||||||
if (this.weakenedCategories.includes(moveCategory)) {
|
if (this.weakenedCategories.includes(moveCategory)) {
|
||||||
const bypassed = new BooleanHolder(false);
|
const bypassed = new BooleanHolder(false);
|
||||||
applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
|
applyAbAttrs("InfiltratorAbAttr", { pokemon: attacker, bypassed });
|
||||||
if (bypassed.value) {
|
if (bypassed.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -440,18 +444,18 @@ class MatBlockTag extends ConditionalProtectTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAdd(_arena: Arena) {
|
onAdd(_arena: Arena) {
|
||||||
if (this.sourceId) {
|
const source = this.getSourcePokemon();
|
||||||
const source = globalScene.getPokemonById(this.sourceId);
|
if (!source) {
|
||||||
if (source) {
|
console.warn(`Failed to get source Pokemon for Mat Block message; id: ${this.sourceId}`);
|
||||||
globalScene.phaseManager.queueMessage(
|
return;
|
||||||
i18next.t("arenaTag:matBlockOnAdd", {
|
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.warn("Failed to get source for MatBlockTag onAdd");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.onAdd(_arena);
|
||||||
|
globalScene.phaseManager.queueMessage(
|
||||||
|
i18next.t("arenaTag:matBlockOnAdd", {
|
||||||
|
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -511,7 +515,12 @@ export class NoCritTag extends ArenaTag {
|
|||||||
|
|
||||||
/** Queues a message upon removing this effect from the field */
|
/** Queues a message upon removing this effect from the field */
|
||||||
onRemove(_arena: Arena): void {
|
onRemove(_arena: Arena): void {
|
||||||
const source = globalScene.getPokemonById(this.sourceId!); // TODO: is this bang correct?
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for NoCritTag on remove message; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("arenaTag:noCritOnRemove", {
|
i18next.t("arenaTag:noCritOnRemove", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(source ?? undefined),
|
pokemonNameWithAffix: getPokemonNameWithAffix(source ?? undefined),
|
||||||
@ -522,7 +531,7 @@ export class NoCritTag extends ArenaTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Wish_(move) Wish}.
|
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Wish_(move) | Wish}.
|
||||||
* Heals the Pokémon in the user's position the turn after Wish is used.
|
* Heals the Pokémon in the user's position the turn after Wish is used.
|
||||||
*/
|
*/
|
||||||
class WishTag extends ArenaTag {
|
class WishTag extends ArenaTag {
|
||||||
@ -535,18 +544,20 @@ class WishTag extends ArenaTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAdd(_arena: Arena): void {
|
onAdd(_arena: Arena): void {
|
||||||
if (this.sourceId) {
|
const source = this.getSourcePokemon();
|
||||||
const user = globalScene.getPokemonById(this.sourceId);
|
if (!source) {
|
||||||
if (user) {
|
console.warn(`Failed to get source Pokemon for WishTag on add message; id: ${this.sourceId}`);
|
||||||
this.battlerIndex = user.getBattlerIndex();
|
return;
|
||||||
this.triggerMessage = i18next.t("arenaTag:wishTagOnAdd", {
|
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(user),
|
|
||||||
});
|
|
||||||
this.healHp = toDmgValue(user.getMaxHp() / 2);
|
|
||||||
} else {
|
|
||||||
console.warn("Failed to get source for WishTag onAdd");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.onAdd(_arena);
|
||||||
|
this.healHp = toDmgValue(source.getMaxHp() / 2);
|
||||||
|
|
||||||
|
globalScene.phaseManager.queueMessage(
|
||||||
|
i18next.t("arenaTag:wishTagOnAdd", {
|
||||||
|
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemove(_arena: Arena): void {
|
onRemove(_arena: Arena): void {
|
||||||
@ -741,15 +752,23 @@ class SpikesTag extends ArenaTrapTag {
|
|||||||
onAdd(arena: Arena, quiet = false): void {
|
onAdd(arena: Arena, quiet = false): void {
|
||||||
super.onAdd(arena);
|
super.onAdd(arena);
|
||||||
|
|
||||||
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
|
// We assume `quiet=true` means "just add the bloody tag no questions asked"
|
||||||
if (!quiet && source) {
|
if (quiet) {
|
||||||
globalScene.phaseManager.queueMessage(
|
return;
|
||||||
i18next.t("arenaTag:spikesOnAdd", {
|
|
||||||
moveName: this.getMoveName(),
|
|
||||||
opponentDesc: source.getOpponentDescriptor(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for SpikesTag on add message; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
globalScene.phaseManager.queueMessage(
|
||||||
|
i18next.t("arenaTag:spikesOnAdd", {
|
||||||
|
moveName: this.getMoveName(),
|
||||||
|
opponentDesc: source.getOpponentDescriptor(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
||||||
@ -758,7 +777,7 @@ class SpikesTag extends ArenaTrapTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
if (simulated || cancelled.value) {
|
if (simulated || cancelled.value) {
|
||||||
return !cancelled.value;
|
return !cancelled.value;
|
||||||
}
|
}
|
||||||
@ -794,15 +813,23 @@ class ToxicSpikesTag extends ArenaTrapTag {
|
|||||||
onAdd(arena: Arena, quiet = false): void {
|
onAdd(arena: Arena, quiet = false): void {
|
||||||
super.onAdd(arena);
|
super.onAdd(arena);
|
||||||
|
|
||||||
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
|
if (quiet) {
|
||||||
if (!quiet && source) {
|
// We assume `quiet=true` means "just add the bloody tag no questions asked"
|
||||||
globalScene.phaseManager.queueMessage(
|
return;
|
||||||
i18next.t("arenaTag:toxicSpikesOnAdd", {
|
|
||||||
moveName: this.getMoveName(),
|
|
||||||
opponentDesc: source.getOpponentDescriptor(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for ToxicSpikesTag on add message; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
globalScene.phaseManager.queueMessage(
|
||||||
|
i18next.t("arenaTag:toxicSpikesOnAdd", {
|
||||||
|
moveName: this.getMoveName(),
|
||||||
|
opponentDesc: source.getOpponentDescriptor(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemove(arena: Arena): void {
|
onRemove(arena: Arena): void {
|
||||||
@ -905,7 +932,11 @@ class StealthRockTag extends ArenaTrapTag {
|
|||||||
onAdd(arena: Arena, quiet = false): void {
|
onAdd(arena: Arena, quiet = false): void {
|
||||||
super.onAdd(arena);
|
super.onAdd(arena);
|
||||||
|
|
||||||
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
|
if (quiet) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
if (!quiet && source) {
|
if (!quiet && source) {
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("arenaTag:stealthRockOnAdd", {
|
i18next.t("arenaTag:stealthRockOnAdd", {
|
||||||
@ -946,7 +977,7 @@ class StealthRockTag extends ArenaTrapTag {
|
|||||||
|
|
||||||
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -989,21 +1020,35 @@ class StickyWebTag extends ArenaTrapTag {
|
|||||||
|
|
||||||
onAdd(arena: Arena, quiet = false): void {
|
onAdd(arena: Arena, quiet = false): void {
|
||||||
super.onAdd(arena);
|
super.onAdd(arena);
|
||||||
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
|
|
||||||
if (!quiet && source) {
|
// We assume `quiet=true` means "just add the bloody tag no questions asked"
|
||||||
globalScene.phaseManager.queueMessage(
|
if (quiet) {
|
||||||
i18next.t("arenaTag:stickyWebOnAdd", {
|
return;
|
||||||
moveName: this.getMoveName(),
|
|
||||||
opponentDesc: source.getOpponentDescriptor(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for SpikesTag on add message; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
globalScene.phaseManager.queueMessage(
|
||||||
|
i18next.t("arenaTag:stickyWebOnAdd", {
|
||||||
|
moveName: this.getMoveName(),
|
||||||
|
opponentDesc: source.getOpponentDescriptor(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
|
||||||
if (pokemon.isGrounded()) {
|
if (pokemon.isGrounded()) {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled);
|
applyAbAttrs("ProtectStatAbAttr", {
|
||||||
|
pokemon,
|
||||||
|
cancelled,
|
||||||
|
stat: Stat.SPD,
|
||||||
|
stages: -1,
|
||||||
|
});
|
||||||
|
|
||||||
if (simulated) {
|
if (simulated) {
|
||||||
return !cancelled.value;
|
return !cancelled.value;
|
||||||
@ -1061,14 +1106,20 @@ export class TrickRoomTag extends ArenaTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAdd(_arena: Arena): void {
|
onAdd(_arena: Arena): void {
|
||||||
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
|
super.onAdd(_arena);
|
||||||
if (source) {
|
|
||||||
globalScene.phaseManager.queueMessage(
|
const source = this.getSourcePokemon();
|
||||||
i18next.t("arenaTag:trickRoomOnAdd", {
|
if (!source) {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
console.warn(`Failed to get source Pokemon for TrickRoomTag on add message; id: ${this.sourceId}`);
|
||||||
}),
|
return;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
globalScene.phaseManager.queueMessage(
|
||||||
|
i18next.t("arenaTag:trickRoomOnAdd", {
|
||||||
|
moveName: this.getMoveName(),
|
||||||
|
opponentDesc: source.getOpponentDescriptor(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemove(_arena: Arena): void {
|
onRemove(_arena: Arena): void {
|
||||||
@ -1115,6 +1166,13 @@ class TailwindTag extends ArenaTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAdd(_arena: Arena, quiet = false): void {
|
onAdd(_arena: Arena, quiet = false): void {
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onAdd(_arena, quiet);
|
||||||
|
|
||||||
if (!quiet) {
|
if (!quiet) {
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t(
|
i18next.t(
|
||||||
@ -1123,15 +1181,14 @@ class TailwindTag extends ArenaTag {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const source = globalScene.getPokemonById(this.sourceId!); //TODO: this bang is questionable!
|
const field = source.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
||||||
const party = (source?.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField()) ?? [];
|
|
||||||
const phaseManager = globalScene.phaseManager;
|
|
||||||
|
|
||||||
for (const pokemon of party) {
|
for (const pokemon of field) {
|
||||||
// Apply the CHARGED tag to party members with the WIND_POWER ability
|
// Apply the CHARGED tag to party members with the WIND_POWER ability
|
||||||
|
// TODO: This should not be handled here
|
||||||
if (pokemon.hasAbility(AbilityId.WIND_POWER) && !pokemon.getTag(BattlerTagType.CHARGED)) {
|
if (pokemon.hasAbility(AbilityId.WIND_POWER) && !pokemon.getTag(BattlerTagType.CHARGED)) {
|
||||||
pokemon.addTag(BattlerTagType.CHARGED);
|
pokemon.addTag(BattlerTagType.CHARGED);
|
||||||
phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("abilityTriggers:windPowerCharged", {
|
i18next.t("abilityTriggers:windPowerCharged", {
|
||||||
pokemonName: getPokemonNameWithAffix(pokemon),
|
pokemonName: getPokemonNameWithAffix(pokemon),
|
||||||
moveName: this.getMoveName(),
|
moveName: this.getMoveName(),
|
||||||
@ -1142,9 +1199,16 @@ class TailwindTag extends ArenaTag {
|
|||||||
// Raise attack by one stage if party member has WIND_RIDER ability
|
// Raise attack by one stage if party member has WIND_RIDER ability
|
||||||
// TODO: Ability displays should be handled by the ability
|
// TODO: Ability displays should be handled by the ability
|
||||||
if (pokemon.hasAbility(AbilityId.WIND_RIDER)) {
|
if (pokemon.hasAbility(AbilityId.WIND_RIDER)) {
|
||||||
phaseManager.queueAbilityDisplay(pokemon, false, true);
|
globalScene.phaseManager.queueAbilityDisplay(pokemon, false, true);
|
||||||
phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), true, [Stat.ATK], 1, true);
|
globalScene.phaseManager.unshiftNew(
|
||||||
phaseManager.queueAbilityDisplay(pokemon, false, false);
|
"StatStageChangePhase",
|
||||||
|
pokemon.getBattlerIndex(),
|
||||||
|
true,
|
||||||
|
[Stat.ATK],
|
||||||
|
1,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
globalScene.phaseManager.queueAbilityDisplay(pokemon, false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1216,24 +1280,26 @@ class ImprisonTag extends ArenaTrapTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function applies the effects of Imprison to the opposing Pokemon already present on the field.
|
* Apply the effects of Imprison to all opposing on-field Pokemon.
|
||||||
* @param arena
|
|
||||||
*/
|
*/
|
||||||
override onAdd() {
|
override onAdd() {
|
||||||
const source = this.getSourcePokemon();
|
const source = this.getSourcePokemon();
|
||||||
if (source) {
|
if (!source) {
|
||||||
const party = this.getAffectedPokemon();
|
return;
|
||||||
party?.forEach((p: Pokemon) => {
|
|
||||||
if (p.isAllowedInBattle()) {
|
|
||||||
p.addTag(BattlerTagType.IMPRISON, 1, MoveId.IMPRISON, this.sourceId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
globalScene.phaseManager.queueMessage(
|
|
||||||
i18next.t("battlerTags:imprisonOnAdd", {
|
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const party = this.getAffectedPokemon();
|
||||||
|
party.forEach(p => {
|
||||||
|
if (p.isAllowedInBattle()) {
|
||||||
|
p.addTag(BattlerTagType.IMPRISON, 1, MoveId.IMPRISON, this.sourceId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
globalScene.phaseManager.queueMessage(
|
||||||
|
i18next.t("battlerTags:imprisonOnAdd", {
|
||||||
|
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1243,7 +1309,7 @@ class ImprisonTag extends ArenaTrapTag {
|
|||||||
*/
|
*/
|
||||||
override lapse(): boolean {
|
override lapse(): boolean {
|
||||||
const source = this.getSourcePokemon();
|
const source = this.getSourcePokemon();
|
||||||
return source ? source.isActive(true) : false;
|
return !!source?.isActive(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1265,9 +1331,7 @@ class ImprisonTag extends ArenaTrapTag {
|
|||||||
*/
|
*/
|
||||||
override onRemove(): void {
|
override onRemove(): void {
|
||||||
const party = this.getAffectedPokemon();
|
const party = this.getAffectedPokemon();
|
||||||
party?.forEach((p: Pokemon) => {
|
party.forEach(p => p.removeTag(BattlerTagType.IMPRISON));
|
||||||
p.removeTag(BattlerTagType.IMPRISON);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1416,7 +1480,9 @@ export class SuppressAbilitiesTag extends ArenaTag {
|
|||||||
|
|
||||||
for (const fieldPokemon of globalScene.getField(true)) {
|
for (const fieldPokemon of globalScene.getField(true)) {
|
||||||
if (fieldPokemon && fieldPokemon.id !== pokemon.id) {
|
if (fieldPokemon && fieldPokemon.id !== pokemon.id) {
|
||||||
[true, false].forEach(passive => applyOnLoseAbAttrs(fieldPokemon, passive));
|
// TODO: investigate whether we can just remove the foreach and call `applyAbAttrs` directly, providing
|
||||||
|
// the appropriate attributes (preLEaveField and IllusionBreak)
|
||||||
|
[true, false].forEach(passive => applyOnLoseAbAttrs({ pokemon: fieldPokemon, passive }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1438,7 +1504,10 @@ export class SuppressAbilitiesTag extends ArenaTag {
|
|||||||
const setter = globalScene
|
const setter = globalScene
|
||||||
.getField()
|
.getField()
|
||||||
.filter(p => p?.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false))[0];
|
.filter(p => p?.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false))[0];
|
||||||
applyOnGainAbAttrs(setter, setter.getAbility().hasAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr"));
|
applyOnGainAbAttrs({
|
||||||
|
pokemon: setter,
|
||||||
|
passive: setter.getAbility().hasAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1451,7 +1520,7 @@ export class SuppressAbilitiesTag extends ArenaTag {
|
|||||||
for (const pokemon of globalScene.getField(true)) {
|
for (const pokemon of globalScene.getField(true)) {
|
||||||
// There is only one pokemon with this attr on the field on removal, so its abilities are already active
|
// There is only one pokemon with this attr on the field on removal, so its abilities are already active
|
||||||
if (pokemon && !pokemon.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false)) {
|
if (pokemon && !pokemon.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false)) {
|
||||||
[true, false].forEach(passive => applyOnGainAbAttrs(pokemon, passive));
|
[true, false].forEach(passive => applyOnGainAbAttrs({ pokemon, passive }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,7 +111,7 @@ export class BattlerTag {
|
|||||||
* @returns The source {@linkcode Pokemon}, or `null` if none is found
|
* @returns The source {@linkcode Pokemon}, or `null` if none is found
|
||||||
*/
|
*/
|
||||||
public getSourcePokemon(): Pokemon | null {
|
public getSourcePokemon(): Pokemon | null {
|
||||||
return this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
|
return globalScene.getPokemonById(this.sourceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -540,9 +540,13 @@ export class TrappedTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
canAdd(pokemon: Pokemon): boolean {
|
canAdd(pokemon: Pokemon): boolean {
|
||||||
const source = globalScene.getPokemonById(this.sourceId!)!;
|
const source = this.getSourcePokemon();
|
||||||
const move = allMoves[this.sourceMove];
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for TrappedTag canAdd; id: ${this.sourceId}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const move = allMoves[this.sourceMove];
|
||||||
const isGhost = pokemon.isOfType(PokemonType.GHOST);
|
const isGhost = pokemon.isOfType(PokemonType.GHOST);
|
||||||
const isTrapped = pokemon.getTag(TrappedTag);
|
const isTrapped = pokemon.getTag(TrappedTag);
|
||||||
const hasSubstitute = move.hitsSubstitute(source, pokemon);
|
const hasSubstitute = move.hitsSubstitute(source, pokemon);
|
||||||
@ -621,7 +625,7 @@ export class FlinchedTag extends BattlerTag {
|
|||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
applyAbAttrs("FlinchEffectAbAttr", pokemon, null);
|
applyAbAttrs("FlinchEffectAbAttr", { pokemon });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -763,12 +767,20 @@ export class DestinyBondTag extends BattlerTag {
|
|||||||
if (lapseType !== BattlerTagLapseType.CUSTOM) {
|
if (lapseType !== BattlerTagLapseType.CUSTOM) {
|
||||||
return super.lapse(pokemon, lapseType);
|
return super.lapse(pokemon, lapseType);
|
||||||
}
|
}
|
||||||
const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null;
|
|
||||||
if (!source?.isFainted()) {
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for DestinyBondTag lapse; id: ${this.sourceId}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destiny bond stays active until the user faints
|
||||||
|
if (!source.isFainted()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source?.getAlly() === pokemon) {
|
// Don't kill allies or opposing bosses.
|
||||||
|
if (source.getAlly() === pokemon) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -781,6 +793,7 @@ export class DestinyBondTag extends BattlerTag {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drag the foe down with the user
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("battlerTags:destinyBondLapse", {
|
i18next.t("battlerTags:destinyBondLapse", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
||||||
@ -798,17 +811,13 @@ export class InfatuatedTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
canAdd(pokemon: Pokemon): boolean {
|
canAdd(pokemon: Pokemon): boolean {
|
||||||
if (this.sourceId) {
|
const source = this.getSourcePokemon();
|
||||||
const pkm = globalScene.getPokemonById(this.sourceId);
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for InfatuatedTag canAdd; id: ${this.sourceId}`);
|
||||||
if (pkm) {
|
|
||||||
return pokemon.isOppositeGender(pkm);
|
|
||||||
}
|
|
||||||
console.warn("canAdd: this.sourceId is not a valid pokemon id!", this.sourceId);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
console.warn("canAdd: this.sourceId is undefined");
|
|
||||||
return false;
|
return pokemon.isOppositeGender(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
@ -817,7 +826,7 @@ export class InfatuatedTag extends BattlerTag {
|
|||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("battlerTags:infatuatedOnAdd", {
|
i18next.t("battlerTags:infatuatedOnAdd", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
sourcePokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
|
sourcePokemonName: getPokemonNameWithAffix(this.getSourcePokemon()!), // Tag not added + console warns if no source
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -835,28 +844,36 @@ export class InfatuatedTag extends BattlerTag {
|
|||||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
|
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
|
||||||
|
|
||||||
const phaseManager = globalScene.phaseManager;
|
if (!ret) {
|
||||||
|
return false;
|
||||||
if (ret) {
|
|
||||||
phaseManager.queueMessage(
|
|
||||||
i18next.t("battlerTags:infatuatedLapse", {
|
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
||||||
sourcePokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT);
|
|
||||||
|
|
||||||
if (pokemon.randBattleSeedInt(2)) {
|
|
||||||
phaseManager.queueMessage(
|
|
||||||
i18next.t("battlerTags:infatuatedLapseImmobilize", {
|
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
(phaseManager.getCurrentPhase() as MovePhase).cancel();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for InfatuatedTag lapse; id: ${this.sourceId}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const phaseManager = globalScene.phaseManager;
|
||||||
|
phaseManager.queueMessage(
|
||||||
|
i18next.t("battlerTags:infatuatedLapse", {
|
||||||
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
|
sourcePokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT);
|
||||||
|
|
||||||
|
// 50% chance to disrupt the target's action
|
||||||
|
if (pokemon.randBattleSeedInt(2)) {
|
||||||
|
phaseManager.queueMessage(
|
||||||
|
i18next.t("battlerTags:infatuatedLapseImmobilize", {
|
||||||
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
(phaseManager.getCurrentPhase() as MovePhase).cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemove(pokemon: Pokemon): void {
|
onRemove(pokemon: Pokemon): void {
|
||||||
@ -899,6 +916,12 @@ export class SeedTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for SeedTag onAdd; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
super.onAdd(pokemon);
|
super.onAdd(pokemon);
|
||||||
|
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
@ -906,47 +929,51 @@ export class SeedTag extends BattlerTag {
|
|||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
this.sourceIndex = globalScene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct?
|
this.sourceIndex = source.getBattlerIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
|
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
|
||||||
|
|
||||||
if (ret) {
|
if (!ret) {
|
||||||
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
|
return false;
|
||||||
if (source) {
|
|
||||||
const cancelled = new BooleanHolder(false);
|
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
|
||||||
|
|
||||||
if (!cancelled.value) {
|
|
||||||
globalScene.phaseManager.unshiftNew(
|
|
||||||
"CommonAnimPhase",
|
|
||||||
source.getBattlerIndex(),
|
|
||||||
pokemon.getBattlerIndex(),
|
|
||||||
CommonAnim.LEECH_SEED,
|
|
||||||
);
|
|
||||||
|
|
||||||
const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
|
|
||||||
const reverseDrain = pokemon.hasAbilityWithAttr("ReverseDrainAbAttr", false);
|
|
||||||
globalScene.phaseManager.unshiftNew(
|
|
||||||
"PokemonHealPhase",
|
|
||||||
source.getBattlerIndex(),
|
|
||||||
!reverseDrain ? damage : damage * -1,
|
|
||||||
!reverseDrain
|
|
||||||
? i18next.t("battlerTags:seededLapse", {
|
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
||||||
})
|
|
||||||
: i18next.t("battlerTags:seededLapseShed", {
|
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
|
||||||
}),
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
// Check which opponent to restore HP to
|
||||||
|
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for SeedTag lapse; id: ${this.sourceId}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelled = new BooleanHolder(false);
|
||||||
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
|
|
||||||
|
if (cancelled.value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
globalScene.phaseManager.unshiftNew(
|
||||||
|
"CommonAnimPhase",
|
||||||
|
source.getBattlerIndex(),
|
||||||
|
pokemon.getBattlerIndex(),
|
||||||
|
CommonAnim.LEECH_SEED,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Damage the target and restore our HP (or take damage in the case of liquid ooze)
|
||||||
|
const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
|
||||||
|
const reverseDrain = pokemon.hasAbilityWithAttr("ReverseDrainAbAttr", false);
|
||||||
|
globalScene.phaseManager.unshiftNew(
|
||||||
|
"PokemonHealPhase",
|
||||||
|
source.getBattlerIndex(),
|
||||||
|
reverseDrain ? -damage : damage,
|
||||||
|
i18next.t(reverseDrain ? "battlerTags:seededLapseShed" : "battlerTags:seededLapse", {
|
||||||
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDescriptor(): string {
|
getDescriptor(): string {
|
||||||
@ -1006,7 +1033,7 @@ export class PowderTag extends BattlerTag {
|
|||||||
globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER);
|
globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER);
|
||||||
|
|
||||||
const cancelDamage = new BooleanHolder(false);
|
const cancelDamage = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelDamage);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled: cancelDamage });
|
||||||
if (!cancelDamage.value) {
|
if (!cancelDamage.value) {
|
||||||
pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
||||||
}
|
}
|
||||||
@ -1056,7 +1083,7 @@ export class NightmareTag extends BattlerTag {
|
|||||||
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE); // TODO: Update animation type
|
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE); // TODO: Update animation type
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
||||||
@ -1195,9 +1222,15 @@ export class HelpingHandTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for HelpingHandTag onAdd; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("battlerTags:helpingHandOnAdd", {
|
i18next.t("battlerTags:helpingHandOnAdd", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
|
pokemonNameWithAffix: getPokemonNameWithAffix(source),
|
||||||
pokemonName: getPokemonNameWithAffix(pokemon),
|
pokemonName: getPokemonNameWithAffix(pokemon),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -1219,9 +1252,7 @@ export class IngrainTag extends TrappedTag {
|
|||||||
* @returns boolean True if the tag can be added, false otherwise
|
* @returns boolean True if the tag can be added, false otherwise
|
||||||
*/
|
*/
|
||||||
canAdd(pokemon: Pokemon): boolean {
|
canAdd(pokemon: Pokemon): boolean {
|
||||||
const isTrapped = pokemon.getTag(BattlerTagType.TRAPPED);
|
return !pokemon.getTag(BattlerTagType.TRAPPED);
|
||||||
|
|
||||||
return !isTrapped;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
@ -1409,7 +1440,7 @@ export abstract class DamagingTrapTag extends TrappedTag {
|
|||||||
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, this.commonAnim);
|
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, this.commonAnim);
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
|
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
|
||||||
@ -1420,15 +1451,22 @@ export abstract class DamagingTrapTag extends TrappedTag {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Condense all these tags into 1 singular tag with a modified message func
|
||||||
export class BindTag extends DamagingTrapTag {
|
export class BindTag extends DamagingTrapTag {
|
||||||
constructor(turnCount: number, sourceId: number) {
|
constructor(turnCount: number, sourceId: number) {
|
||||||
super(BattlerTagType.BIND, CommonAnim.BIND, turnCount, MoveId.BIND, sourceId);
|
super(BattlerTagType.BIND, CommonAnim.BIND, turnCount, MoveId.BIND, sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTrapMessage(pokemon: Pokemon): string {
|
getTrapMessage(pokemon: Pokemon): string {
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for BindTag getTrapMessage; id: ${this.sourceId}`);
|
||||||
|
return "ERROR - CHECK CONSOLE AND REPORT";
|
||||||
|
}
|
||||||
|
|
||||||
return i18next.t("battlerTags:bindOnTrap", {
|
return i18next.t("battlerTags:bindOnTrap", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
sourcePokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
|
sourcePokemonName: getPokemonNameWithAffix(source),
|
||||||
moveName: this.getMoveName(),
|
moveName: this.getMoveName(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1440,9 +1478,16 @@ export class WrapTag extends DamagingTrapTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTrapMessage(pokemon: Pokemon): string {
|
getTrapMessage(pokemon: Pokemon): string {
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for WrapTag getTrapMessage; id: ${this.sourceId}`);
|
||||||
|
return "ERROR - CHECK CONSOLE AND REPORT";
|
||||||
|
}
|
||||||
|
|
||||||
return i18next.t("battlerTags:wrapOnTrap", {
|
return i18next.t("battlerTags:wrapOnTrap", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
sourcePokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
|
sourcePokemonName: getPokemonNameWithAffix(source),
|
||||||
|
moveName: this.getMoveName(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1473,8 +1518,14 @@ export class ClampTag extends DamagingTrapTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTrapMessage(pokemon: Pokemon): string {
|
getTrapMessage(pokemon: Pokemon): string {
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for ClampTag getTrapMessage; id: ${this.sourceId}`);
|
||||||
|
return "ERROR - CHECK CONSOLE AND REPORT ASAP";
|
||||||
|
}
|
||||||
|
|
||||||
return i18next.t("battlerTags:clampOnTrap", {
|
return i18next.t("battlerTags:clampOnTrap", {
|
||||||
sourcePokemonNameWithAffix: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
|
sourcePokemonNameWithAffix: getPokemonNameWithAffix(source),
|
||||||
pokemonName: getPokemonNameWithAffix(pokemon),
|
pokemonName: getPokemonNameWithAffix(pokemon),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1523,9 +1574,15 @@ export class ThunderCageTag extends DamagingTrapTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTrapMessage(pokemon: Pokemon): string {
|
getTrapMessage(pokemon: Pokemon): string {
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for ThunderCageTag getTrapMessage; id: ${this.sourceId}`);
|
||||||
|
return "ERROR - PLEASE REPORT ASAP";
|
||||||
|
}
|
||||||
|
|
||||||
return i18next.t("battlerTags:thunderCageOnTrap", {
|
return i18next.t("battlerTags:thunderCageOnTrap", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
sourcePokemonNameWithAffix: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
|
sourcePokemonNameWithAffix: getPokemonNameWithAffix(source),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1536,9 +1593,15 @@ export class InfestationTag extends DamagingTrapTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTrapMessage(pokemon: Pokemon): string {
|
getTrapMessage(pokemon: Pokemon): string {
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for InfestationTag getTrapMessage; id: ${this.sourceId}`);
|
||||||
|
return "ERROR - CHECK CONSOLE AND REPORT";
|
||||||
|
}
|
||||||
|
|
||||||
return i18next.t("battlerTags:infestationOnTrap", {
|
return i18next.t("battlerTags:infestationOnTrap", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
sourcePokemonNameWithAffix: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct?
|
sourcePokemonNameWithAffix: getPokemonNameWithAffix(source),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1642,7 +1705,7 @@ export class ContactDamageProtectedTag extends ContactProtectedTag {
|
|||||||
*/
|
*/
|
||||||
override onContact(attacker: Pokemon, user: Pokemon): void {
|
override onContact(attacker: Pokemon, user: Pokemon): void {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon: user, cancelled });
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), {
|
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), {
|
||||||
result: HitResult.INDIRECT,
|
result: HitResult.INDIRECT,
|
||||||
@ -2221,14 +2284,19 @@ export class SaltCuredTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
super.onAdd(pokemon);
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for SaltCureTag onAdd; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onAdd(pokemon);
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("battlerTags:saltCuredOnAdd", {
|
i18next.t("battlerTags:saltCuredOnAdd", {
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
this.sourceIndex = globalScene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct?
|
this.sourceIndex = source.getBattlerIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
@ -2243,7 +2311,7 @@ export class SaltCuredTag extends BattlerTag {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
const pokemonSteelOrWater = pokemon.isOfType(PokemonType.STEEL) || pokemon.isOfType(PokemonType.WATER);
|
const pokemonSteelOrWater = pokemon.isOfType(PokemonType.STEEL) || pokemon.isOfType(PokemonType.WATER);
|
||||||
@ -2281,8 +2349,14 @@ export class CursedTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for CursedTag onAdd; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
super.onAdd(pokemon);
|
super.onAdd(pokemon);
|
||||||
this.sourceIndex = globalScene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct?
|
this.sourceIndex = source.getBattlerIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
@ -2297,7 +2371,7 @@ export class CursedTag extends BattlerTag {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
|
||||||
@ -2632,7 +2706,7 @@ export class GulpMissileTag extends BattlerTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", attacker, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon: attacker, cancelled });
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), { result: HitResult.INDIRECT });
|
attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), { result: HitResult.INDIRECT });
|
||||||
@ -2902,7 +2976,13 @@ export class SubstituteTag extends BattlerTag {
|
|||||||
|
|
||||||
/** Sets the Substitute's HP and queues an on-add battle animation that initializes the Substitute's sprite. */
|
/** Sets the Substitute's HP and queues an on-add battle animation that initializes the Substitute's sprite. */
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
this.hp = Math.floor(globalScene.getPokemonById(this.sourceId!)!.getMaxHp() / 4);
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for SubstituteTag onAdd; id: ${this.sourceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hp = Math.floor(source.getMaxHp() / 4);
|
||||||
this.sourceInFocus = false;
|
this.sourceInFocus = false;
|
||||||
|
|
||||||
// Queue battle animation and message
|
// Queue battle animation and message
|
||||||
@ -3021,14 +3101,7 @@ export class MysteryEncounterPostSummonTag extends BattlerTag {
|
|||||||
const ret = super.lapse(pokemon, lapseType);
|
const ret = super.lapse(pokemon, lapseType);
|
||||||
|
|
||||||
if (lapseType === BattlerTagLapseType.CUSTOM) {
|
if (lapseType === BattlerTagLapseType.CUSTOM) {
|
||||||
const cancelled = new BooleanHolder(false);
|
pokemon.mysteryEncounterBattleEffects?.(pokemon);
|
||||||
applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled);
|
|
||||||
applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", pokemon, cancelled, false, pokemon);
|
|
||||||
if (!cancelled.value) {
|
|
||||||
if (pokemon.mysteryEncounterBattleEffects) {
|
|
||||||
pokemon.mysteryEncounterBattleEffects(pokemon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@ -3182,13 +3255,14 @@ export class ImprisonTag extends MoveRestrictionBattlerTag {
|
|||||||
*/
|
*/
|
||||||
public override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
public override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
const source = this.getSourcePokemon();
|
const source = this.getSourcePokemon();
|
||||||
if (source) {
|
if (!source) {
|
||||||
if (lapseType === BattlerTagLapseType.PRE_MOVE) {
|
console.warn(`Failed to get source Pokemon for ImprisonTag lapse; id: ${this.sourceId}`);
|
||||||
return super.lapse(pokemon, lapseType) && source.isActive(true);
|
return false;
|
||||||
}
|
|
||||||
return source.isActive(true);
|
|
||||||
}
|
}
|
||||||
return false;
|
if (lapseType === BattlerTagLapseType.PRE_MOVE) {
|
||||||
|
return super.lapse(pokemon, lapseType) && source.isActive(true);
|
||||||
|
}
|
||||||
|
return source.isActive(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3248,12 +3322,20 @@ export class SyrupBombTag extends BattlerTag {
|
|||||||
* Applies the single-stage speed down to the target Pokemon and decrements the tag's turn count
|
* Applies the single-stage speed down to the target Pokemon and decrements the tag's turn count
|
||||||
* @param pokemon - The target {@linkcode Pokemon}
|
* @param pokemon - The target {@linkcode Pokemon}
|
||||||
* @param _lapseType - N/A
|
* @param _lapseType - N/A
|
||||||
* @returns `true` if the `turnCount` is still greater than `0`; `false` if the `turnCount` is `0` or the target or source Pokemon has been removed from the field
|
* @returns Whether the tag should persist (`turnsRemaining > 0` and source still on field)
|
||||||
*/
|
*/
|
||||||
override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
|
override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
|
||||||
if (this.sourceId && !globalScene.getPokemonById(this.sourceId)?.isActive(true)) {
|
const source = this.getSourcePokemon();
|
||||||
|
if (!source) {
|
||||||
|
console.warn(`Failed to get source Pokemon for SyrupBombTag lapse; id: ${this.sourceId}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Syrup bomb clears immediately if source leaves field/faints
|
||||||
|
if (!source.isActive(true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Custom message in lieu of an animation in mainline
|
// Custom message in lieu of an animation in mainline
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("battlerTags:syrupBombLapse", {
|
i18next.t("battlerTags:syrupBombLapse", {
|
||||||
@ -3270,7 +3352,7 @@ export class SyrupBombTag extends BattlerTag {
|
|||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
return --this.turnCount > 0;
|
return super.lapse(pokemon, _lapseType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,28 +35,28 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
|
|||||||
case BerryType.APICOT:
|
case BerryType.APICOT:
|
||||||
case BerryType.SALAC:
|
case BerryType.SALAC:
|
||||||
return (pokemon: Pokemon) => {
|
return (pokemon: Pokemon) => {
|
||||||
const threshold = new NumberHolder(0.25);
|
const hpRatioReq = new NumberHolder(0.25);
|
||||||
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
|
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
|
||||||
const stat: BattleStat = berryType - BerryType.ENIGMA;
|
const stat: BattleStat = berryType - BerryType.ENIGMA;
|
||||||
applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
|
applyAbAttrs("ReduceBerryUseThresholdAbAttr", { pokemon, hpRatioReq });
|
||||||
return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6;
|
return pokemon.getHpRatio() < hpRatioReq.value && pokemon.getStatStage(stat) < 6;
|
||||||
};
|
};
|
||||||
case BerryType.LANSAT:
|
case BerryType.LANSAT:
|
||||||
return (pokemon: Pokemon) => {
|
return (pokemon: Pokemon) => {
|
||||||
const threshold = new NumberHolder(0.25);
|
const hpRatioReq = new NumberHolder(0.25);
|
||||||
applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
|
applyAbAttrs("ReduceBerryUseThresholdAbAttr", { pokemon, hpRatioReq });
|
||||||
return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST);
|
return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST);
|
||||||
};
|
};
|
||||||
case BerryType.STARF:
|
case BerryType.STARF:
|
||||||
return (pokemon: Pokemon) => {
|
return (pokemon: Pokemon) => {
|
||||||
const threshold = new NumberHolder(0.25);
|
const hpRatioReq = new NumberHolder(0.25);
|
||||||
applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
|
applyAbAttrs("ReduceBerryUseThresholdAbAttr", { pokemon, hpRatioReq });
|
||||||
return pokemon.getHpRatio() < 0.25;
|
return pokemon.getHpRatio() < 0.25;
|
||||||
};
|
};
|
||||||
case BerryType.LEPPA:
|
case BerryType.LEPPA:
|
||||||
return (pokemon: Pokemon) => {
|
return (pokemon: Pokemon) => {
|
||||||
const threshold = new NumberHolder(0.25);
|
const hpRatioReq = new NumberHolder(0.25);
|
||||||
applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
|
applyAbAttrs("ReduceBerryUseThresholdAbAttr", { pokemon, hpRatioReq });
|
||||||
return !!pokemon.getMoveset().find(m => !m.getPpRatio());
|
return !!pokemon.getMoveset().find(m => !m.getPpRatio());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -72,7 +72,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
|||||||
case BerryType.ENIGMA:
|
case BerryType.ENIGMA:
|
||||||
{
|
{
|
||||||
const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4));
|
const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4));
|
||||||
applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, hpHealed);
|
applyAbAttrs("DoubleBerryEffectAbAttr", { pokemon: consumer, effectValue: hpHealed });
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
"PokemonHealPhase",
|
"PokemonHealPhase",
|
||||||
consumer.getBattlerIndex(),
|
consumer.getBattlerIndex(),
|
||||||
@ -105,7 +105,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
|||||||
// Offset BerryType such that LIECHI --> Stat.ATK = 1, GANLON --> Stat.DEF = 2, etc etc.
|
// Offset BerryType such that LIECHI --> Stat.ATK = 1, GANLON --> Stat.DEF = 2, etc etc.
|
||||||
const stat: BattleStat = berryType - BerryType.ENIGMA;
|
const stat: BattleStat = berryType - BerryType.ENIGMA;
|
||||||
const statStages = new NumberHolder(1);
|
const statStages = new NumberHolder(1);
|
||||||
applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, statStages);
|
applyAbAttrs("DoubleBerryEffectAbAttr", { pokemon: consumer, effectValue: statStages });
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
"StatStageChangePhase",
|
"StatStageChangePhase",
|
||||||
consumer.getBattlerIndex(),
|
consumer.getBattlerIndex(),
|
||||||
@ -126,7 +126,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
|||||||
{
|
{
|
||||||
const randStat = randSeedInt(Stat.SPD, Stat.ATK);
|
const randStat = randSeedInt(Stat.SPD, Stat.ATK);
|
||||||
const stages = new NumberHolder(2);
|
const stages = new NumberHolder(2);
|
||||||
applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, stages);
|
applyAbAttrs("DoubleBerryEffectAbAttr", { pokemon: consumer, effectValue: stages });
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
"StatStageChangePhase",
|
"StatStageChangePhase",
|
||||||
consumer.getBattlerIndex(),
|
consumer.getBattlerIndex(),
|
||||||
|
@ -33,11 +33,7 @@ import type { ArenaTrapTag } from "../arena-tag";
|
|||||||
import { WeakenMoveTypeTag } from "../arena-tag";
|
import { WeakenMoveTypeTag } from "../arena-tag";
|
||||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||||
import {
|
import {
|
||||||
applyAbAttrs,
|
applyAbAttrs
|
||||||
applyPostAttackAbAttrs,
|
|
||||||
applyPostItemLostAbAttrs,
|
|
||||||
applyPreAttackAbAttrs,
|
|
||||||
applyPreDefendAbAttrs
|
|
||||||
} from "../abilities/apply-ab-attrs";
|
} from "../abilities/apply-ab-attrs";
|
||||||
import { allAbilities, allMoves } from "../data-lists";
|
import { allAbilities, allMoves } from "../data-lists";
|
||||||
import {
|
import {
|
||||||
@ -89,9 +85,10 @@ import { MoveEffectTrigger } from "#enums/MoveEffectTrigger";
|
|||||||
import { MultiHitType } from "#enums/MultiHitType";
|
import { MultiHitType } from "#enums/MultiHitType";
|
||||||
import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves, invalidSketchMoves } from "./invalid-moves";
|
import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves, invalidSketchMoves } from "./invalid-moves";
|
||||||
import { isVirtual, MoveUseMode } from "#enums/move-use-mode";
|
import { isVirtual, MoveUseMode } from "#enums/move-use-mode";
|
||||||
import { ChargingMove, MoveAttrMap, MoveAttrString, MoveKindString, MoveClassMap } from "#app/@types/move-types";
|
import type { ChargingMove, MoveAttrMap, MoveAttrString, MoveKindString, MoveClassMap } from "#app/@types/move-types";
|
||||||
import { applyMoveAttrs } from "./apply-attrs";
|
import { applyMoveAttrs } from "./apply-attrs";
|
||||||
import { frenzyMissFunc, getMoveTargets } from "./move-utils";
|
import { frenzyMissFunc, getMoveTargets } from "./move-utils";
|
||||||
|
import { AbAttrBaseParams, AbAttrParamsWithCancel, PreAttackModifyPowerAbAttrParams } from "../abilities/ability";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function used to conditionally determine execution of a given {@linkcode MoveAttr}.
|
* A function used to conditionally determine execution of a given {@linkcode MoveAttr}.
|
||||||
@ -347,7 +344,7 @@ export default abstract class Move implements Localizable {
|
|||||||
|
|
||||||
const bypassed = new BooleanHolder(false);
|
const bypassed = new BooleanHolder(false);
|
||||||
// TODO: Allow this to be simulated
|
// TODO: Allow this to be simulated
|
||||||
applyAbAttrs("InfiltratorAbAttr", user, null, false, bypassed);
|
applyAbAttrs("InfiltratorAbAttr", {pokemon: user, bypassed});
|
||||||
|
|
||||||
return !bypassed.value
|
return !bypassed.value
|
||||||
&& !this.hasFlag(MoveFlags.SOUND_BASED)
|
&& !this.hasFlag(MoveFlags.SOUND_BASED)
|
||||||
@ -645,7 +642,7 @@ export default abstract class Move implements Localizable {
|
|||||||
case MoveFlags.IGNORE_ABILITIES:
|
case MoveFlags.IGNORE_ABILITIES:
|
||||||
if (user.hasAbilityWithAttr("MoveAbilityBypassAbAttr")) {
|
if (user.hasAbilityWithAttr("MoveAbilityBypassAbAttr")) {
|
||||||
const abilityEffectsIgnored = new BooleanHolder(false);
|
const abilityEffectsIgnored = new BooleanHolder(false);
|
||||||
applyAbAttrs("MoveAbilityBypassAbAttr", user, abilityEffectsIgnored, false, this);
|
applyAbAttrs("MoveAbilityBypassAbAttr", {pokemon: user, cancelled: abilityEffectsIgnored, move: this});
|
||||||
if (abilityEffectsIgnored.value) {
|
if (abilityEffectsIgnored.value) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -762,7 +759,7 @@ export default abstract class Move implements Localizable {
|
|||||||
const moveAccuracy = new NumberHolder(this.accuracy);
|
const moveAccuracy = new NumberHolder(this.accuracy);
|
||||||
|
|
||||||
applyMoveAttrs("VariableAccuracyAttr", user, target, this, moveAccuracy);
|
applyMoveAttrs("VariableAccuracyAttr", user, target, this, moveAccuracy);
|
||||||
applyPreDefendAbAttrs("WonderSkinAbAttr", target, user, this, { value: false }, simulated, moveAccuracy);
|
applyAbAttrs("WonderSkinAbAttr", {pokemon: target, opponent: user, move: this, simulated, accuracy: moveAccuracy});
|
||||||
|
|
||||||
if (moveAccuracy.value === -1) {
|
if (moveAccuracy.value === -1) {
|
||||||
return moveAccuracy.value;
|
return moveAccuracy.value;
|
||||||
@ -805,17 +802,25 @@ export default abstract class Move implements Localizable {
|
|||||||
const typeChangeMovePowerMultiplier = new NumberHolder(1);
|
const typeChangeMovePowerMultiplier = new NumberHolder(1);
|
||||||
const typeChangeHolder = new NumberHolder(this.type);
|
const typeChangeHolder = new NumberHolder(this.type);
|
||||||
|
|
||||||
applyPreAttackAbAttrs("MoveTypeChangeAbAttr", source, target, this, true, typeChangeHolder, typeChangeMovePowerMultiplier);
|
applyAbAttrs("MoveTypeChangeAbAttr", {pokemon: source, opponent: target, move: this, simulated: true, moveType: typeChangeHolder, power: typeChangeMovePowerMultiplier});
|
||||||
|
|
||||||
const sourceTeraType = source.getTeraType();
|
const sourceTeraType = source.getTeraType();
|
||||||
if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
|
if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
|
||||||
power.value = 60;
|
power.value = 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
applyPreAttackAbAttrs("VariableMovePowerAbAttr", source, target, this, simulated, power);
|
const abAttrParams: PreAttackModifyPowerAbAttrParams = {
|
||||||
|
pokemon: source,
|
||||||
|
opponent: target,
|
||||||
|
simulated,
|
||||||
|
power,
|
||||||
|
move: this,
|
||||||
|
}
|
||||||
|
|
||||||
|
applyAbAttrs("VariableMovePowerAbAttr", abAttrParams);
|
||||||
const ally = source.getAlly();
|
const ally = source.getAlly();
|
||||||
if (!isNullOrUndefined(ally)) {
|
if (!isNullOrUndefined(ally)) {
|
||||||
applyPreAttackAbAttrs("AllyMoveCategoryPowerBoostAbAttr", ally, target, this, simulated, power);
|
applyAbAttrs("AllyMoveCategoryPowerBoostAbAttr", {...abAttrParams, pokemon: ally});
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldAuras = new Set(
|
const fieldAuras = new Set(
|
||||||
@ -827,11 +832,12 @@ export default abstract class Move implements Localizable {
|
|||||||
.flat(),
|
.flat(),
|
||||||
);
|
);
|
||||||
for (const aura of fieldAuras) {
|
for (const aura of fieldAuras) {
|
||||||
aura.applyPreAttack(source, null, simulated, target, this, [ power ]);
|
// TODO: Refactor the fieldAura attribute so that its apply method is not directly called
|
||||||
|
aura.apply({pokemon: source, simulated, opponent: target, move: this, power});
|
||||||
}
|
}
|
||||||
|
|
||||||
const alliedField: Pokemon[] = source.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
const alliedField: Pokemon[] = source.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
||||||
alliedField.forEach(p => applyPreAttackAbAttrs("UserFieldMoveTypePowerBoostAbAttr", p, target, this, simulated, power));
|
alliedField.forEach(p => applyAbAttrs("UserFieldMoveTypePowerBoostAbAttr", {pokemon: p, opponent: target, move: this, simulated, power}));
|
||||||
|
|
||||||
power.value *= typeChangeMovePowerMultiplier.value;
|
power.value *= typeChangeMovePowerMultiplier.value;
|
||||||
|
|
||||||
@ -858,7 +864,7 @@ export default abstract class Move implements Localizable {
|
|||||||
const priority = new NumberHolder(this.priority);
|
const priority = new NumberHolder(this.priority);
|
||||||
|
|
||||||
applyMoveAttrs("IncrementMovePriorityAttr", user, null, this, priority);
|
applyMoveAttrs("IncrementMovePriorityAttr", user, null, this, priority);
|
||||||
applyAbAttrs("ChangeMovePriorityAbAttr", user, null, simulated, this, priority);
|
applyAbAttrs("ChangeMovePriorityAbAttr", {pokemon: user, simulated, move: this, priority});
|
||||||
|
|
||||||
return priority.value;
|
return priority.value;
|
||||||
}
|
}
|
||||||
@ -1310,7 +1316,7 @@ export class MoveEffectAttr extends MoveAttr {
|
|||||||
getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): number {
|
getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): number {
|
||||||
const moveChance = new NumberHolder(this.effectChanceOverride ?? move.chance);
|
const moveChance = new NumberHolder(this.effectChanceOverride ?? move.chance);
|
||||||
|
|
||||||
applyAbAttrs("MoveEffectChanceMultiplierAbAttr", user, null, !showAbility, moveChance, move);
|
applyAbAttrs("MoveEffectChanceMultiplierAbAttr", {pokemon: user, simulated: !showAbility, chance: moveChance, move});
|
||||||
|
|
||||||
if ((!move.hasAttr("FlinchAttr") || moveChance.value <= move.chance) && !move.hasAttr("SecretPowerAttr")) {
|
if ((!move.hasAttr("FlinchAttr") || moveChance.value <= move.chance) && !move.hasAttr("SecretPowerAttr")) {
|
||||||
const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||||
@ -1318,7 +1324,7 @@ export class MoveEffectAttr extends MoveAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!selfEffect) {
|
if (!selfEffect) {
|
||||||
applyPreDefendAbAttrs("IgnoreMoveEffectsAbAttr", target, user, null, null, !showAbility, moveChance);
|
applyAbAttrs("IgnoreMoveEffectsAbAttr", {pokemon: target, move, simulated: !showAbility, chance: moveChance});
|
||||||
}
|
}
|
||||||
return moveChance.value;
|
return moveChance.value;
|
||||||
}
|
}
|
||||||
@ -1709,8 +1715,9 @@ export class RecoilAttr extends MoveEffectAttr {
|
|||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
if (!this.unblockable) {
|
if (!this.unblockable) {
|
||||||
applyAbAttrs("BlockRecoilDamageAttr", user, cancelled);
|
const abAttrParams: AbAttrParamsWithCancel = {pokemon: user, cancelled};
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
|
applyAbAttrs("BlockRecoilDamageAttr", abAttrParams);
|
||||||
|
applyAbAttrs("BlockNonDirectDamageAbAttr", abAttrParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
@ -1843,7 +1850,7 @@ export class HalfSacrificialAttr extends MoveEffectAttr {
|
|||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
// Check to see if the Pokemon has an ability that blocks non-direct damage
|
// Check to see if the Pokemon has an ability that blocks non-direct damage
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", {pokemon: user, cancelled});
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT, ignoreSegments: true });
|
user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT, ignoreSegments: true });
|
||||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", { pokemonName: getPokemonNameWithAffix(user) })); // Queue recoil message
|
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", { pokemonName: getPokemonNameWithAffix(user) })); // Queue recoil message
|
||||||
@ -2042,7 +2049,7 @@ export class FlameBurstAttr extends MoveEffectAttr {
|
|||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
|
|
||||||
if (!isNullOrUndefined(targetAlly)) {
|
if (!isNullOrUndefined(targetAlly)) {
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", targetAlly, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", {pokemon: targetAlly, cancelled});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancelled.value || !targetAlly || targetAlly.switchOutStatus) {
|
if (cancelled.value || !targetAlly || targetAlly.switchOutStatus) {
|
||||||
@ -2414,7 +2421,7 @@ export class MultiHitAttr extends MoveAttr {
|
|||||||
{
|
{
|
||||||
const rand = user.randBattleSeedInt(20);
|
const rand = user.randBattleSeedInt(20);
|
||||||
const hitValue = new NumberHolder(rand);
|
const hitValue = new NumberHolder(rand);
|
||||||
applyAbAttrs("MaxMultiHitAbAttr", user, null, false, hitValue);
|
applyAbAttrs("MaxMultiHitAbAttr", {pokemon: user, hits: hitValue});
|
||||||
if (hitValue.value >= 13) {
|
if (hitValue.value >= 13) {
|
||||||
return 2;
|
return 2;
|
||||||
} else if (hitValue.value >= 6) {
|
} else if (hitValue.value >= 6) {
|
||||||
@ -2522,7 +2529,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
if (((!pokemon.status || this.overrideStatus) || (pokemon.status.effect === this.effect && moveChance < 0))
|
if (((!pokemon.status || this.overrideStatus) || (pokemon.status.effect === this.effect && moveChance < 0))
|
||||||
&& pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining, null, this.overrideStatus, quiet)) {
|
&& pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining, null, this.overrideStatus, quiet)) {
|
||||||
applyPostAttackAbAttrs("ConfusionOnStatusEffectAbAttr", user, target, move, null, false, this.effect);
|
applyAbAttrs("ConfusionOnStatusEffectAbAttr", {pokemon: user, opponent: target, move, effect: this.effect});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2574,7 +2581,7 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
|
|||||||
apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
|
apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
|
||||||
const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : undefined);
|
const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : undefined);
|
||||||
|
|
||||||
if (target.status) {
|
if (target.status || !statusToApply) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
const canSetStatus = target.canSetStatus(statusToApply, true, false, user);
|
const canSetStatus = target.canSetStatus(statusToApply, true, false, user);
|
||||||
@ -2590,7 +2597,8 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||||
return !target.status && target.canSetStatus(user.status?.effect, true, false, user) ? -10 : 0;
|
const statusToApply = user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : undefined);
|
||||||
|
return !target.status && statusToApply && target.canSetStatus(statusToApply, true, false, user) ? -10 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2678,7 +2686,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
|
|||||||
// Check for abilities that block item theft
|
// Check for abilities that block item theft
|
||||||
// TODO: This should not trigger if the target would faint beforehand
|
// TODO: This should not trigger if the target would faint beforehand
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockItemTheftAbAttr", target, cancelled);
|
applyAbAttrs("BlockItemTheftAbAttr", {pokemon: target, cancelled});
|
||||||
|
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
return false;
|
return false;
|
||||||
@ -2795,8 +2803,8 @@ export class EatBerryAttr extends MoveEffectAttr {
|
|||||||
protected eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) {
|
protected eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) {
|
||||||
// consumer eats berry, owner triggers unburden and similar effects
|
// consumer eats berry, owner triggers unburden and similar effects
|
||||||
getBerryEffectFunc(this.chosenBerry.berryType)(consumer);
|
getBerryEffectFunc(this.chosenBerry.berryType)(consumer);
|
||||||
applyPostItemLostAbAttrs("PostItemLostAbAttr", berryOwner, false);
|
applyAbAttrs("PostItemLostAbAttr", {pokemon: berryOwner});
|
||||||
applyAbAttrs("HealFromBerryUseAbAttr", consumer, new BooleanHolder(false));
|
applyAbAttrs("HealFromBerryUseAbAttr", {pokemon: consumer});
|
||||||
consumer.recordEatenBerry(this.chosenBerry.berryType, updateHarvest);
|
consumer.recordEatenBerry(this.chosenBerry.berryType, updateHarvest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2821,7 +2829,7 @@ export class StealEatBerryAttr extends EatBerryAttr {
|
|||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||||
// check for abilities that block item theft
|
// check for abilities that block item theft
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockItemTheftAbAttr", target, cancelled);
|
applyAbAttrs("BlockItemTheftAbAttr", {pokemon: target, cancelled});
|
||||||
if (cancelled.value === true) {
|
if (cancelled.value === true) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -2835,7 +2843,7 @@ export class StealEatBerryAttr extends EatBerryAttr {
|
|||||||
|
|
||||||
// pick a random berry and eat it
|
// pick a random berry and eat it
|
||||||
this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)];
|
this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)];
|
||||||
applyPostItemLostAbAttrs("PostItemLostAbAttr", target, false);
|
applyAbAttrs("PostItemLostAbAttr", {pokemon: target});
|
||||||
const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name });
|
const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name });
|
||||||
globalScene.phaseManager.queueMessage(message);
|
globalScene.phaseManager.queueMessage(message);
|
||||||
this.reduceBerryModifier(target);
|
this.reduceBerryModifier(target);
|
||||||
@ -3026,7 +3034,7 @@ export class OneHitKOAttr extends MoveAttr {
|
|||||||
getCondition(): MoveConditionFunc {
|
getCondition(): MoveConditionFunc {
|
||||||
return (user, target, move) => {
|
return (user, target, move) => {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockOneHitKOAbAttr", target, cancelled);
|
applyAbAttrs("BlockOneHitKOAbAttr", {pokemon: target, cancelled});
|
||||||
return !cancelled.value && user.level >= target.level;
|
return !cancelled.value && user.level >= target.level;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -5438,7 +5446,7 @@ export class NoEffectAttr extends MoveAttr {
|
|||||||
|
|
||||||
const crashDamageFunc = (user: Pokemon, move: Move) => {
|
const crashDamageFunc = (user: Pokemon, move: Move) => {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", {pokemon: user, cancelled});
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -6437,9 +6445,9 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getFailedText(_user: Pokemon, target: Pokemon, _move: Move): string | undefined {
|
getFailedText(_user: Pokemon, target: Pokemon, _move: Move): string | undefined {
|
||||||
const blockedByAbility = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("ForceSwitchOutImmunityAbAttr", target, blockedByAbility);
|
applyAbAttrs("ForceSwitchOutImmunityAbAttr", {pokemon: target, cancelled});
|
||||||
if (blockedByAbility.value) {
|
if (cancelled.value) {
|
||||||
return i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) });
|
return i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6478,7 +6486,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const blockedByAbility = new BooleanHolder(false);
|
const blockedByAbility = new BooleanHolder(false);
|
||||||
applyAbAttrs("ForceSwitchOutImmunityAbAttr", target, blockedByAbility);
|
applyAbAttrs("ForceSwitchOutImmunityAbAttr", {pokemon: target, cancelled: blockedByAbility});
|
||||||
if (blockedByAbility.value) {
|
if (blockedByAbility.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -6887,12 +6895,12 @@ export class RandomMovesetMoveAttr extends CallMoveAttr {
|
|||||||
// includeParty will be true for Assist, false for Sleep Talk
|
// includeParty will be true for Assist, false for Sleep Talk
|
||||||
let allies: Pokemon[];
|
let allies: Pokemon[];
|
||||||
if (this.includeParty) {
|
if (this.includeParty) {
|
||||||
allies = user.isPlayer() ? globalScene.getPlayerParty().filter(p => p !== user) : globalScene.getEnemyParty().filter(p => p !== user);
|
allies = (user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter(p => p !== user);
|
||||||
} else {
|
} else {
|
||||||
allies = [ user ];
|
allies = [ user ];
|
||||||
}
|
}
|
||||||
const partyMoveset = allies.map(p => p.moveset).flat();
|
const partyMoveset = allies.flatMap(p => p.moveset);
|
||||||
const moves = partyMoveset.filter(m => !this.invalidMoves.has(m!.moveId) && !m!.getMove().name.endsWith(" (N)"));
|
const moves = partyMoveset.filter(m => !this.invalidMoves.has(m.moveId) && !m.getMove().name.endsWith(" (N)"));
|
||||||
if (moves.length === 0) {
|
if (moves.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -7987,7 +7995,7 @@ const failIfSingleBattle: MoveConditionFunc = (user, target, move) => globalScen
|
|||||||
|
|
||||||
const failIfDampCondition: MoveConditionFunc = (user, target, move) => {
|
const failIfDampCondition: MoveConditionFunc = (user, target, move) => {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
globalScene.getField(true).map(p=>applyAbAttrs("FieldPreventExplosiveMovesAbAttr", p, cancelled));
|
globalScene.getField(true).map(p=>applyAbAttrs("FieldPreventExplosiveMovesAbAttr", {pokemon: p, cancelled}));
|
||||||
// Queue a message if an ability prevented usage of the move
|
// Queue a message if an ability prevented usage of the move
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name }));
|
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name }));
|
||||||
|
@ -328,7 +328,7 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder
|
|||||||
.withOptionPhase(async () => {
|
.withOptionPhase(async () => {
|
||||||
// Show the Oricorio a dance, and recruit it
|
// Show the Oricorio a dance, and recruit it
|
||||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||||
const oricorio = encounter.misc.oricorioData.toPokemon();
|
const oricorio = encounter.misc.oricorioData.toPokemon() as EnemyPokemon;
|
||||||
oricorio.passive = true;
|
oricorio.passive = true;
|
||||||
|
|
||||||
// Ensure the Oricorio's moveset gains the Dance move the player used
|
// Ensure the Oricorio's moveset gains the Dance move the player used
|
||||||
|
@ -24,7 +24,7 @@ import { PokemonType } from "#enums/pokemon-type";
|
|||||||
import { BerryType } from "#enums/berry-type";
|
import { BerryType } from "#enums/berry-type";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
||||||
import { applyPostBattleInitAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
@ -221,7 +221,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
|
|||||||
|
|
||||||
// Each trainer battle is supposed to be a new fight, so reset all per-battle activation effects
|
// Each trainer battle is supposed to be a new fight, so reset all per-battle activation effects
|
||||||
pokemon.resetBattleAndWaveData();
|
pokemon.resetBattleAndWaveData();
|
||||||
applyPostBattleInitAbAttrs("PostBattleInitAbAttr", pokemon);
|
applyAbAttrs("PostBattleInitAbAttr", { pokemon });
|
||||||
}
|
}
|
||||||
|
|
||||||
globalScene.phaseManager.unshiftNew("ShowTrainerPhase");
|
globalScene.phaseManager.unshiftNew("ShowTrainerPhase");
|
||||||
|
@ -20,11 +20,7 @@ import { ArenaTrapTag, getArenaTag } from "#app/data/arena-tag";
|
|||||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||||
import type { BattlerIndex } from "#enums/battler-index";
|
import type { BattlerIndex } from "#enums/battler-index";
|
||||||
import { Terrain, TerrainType } from "#app/data/terrain";
|
import { Terrain, TerrainType } from "#app/data/terrain";
|
||||||
import {
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
applyAbAttrs,
|
|
||||||
applyPostTerrainChangeAbAttrs,
|
|
||||||
applyPostWeatherChangeAbAttrs,
|
|
||||||
} from "#app/data/abilities/apply-ab-attrs";
|
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
||||||
@ -372,7 +368,7 @@ export class Arena {
|
|||||||
pokemon.findAndRemoveTags(
|
pokemon.findAndRemoveTags(
|
||||||
t => "weatherTypes" in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather),
|
t => "weatherTypes" in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather),
|
||||||
);
|
);
|
||||||
applyPostWeatherChangeAbAttrs("PostWeatherChangeAbAttr", pokemon, weather);
|
applyAbAttrs("PostWeatherChangeAbAttr", { pokemon, weather });
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -461,8 +457,8 @@ export class Arena {
|
|||||||
pokemon.findAndRemoveTags(
|
pokemon.findAndRemoveTags(
|
||||||
t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain),
|
t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain),
|
||||||
);
|
);
|
||||||
applyPostTerrainChangeAbAttrs("PostTerrainChangeAbAttr", pokemon, terrain);
|
applyAbAttrs("PostTerrainChangeAbAttr", { pokemon, terrain });
|
||||||
applyAbAttrs("TerrainEventTypeChangeAbAttr", pokemon, null, false);
|
applyAbAttrs("TerrainEventTypeChangeAbAttr", { pokemon });
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -108,23 +108,8 @@ import { WeatherType } from "#enums/weather-type";
|
|||||||
import { NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
|
import { NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
|
||||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||||
import type { SuppressAbilitiesTag } from "#app/data/arena-tag";
|
import type { SuppressAbilitiesTag } from "#app/data/arena-tag";
|
||||||
import type { Ability } from "#app/data/abilities/ability";
|
import type { Ability, PreAttackModifyDamageAbAttrParams } from "#app/data/abilities/ability";
|
||||||
import {
|
import { applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
applyAbAttrs,
|
|
||||||
applyStatMultiplierAbAttrs,
|
|
||||||
applyPreApplyBattlerTagAbAttrs,
|
|
||||||
applyPreAttackAbAttrs,
|
|
||||||
applyPreDefendAbAttrs,
|
|
||||||
applyPreSetStatusAbAttrs,
|
|
||||||
applyFieldStatMultiplierAbAttrs,
|
|
||||||
applyCheckTrappedAbAttrs,
|
|
||||||
applyPostDamageAbAttrs,
|
|
||||||
applyPostItemLostAbAttrs,
|
|
||||||
applyOnGainAbAttrs,
|
|
||||||
applyPreLeaveFieldAbAttrs,
|
|
||||||
applyOnLoseAbAttrs,
|
|
||||||
applyAllyStatMultiplierAbAttrs,
|
|
||||||
} from "#app/data/abilities/apply-ab-attrs";
|
|
||||||
import { allAbilities } from "#app/data/data-lists";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import type PokemonData from "#app/system/pokemon-data";
|
import type PokemonData from "#app/system/pokemon-data";
|
||||||
import { BattlerIndex } from "#enums/battler-index";
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
@ -189,7 +174,7 @@ import { HitResult } from "#enums/hit-result";
|
|||||||
import { AiType } from "#enums/ai-type";
|
import { AiType } from "#enums/ai-type";
|
||||||
import type { MoveResult } from "#enums/move-result";
|
import type { MoveResult } from "#enums/move-result";
|
||||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||||
import type { AbAttrMap, AbAttrString } from "#app/@types/ability-types";
|
import type { AbAttrMap, AbAttrString, TypeMultiplierAbAttrParams } from "#app/@types/ability-types";
|
||||||
|
|
||||||
/** Base typeclass for damage parameter methods, used for DRY */
|
/** Base typeclass for damage parameter methods, used for DRY */
|
||||||
type damageParams = {
|
type damageParams = {
|
||||||
@ -1364,7 +1349,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
applyMoveAttrs("HighCritAttr", source, this, move, critStage);
|
applyMoveAttrs("HighCritAttr", source, this, move, critStage);
|
||||||
globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage);
|
globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage);
|
||||||
globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
|
globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
|
||||||
applyAbAttrs("BonusCritAbAttr", source, null, false, critStage);
|
applyAbAttrs("BonusCritAbAttr", { pokemon: source, critStage });
|
||||||
const critBoostTag = source.getTag(CritBoostTag);
|
const critBoostTag = source.getTag(CritBoostTag);
|
||||||
if (critBoostTag) {
|
if (critBoostTag) {
|
||||||
// Dragon cheer only gives +1 crit stage to non-dragon types
|
// Dragon cheer only gives +1 crit stage to non-dragon types
|
||||||
@ -1415,46 +1400,52 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
simulated = true,
|
simulated = true,
|
||||||
ignoreHeldItems = false,
|
ignoreHeldItems = false,
|
||||||
): number {
|
): number {
|
||||||
const statValue = new NumberHolder(this.getStat(stat, false));
|
const statVal = new NumberHolder(this.getStat(stat, false));
|
||||||
if (!ignoreHeldItems) {
|
if (!ignoreHeldItems) {
|
||||||
globalScene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statValue);
|
globalScene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Ruin abilities here are never ignored, but they reveal themselves on summon anyway
|
// The Ruin abilities here are never ignored, but they reveal themselves on summon anyway
|
||||||
const fieldApplied = new BooleanHolder(false);
|
const fieldApplied = new BooleanHolder(false);
|
||||||
for (const pokemon of globalScene.getField(true)) {
|
for (const pokemon of globalScene.getField(true)) {
|
||||||
applyFieldStatMultiplierAbAttrs(
|
applyAbAttrs("FieldMultiplyStatAbAttr", {
|
||||||
"FieldMultiplyStatAbAttr",
|
|
||||||
pokemon,
|
pokemon,
|
||||||
stat,
|
stat,
|
||||||
statValue,
|
statVal,
|
||||||
this,
|
target: this,
|
||||||
fieldApplied,
|
hasApplied: fieldApplied,
|
||||||
simulated,
|
simulated,
|
||||||
);
|
});
|
||||||
if (fieldApplied.value) {
|
if (fieldApplied.value) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!ignoreAbility) {
|
if (!ignoreAbility) {
|
||||||
applyStatMultiplierAbAttrs("StatMultiplierAbAttr", this, stat, statValue, simulated);
|
applyAbAttrs("StatMultiplierAbAttr", {
|
||||||
|
pokemon: this,
|
||||||
|
stat,
|
||||||
|
statVal,
|
||||||
|
simulated,
|
||||||
|
// TODO: maybe just don't call this if the move is none?
|
||||||
|
move: move ?? allMoves[MoveId.NONE],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const ally = this.getAlly();
|
const ally = this.getAlly();
|
||||||
if (!isNullOrUndefined(ally)) {
|
if (!isNullOrUndefined(ally)) {
|
||||||
applyAllyStatMultiplierAbAttrs(
|
applyAbAttrs("AllyStatMultiplierAbAttr", {
|
||||||
"AllyStatMultiplierAbAttr",
|
pokemon: ally,
|
||||||
ally,
|
|
||||||
stat,
|
stat,
|
||||||
statValue,
|
statVal,
|
||||||
simulated,
|
simulated,
|
||||||
this,
|
// TODO: maybe just don't call this if the move is none?
|
||||||
move?.hasFlag(MoveFlags.IGNORE_ABILITIES) || ignoreAllyAbility,
|
move: move ?? allMoves[MoveId.NONE],
|
||||||
);
|
ignoreAbility: move?.hasFlag(MoveFlags.IGNORE_ABILITIES) || ignoreAllyAbility,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret =
|
let ret =
|
||||||
statValue.value *
|
statVal.value *
|
||||||
this.getStatStageMultiplier(stat, opponent, move, ignoreOppAbility, isCritical, simulated, ignoreHeldItems);
|
this.getStatStageMultiplier(stat, opponent, move, ignoreOppAbility, isCritical, simulated, ignoreHeldItems);
|
||||||
|
|
||||||
switch (stat) {
|
switch (stat) {
|
||||||
@ -2045,20 +2036,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* @param ability New Ability
|
* @param ability New Ability
|
||||||
*/
|
*/
|
||||||
public setTempAbility(ability: Ability, passive = false): void {
|
public setTempAbility(ability: Ability, passive = false): void {
|
||||||
applyOnLoseAbAttrs(this, passive);
|
applyOnLoseAbAttrs({ pokemon: this, passive });
|
||||||
if (passive) {
|
if (passive) {
|
||||||
this.summonData.passiveAbility = ability.id;
|
this.summonData.passiveAbility = ability.id;
|
||||||
} else {
|
} else {
|
||||||
this.summonData.ability = ability.id;
|
this.summonData.ability = ability.id;
|
||||||
}
|
}
|
||||||
applyOnGainAbAttrs(this, passive);
|
applyOnGainAbAttrs({ pokemon: this, passive });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Suppresses an ability and calls its onlose attributes
|
* Suppresses an ability and calls its onlose attributes
|
||||||
*/
|
*/
|
||||||
public suppressAbility() {
|
public suppressAbility() {
|
||||||
[true, false].forEach(passive => applyOnLoseAbAttrs(this, passive));
|
[true, false].forEach(passive => applyOnLoseAbAttrs({ pokemon: this, passive }));
|
||||||
this.summonData.abilitySuppressed = true;
|
this.summonData.abilitySuppressed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2194,7 +2185,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const weight = new NumberHolder(this.species.weight - weightRemoved);
|
const weight = new NumberHolder(this.species.weight - weightRemoved);
|
||||||
|
|
||||||
// This will trigger the ability overlay so only call this function when necessary
|
// This will trigger the ability overlay so only call this function when necessary
|
||||||
applyAbAttrs("WeightMultiplierAbAttr", this, null, false, weight);
|
applyAbAttrs("WeightMultiplierAbAttr", { pokemon: this, weight });
|
||||||
return Math.max(minWeight, weight.value);
|
return Math.max(minWeight, weight.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2256,7 +2247,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const trappedByAbility = new BooleanHolder(false);
|
/** Holds whether the pokemon is trapped due to an ability */
|
||||||
|
const trapped = new BooleanHolder(false);
|
||||||
/**
|
/**
|
||||||
* Contains opposing Pokemon (Enemy/Player Pokemon) depending on perspective
|
* Contains opposing Pokemon (Enemy/Player Pokemon) depending on perspective
|
||||||
* Afterwards, it filters out Pokemon that have been switched out of the field so trapped abilities/moves do not trigger
|
* Afterwards, it filters out Pokemon that have been switched out of the field so trapped abilities/moves do not trigger
|
||||||
@ -2265,14 +2257,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const opposingField = opposingFieldUnfiltered.filter(enemyPkm => enemyPkm.switchOutStatus === false);
|
const opposingField = opposingFieldUnfiltered.filter(enemyPkm => enemyPkm.switchOutStatus === false);
|
||||||
|
|
||||||
for (const opponent of opposingField) {
|
for (const opponent of opposingField) {
|
||||||
applyCheckTrappedAbAttrs("CheckTrappedAbAttr", opponent, trappedByAbility, this, trappedAbMessages, simulated);
|
applyAbAttrs("CheckTrappedAbAttr", { pokemon: opponent, trapped, opponent: this, simulated }, trappedAbMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||||
return (
|
return (
|
||||||
trappedByAbility.value ||
|
trapped.value || !!this.getTag(TrappedTag) || !!globalScene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, side)
|
||||||
!!this.getTag(TrappedTag) ||
|
|
||||||
!!globalScene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, side)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2287,7 +2277,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const moveTypeHolder = new NumberHolder(move.type);
|
const moveTypeHolder = new NumberHolder(move.type);
|
||||||
|
|
||||||
applyMoveAttrs("VariableMoveTypeAttr", this, null, move, moveTypeHolder);
|
applyMoveAttrs("VariableMoveTypeAttr", this, null, move, moveTypeHolder);
|
||||||
applyPreAttackAbAttrs("MoveTypeChangeAbAttr", this, null, move, simulated, moveTypeHolder);
|
|
||||||
|
const power = new NumberHolder(move.power);
|
||||||
|
applyAbAttrs("MoveTypeChangeAbAttr", {
|
||||||
|
pokemon: this,
|
||||||
|
move,
|
||||||
|
simulated,
|
||||||
|
moveType: moveTypeHolder,
|
||||||
|
power,
|
||||||
|
opponent: this,
|
||||||
|
});
|
||||||
|
|
||||||
// If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type,
|
// If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type,
|
||||||
// then bypass the check for ion deluge and electrify
|
// then bypass the check for ion deluge and electrify
|
||||||
@ -2351,17 +2350,31 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cancelledHolder = cancelled ?? new BooleanHolder(false);
|
const cancelledHolder = cancelled ?? new BooleanHolder(false);
|
||||||
|
// TypeMultiplierAbAttrParams is shared amongst the type of AbAttrs we will be invoking
|
||||||
|
const commonAbAttrParams: TypeMultiplierAbAttrParams = {
|
||||||
|
pokemon: this,
|
||||||
|
opponent: source,
|
||||||
|
move,
|
||||||
|
cancelled: cancelledHolder,
|
||||||
|
simulated,
|
||||||
|
typeMultiplier,
|
||||||
|
};
|
||||||
if (!ignoreAbility) {
|
if (!ignoreAbility) {
|
||||||
applyPreDefendAbAttrs("TypeImmunityAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier);
|
applyAbAttrs("TypeImmunityAbAttr", commonAbAttrParams);
|
||||||
|
|
||||||
if (!cancelledHolder.value) {
|
if (!cancelledHolder.value) {
|
||||||
applyPreDefendAbAttrs("MoveImmunityAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier);
|
applyAbAttrs("MoveImmunityAbAttr", commonAbAttrParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cancelledHolder.value) {
|
if (!cancelledHolder.value) {
|
||||||
const defendingSidePlayField = this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
const defendingSidePlayField = this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
||||||
defendingSidePlayField.forEach(p =>
|
defendingSidePlayField.forEach(p =>
|
||||||
applyPreDefendAbAttrs("FieldPriorityMoveImmunityAbAttr", p, source, move, cancelledHolder),
|
applyAbAttrs("FieldPriorityMoveImmunityAbAttr", {
|
||||||
|
pokemon: p,
|
||||||
|
opponent: source,
|
||||||
|
move,
|
||||||
|
cancelled: cancelledHolder,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2376,7 +2389,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
// Apply Tera Shell's effect to attacks after all immunities are accounted for
|
// Apply Tera Shell's effect to attacks after all immunities are accounted for
|
||||||
if (!ignoreAbility && move.category !== MoveCategory.STATUS) {
|
if (!ignoreAbility && move.category !== MoveCategory.STATUS) {
|
||||||
applyPreDefendAbAttrs("FullHpResistTypeAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier);
|
applyAbAttrs("FullHpResistTypeAbAttr", commonAbAttrParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (move.category === MoveCategory.STATUS && move.hitsSubstitute(source, this)) {
|
if (move.category === MoveCategory.STATUS && move.hitsSubstitute(source, this)) {
|
||||||
@ -2420,16 +2433,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let multiplier = types
|
let multiplier = types
|
||||||
.map(defType => {
|
.map(defenderType => {
|
||||||
const multiplier = new NumberHolder(getTypeDamageMultiplier(moveType, defType));
|
const multiplier = new NumberHolder(getTypeDamageMultiplier(moveType, defenderType));
|
||||||
applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multiplier);
|
applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multiplier);
|
||||||
if (move) {
|
if (move) {
|
||||||
applyMoveAttrs("VariableMoveTypeChartAttr", null, this, move, multiplier, defType);
|
applyMoveAttrs("VariableMoveTypeChartAttr", null, this, move, multiplier, defenderType);
|
||||||
}
|
}
|
||||||
if (source) {
|
if (source) {
|
||||||
const ignoreImmunity = new BooleanHolder(false);
|
const ignoreImmunity = new BooleanHolder(false);
|
||||||
if (source.isActive(true) && source.hasAbilityWithAttr("IgnoreTypeImmunityAbAttr")) {
|
if (source.isActive(true) && source.hasAbilityWithAttr("IgnoreTypeImmunityAbAttr")) {
|
||||||
applyAbAttrs("IgnoreTypeImmunityAbAttr", source, ignoreImmunity, simulated, moveType, defType);
|
applyAbAttrs("IgnoreTypeImmunityAbAttr", {
|
||||||
|
pokemon: source,
|
||||||
|
cancelled: ignoreImmunity,
|
||||||
|
simulated,
|
||||||
|
moveType,
|
||||||
|
defenderType,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (ignoreImmunity.value) {
|
if (ignoreImmunity.value) {
|
||||||
if (multiplier.value === 0) {
|
if (multiplier.value === 0) {
|
||||||
@ -2438,7 +2457,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const exposedTags = this.findTags(tag => tag instanceof ExposedTag) as ExposedTag[];
|
const exposedTags = this.findTags(tag => tag instanceof ExposedTag) as ExposedTag[];
|
||||||
if (exposedTags.some(t => t.ignoreImmunity(defType, moveType))) {
|
if (exposedTags.some(t => t.ignoreImmunity(defenderType, moveType))) {
|
||||||
if (multiplier.value === 0) {
|
if (multiplier.value === 0) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -3383,7 +3402,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!ignoreOppAbility) {
|
if (!ignoreOppAbility) {
|
||||||
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", opponent, null, simulated, stat, ignoreStatStage);
|
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", {
|
||||||
|
pokemon: opponent,
|
||||||
|
ignored: ignoreStatStage,
|
||||||
|
stat,
|
||||||
|
simulated,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (move) {
|
if (move) {
|
||||||
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, opponent, move, ignoreStatStage);
|
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, opponent, move, ignoreStatStage);
|
||||||
@ -3422,8 +3446,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const ignoreAccStatStage = new BooleanHolder(false);
|
const ignoreAccStatStage = new BooleanHolder(false);
|
||||||
const ignoreEvaStatStage = new BooleanHolder(false);
|
const ignoreEvaStatStage = new BooleanHolder(false);
|
||||||
|
|
||||||
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", target, null, false, Stat.ACC, ignoreAccStatStage);
|
// TODO: consider refactoring this method to accept `simulated` and then pass simulated to these applyAbAttrs
|
||||||
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", this, null, false, Stat.EVA, ignoreEvaStatStage);
|
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", { pokemon: target, stat: Stat.ACC, ignored: ignoreAccStatStage });
|
||||||
|
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", { pokemon: this, stat: Stat.EVA, ignored: ignoreEvaStatStage });
|
||||||
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage);
|
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage);
|
||||||
|
|
||||||
globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage);
|
globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage);
|
||||||
@ -3443,33 +3468,40 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
: 3 / (3 + Math.min(targetEvaStage.value - userAccStage.value, 6));
|
: 3 / (3 + Math.min(targetEvaStage.value - userAccStage.value, 6));
|
||||||
}
|
}
|
||||||
|
|
||||||
applyStatMultiplierAbAttrs("StatMultiplierAbAttr", this, Stat.ACC, accuracyMultiplier, false, sourceMove);
|
applyAbAttrs("StatMultiplierAbAttr", {
|
||||||
|
pokemon: this,
|
||||||
|
stat: Stat.ACC,
|
||||||
|
statVal: accuracyMultiplier,
|
||||||
|
move: sourceMove,
|
||||||
|
});
|
||||||
|
|
||||||
const evasionMultiplier = new NumberHolder(1);
|
const evasionMultiplier = new NumberHolder(1);
|
||||||
applyStatMultiplierAbAttrs("StatMultiplierAbAttr", target, Stat.EVA, evasionMultiplier);
|
applyAbAttrs("StatMultiplierAbAttr", {
|
||||||
|
pokemon: target,
|
||||||
|
stat: Stat.EVA,
|
||||||
|
statVal: evasionMultiplier,
|
||||||
|
move: sourceMove,
|
||||||
|
});
|
||||||
|
|
||||||
const ally = this.getAlly();
|
const ally = this.getAlly();
|
||||||
if (!isNullOrUndefined(ally)) {
|
if (!isNullOrUndefined(ally)) {
|
||||||
const ignore =
|
const ignore =
|
||||||
this.hasAbilityWithAttr("MoveAbilityBypassAbAttr") || sourceMove.hasFlag(MoveFlags.IGNORE_ABILITIES);
|
this.hasAbilityWithAttr("MoveAbilityBypassAbAttr") || sourceMove.hasFlag(MoveFlags.IGNORE_ABILITIES);
|
||||||
applyAllyStatMultiplierAbAttrs(
|
applyAbAttrs("AllyStatMultiplierAbAttr", {
|
||||||
"AllyStatMultiplierAbAttr",
|
pokemon: ally,
|
||||||
ally,
|
stat: Stat.ACC,
|
||||||
Stat.ACC,
|
statVal: accuracyMultiplier,
|
||||||
accuracyMultiplier,
|
ignoreAbility: ignore,
|
||||||
false,
|
move: sourceMove,
|
||||||
this,
|
});
|
||||||
ignore,
|
|
||||||
);
|
applyAbAttrs("AllyStatMultiplierAbAttr", {
|
||||||
applyAllyStatMultiplierAbAttrs(
|
pokemon: ally,
|
||||||
"AllyStatMultiplierAbAttr",
|
stat: Stat.EVA,
|
||||||
ally,
|
statVal: evasionMultiplier,
|
||||||
Stat.EVA,
|
ignoreAbility: ignore,
|
||||||
evasionMultiplier,
|
move: sourceMove,
|
||||||
false,
|
});
|
||||||
this,
|
|
||||||
ignore,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return accuracyMultiplier.value / evasionMultiplier.value;
|
return accuracyMultiplier.value / evasionMultiplier.value;
|
||||||
@ -3584,7 +3616,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
applyMoveAttrs("CombinedPledgeStabBoostAttr", source, this, move, stabMultiplier);
|
applyMoveAttrs("CombinedPledgeStabBoostAttr", source, this, move, stabMultiplier);
|
||||||
|
|
||||||
if (!ignoreSourceAbility) {
|
if (!ignoreSourceAbility) {
|
||||||
applyAbAttrs("StabBoostAbAttr", source, null, simulated, stabMultiplier);
|
applyAbAttrs("StabBoostAbAttr", { pokemon: source, simulated, multiplier: stabMultiplier });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source.isTerastallized && sourceTeraType === moveType && moveType !== PokemonType.STELLAR) {
|
if (source.isTerastallized && sourceTeraType === moveType && moveType !== PokemonType.STELLAR) {
|
||||||
@ -3731,16 +3763,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
null,
|
null,
|
||||||
multiStrikeEnhancementMultiplier,
|
multiStrikeEnhancementMultiplier,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!ignoreSourceAbility) {
|
if (!ignoreSourceAbility) {
|
||||||
applyPreAttackAbAttrs(
|
applyAbAttrs("AddSecondStrikeAbAttr", {
|
||||||
"AddSecondStrikeAbAttr",
|
pokemon: source,
|
||||||
source,
|
|
||||||
this,
|
|
||||||
move,
|
move,
|
||||||
simulated,
|
simulated,
|
||||||
null,
|
multiplier: multiStrikeEnhancementMultiplier,
|
||||||
multiStrikeEnhancementMultiplier,
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Doubles damage if this Pokemon's last move was Glaive Rush */
|
/** Doubles damage if this Pokemon's last move was Glaive Rush */
|
||||||
@ -3751,7 +3781,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
/** The damage multiplier when the given move critically hits */
|
/** The damage multiplier when the given move critically hits */
|
||||||
const criticalMultiplier = new NumberHolder(isCritical ? 1.5 : 1);
|
const criticalMultiplier = new NumberHolder(isCritical ? 1.5 : 1);
|
||||||
applyAbAttrs("MultCritAbAttr", source, null, simulated, criticalMultiplier);
|
applyAbAttrs("MultCritAbAttr", { pokemon: source, simulated, critMult: criticalMultiplier });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A multiplier for random damage spread in the range [0.85, 1]
|
* A multiplier for random damage spread in the range [0.85, 1]
|
||||||
@ -3772,7 +3802,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
) {
|
) {
|
||||||
const burnDamageReductionCancelled = new BooleanHolder(false);
|
const burnDamageReductionCancelled = new BooleanHolder(false);
|
||||||
if (!ignoreSourceAbility) {
|
if (!ignoreSourceAbility) {
|
||||||
applyAbAttrs("BypassBurnDamageReductionAbAttr", source, burnDamageReductionCancelled, simulated);
|
applyAbAttrs("BypassBurnDamageReductionAbAttr", {
|
||||||
|
pokemon: source,
|
||||||
|
cancelled: burnDamageReductionCancelled,
|
||||||
|
simulated,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (!burnDamageReductionCancelled.value) {
|
if (!burnDamageReductionCancelled.value) {
|
||||||
burnMultiplier = 0.5;
|
burnMultiplier = 0.5;
|
||||||
@ -3836,7 +3870,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
/** Doubles damage if the attacker has Tinted Lens and is using a resisted move */
|
/** Doubles damage if the attacker has Tinted Lens and is using a resisted move */
|
||||||
if (!ignoreSourceAbility) {
|
if (!ignoreSourceAbility) {
|
||||||
applyPreAttackAbAttrs("DamageBoostAbAttr", source, this, move, simulated, damage);
|
applyAbAttrs("DamageBoostAbAttr", {
|
||||||
|
pokemon: source,
|
||||||
|
opponent: this,
|
||||||
|
move,
|
||||||
|
simulated,
|
||||||
|
damage,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Apply the enemy's Damage and Resistance tokens */
|
/** Apply the enemy's Damage and Resistance tokens */
|
||||||
@ -3847,14 +3887,25 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
globalScene.applyModifiers(EnemyDamageReducerModifier, false, damage);
|
globalScene.applyModifiers(EnemyDamageReducerModifier, false, damage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const abAttrParams: PreAttackModifyDamageAbAttrParams = {
|
||||||
|
pokemon: this,
|
||||||
|
opponent: source,
|
||||||
|
move,
|
||||||
|
simulated,
|
||||||
|
damage,
|
||||||
|
};
|
||||||
/** Apply this Pokemon's post-calc defensive modifiers (e.g. Fur Coat) */
|
/** Apply this Pokemon's post-calc defensive modifiers (e.g. Fur Coat) */
|
||||||
if (!ignoreAbility) {
|
if (!ignoreAbility) {
|
||||||
applyPreDefendAbAttrs("ReceivedMoveDamageMultiplierAbAttr", this, source, move, cancelled, simulated, damage);
|
applyAbAttrs("ReceivedMoveDamageMultiplierAbAttr", abAttrParams);
|
||||||
|
|
||||||
const ally = this.getAlly();
|
const ally = this.getAlly();
|
||||||
/** Additionally apply friend guard damage reduction if ally has it. */
|
/** Additionally apply friend guard damage reduction if ally has it. */
|
||||||
if (globalScene.currentBattle.double && !isNullOrUndefined(ally) && ally.isActive(true)) {
|
if (globalScene.currentBattle.double && !isNullOrUndefined(ally) && ally.isActive(true)) {
|
||||||
applyPreDefendAbAttrs("AlliedFieldDamageReductionAbAttr", ally, source, move, cancelled, simulated, damage);
|
applyAbAttrs("AlliedFieldDamageReductionAbAttr", {
|
||||||
|
...abAttrParams,
|
||||||
|
// Same parameters as before, except we are applying the ally's ability
|
||||||
|
pokemon: ally,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3862,7 +3913,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
applyMoveAttrs("ModifiedDamageAttr", source, this, move, damage);
|
applyMoveAttrs("ModifiedDamageAttr", source, this, move, damage);
|
||||||
|
|
||||||
if (this.isFullHp() && !ignoreAbility) {
|
if (this.isFullHp() && !ignoreAbility) {
|
||||||
applyPreDefendAbAttrs("PreDefendFullHpEndureAbAttr", this, source, move, cancelled, false, damage);
|
applyAbAttrs("PreDefendFullHpEndureAbAttr", abAttrParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
// debug message for when damage is applied (i.e. not simulated)
|
// debug message for when damage is applied (i.e. not simulated)
|
||||||
@ -3900,7 +3951,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
const alwaysCrit = new BooleanHolder(false);
|
const alwaysCrit = new BooleanHolder(false);
|
||||||
applyMoveAttrs("CritOnlyAttr", source, this, move, alwaysCrit);
|
applyMoveAttrs("CritOnlyAttr", source, this, move, alwaysCrit);
|
||||||
applyAbAttrs("ConditionalCritAbAttr", source, null, false, alwaysCrit, this, move);
|
applyAbAttrs("ConditionalCritAbAttr", { pokemon: source, isCritical: alwaysCrit, target: this, move });
|
||||||
const alwaysCritTag = !!source.getTag(BattlerTagType.ALWAYS_CRIT);
|
const alwaysCritTag = !!source.getTag(BattlerTagType.ALWAYS_CRIT);
|
||||||
const critChance = [24, 8, 2, 1][Phaser.Math.Clamp(this.getCritStage(source, move), 0, 3)];
|
const critChance = [24, 8, 2, 1][Phaser.Math.Clamp(this.getCritStage(source, move), 0, 3)];
|
||||||
|
|
||||||
@ -3911,7 +3962,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
// apply crit block effects from lucky chant & co., overriding previous effects
|
// apply crit block effects from lucky chant & co., overriding previous effects
|
||||||
const blockCrit = new BooleanHolder(false);
|
const blockCrit = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockCritAbAttr", this, null, false, blockCrit);
|
applyAbAttrs("BlockCritAbAttr", { pokemon: this, blockCrit });
|
||||||
const blockCritTag = globalScene.arena.getTagOnSide(
|
const blockCritTag = globalScene.arena.getTagOnSide(
|
||||||
NoCritTag,
|
NoCritTag,
|
||||||
this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY,
|
this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY,
|
||||||
@ -4023,7 +4074,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* Multi-hits are handled in move-effect-phase.ts for PostDamageAbAttr
|
* Multi-hits are handled in move-effect-phase.ts for PostDamageAbAttr
|
||||||
*/
|
*/
|
||||||
if (!source || source.turnData.hitCount <= 1) {
|
if (!source || source.turnData.hitCount <= 1) {
|
||||||
applyPostDamageAbAttrs("PostDamageAbAttr", this, damage, this.hasPassive(), false, [], source);
|
applyAbAttrs("PostDamageAbAttr", { pokemon: this, damage, source });
|
||||||
}
|
}
|
||||||
return damage;
|
return damage;
|
||||||
}
|
}
|
||||||
@ -4071,11 +4122,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const stubTag = new BattlerTag(tagType, 0, 0);
|
const stubTag = new BattlerTag(tagType, 0, 0);
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyPreApplyBattlerTagAbAttrs("BattlerTagImmunityAbAttr", this, stubTag, cancelled, true);
|
applyAbAttrs("BattlerTagImmunityAbAttr", { pokemon: this, tag: stubTag, cancelled, simulated: true });
|
||||||
|
|
||||||
const userField = this.getAlliedField();
|
const userField = this.getAlliedField();
|
||||||
userField.forEach(pokemon =>
|
userField.forEach(pokemon =>
|
||||||
applyPreApplyBattlerTagAbAttrs("UserFieldBattlerTagImmunityAbAttr", pokemon, stubTag, cancelled, true, this),
|
applyAbAttrs("UserFieldBattlerTagImmunityAbAttr", {
|
||||||
|
pokemon,
|
||||||
|
tag: stubTag,
|
||||||
|
cancelled,
|
||||||
|
simulated: true,
|
||||||
|
target: this,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return !cancelled.value;
|
return !cancelled.value;
|
||||||
@ -4091,13 +4148,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
const newTag = getBattlerTag(tagType, turnCount, sourceMove!, sourceId!); // TODO: are the bangs correct?
|
const newTag = getBattlerTag(tagType, turnCount, sourceMove!, sourceId!); // TODO: are the bangs correct?
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyPreApplyBattlerTagAbAttrs("BattlerTagImmunityAbAttr", this, newTag, cancelled);
|
applyAbAttrs("BattlerTagImmunityAbAttr", { pokemon: this, tag: newTag, cancelled });
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const pokemon of this.getAlliedField()) {
|
for (const pokemon of this.getAlliedField()) {
|
||||||
applyPreApplyBattlerTagAbAttrs("UserFieldBattlerTagImmunityAbAttr", pokemon, newTag, cancelled, false, this);
|
applyAbAttrs("UserFieldBattlerTagImmunityAbAttr", { pokemon, tag: newTag, cancelled, target: this });
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -4125,7 +4182,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
getTag<T extends BattlerTag>(tagType: Constructor<T>): T | undefined;
|
getTag<T extends BattlerTag>(tagType: Constructor<T>): T | undefined;
|
||||||
|
|
||||||
getTag(tagType: BattlerTagType | Constructor<BattlerTag>): BattlerTag | undefined {
|
getTag(tagType: BattlerTagType | Constructor<BattlerTag>): BattlerTag | undefined {
|
||||||
return tagType instanceof Function
|
return typeof tagType === "function"
|
||||||
? this.summonData.tags.find(t => t instanceof tagType)
|
? this.summonData.tags.find(t => t instanceof tagType)
|
||||||
: this.summonData.tags.find(t => t.tagType === tagType);
|
: this.summonData.tags.find(t => t.tagType === tagType);
|
||||||
}
|
}
|
||||||
@ -4620,7 +4677,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* @param ignoreField Whether any field effects (weather, terrain, etc.) should be considered
|
* @param ignoreField Whether any field effects (weather, terrain, etc.) should be considered
|
||||||
*/
|
*/
|
||||||
canSetStatus(
|
canSetStatus(
|
||||||
effect: StatusEffect | undefined,
|
effect: StatusEffect,
|
||||||
quiet = false,
|
quiet = false,
|
||||||
overrideStatus = false,
|
overrideStatus = false,
|
||||||
sourcePokemon: Pokemon | null = null,
|
sourcePokemon: Pokemon | null = null,
|
||||||
@ -4651,8 +4708,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
// Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity
|
// Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity
|
||||||
const cancelImmunity = new BooleanHolder(false);
|
const cancelImmunity = new BooleanHolder(false);
|
||||||
|
// TODO: Determine if we need to pass `quiet` as the value for simulated in this call
|
||||||
if (sourcePokemon) {
|
if (sourcePokemon) {
|
||||||
applyAbAttrs("IgnoreTypeStatusEffectImmunityAbAttr", sourcePokemon, cancelImmunity, false, effect, defType);
|
applyAbAttrs("IgnoreTypeStatusEffectImmunityAbAttr", {
|
||||||
|
pokemon: sourcePokemon,
|
||||||
|
cancelled: cancelImmunity,
|
||||||
|
statusEffect: effect,
|
||||||
|
defenderType: defType,
|
||||||
|
});
|
||||||
if (cancelImmunity.value) {
|
if (cancelImmunity.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -4701,21 +4764,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyPreSetStatusAbAttrs("StatusEffectImmunityAbAttr", this, effect, cancelled, quiet);
|
applyAbAttrs("StatusEffectImmunityAbAttr", { pokemon: this, effect, cancelled, simulated: quiet });
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const pokemon of this.getAlliedField()) {
|
for (const pokemon of this.getAlliedField()) {
|
||||||
applyPreSetStatusAbAttrs(
|
applyAbAttrs("UserFieldStatusEffectImmunityAbAttr", {
|
||||||
"UserFieldStatusEffectImmunityAbAttr",
|
|
||||||
pokemon,
|
pokemon,
|
||||||
effect,
|
effect,
|
||||||
cancelled,
|
cancelled,
|
||||||
quiet,
|
simulated: quiet,
|
||||||
this,
|
target: this,
|
||||||
sourcePokemon,
|
source: sourcePokemon,
|
||||||
);
|
});
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -4746,6 +4808,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
overrideStatus?: boolean,
|
overrideStatus?: boolean,
|
||||||
quiet = true,
|
quiet = true,
|
||||||
): boolean {
|
): boolean {
|
||||||
|
if (!effect) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!this.canSetStatus(effect, quiet, overrideStatus, sourcePokemon)) {
|
if (!this.canSetStatus(effect, quiet, overrideStatus, sourcePokemon)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -4804,7 +4869,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sleepTurnsRemaining = sleepTurnsRemaining!; // tell TS compiler it's defined
|
sleepTurnsRemaining = sleepTurnsRemaining!; // tell TS compiler it's defined
|
||||||
effect = effect!; // If `effect` is undefined then `trySetStatus()` will have already returned early via the `canSetStatus()` call
|
|
||||||
this.status = new Status(effect, 0, sleepTurnsRemaining?.value);
|
this.status = new Status(effect, 0, sleepTurnsRemaining?.value);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -4865,7 +4929,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
if (globalScene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) {
|
if (globalScene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) {
|
||||||
const bypassed = new BooleanHolder(false);
|
const bypassed = new BooleanHolder(false);
|
||||||
if (attacker) {
|
if (attacker) {
|
||||||
applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
|
applyAbAttrs("InfiltratorAbAttr", { pokemon: attacker, bypassed });
|
||||||
}
|
}
|
||||||
return !bypassed.value;
|
return !bypassed.value;
|
||||||
}
|
}
|
||||||
@ -5411,7 +5475,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
this.hideInfo();
|
this.hideInfo();
|
||||||
}
|
}
|
||||||
// Trigger abilities that activate upon leaving the field
|
// Trigger abilities that activate upon leaving the field
|
||||||
applyPreLeaveFieldAbAttrs("PreLeaveFieldAbAttr", this);
|
applyAbAttrs("PreLeaveFieldAbAttr", { pokemon: this });
|
||||||
this.setSwitchOutStatus(true);
|
this.setSwitchOutStatus(true);
|
||||||
globalScene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true);
|
globalScene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true);
|
||||||
globalScene.field.remove(this, destroy);
|
globalScene.field.remove(this, destroy);
|
||||||
@ -5471,7 +5535,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
globalScene.removeModifier(heldItem, this.isEnemy());
|
globalScene.removeModifier(heldItem, this.isEnemy());
|
||||||
}
|
}
|
||||||
if (forBattle) {
|
if (forBattle) {
|
||||||
applyPostItemLostAbAttrs("PostItemLostAbAttr", this, false);
|
applyAbAttrs("PostItemLostAbAttr", { pokemon: this });
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -42,7 +42,7 @@ import type {
|
|||||||
import { getModifierType } from "#app/utils/modifier-utils";
|
import { getModifierType } from "#app/utils/modifier-utils";
|
||||||
import { Color, ShadowColor } from "#enums/color";
|
import { Color, ShadowColor } from "#enums/color";
|
||||||
import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters";
|
import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters";
|
||||||
import { applyAbAttrs, applyPostItemLostAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type { ModifierInstanceMap, ModifierString } from "#app/@types/modifier-types";
|
import type { ModifierInstanceMap, ModifierString } from "#app/@types/modifier-types";
|
||||||
|
|
||||||
@ -751,7 +751,7 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getPokemon(): Pokemon | undefined {
|
getPokemon(): Pokemon | undefined {
|
||||||
return this.pokemonId ? (globalScene.getPokemonById(this.pokemonId) ?? undefined) : undefined;
|
return globalScene.getPokemonById(this.pokemonId) ?? undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
getScoreMultiplier(): number {
|
getScoreMultiplier(): number {
|
||||||
@ -1879,7 +1879,7 @@ export class BerryModifier extends PokemonHeldItemModifier {
|
|||||||
|
|
||||||
// munch the berry and trigger unburden-like effects
|
// munch the berry and trigger unburden-like effects
|
||||||
getBerryEffectFunc(this.berryType)(pokemon);
|
getBerryEffectFunc(this.berryType)(pokemon);
|
||||||
applyPostItemLostAbAttrs("PostItemLostAbAttr", pokemon, false);
|
applyAbAttrs("PostItemLostAbAttr", { pokemon });
|
||||||
|
|
||||||
// Update berry eaten trackers for Belch, Harvest, Cud Chew, etc.
|
// Update berry eaten trackers for Belch, Harvest, Cud Chew, etc.
|
||||||
// Don't recover it if we proc berry pouch (no item duplication)
|
// Don't recover it if we proc berry pouch (no item duplication)
|
||||||
@ -1967,7 +1967,7 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier {
|
|||||||
// Reapply Commander on the Pokemon's side of the field, if applicable
|
// Reapply Commander on the Pokemon's side of the field, if applicable
|
||||||
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
||||||
for (const p of field) {
|
for (const p of field) {
|
||||||
applyAbAttrs("CommanderAbAttr", p, null, false);
|
applyAbAttrs("CommanderAbAttr", { pokemon: p });
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { applyAbAttrs, applyPreLeaveFieldAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
|
import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
|
||||||
@ -25,10 +25,10 @@ export class AttemptRunPhase extends PokemonPhase {
|
|||||||
|
|
||||||
this.attemptRunAway(playerField, enemyField, escapeChance);
|
this.attemptRunAway(playerField, enemyField, escapeChance);
|
||||||
|
|
||||||
applyAbAttrs("RunSuccessAbAttr", playerPokemon, null, false, escapeChance);
|
applyAbAttrs("RunSuccessAbAttr", { pokemon: playerPokemon, chance: escapeChance });
|
||||||
|
|
||||||
if (playerPokemon.randBattleSeedInt(100) < escapeChance.value && !this.forceFailEscape) {
|
if (playerPokemon.randBattleSeedInt(100) < escapeChance.value && !this.forceFailEscape) {
|
||||||
enemyField.forEach(enemyPokemon => applyPreLeaveFieldAbAttrs("PreLeaveFieldAbAttr", enemyPokemon));
|
enemyField.forEach(enemyPokemon => applyAbAttrs("PreLeaveFieldAbAttr", { pokemon: enemyPokemon }));
|
||||||
|
|
||||||
globalScene.playSound("se/flee");
|
globalScene.playSound("se/flee");
|
||||||
globalScene.phaseManager.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
|
globalScene.phaseManager.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { applyPostBattleAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier";
|
import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||||
import { BattlePhase } from "./battle-phase";
|
import { BattlePhase } from "./battle-phase";
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ export class BattleEndPhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const pokemon of globalScene.getPokemonAllowedInBattle()) {
|
for (const pokemon of globalScene.getPokemonAllowedInBattle()) {
|
||||||
applyPostBattleAbAttrs("PostBattleAbAttr", pokemon, false, this.isVictory);
|
applyAbAttrs("PostBattleAbAttr", { pokemon, victory: this.isVictory });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (globalScene.currentBattle.moneyScattered) {
|
if (globalScene.currentBattle.moneyScattered) {
|
||||||
|
@ -20,7 +20,7 @@ export class BerryPhase extends FieldPhase {
|
|||||||
|
|
||||||
this.executeForAll(pokemon => {
|
this.executeForAll(pokemon => {
|
||||||
this.eatBerries(pokemon);
|
this.eatBerries(pokemon);
|
||||||
applyAbAttrs("RepeatBerryNextTurnAbAttr", pokemon, null);
|
applyAbAttrs("CudChewConsumeBerryAbAttr", { pokemon });
|
||||||
});
|
});
|
||||||
|
|
||||||
this.end();
|
this.end();
|
||||||
@ -42,7 +42,7 @@ export class BerryPhase extends FieldPhase {
|
|||||||
|
|
||||||
// TODO: If both opponents on field have unnerve, which one displays its message?
|
// TODO: If both opponents on field have unnerve, which one displays its message?
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
pokemon.getOpponents().forEach(opp => applyAbAttrs("PreventBerryUseAbAttr", opp, cancelled));
|
pokemon.getOpponents().forEach(opp => applyAbAttrs("PreventBerryUseAbAttr", { pokemon: opp, cancelled }));
|
||||||
if (cancelled.value) {
|
if (cancelled.value) {
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
i18next.t("abilityTriggers:preventBerryUse", {
|
i18next.t("abilityTriggers:preventBerryUse", {
|
||||||
@ -70,6 +70,6 @@ export class BerryPhase extends FieldPhase {
|
|||||||
globalScene.updateModifiers(pokemon.isPlayer());
|
globalScene.updateModifiers(pokemon.isPlayer());
|
||||||
|
|
||||||
// AbilityId.CHEEK_POUCH only works once per round of nom noms
|
// AbilityId.CHEEK_POUCH only works once per round of nom noms
|
||||||
applyAbAttrs("HealFromBerryUseAbAttr", pokemon, new BooleanHolder(false));
|
applyAbAttrs("HealFromBerryUseAbAttr", { pokemon });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { BattlerIndex } from "#enums/battler-index";
|
|||||||
import { BattleType } from "#enums/battle-type";
|
import { BattleType } from "#enums/battle-type";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
||||||
import { applyAbAttrs, applyPreSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { initEncounterAnims, loadEncounterAnimAssets } from "#app/data/battle-anims";
|
import { initEncounterAnims, loadEncounterAnimAssets } from "#app/data/battle-anims";
|
||||||
import { getCharVariantFromDialogue } from "#app/data/dialogue";
|
import { getCharVariantFromDialogue } from "#app/data/dialogue";
|
||||||
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
@ -128,7 +128,7 @@ export class EncounterPhase extends BattlePhase {
|
|||||||
.slice(0, !battle.double ? 1 : 2)
|
.slice(0, !battle.double ? 1 : 2)
|
||||||
.reverse()
|
.reverse()
|
||||||
.forEach(playerPokemon => {
|
.forEach(playerPokemon => {
|
||||||
applyAbAttrs("SyncEncounterNatureAbAttr", playerPokemon, null, false, battle.enemyParty[e]);
|
applyAbAttrs("SyncEncounterNatureAbAttr", { pokemon: playerPokemon, target: battle.enemyParty[e] });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -249,7 +249,7 @@ export class EncounterPhase extends BattlePhase {
|
|||||||
if (e < (battle.double ? 2 : 1)) {
|
if (e < (battle.double ? 2 : 1)) {
|
||||||
if (battle.battleType === BattleType.WILD) {
|
if (battle.battleType === BattleType.WILD) {
|
||||||
for (const pokemon of globalScene.getField()) {
|
for (const pokemon of globalScene.getField()) {
|
||||||
applyPreSummonAbAttrs("PreSummonAbAttr", pokemon, []);
|
applyAbAttrs("PreSummonAbAttr", { pokemon });
|
||||||
}
|
}
|
||||||
globalScene.field.add(enemyPokemon);
|
globalScene.field.add(enemyPokemon);
|
||||||
battle.seenEnemyPartyMemberIds.add(enemyPokemon.id);
|
battle.seenEnemyPartyMemberIds.add(enemyPokemon.id);
|
||||||
|
@ -23,6 +23,8 @@ export class EvolutionPhase extends Phase {
|
|||||||
protected pokemon: PlayerPokemon;
|
protected pokemon: PlayerPokemon;
|
||||||
protected lastLevel: number;
|
protected lastLevel: number;
|
||||||
|
|
||||||
|
protected evoChain: Phaser.Tweens.TweenChain | null = null;
|
||||||
|
|
||||||
private preEvolvedPokemonName: string;
|
private preEvolvedPokemonName: string;
|
||||||
|
|
||||||
private evolution: SpeciesFormEvolution | null;
|
private evolution: SpeciesFormEvolution | null;
|
||||||
@ -40,13 +42,23 @@ export class EvolutionPhase extends Phase {
|
|||||||
protected pokemonEvoSprite: Phaser.GameObjects.Sprite;
|
protected pokemonEvoSprite: Phaser.GameObjects.Sprite;
|
||||||
protected pokemonEvoTintSprite: Phaser.GameObjects.Sprite;
|
protected pokemonEvoTintSprite: Phaser.GameObjects.Sprite;
|
||||||
|
|
||||||
constructor(pokemon: PlayerPokemon, evolution: SpeciesFormEvolution | null, lastLevel: number) {
|
/** Whether the evolution can be cancelled by the player */
|
||||||
|
protected canCancel: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param pokemon - The Pokemon that is evolving
|
||||||
|
* @param evolution - The form being evolved into
|
||||||
|
* @param lastLevel - The level at which the Pokemon is evolving
|
||||||
|
* @param canCancel - Whether the evolution can be cancelled by the player
|
||||||
|
*/
|
||||||
|
constructor(pokemon: PlayerPokemon, evolution: SpeciesFormEvolution | null, lastLevel: number, canCancel = true) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.pokemon = pokemon;
|
this.pokemon = pokemon;
|
||||||
this.evolution = evolution;
|
this.evolution = evolution;
|
||||||
this.lastLevel = lastLevel;
|
this.lastLevel = lastLevel;
|
||||||
this.fusionSpeciesEvolved = evolution instanceof FusionSpeciesFormEvolution;
|
this.fusionSpeciesEvolved = evolution instanceof FusionSpeciesFormEvolution;
|
||||||
|
this.canCancel = canCancel;
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(): boolean {
|
validate(): boolean {
|
||||||
@ -57,198 +69,227 @@ export class EvolutionPhase extends Phase {
|
|||||||
return globalScene.ui.setModeForceTransition(UiMode.EVOLUTION_SCENE);
|
return globalScene.ui.setModeForceTransition(UiMode.EVOLUTION_SCENE);
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
/**
|
||||||
super.start();
|
* Set up the following evolution assets
|
||||||
|
* - {@linkcode evolutionContainer}
|
||||||
|
* - {@linkcode evolutionBaseBg}
|
||||||
|
* - {@linkcode evolutionBg}
|
||||||
|
* - {@linkcode evolutionBgOverlay}
|
||||||
|
* - {@linkcode evolutionOverlay}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private setupEvolutionAssets(): void {
|
||||||
|
this.evolutionHandler = globalScene.ui.getHandler() as EvolutionSceneHandler;
|
||||||
|
this.evolutionContainer = this.evolutionHandler.evolutionContainer;
|
||||||
|
this.evolutionBaseBg = globalScene.add.image(0, 0, "default_bg").setOrigin(0);
|
||||||
|
|
||||||
this.setMode().then(() => {
|
this.evolutionBg = globalScene.add
|
||||||
if (!this.validate()) {
|
.video(0, 0, "evo_bg")
|
||||||
return this.end();
|
.stop()
|
||||||
}
|
.setOrigin(0)
|
||||||
|
.setScale(0.4359673025)
|
||||||
|
.setVisible(false);
|
||||||
|
|
||||||
globalScene.fadeOutBgm(undefined, false);
|
this.evolutionBgOverlay = globalScene.add
|
||||||
|
.rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0x262626)
|
||||||
|
.setOrigin(0)
|
||||||
|
.setAlpha(0);
|
||||||
|
this.evolutionContainer.add([this.evolutionBaseBg, this.evolutionBgOverlay, this.evolutionBg]);
|
||||||
|
|
||||||
this.evolutionHandler = globalScene.ui.getHandler() as EvolutionSceneHandler;
|
this.evolutionOverlay = globalScene.add.rectangle(
|
||||||
|
0,
|
||||||
|
-globalScene.game.canvas.height / 6,
|
||||||
|
globalScene.game.canvas.width / 6,
|
||||||
|
globalScene.game.canvas.height / 6 - 48,
|
||||||
|
0xffffff,
|
||||||
|
);
|
||||||
|
this.evolutionOverlay.setOrigin(0).setAlpha(0);
|
||||||
|
globalScene.ui.add(this.evolutionOverlay);
|
||||||
|
}
|
||||||
|
|
||||||
this.evolutionContainer = this.evolutionHandler.evolutionContainer;
|
/**
|
||||||
|
* Configure the sprite, setting its pipeline data
|
||||||
|
* @param pokemon - The pokemon object that the sprite information is configured from
|
||||||
|
* @param sprite - The sprite object to configure
|
||||||
|
* @param setPipeline - Whether to also set the pipeline; should be false
|
||||||
|
* if the sprite is only being updated with new sprite assets
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @returns The sprite object that was passed in
|
||||||
|
*/
|
||||||
|
protected configureSprite(pokemon: Pokemon, sprite: Phaser.GameObjects.Sprite, setPipeline = true): typeof sprite {
|
||||||
|
const spriteKey = pokemon.getSpriteKey(true);
|
||||||
|
try {
|
||||||
|
sprite.play(spriteKey);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||||
|
}
|
||||||
|
|
||||||
this.evolutionBaseBg = globalScene.add.image(0, 0, "default_bg");
|
if (setPipeline) {
|
||||||
this.evolutionBaseBg.setOrigin(0, 0);
|
sprite.setPipeline(globalScene.spritePipeline, {
|
||||||
this.evolutionContainer.add(this.evolutionBaseBg);
|
tone: [0.0, 0.0, 0.0, 0.0],
|
||||||
|
hasShadow: false,
|
||||||
this.evolutionBg = globalScene.add.video(0, 0, "evo_bg").stop();
|
teraColor: getTypeRgb(pokemon.getTeraType()),
|
||||||
this.evolutionBg.setOrigin(0, 0);
|
isTerastallized: pokemon.isTerastallized,
|
||||||
this.evolutionBg.setScale(0.4359673025);
|
});
|
||||||
this.evolutionBg.setVisible(false);
|
}
|
||||||
this.evolutionContainer.add(this.evolutionBg);
|
|
||||||
|
sprite
|
||||||
this.evolutionBgOverlay = globalScene.add.rectangle(
|
.setPipelineData("ignoreTimeTint", true)
|
||||||
0,
|
.setPipelineData("spriteKey", pokemon.getSpriteKey())
|
||||||
0,
|
.setPipelineData("shiny", pokemon.shiny)
|
||||||
globalScene.game.canvas.width / 6,
|
.setPipelineData("variant", pokemon.variant);
|
||||||
globalScene.game.canvas.height / 6,
|
|
||||||
0x262626,
|
for (let k of ["spriteColors", "fusionSpriteColors"]) {
|
||||||
);
|
if (pokemon.summonData.speciesForm) {
|
||||||
this.evolutionBgOverlay.setOrigin(0, 0);
|
k += "Base";
|
||||||
this.evolutionBgOverlay.setAlpha(0);
|
}
|
||||||
this.evolutionContainer.add(this.evolutionBgOverlay);
|
sprite.pipelineData[k] = pokemon.getSprite().pipelineData[k];
|
||||||
|
}
|
||||||
const getPokemonSprite = () => {
|
|
||||||
const ret = globalScene.addPokemonSprite(
|
return sprite;
|
||||||
this.pokemon,
|
}
|
||||||
this.evolutionBaseBg.displayWidth / 2,
|
|
||||||
this.evolutionBaseBg.displayHeight / 2,
|
private getPokemonSprite(): Phaser.GameObjects.Sprite {
|
||||||
"pkmn__sub",
|
const sprite = globalScene.addPokemonSprite(
|
||||||
);
|
this.pokemon,
|
||||||
ret.setPipeline(globalScene.spritePipeline, {
|
this.evolutionBaseBg.displayWidth / 2,
|
||||||
tone: [0.0, 0.0, 0.0, 0.0],
|
this.evolutionBaseBg.displayHeight / 2,
|
||||||
ignoreTimeTint: true,
|
"pkmn__sub",
|
||||||
});
|
);
|
||||||
return ret;
|
sprite.setPipeline(globalScene.spritePipeline, {
|
||||||
};
|
tone: [0.0, 0.0, 0.0, 0.0],
|
||||||
|
ignoreTimeTint: true,
|
||||||
this.evolutionContainer.add((this.pokemonSprite = getPokemonSprite()));
|
});
|
||||||
this.evolutionContainer.add((this.pokemonTintSprite = getPokemonSprite()));
|
return sprite;
|
||||||
this.evolutionContainer.add((this.pokemonEvoSprite = getPokemonSprite()));
|
}
|
||||||
this.evolutionContainer.add((this.pokemonEvoTintSprite = getPokemonSprite()));
|
|
||||||
|
/**
|
||||||
this.pokemonTintSprite.setAlpha(0);
|
* Initialize {@linkcode pokemonSprite}, {@linkcode pokemonTintSprite}, {@linkcode pokemonEvoSprite}, and {@linkcode pokemonEvoTintSprite}
|
||||||
this.pokemonTintSprite.setTintFill(0xffffff);
|
* and add them to the {@linkcode evolutionContainer}
|
||||||
this.pokemonEvoSprite.setVisible(false);
|
*/
|
||||||
this.pokemonEvoTintSprite.setVisible(false);
|
private setupPokemonSprites(): void {
|
||||||
this.pokemonEvoTintSprite.setTintFill(0xffffff);
|
this.pokemonSprite = this.configureSprite(this.pokemon, this.getPokemonSprite());
|
||||||
|
this.pokemonTintSprite = this.configureSprite(
|
||||||
this.evolutionOverlay = globalScene.add.rectangle(
|
this.pokemon,
|
||||||
0,
|
this.getPokemonSprite().setAlpha(0).setTintFill(0xffffff),
|
||||||
-globalScene.game.canvas.height / 6,
|
);
|
||||||
globalScene.game.canvas.width / 6,
|
this.pokemonEvoSprite = this.configureSprite(this.pokemon, this.getPokemonSprite().setVisible(false));
|
||||||
globalScene.game.canvas.height / 6 - 48,
|
this.pokemonEvoTintSprite = this.configureSprite(
|
||||||
0xffffff,
|
this.pokemon,
|
||||||
);
|
this.getPokemonSprite().setVisible(false).setTintFill(0xffffff),
|
||||||
this.evolutionOverlay.setOrigin(0, 0);
|
);
|
||||||
this.evolutionOverlay.setAlpha(0);
|
|
||||||
globalScene.ui.add(this.evolutionOverlay);
|
this.evolutionContainer.add([
|
||||||
|
this.pokemonSprite,
|
||||||
[this.pokemonSprite, this.pokemonTintSprite, this.pokemonEvoSprite, this.pokemonEvoTintSprite].map(sprite => {
|
this.pokemonTintSprite,
|
||||||
const spriteKey = this.pokemon.getSpriteKey(true);
|
this.pokemonEvoSprite,
|
||||||
try {
|
this.pokemonEvoTintSprite,
|
||||||
sprite.play(spriteKey);
|
]);
|
||||||
} catch (err: unknown) {
|
}
|
||||||
console.error(`Failed to play animation for ${spriteKey}`, err);
|
|
||||||
}
|
async start() {
|
||||||
|
super.start();
|
||||||
sprite.setPipeline(globalScene.spritePipeline, {
|
await this.setMode();
|
||||||
tone: [0.0, 0.0, 0.0, 0.0],
|
|
||||||
hasShadow: false,
|
if (!this.validate()) {
|
||||||
teraColor: getTypeRgb(this.pokemon.getTeraType()),
|
return this.end();
|
||||||
isTerastallized: this.pokemon.isTerastallized,
|
}
|
||||||
});
|
this.setupEvolutionAssets();
|
||||||
sprite.setPipelineData("ignoreTimeTint", true);
|
this.setupPokemonSprites();
|
||||||
sprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey());
|
this.preEvolvedPokemonName = getPokemonNameWithAffix(this.pokemon);
|
||||||
sprite.setPipelineData("shiny", this.pokemon.shiny);
|
this.doEvolution();
|
||||||
sprite.setPipelineData("variant", this.pokemon.variant);
|
}
|
||||||
["spriteColors", "fusionSpriteColors"].map(k => {
|
|
||||||
if (this.pokemon.summonData.speciesForm) {
|
/**
|
||||||
k += "Base";
|
* Update the sprites depicting the evolved Pokemon
|
||||||
}
|
* @param evolvedPokemon - The evolved Pokemon
|
||||||
sprite.pipelineData[k] = this.pokemon.getSprite().pipelineData[k];
|
*/
|
||||||
});
|
private updateEvolvedPokemonSprites(evolvedPokemon: Pokemon): void {
|
||||||
|
this.configureSprite(evolvedPokemon, this.pokemonEvoSprite, false);
|
||||||
|
this.configureSprite(evolvedPokemon, this.pokemonEvoTintSprite, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the evolution tween and begins playing it
|
||||||
|
*/
|
||||||
|
private playEvolutionAnimation(evolvedPokemon: Pokemon): void {
|
||||||
|
globalScene.time.delayedCall(1000, () => {
|
||||||
|
this.evolutionBgm = globalScene.playSoundWithoutBgm("evolution");
|
||||||
|
globalScene.tweens.add({
|
||||||
|
targets: this.evolutionBgOverlay,
|
||||||
|
alpha: 1,
|
||||||
|
delay: 500,
|
||||||
|
duration: 1500,
|
||||||
|
ease: "Sine.easeOut",
|
||||||
|
onComplete: () => {
|
||||||
|
globalScene.time.delayedCall(1000, () => {
|
||||||
|
this.evolutionBg.setVisible(true).play();
|
||||||
|
});
|
||||||
|
globalScene.playSound("se/charge");
|
||||||
|
this.doSpiralUpward();
|
||||||
|
this.fadeOutPokemonSprite(evolvedPokemon);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
this.preEvolvedPokemonName = getPokemonNameWithAffix(this.pokemon);
|
|
||||||
this.doEvolution();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fadeOutPokemonSprite(evolvedPokemon: Pokemon): void {
|
||||||
|
globalScene.tweens.addCounter({
|
||||||
|
from: 0,
|
||||||
|
to: 1,
|
||||||
|
duration: 2000,
|
||||||
|
onUpdate: t => {
|
||||||
|
this.pokemonTintSprite.setAlpha(t.getValue());
|
||||||
|
},
|
||||||
|
onComplete: () => {
|
||||||
|
this.pokemonSprite.setVisible(false);
|
||||||
|
globalScene.time.delayedCall(1100, () => {
|
||||||
|
globalScene.playSound("se/beam");
|
||||||
|
this.doArcDownward();
|
||||||
|
this.prepareForCycle(evolvedPokemon);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares the evolution cycle by setting up the tint sprites and starting the cycle
|
||||||
|
*/
|
||||||
|
private prepareForCycle(evolvedPokemon: Pokemon): void {
|
||||||
|
globalScene.time.delayedCall(1500, () => {
|
||||||
|
this.pokemonEvoTintSprite.setScale(0.25).setVisible(true);
|
||||||
|
this.evolutionHandler.canCancel = this.canCancel;
|
||||||
|
this.doCycle(1, undefined, () => {
|
||||||
|
if (this.evolutionHandler.cancelled) {
|
||||||
|
this.handleFailedEvolution(evolvedPokemon);
|
||||||
|
} else {
|
||||||
|
this.handleSuccessEvolution(evolvedPokemon);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the evolution text and then commence the evolution animation
|
||||||
|
*/
|
||||||
doEvolution(): void {
|
doEvolution(): void {
|
||||||
globalScene.ui.showText(
|
globalScene.ui.showText(
|
||||||
i18next.t("menu:evolving", { pokemonName: this.preEvolvedPokemonName }),
|
i18next.t("menu:evolving", { pokemonName: this.preEvolvedPokemonName }),
|
||||||
null,
|
null,
|
||||||
() => {
|
() => {
|
||||||
this.pokemon.cry();
|
this.pokemon.cry();
|
||||||
|
|
||||||
this.pokemon.getPossibleEvolution(this.evolution).then(evolvedPokemon => {
|
this.pokemon.getPossibleEvolution(this.evolution).then(evolvedPokemon => {
|
||||||
[this.pokemonEvoSprite, this.pokemonEvoTintSprite].map(sprite => {
|
this.updateEvolvedPokemonSprites(evolvedPokemon);
|
||||||
const spriteKey = evolvedPokemon.getSpriteKey(true);
|
this.playEvolutionAnimation(evolvedPokemon);
|
||||||
try {
|
|
||||||
sprite.play(spriteKey);
|
|
||||||
} catch (err: unknown) {
|
|
||||||
console.error(`Failed to play animation for ${spriteKey}`, err);
|
|
||||||
}
|
|
||||||
|
|
||||||
sprite.setPipelineData("ignoreTimeTint", true);
|
|
||||||
sprite.setPipelineData("spriteKey", evolvedPokemon.getSpriteKey());
|
|
||||||
sprite.setPipelineData("shiny", evolvedPokemon.shiny);
|
|
||||||
sprite.setPipelineData("variant", evolvedPokemon.variant);
|
|
||||||
["spriteColors", "fusionSpriteColors"].map(k => {
|
|
||||||
if (evolvedPokemon.summonData.speciesForm) {
|
|
||||||
k += "Base";
|
|
||||||
}
|
|
||||||
sprite.pipelineData[k] = evolvedPokemon.getSprite().pipelineData[k];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
globalScene.time.delayedCall(1000, () => {
|
|
||||||
this.evolutionBgm = globalScene.playSoundWithoutBgm("evolution");
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: this.evolutionBgOverlay,
|
|
||||||
alpha: 1,
|
|
||||||
delay: 500,
|
|
||||||
duration: 1500,
|
|
||||||
ease: "Sine.easeOut",
|
|
||||||
onComplete: () => {
|
|
||||||
globalScene.time.delayedCall(1000, () => {
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: this.evolutionBgOverlay,
|
|
||||||
alpha: 0,
|
|
||||||
duration: 250,
|
|
||||||
});
|
|
||||||
this.evolutionBg.setVisible(true);
|
|
||||||
this.evolutionBg.play();
|
|
||||||
});
|
|
||||||
globalScene.playSound("se/charge");
|
|
||||||
this.doSpiralUpward();
|
|
||||||
globalScene.tweens.addCounter({
|
|
||||||
from: 0,
|
|
||||||
to: 1,
|
|
||||||
duration: 2000,
|
|
||||||
onUpdate: t => {
|
|
||||||
this.pokemonTintSprite.setAlpha(t.getValue());
|
|
||||||
},
|
|
||||||
onComplete: () => {
|
|
||||||
this.pokemonSprite.setVisible(false);
|
|
||||||
globalScene.time.delayedCall(1100, () => {
|
|
||||||
globalScene.playSound("se/beam");
|
|
||||||
this.doArcDownward();
|
|
||||||
globalScene.time.delayedCall(1500, () => {
|
|
||||||
this.pokemonEvoTintSprite.setScale(0.25);
|
|
||||||
this.pokemonEvoTintSprite.setVisible(true);
|
|
||||||
this.evolutionHandler.canCancel = true;
|
|
||||||
this.doCycle(1).then(success => {
|
|
||||||
if (success) {
|
|
||||||
this.handleSuccessEvolution(evolvedPokemon);
|
|
||||||
} else {
|
|
||||||
this.handleFailedEvolution(evolvedPokemon);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
1000,
|
1000,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Used exclusively by {@linkcode handleFailedEvolution} to fade out the evolution sprites and music */
|
||||||
* Handles a failed/stopped evolution
|
private fadeOutEvolutionAssets(): void {
|
||||||
* @param evolvedPokemon - The evolved Pokemon
|
|
||||||
*/
|
|
||||||
private handleFailedEvolution(evolvedPokemon: Pokemon): void {
|
|
||||||
this.pokemonSprite.setVisible(true);
|
|
||||||
this.pokemonTintSprite.setScale(1);
|
|
||||||
globalScene.tweens.add({
|
globalScene.tweens.add({
|
||||||
targets: [this.evolutionBg, this.pokemonTintSprite, this.pokemonEvoSprite, this.pokemonEvoTintSprite],
|
targets: [this.evolutionBg, this.pokemonTintSprite, this.pokemonEvoSprite, this.pokemonEvoTintSprite],
|
||||||
alpha: 0,
|
alpha: 0,
|
||||||
@ -257,9 +298,40 @@ export class EvolutionPhase extends Phase {
|
|||||||
this.evolutionBg.setVisible(false);
|
this.evolutionBg.setVisible(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
SoundFade.fadeOut(globalScene, this.evolutionBgm, 100);
|
SoundFade.fadeOut(globalScene, this.evolutionBgm, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the confirmation prompt for pausing evolutions
|
||||||
|
* @param endCallback - The callback to call after either option is selected.
|
||||||
|
* This should end the evolution phase
|
||||||
|
*/
|
||||||
|
private showPauseEvolutionConfirmation(endCallback: () => void): void {
|
||||||
|
globalScene.ui.setOverlayMode(
|
||||||
|
UiMode.CONFIRM,
|
||||||
|
() => {
|
||||||
|
globalScene.ui.revertMode();
|
||||||
|
this.pokemon.pauseEvolutions = true;
|
||||||
|
globalScene.ui.showText(
|
||||||
|
i18next.t("menu:evolutionsPaused", {
|
||||||
|
pokemonName: this.preEvolvedPokemonName,
|
||||||
|
}),
|
||||||
|
null,
|
||||||
|
endCallback,
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
globalScene.ui.revertMode();
|
||||||
|
globalScene.time.delayedCall(3000, endCallback);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used exclusively by {@linkcode handleFailedEvolution} to show the failed evolution UI messages
|
||||||
|
*/
|
||||||
|
private showFailedEvolutionUI(evolvedPokemon: Pokemon): void {
|
||||||
globalScene.phaseManager.unshiftNew("EndEvolutionPhase");
|
globalScene.phaseManager.unshiftNew("EndEvolutionPhase");
|
||||||
|
|
||||||
globalScene.ui.showText(
|
globalScene.ui.showText(
|
||||||
@ -280,25 +352,7 @@ export class EvolutionPhase extends Phase {
|
|||||||
evolvedPokemon.destroy();
|
evolvedPokemon.destroy();
|
||||||
this.end();
|
this.end();
|
||||||
};
|
};
|
||||||
globalScene.ui.setOverlayMode(
|
this.showPauseEvolutionConfirmation(end);
|
||||||
UiMode.CONFIRM,
|
|
||||||
() => {
|
|
||||||
globalScene.ui.revertMode();
|
|
||||||
this.pokemon.pauseEvolutions = true;
|
|
||||||
globalScene.ui.showText(
|
|
||||||
i18next.t("menu:evolutionsPaused", {
|
|
||||||
pokemonName: this.preEvolvedPokemonName,
|
|
||||||
}),
|
|
||||||
null,
|
|
||||||
end,
|
|
||||||
3000,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
globalScene.ui.revertMode();
|
|
||||||
globalScene.time.delayedCall(3000, end);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -307,6 +361,93 @@ export class EvolutionPhase extends Phase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fade out the evolution assets, show the failed evolution UI messages, and enqueue the EndEvolutionPhase
|
||||||
|
* @param evolvedPokemon - The evolved Pokemon
|
||||||
|
*/
|
||||||
|
private handleFailedEvolution(evolvedPokemon: Pokemon): void {
|
||||||
|
this.pokemonSprite.setVisible(true);
|
||||||
|
this.pokemonTintSprite.setScale(1);
|
||||||
|
this.fadeOutEvolutionAssets();
|
||||||
|
|
||||||
|
globalScene.phaseManager.unshiftNew("EndEvolutionPhase");
|
||||||
|
this.showFailedEvolutionUI(evolvedPokemon);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fadeout evolution music, play the cry, show the evolution completed text, and end the phase
|
||||||
|
*/
|
||||||
|
private onEvolutionComplete(evolvedPokemon: Pokemon) {
|
||||||
|
SoundFade.fadeOut(globalScene, this.evolutionBgm, 100);
|
||||||
|
globalScene.time.delayedCall(250, () => {
|
||||||
|
this.pokemon.cry();
|
||||||
|
globalScene.time.delayedCall(1250, () => {
|
||||||
|
globalScene.playSoundWithoutBgm("evolution_fanfare");
|
||||||
|
|
||||||
|
evolvedPokemon.destroy();
|
||||||
|
globalScene.ui.showText(
|
||||||
|
i18next.t("menu:evolutionDone", {
|
||||||
|
pokemonName: this.preEvolvedPokemonName,
|
||||||
|
evolvedPokemonName: this.pokemon.species.getExpandedSpeciesName(),
|
||||||
|
}),
|
||||||
|
null,
|
||||||
|
() => this.end(),
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
fixedInt(4000),
|
||||||
|
);
|
||||||
|
globalScene.time.delayedCall(fixedInt(4250), () => globalScene.playBgm());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private postEvolve(evolvedPokemon: Pokemon): void {
|
||||||
|
const learnSituation: LearnMoveSituation = this.fusionSpeciesEvolved
|
||||||
|
? LearnMoveSituation.EVOLUTION_FUSED
|
||||||
|
: this.pokemon.fusionSpecies
|
||||||
|
? LearnMoveSituation.EVOLUTION_FUSED_BASE
|
||||||
|
: LearnMoveSituation.EVOLUTION;
|
||||||
|
const levelMoves = this.pokemon
|
||||||
|
.getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation)
|
||||||
|
.filter(lm => lm[0] === EVOLVE_MOVE);
|
||||||
|
for (const lm of levelMoves) {
|
||||||
|
globalScene.phaseManager.unshiftNew("LearnMovePhase", globalScene.getPlayerParty().indexOf(this.pokemon), lm[1]);
|
||||||
|
}
|
||||||
|
globalScene.phaseManager.unshiftNew("EndEvolutionPhase");
|
||||||
|
|
||||||
|
globalScene.playSound("se/shine");
|
||||||
|
this.doSpray();
|
||||||
|
|
||||||
|
globalScene.tweens.chain({
|
||||||
|
targets: null,
|
||||||
|
tweens: [
|
||||||
|
{
|
||||||
|
targets: this.evolutionOverlay,
|
||||||
|
alpha: 1,
|
||||||
|
duration: 250,
|
||||||
|
easing: "Sine.easeIn",
|
||||||
|
onComplete: () => {
|
||||||
|
this.evolutionBgOverlay.setAlpha(1);
|
||||||
|
this.evolutionBg.setVisible(false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: [this.evolutionOverlay, this.pokemonEvoTintSprite],
|
||||||
|
alpha: 0,
|
||||||
|
duration: 2000,
|
||||||
|
delay: 150,
|
||||||
|
easing: "Sine.easeIn",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: this.evolutionBgOverlay,
|
||||||
|
alpha: 0,
|
||||||
|
duration: 250,
|
||||||
|
onComplete: () => this.onEvolutionComplete(evolvedPokemon),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a successful evolution
|
* Handles a successful evolution
|
||||||
* @param evolvedPokemon - The evolved Pokemon
|
* @param evolvedPokemon - The evolved Pokemon
|
||||||
@ -316,85 +457,15 @@ export class EvolutionPhase extends Phase {
|
|||||||
this.pokemonEvoSprite.setVisible(true);
|
this.pokemonEvoSprite.setVisible(true);
|
||||||
this.doCircleInward();
|
this.doCircleInward();
|
||||||
|
|
||||||
const onEvolutionComplete = () => {
|
|
||||||
SoundFade.fadeOut(globalScene, this.evolutionBgm, 100);
|
|
||||||
globalScene.time.delayedCall(250, () => {
|
|
||||||
this.pokemon.cry();
|
|
||||||
globalScene.time.delayedCall(1250, () => {
|
|
||||||
globalScene.playSoundWithoutBgm("evolution_fanfare");
|
|
||||||
|
|
||||||
evolvedPokemon.destroy();
|
|
||||||
globalScene.ui.showText(
|
|
||||||
i18next.t("menu:evolutionDone", {
|
|
||||||
pokemonName: this.preEvolvedPokemonName,
|
|
||||||
evolvedPokemonName: this.pokemon.species.getExpandedSpeciesName(),
|
|
||||||
}),
|
|
||||||
null,
|
|
||||||
() => this.end(),
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
fixedInt(4000),
|
|
||||||
);
|
|
||||||
globalScene.time.delayedCall(fixedInt(4250), () => globalScene.playBgm());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
globalScene.time.delayedCall(900, () => {
|
globalScene.time.delayedCall(900, () => {
|
||||||
this.evolutionHandler.canCancel = false;
|
this.evolutionHandler.canCancel = this.canCancel;
|
||||||
|
|
||||||
this.pokemon.evolve(this.evolution, this.pokemon.species).then(() => {
|
this.pokemon.evolve(this.evolution, this.pokemon.species).then(() => this.postEvolve(evolvedPokemon));
|
||||||
const learnSituation: LearnMoveSituation = this.fusionSpeciesEvolved
|
|
||||||
? LearnMoveSituation.EVOLUTION_FUSED
|
|
||||||
: this.pokemon.fusionSpecies
|
|
||||||
? LearnMoveSituation.EVOLUTION_FUSED_BASE
|
|
||||||
: LearnMoveSituation.EVOLUTION;
|
|
||||||
const levelMoves = this.pokemon
|
|
||||||
.getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation)
|
|
||||||
.filter(lm => lm[0] === EVOLVE_MOVE);
|
|
||||||
for (const lm of levelMoves) {
|
|
||||||
globalScene.phaseManager.unshiftNew(
|
|
||||||
"LearnMovePhase",
|
|
||||||
globalScene.getPlayerParty().indexOf(this.pokemon),
|
|
||||||
lm[1],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
globalScene.phaseManager.unshiftNew("EndEvolutionPhase");
|
|
||||||
|
|
||||||
globalScene.playSound("se/shine");
|
|
||||||
this.doSpray();
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: this.evolutionOverlay,
|
|
||||||
alpha: 1,
|
|
||||||
duration: 250,
|
|
||||||
easing: "Sine.easeIn",
|
|
||||||
onComplete: () => {
|
|
||||||
this.evolutionBgOverlay.setAlpha(1);
|
|
||||||
this.evolutionBg.setVisible(false);
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: [this.evolutionOverlay, this.pokemonEvoTintSprite],
|
|
||||||
alpha: 0,
|
|
||||||
duration: 2000,
|
|
||||||
delay: 150,
|
|
||||||
easing: "Sine.easeIn",
|
|
||||||
onComplete: () => {
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: this.evolutionBgOverlay,
|
|
||||||
alpha: 0,
|
|
||||||
duration: 250,
|
|
||||||
onComplete: onEvolutionComplete,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
doSpiralUpward() {
|
doSpiralUpward() {
|
||||||
let f = 0;
|
let f = 0;
|
||||||
|
|
||||||
globalScene.tweens.addCounter({
|
globalScene.tweens.addCounter({
|
||||||
repeat: 64,
|
repeat: 64,
|
||||||
duration: getFrameMs(1),
|
duration: getFrameMs(1),
|
||||||
@ -430,34 +501,41 @@ export class EvolutionPhase extends Phase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
doCycle(l: number, lastCycle = 15): Promise<boolean> {
|
/**
|
||||||
return new Promise(resolve => {
|
* Return a tween chain that cycles the evolution sprites
|
||||||
const isLastCycle = l === lastCycle;
|
*/
|
||||||
globalScene.tweens.add({
|
doCycle(cycles: number, lastCycle = 15, onComplete = () => {}): void {
|
||||||
targets: this.pokemonTintSprite,
|
// Make our tween start both at the same time
|
||||||
scale: 0.25,
|
const tweens: Phaser.Types.Tweens.TweenBuilderConfig[] = [];
|
||||||
|
for (let i = cycles; i <= lastCycle; i += 0.5) {
|
||||||
|
tweens.push({
|
||||||
|
targets: [this.pokemonTintSprite, this.pokemonEvoTintSprite],
|
||||||
|
scale: (_target, _key, _value, targetIndex: number, _totalTargets, _tween) => (targetIndex === 0 ? 0.25 : 1),
|
||||||
ease: "Cubic.easeInOut",
|
ease: "Cubic.easeInOut",
|
||||||
duration: 500 / l,
|
duration: 500 / i,
|
||||||
yoyo: !isLastCycle,
|
yoyo: i !== lastCycle,
|
||||||
});
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: this.pokemonEvoTintSprite,
|
|
||||||
scale: 1,
|
|
||||||
ease: "Cubic.easeInOut",
|
|
||||||
duration: 500 / l,
|
|
||||||
yoyo: !isLastCycle,
|
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
if (this.evolutionHandler.cancelled) {
|
if (this.evolutionHandler.cancelled) {
|
||||||
return resolve(false);
|
// cause the tween chain to complete instantly, skipping the remaining tweens.
|
||||||
|
this.pokemonEvoTintSprite.setScale(1);
|
||||||
|
this.pokemonEvoTintSprite.setVisible(false);
|
||||||
|
this.evoChain?.complete?.();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (l < lastCycle) {
|
if (i === lastCycle) {
|
||||||
this.doCycle(l + 0.5, lastCycle).then(success => resolve(success));
|
this.pokemonEvoTintSprite.setScale(1);
|
||||||
} else {
|
|
||||||
this.pokemonTintSprite.setVisible(false);
|
|
||||||
resolve(true);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.evoChain = globalScene.tweens.chain({
|
||||||
|
targets: null,
|
||||||
|
tweens,
|
||||||
|
onComplete: () => {
|
||||||
|
this.evoChain = null;
|
||||||
|
onComplete();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import type { BattlerIndex } from "#enums/battler-index";
|
import type { BattlerIndex } from "#enums/battler-index";
|
||||||
import { BattleType } from "#enums/battle-type";
|
import { BattleType } from "#enums/battle-type";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import {
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
applyPostFaintAbAttrs,
|
|
||||||
applyPostKnockOutAbAttrs,
|
|
||||||
applyPostVictoryAbAttrs,
|
|
||||||
} from "#app/data/abilities/apply-ab-attrs";
|
|
||||||
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
||||||
import { battleSpecDialogue } from "#app/data/dialogue";
|
import { battleSpecDialogue } from "#app/data/dialogue";
|
||||||
import { allMoves } from "#app/data/data-lists";
|
import { allMoves } from "#app/data/data-lists";
|
||||||
@ -117,29 +113,31 @@ export class FaintPhase extends PokemonPhase {
|
|||||||
|
|
||||||
pokemon.resetTera();
|
pokemon.resetTera();
|
||||||
|
|
||||||
|
// TODO: this can be simplified by just checking whether lastAttack is defined
|
||||||
if (pokemon.turnData.attacksReceived?.length) {
|
if (pokemon.turnData.attacksReceived?.length) {
|
||||||
const lastAttack = pokemon.turnData.attacksReceived[0];
|
const lastAttack = pokemon.turnData.attacksReceived[0];
|
||||||
applyPostFaintAbAttrs(
|
applyAbAttrs("PostFaintAbAttr", {
|
||||||
"PostFaintAbAttr",
|
pokemon: pokemon,
|
||||||
pokemon,
|
// TODO: We should refactor lastAttack's sourceId to forbid null and just use undefined
|
||||||
globalScene.getPokemonById(lastAttack.sourceId)!,
|
attacker: globalScene.getPokemonById(lastAttack.sourceId) ?? undefined,
|
||||||
new PokemonMove(lastAttack.move).getMove(),
|
// TODO: improve the way that we provide the move that knocked out the pokemon...
|
||||||
lastAttack.result,
|
move: new PokemonMove(lastAttack.move).getMove(),
|
||||||
); // TODO: is this bang correct?
|
hitResult: lastAttack.result,
|
||||||
|
}); // TODO: is this bang correct?
|
||||||
} else {
|
} else {
|
||||||
//If killed by indirect damage, apply post-faint abilities without providing a last move
|
//If killed by indirect damage, apply post-faint abilities without providing a last move
|
||||||
applyPostFaintAbAttrs("PostFaintAbAttr", pokemon);
|
applyAbAttrs("PostFaintAbAttr", { pokemon });
|
||||||
}
|
}
|
||||||
|
|
||||||
const alivePlayField = globalScene.getField(true);
|
const alivePlayField = globalScene.getField(true);
|
||||||
for (const p of alivePlayField) {
|
for (const p of alivePlayField) {
|
||||||
applyPostKnockOutAbAttrs("PostKnockOutAbAttr", p, pokemon);
|
applyAbAttrs("PostKnockOutAbAttr", { pokemon: p, victim: pokemon });
|
||||||
}
|
}
|
||||||
if (pokemon.turnData.attacksReceived?.length) {
|
if (pokemon.turnData.attacksReceived?.length) {
|
||||||
const defeatSource = this.source;
|
const defeatSource = this.source;
|
||||||
|
|
||||||
if (defeatSource?.isOnField()) {
|
if (defeatSource?.isOnField()) {
|
||||||
applyPostVictoryAbAttrs("PostVictoryAbAttr", defeatSource);
|
applyAbAttrs("PostVictoryAbAttr", { pokemon: defeatSource });
|
||||||
const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move];
|
const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move];
|
||||||
const pvattrs = pvmove.getAttrs("PostVictoryStatStageChangeAttr");
|
const pvattrs = pvmove.getAttrs("PostVictoryStatStageChangeAttr");
|
||||||
if (pvattrs.length) {
|
if (pvattrs.length) {
|
||||||
|
@ -3,7 +3,7 @@ import { fixedInt } from "#app/utils/common";
|
|||||||
import { achvs } from "../system/achv";
|
import { achvs } from "../system/achv";
|
||||||
import type { SpeciesFormChange } from "../data/pokemon-forms";
|
import type { SpeciesFormChange } from "../data/pokemon-forms";
|
||||||
import { getSpeciesFormChangeMessage } from "#app/data/pokemon-forms/form-change-triggers";
|
import { getSpeciesFormChangeMessage } from "#app/data/pokemon-forms/form-change-triggers";
|
||||||
import type { PlayerPokemon } from "../field/pokemon";
|
import type { default as Pokemon, PlayerPokemon } from "../field/pokemon";
|
||||||
import { UiMode } from "#enums/ui-mode";
|
import { UiMode } from "#enums/ui-mode";
|
||||||
import type PartyUiHandler from "../ui/party-ui-handler";
|
import type PartyUiHandler from "../ui/party-ui-handler";
|
||||||
import { getPokemonNameWithAffix } from "../messages";
|
import { getPokemonNameWithAffix } from "../messages";
|
||||||
@ -34,146 +34,158 @@ export class FormChangePhase extends EvolutionPhase {
|
|||||||
return globalScene.ui.setOverlayMode(UiMode.EVOLUTION_SCENE);
|
return globalScene.ui.setOverlayMode(UiMode.EVOLUTION_SCENE);
|
||||||
}
|
}
|
||||||
|
|
||||||
doEvolution(): void {
|
/**
|
||||||
const preName = getPokemonNameWithAffix(this.pokemon);
|
* Commence the tweens that play after the form change animation finishes
|
||||||
|
* @param transformedPokemon - The Pokemon after the evolution
|
||||||
this.pokemon.getPossibleForm(this.formChange).then(transformedPokemon => {
|
* @param preName - The name of the Pokemon before the evolution
|
||||||
[this.pokemonEvoSprite, this.pokemonEvoTintSprite].map(sprite => {
|
*/
|
||||||
const spriteKey = transformedPokemon.getSpriteKey(true);
|
private postFormChangeTweens(transformedPokemon: Pokemon, preName: string): void {
|
||||||
try {
|
globalScene.tweens.chain({
|
||||||
sprite.play(spriteKey);
|
targets: null,
|
||||||
} catch (err: unknown) {
|
tweens: [
|
||||||
console.error(`Failed to play animation for ${spriteKey}`, err);
|
{
|
||||||
|
targets: this.evolutionOverlay,
|
||||||
|
alpha: 1,
|
||||||
|
duration: 250,
|
||||||
|
easing: "Sine.easeIn",
|
||||||
|
onComplete: () => {
|
||||||
|
this.evolutionBgOverlay.setAlpha(1);
|
||||||
|
this.evolutionBg.setVisible(false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: [this.evolutionOverlay, this.pokemonEvoTintSprite],
|
||||||
|
alpha: 0,
|
||||||
|
duration: 2000,
|
||||||
|
delay: 150,
|
||||||
|
easing: "Sine.easeIn",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targets: this.evolutionBgOverlay,
|
||||||
|
alpha: 0,
|
||||||
|
duration: 250,
|
||||||
|
completeDelay: 250,
|
||||||
|
onComplete: () => this.pokemon.cry(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 1.25 seconds after the pokemon cry
|
||||||
|
completeDelay: 1250,
|
||||||
|
onComplete: () => {
|
||||||
|
let playEvolutionFanfare = false;
|
||||||
|
if (this.formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1) {
|
||||||
|
globalScene.validateAchv(achvs.MEGA_EVOLVE);
|
||||||
|
playEvolutionFanfare = true;
|
||||||
|
} else if (
|
||||||
|
this.formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1 ||
|
||||||
|
this.formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1
|
||||||
|
) {
|
||||||
|
globalScene.validateAchv(achvs.GIGANTAMAX);
|
||||||
|
playEvolutionFanfare = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
sprite.setPipelineData("ignoreTimeTint", true);
|
const delay = playEvolutionFanfare ? 4000 : 1750;
|
||||||
sprite.setPipelineData("spriteKey", transformedPokemon.getSpriteKey());
|
globalScene.playSoundWithoutBgm(playEvolutionFanfare ? "evolution_fanfare" : "minor_fanfare");
|
||||||
sprite.setPipelineData("shiny", transformedPokemon.shiny);
|
transformedPokemon.destroy();
|
||||||
sprite.setPipelineData("variant", transformedPokemon.variant);
|
globalScene.ui.showText(
|
||||||
["spriteColors", "fusionSpriteColors"].map(k => {
|
getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName),
|
||||||
if (transformedPokemon.summonData.speciesForm) {
|
null,
|
||||||
k += "Base";
|
() => this.end(),
|
||||||
}
|
null,
|
||||||
sprite.pipelineData[k] = transformedPokemon.getSprite().pipelineData[k];
|
true,
|
||||||
});
|
fixedInt(delay),
|
||||||
});
|
);
|
||||||
|
globalScene.time.delayedCall(fixedInt(delay + 250), () => globalScene.playBgm());
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
globalScene.time.delayedCall(250, () => {
|
/**
|
||||||
globalScene.tweens.add({
|
* Commence the animations that occur once the form change evolution cycle ({@linkcode doCycle}) is complete
|
||||||
|
*
|
||||||
|
* @privateRemarks
|
||||||
|
* This would prefer {@linkcode doCycle} to be refactored and de-promisified so this can be moved into {@linkcode beginTweens}
|
||||||
|
* @param preName - The name of the Pokemon before the evolution
|
||||||
|
* @param transformedPokemon - The Pokemon being transformed into
|
||||||
|
*/
|
||||||
|
private afterCycle(preName: string, transformedPokemon: Pokemon): void {
|
||||||
|
globalScene.playSound("se/sparkle");
|
||||||
|
this.pokemonEvoSprite.setVisible(true);
|
||||||
|
this.doCircleInward();
|
||||||
|
globalScene.time.delayedCall(900, () => {
|
||||||
|
this.pokemon.changeForm(this.formChange).then(() => {
|
||||||
|
if (!this.modal) {
|
||||||
|
globalScene.phaseManager.unshiftNew("EndEvolutionPhase");
|
||||||
|
}
|
||||||
|
globalScene.playSound("se/shine");
|
||||||
|
this.doSpray();
|
||||||
|
this.postFormChangeTweens(transformedPokemon, preName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commence the sequence of tweens and events that occur during the evolution animation
|
||||||
|
* @param preName The name of the Pokemon before the evolution
|
||||||
|
* @param transformedPokemon The Pokemon after the evolution
|
||||||
|
*/
|
||||||
|
private beginTweens(preName: string, transformedPokemon: Pokemon): void {
|
||||||
|
globalScene.tweens.chain({
|
||||||
|
// Starts 250ms after sprites have been configured
|
||||||
|
targets: null,
|
||||||
|
tweens: [
|
||||||
|
// Step 1: Fade in the background overlay
|
||||||
|
{
|
||||||
|
delay: 250,
|
||||||
targets: this.evolutionBgOverlay,
|
targets: this.evolutionBgOverlay,
|
||||||
alpha: 1,
|
alpha: 1,
|
||||||
delay: 500,
|
|
||||||
duration: 1500,
|
duration: 1500,
|
||||||
ease: "Sine.easeOut",
|
ease: "Sine.easeOut",
|
||||||
|
// We want the backkground overlay to fade out after it fades in
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
globalScene.time.delayedCall(1000, () => {
|
globalScene.tweens.add({
|
||||||
globalScene.tweens.add({
|
targets: this.evolutionBgOverlay,
|
||||||
targets: this.evolutionBgOverlay,
|
alpha: 0,
|
||||||
alpha: 0,
|
duration: 250,
|
||||||
duration: 250,
|
delay: 1000,
|
||||||
});
|
|
||||||
this.evolutionBg.setVisible(true);
|
|
||||||
this.evolutionBg.play();
|
|
||||||
});
|
});
|
||||||
|
this.evolutionBg.setVisible(true).play();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Step 2: Play the sounds and fade in the tint sprite
|
||||||
|
{
|
||||||
|
targets: this.pokemonTintSprite,
|
||||||
|
alpha: { from: 0, to: 1 },
|
||||||
|
duration: 2000,
|
||||||
|
onStart: () => {
|
||||||
globalScene.playSound("se/charge");
|
globalScene.playSound("se/charge");
|
||||||
this.doSpiralUpward();
|
this.doSpiralUpward();
|
||||||
globalScene.tweens.addCounter({
|
|
||||||
from: 0,
|
|
||||||
to: 1,
|
|
||||||
duration: 2000,
|
|
||||||
onUpdate: t => {
|
|
||||||
this.pokemonTintSprite.setAlpha(t.getValue());
|
|
||||||
},
|
|
||||||
onComplete: () => {
|
|
||||||
this.pokemonSprite.setVisible(false);
|
|
||||||
globalScene.time.delayedCall(1100, () => {
|
|
||||||
globalScene.playSound("se/beam");
|
|
||||||
this.doArcDownward();
|
|
||||||
globalScene.time.delayedCall(1000, () => {
|
|
||||||
this.pokemonEvoTintSprite.setScale(0.25);
|
|
||||||
this.pokemonEvoTintSprite.setVisible(true);
|
|
||||||
this.doCycle(1, 1).then(_success => {
|
|
||||||
globalScene.playSound("se/sparkle");
|
|
||||||
this.pokemonEvoSprite.setVisible(true);
|
|
||||||
this.doCircleInward();
|
|
||||||
globalScene.time.delayedCall(900, () => {
|
|
||||||
this.pokemon.changeForm(this.formChange).then(() => {
|
|
||||||
if (!this.modal) {
|
|
||||||
globalScene.phaseManager.unshiftNew("EndEvolutionPhase");
|
|
||||||
}
|
|
||||||
|
|
||||||
globalScene.playSound("se/shine");
|
|
||||||
this.doSpray();
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: this.evolutionOverlay,
|
|
||||||
alpha: 1,
|
|
||||||
duration: 250,
|
|
||||||
easing: "Sine.easeIn",
|
|
||||||
onComplete: () => {
|
|
||||||
this.evolutionBgOverlay.setAlpha(1);
|
|
||||||
this.evolutionBg.setVisible(false);
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: [this.evolutionOverlay, this.pokemonEvoTintSprite],
|
|
||||||
alpha: 0,
|
|
||||||
duration: 2000,
|
|
||||||
delay: 150,
|
|
||||||
easing: "Sine.easeIn",
|
|
||||||
onComplete: () => {
|
|
||||||
globalScene.tweens.add({
|
|
||||||
targets: this.evolutionBgOverlay,
|
|
||||||
alpha: 0,
|
|
||||||
duration: 250,
|
|
||||||
onComplete: () => {
|
|
||||||
globalScene.time.delayedCall(250, () => {
|
|
||||||
this.pokemon.cry();
|
|
||||||
globalScene.time.delayedCall(1250, () => {
|
|
||||||
let playEvolutionFanfare = false;
|
|
||||||
if (this.formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1) {
|
|
||||||
globalScene.validateAchv(achvs.MEGA_EVOLVE);
|
|
||||||
playEvolutionFanfare = true;
|
|
||||||
} else if (
|
|
||||||
this.formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1 ||
|
|
||||||
this.formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1
|
|
||||||
) {
|
|
||||||
globalScene.validateAchv(achvs.GIGANTAMAX);
|
|
||||||
playEvolutionFanfare = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const delay = playEvolutionFanfare ? 4000 : 1750;
|
|
||||||
globalScene.playSoundWithoutBgm(
|
|
||||||
playEvolutionFanfare ? "evolution_fanfare" : "minor_fanfare",
|
|
||||||
);
|
|
||||||
|
|
||||||
transformedPokemon.destroy();
|
|
||||||
globalScene.ui.showText(
|
|
||||||
getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName),
|
|
||||||
null,
|
|
||||||
() => this.end(),
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
fixedInt(delay),
|
|
||||||
);
|
|
||||||
globalScene.time.delayedCall(fixedInt(delay + 250), () =>
|
|
||||||
globalScene.playBgm(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
onComplete: () => {
|
||||||
|
this.pokemonSprite.setVisible(false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
// Step 3: Commence the form change animation via doCycle then continue the animation chain with afterCycle
|
||||||
|
completeDelay: 1100,
|
||||||
|
onComplete: () => {
|
||||||
|
globalScene.playSound("se/beam");
|
||||||
|
this.doArcDownward();
|
||||||
|
globalScene.time.delayedCall(1000, () => {
|
||||||
|
this.pokemonEvoTintSprite.setScale(0.25).setVisible(true);
|
||||||
|
this.doCycle(1, 1, () => this.afterCycle(preName, transformedPokemon));
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
doEvolution(): void {
|
||||||
|
const preName = getPokemonNameWithAffix(this.pokemon, false);
|
||||||
|
|
||||||
|
this.pokemon.getPossibleForm(this.formChange).then(transformedPokemon => {
|
||||||
|
this.configureSprite(transformedPokemon, this.pokemonEvoSprite, false);
|
||||||
|
this.configureSprite(transformedPokemon, this.pokemonEvoTintSprite, false);
|
||||||
|
this.beginTweens(preName, transformedPokemon);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,6 @@
|
|||||||
import { BattlerIndex } from "#enums/battler-index";
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import {
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
applyExecutedMoveAbAttrs,
|
|
||||||
applyPostAttackAbAttrs,
|
|
||||||
applyPostDamageAbAttrs,
|
|
||||||
applyPostDefendAbAttrs,
|
|
||||||
applyPreAttackAbAttrs,
|
|
||||||
} from "#app/data/abilities/apply-ab-attrs";
|
|
||||||
import { ConditionalProtectTag } from "#app/data/arena-tag";
|
import { ConditionalProtectTag } from "#app/data/arena-tag";
|
||||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||||
import { MoveAnim } from "#app/data/battle-anims";
|
import { MoveAnim } from "#app/data/battle-anims";
|
||||||
@ -322,7 +316,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
// Assume single target for multi hit
|
// Assume single target for multi hit
|
||||||
applyMoveAttrs("MultiHitAttr", user, this.getFirstTarget() ?? null, move, hitCount);
|
applyMoveAttrs("MultiHitAttr", user, this.getFirstTarget() ?? null, move, hitCount);
|
||||||
// If Parental Bond is applicable, add another hit
|
// If Parental Bond is applicable, add another hit
|
||||||
applyPreAttackAbAttrs("AddSecondStrikeAbAttr", user, null, move, false, hitCount, null);
|
applyAbAttrs("AddSecondStrikeAbAttr", { pokemon: user, move, hitCount });
|
||||||
// If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses
|
// If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses
|
||||||
globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount);
|
globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount);
|
||||||
// Set the user's relevant turnData fields to reflect the final hit count
|
// Set the user's relevant turnData fields to reflect the final hit count
|
||||||
@ -370,7 +364,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
// Add to the move history entry
|
// Add to the move history entry
|
||||||
if (this.firstHit) {
|
if (this.firstHit) {
|
||||||
user.pushMoveHistory(this.moveHistoryEntry);
|
user.pushMoveHistory(this.moveHistoryEntry);
|
||||||
applyExecutedMoveAbAttrs("ExecutedMoveAbAttr", user);
|
applyAbAttrs("ExecutedMoveAbAttr", { pokemon: user });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -439,7 +433,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
* @param hitResult - The {@linkcode HitResult} of the attempted move
|
* @param hitResult - The {@linkcode HitResult} of the attempted move
|
||||||
*/
|
*/
|
||||||
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void {
|
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void {
|
||||||
applyPostDefendAbAttrs("PostDefendAbAttr", target, user, this.move, hitResult);
|
applyAbAttrs("PostDefendAbAttr", { pokemon: target, opponent: user, move: this.move, hitResult });
|
||||||
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
|
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -805,7 +799,9 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
|
|
||||||
// Multi-hit check for Wimp Out/Emergency Exit
|
// Multi-hit check for Wimp Out/Emergency Exit
|
||||||
if (user.turnData.hitCount > 1) {
|
if (user.turnData.hitCount > 1) {
|
||||||
applyPostDamageAbAttrs("PostDamageAbAttr", target, 0, target.hasPassive(), false, [], user);
|
// TODO: Investigate why 0 is being passed for damage amount here
|
||||||
|
// and then determing if refactoring `applyMove` to return the damage dealt is appropriate.
|
||||||
|
applyAbAttrs("PostDamageAbAttr", { pokemon: target, damage: 0, source: user });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -999,7 +995,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, false);
|
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, false);
|
||||||
this.applyHeldItemFlinchCheck(user, target, dealsDamage);
|
this.applyHeldItemFlinchCheck(user, target, dealsDamage);
|
||||||
this.applyOnGetHitAbEffects(user, target, hitResult);
|
this.applyOnGetHitAbEffects(user, target, hitResult);
|
||||||
applyPostAttackAbAttrs("PostAttackAbAttr", user, target, this.move, hitResult);
|
applyAbAttrs("PostAttackAbAttr", { pokemon: user, opponent: target, move: this.move, hitResult });
|
||||||
|
|
||||||
// We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens
|
// We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens
|
||||||
if (!user.isPlayer() && this.move.is("AttackMove")) {
|
if (!user.isPlayer() && this.move.is("AttackMove")) {
|
||||||
|
@ -2,8 +2,8 @@ import { globalScene } from "#app/global-scene";
|
|||||||
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
import type { BattlerIndex } from "#enums/battler-index";
|
import type { BattlerIndex } from "#enums/battler-index";
|
||||||
import { applyPostSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
|
|
||||||
export class MoveEndPhase extends PokemonPhase {
|
export class MoveEndPhase extends PokemonPhase {
|
||||||
public readonly phaseName = "MoveEndPhase";
|
public readonly phaseName = "MoveEndPhase";
|
||||||
@ -30,7 +30,7 @@ export class MoveEndPhase extends PokemonPhase {
|
|||||||
globalScene.arena.setIgnoreAbilities(false);
|
globalScene.arena.setIgnoreAbilities(false);
|
||||||
for (const target of this.targets) {
|
for (const target of this.targets) {
|
||||||
if (target) {
|
if (target) {
|
||||||
applyPostSummonAbAttrs("PostSummonRemoveEffectAbAttr", target);
|
applyAbAttrs("PostSummonRemoveEffectAbAttr", { pokemon: target });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { BattlerIndex } from "#enums/battler-index";
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import type { DelayedAttackTag } from "#app/data/arena-tag";
|
import type { DelayedAttackTag } from "#app/data/arena-tag";
|
||||||
import { CommonAnim } from "#enums/move-anims-common";
|
import { CommonAnim } from "#enums/move-anims-common";
|
||||||
import { CenterOfAttentionTag } from "#app/data/battler-tags";
|
import { CenterOfAttentionTag } from "#app/data/battler-tags";
|
||||||
@ -228,14 +228,11 @@ export class MovePhase extends BattlePhase {
|
|||||||
case StatusEffect.SLEEP: {
|
case StatusEffect.SLEEP: {
|
||||||
applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove());
|
applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove());
|
||||||
const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0);
|
const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0);
|
||||||
applyAbAttrs(
|
applyAbAttrs("ReduceStatusEffectDurationAbAttr", {
|
||||||
"ReduceStatusEffectDurationAbAttr",
|
pokemon: this.pokemon,
|
||||||
this.pokemon,
|
statusEffect: this.pokemon.status.effect,
|
||||||
null,
|
duration: turnsRemaining,
|
||||||
false,
|
});
|
||||||
this.pokemon.status.effect,
|
|
||||||
turnsRemaining,
|
|
||||||
);
|
|
||||||
this.pokemon.status.sleepTurnsRemaining = turnsRemaining.value;
|
this.pokemon.status.sleepTurnsRemaining = turnsRemaining.value;
|
||||||
healed = this.pokemon.status.sleepTurnsRemaining <= 0;
|
healed = this.pokemon.status.sleepTurnsRemaining <= 0;
|
||||||
activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP);
|
activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP);
|
||||||
@ -396,7 +393,8 @@ export class MovePhase extends BattlePhase {
|
|||||||
*/
|
*/
|
||||||
if (success) {
|
if (success) {
|
||||||
const move = this.move.getMove();
|
const move = this.move.getMove();
|
||||||
applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, move);
|
// TODO: Investigate whether PokemonTypeChangeAbAttr can drop the "opponent" parameter
|
||||||
|
applyAbAttrs("PokemonTypeChangeAbAttr", { pokemon: this.pokemon, move, opponent: targets[0] });
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
"MoveEffectPhase",
|
"MoveEffectPhase",
|
||||||
this.pokemon.getBattlerIndex(),
|
this.pokemon.getBattlerIndex(),
|
||||||
@ -406,7 +404,11 @@ export class MovePhase extends BattlePhase {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if ([MoveId.ROAR, MoveId.WHIRLWIND, MoveId.TRICK_OR_TREAT, MoveId.FORESTS_CURSE].includes(this.move.moveId)) {
|
if ([MoveId.ROAR, MoveId.WHIRLWIND, MoveId.TRICK_OR_TREAT, MoveId.FORESTS_CURSE].includes(this.move.moveId)) {
|
||||||
applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, this.move.getMove());
|
applyAbAttrs("PokemonTypeChangeAbAttr", {
|
||||||
|
pokemon: this.pokemon,
|
||||||
|
move: this.move.getMove(),
|
||||||
|
opponent: targets[0],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pokemon.pushMoveHistory({
|
this.pokemon.pushMoveHistory({
|
||||||
@ -438,7 +440,7 @@ export class MovePhase extends BattlePhase {
|
|||||||
if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !dancerModes.includes(this.useMode)) {
|
if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !dancerModes.includes(this.useMode)) {
|
||||||
// TODO: Fix in dancer PR to move to MEP for hit checks
|
// TODO: Fix in dancer PR to move to MEP for hit checks
|
||||||
globalScene.getField(true).forEach(pokemon => {
|
globalScene.getField(true).forEach(pokemon => {
|
||||||
applyPostMoveUsedAbAttrs("PostMoveUsedAbAttr", pokemon, this.move, this.pokemon, this.targets);
|
applyAbAttrs("PostMoveUsedAbAttr", { pokemon, move: this.move, source: this.pokemon, targets: this.targets });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -470,7 +472,11 @@ export class MovePhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Protean and Libero apply on the charging turn of charge moves
|
// Protean and Libero apply on the charging turn of charge moves
|
||||||
applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, this.move.getMove());
|
applyAbAttrs("PokemonTypeChangeAbAttr", {
|
||||||
|
pokemon: this.pokemon,
|
||||||
|
move: this.move.getMove(),
|
||||||
|
opponent: targets[0],
|
||||||
|
});
|
||||||
|
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
"MoveChargePhase",
|
"MoveChargePhase",
|
||||||
@ -523,7 +529,12 @@ export class MovePhase extends BattlePhase {
|
|||||||
.getField(true)
|
.getField(true)
|
||||||
.filter(p => p !== this.pokemon)
|
.filter(p => p !== this.pokemon)
|
||||||
.forEach(p =>
|
.forEach(p =>
|
||||||
applyAbAttrs("RedirectMoveAbAttr", p, null, false, this.move.moveId, redirectTarget, this.pokemon),
|
applyAbAttrs("RedirectMoveAbAttr", {
|
||||||
|
pokemon: p,
|
||||||
|
moveId: this.move.moveId,
|
||||||
|
targetIndex: redirectTarget,
|
||||||
|
sourcePokemon: this.pokemon,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
/** `true` if an Ability is responsible for redirecting the move to another target; `false` otherwise */
|
/** `true` if an Ability is responsible for redirecting the move to another target; `false` otherwise */
|
||||||
|
@ -14,7 +14,7 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase {
|
|||||||
if (pokemon) {
|
if (pokemon) {
|
||||||
pokemon.resetBattleAndWaveData();
|
pokemon.resetBattleAndWaveData();
|
||||||
if (pokemon.isOnField()) {
|
if (pokemon.isOnField()) {
|
||||||
applyAbAttrs("PostBiomeChangeAbAttr", pokemon, null);
|
applyAbAttrs("PostBiomeChangeAbAttr", { pokemon });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import type Pokemon from "#app/field/pokemon";
|
|||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
||||||
import { applyPostSetStatusAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { isNullOrUndefined } from "#app/utils/common";
|
import { isNullOrUndefined } from "#app/utils/common";
|
||||||
|
|
||||||
export class ObtainStatusEffectPhase extends PokemonPhase {
|
export class ObtainStatusEffectPhase extends PokemonPhase {
|
||||||
@ -53,7 +53,11 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
|
|||||||
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeStatusEffectTrigger, true);
|
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeStatusEffectTrigger, true);
|
||||||
// If mold breaker etc was used to set this status, it shouldn't apply to abilities activated afterwards
|
// If mold breaker etc was used to set this status, it shouldn't apply to abilities activated afterwards
|
||||||
globalScene.arena.setIgnoreAbilities(false);
|
globalScene.arena.setIgnoreAbilities(false);
|
||||||
applyPostSetStatusAbAttrs("PostSetStatusAbAttr", pokemon, this.statusEffect, this.sourcePokemon);
|
applyAbAttrs("PostSetStatusAbAttr", {
|
||||||
|
pokemon,
|
||||||
|
effect: this.statusEffect,
|
||||||
|
sourcePokemon: this.sourcePokemon ?? undefined,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.end();
|
this.end();
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { applyPostSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { PostSummonPhase } from "#app/phases/post-summon-phase";
|
import { PostSummonPhase } from "#app/phases/post-summon-phase";
|
||||||
import type { BattlerIndex } from "#enums/battler-index";
|
import type { BattlerIndex } from "#enums/battler-index";
|
||||||
|
|
||||||
@ -16,7 +16,8 @@ export class PostSummonActivateAbilityPhase extends PostSummonPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
applyPostSummonAbAttrs("PostSummonAbAttr", this.getPokemon(), this.passive, false);
|
// TODO: Check with Dean on whether or not passive must be provided to `this.passive`
|
||||||
|
applyAbAttrs("PostSummonAbAttr", { pokemon: this.getPokemon(), passive: this.passive });
|
||||||
|
|
||||||
this.end();
|
this.end();
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ export class PostSummonPhase extends PokemonPhase {
|
|||||||
|
|
||||||
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
||||||
for (const p of field) {
|
for (const p of field) {
|
||||||
applyAbAttrs("CommanderAbAttr", p, null, false);
|
applyAbAttrs("CommanderAbAttr", { pokemon: p });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.end();
|
this.end();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type { BattlerIndex } from "#enums/battler-index";
|
import type { BattlerIndex } from "#enums/battler-index";
|
||||||
import { applyAbAttrs, applyPostDamageAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { CommonBattleAnim } from "#app/data/battle-anims";
|
import { CommonBattleAnim } from "#app/data/battle-anims";
|
||||||
import { CommonAnim } from "#enums/move-anims-common";
|
import { CommonAnim } from "#enums/move-anims-common";
|
||||||
import { getStatusEffectActivationText } from "#app/data/status-effect";
|
import { getStatusEffectActivationText } from "#app/data/status-effect";
|
||||||
@ -22,8 +22,8 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
|
|||||||
if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn() && !pokemon.switchOutStatus) {
|
if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn() && !pokemon.switchOutStatus) {
|
||||||
pokemon.status.incrementTurn();
|
pokemon.status.incrementTurn();
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
applyAbAttrs("BlockStatusDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockStatusDamageAbAttr", { pokemon, cancelled });
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
globalScene.phaseManager.queueMessage(
|
globalScene.phaseManager.queueMessage(
|
||||||
@ -39,14 +39,14 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
|
|||||||
break;
|
break;
|
||||||
case StatusEffect.BURN:
|
case StatusEffect.BURN:
|
||||||
damage.value = Math.max(pokemon.getMaxHp() >> 4, 1);
|
damage.value = Math.max(pokemon.getMaxHp() >> 4, 1);
|
||||||
applyAbAttrs("ReduceBurnDamageAbAttr", pokemon, null, false, damage);
|
applyAbAttrs("ReduceBurnDamageAbAttr", { pokemon, burnDamage: damage });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (damage.value) {
|
if (damage.value) {
|
||||||
// Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ...
|
// Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ...
|
||||||
globalScene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true));
|
globalScene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true));
|
||||||
pokemon.updateInfo();
|
pokemon.updateInfo();
|
||||||
applyPostDamageAbAttrs("PostDamageAbAttr", pokemon, damage.value, pokemon.hasPassive(), false, []);
|
applyAbAttrs("PostDamageAbAttr", { pokemon, damage: damage.value });
|
||||||
}
|
}
|
||||||
new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(false, () => this.end());
|
new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(false, () => this.end());
|
||||||
} else {
|
} else {
|
||||||
|
@ -181,9 +181,10 @@ export class QuietFormChangePhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.formChange.trigger instanceof SpeciesFormChangeTeraTrigger) {
|
if (this.formChange.trigger instanceof SpeciesFormChangeTeraTrigger) {
|
||||||
applyAbAttrs("PostTeraFormChangeStatChangeAbAttr", this.pokemon, null);
|
const params = { pokemon: this.pokemon };
|
||||||
applyAbAttrs("ClearWeatherAbAttr", this.pokemon, null);
|
applyAbAttrs("PostTeraFormChangeStatChangeAbAttr", params);
|
||||||
applyAbAttrs("ClearTerrainAbAttr", this.pokemon, null);
|
applyAbAttrs("ClearWeatherAbAttr", params);
|
||||||
|
applyAbAttrs("ClearTerrainAbAttr", params);
|
||||||
}
|
}
|
||||||
|
|
||||||
super.end();
|
super.end();
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type { BattlerIndex } from "#enums/battler-index";
|
import type { BattlerIndex } from "#enums/battler-index";
|
||||||
import {
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
applyAbAttrs,
|
|
||||||
applyPostStatStageChangeAbAttrs,
|
|
||||||
applyPreStatStageChangeAbAttrs,
|
|
||||||
} from "#app/data/abilities/apply-ab-attrs";
|
|
||||||
import { MistTag } from "#app/data/arena-tag";
|
import { MistTag } from "#app/data/arena-tag";
|
||||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||||
import type { ArenaTag } from "#app/data/arena-tag";
|
import type { ArenaTag } from "#app/data/arena-tag";
|
||||||
@ -18,6 +14,10 @@ import { PokemonPhase } from "./pokemon-phase";
|
|||||||
import { Stat, type BattleStat, getStatKey, getStatStageChangeDescriptionKey } from "#enums/stat";
|
import { Stat, type BattleStat, getStatKey, getStatStageChangeDescriptionKey } from "#enums/stat";
|
||||||
import { OctolockTag } from "#app/data/battler-tags";
|
import { OctolockTag } from "#app/data/battler-tags";
|
||||||
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
||||||
|
import type {
|
||||||
|
ConditionalUserFieldProtectStatAbAttrParams,
|
||||||
|
PreStatStageChangeAbAttrParams,
|
||||||
|
} from "#app/@types/ability-types";
|
||||||
|
|
||||||
export type StatStageChangeCallback = (
|
export type StatStageChangeCallback = (
|
||||||
target: Pokemon | null,
|
target: Pokemon | null,
|
||||||
@ -126,7 +126,7 @@ export class StatStageChangePhase extends PokemonPhase {
|
|||||||
const stages = new NumberHolder(this.stages);
|
const stages = new NumberHolder(this.stages);
|
||||||
|
|
||||||
if (!this.ignoreAbilities) {
|
if (!this.ignoreAbilities) {
|
||||||
applyAbAttrs("StatStageChangeMultiplierAbAttr", pokemon, null, false, stages);
|
applyAbAttrs("StatStageChangeMultiplierAbAttr", { pokemon, numStages: stages });
|
||||||
}
|
}
|
||||||
|
|
||||||
let simulate = false;
|
let simulate = false;
|
||||||
@ -146,42 +146,38 @@ export class StatStageChangePhase extends PokemonPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!cancelled.value && !this.selfTarget && stages.value < 0) {
|
if (!cancelled.value && !this.selfTarget && stages.value < 0) {
|
||||||
applyPreStatStageChangeAbAttrs("ProtectStatAbAttr", pokemon, stat, cancelled, simulate);
|
const abAttrParams: PreStatStageChangeAbAttrParams & ConditionalUserFieldProtectStatAbAttrParams = {
|
||||||
applyPreStatStageChangeAbAttrs(
|
|
||||||
"ConditionalUserFieldProtectStatAbAttr",
|
|
||||||
pokemon,
|
pokemon,
|
||||||
stat,
|
stat,
|
||||||
cancelled,
|
cancelled,
|
||||||
simulate,
|
simulated: simulate,
|
||||||
pokemon,
|
target: pokemon,
|
||||||
);
|
stages: this.stages,
|
||||||
|
};
|
||||||
|
applyAbAttrs("ProtectStatAbAttr", abAttrParams);
|
||||||
|
applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", abAttrParams);
|
||||||
|
// TODO: Consider skipping this call if `cancelled` is false.
|
||||||
const ally = pokemon.getAlly();
|
const ally = pokemon.getAlly();
|
||||||
if (!isNullOrUndefined(ally)) {
|
if (!isNullOrUndefined(ally)) {
|
||||||
applyPreStatStageChangeAbAttrs(
|
applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", { ...abAttrParams, pokemon: ally });
|
||||||
"ConditionalUserFieldProtectStatAbAttr",
|
|
||||||
ally,
|
|
||||||
stat,
|
|
||||||
cancelled,
|
|
||||||
simulate,
|
|
||||||
pokemon,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Potential stat reflection due to Mirror Armor, does not apply to Octolock end of turn effect */
|
/** Potential stat reflection due to Mirror Armor, does not apply to Octolock end of turn effect */
|
||||||
if (
|
if (
|
||||||
opponentPokemon !== undefined &&
|
opponentPokemon !== undefined &&
|
||||||
|
// TODO: investigate whether this is stoping mirror armor from applying to non-octolock
|
||||||
|
// reasons for stat drops if the user has the Octolock tag
|
||||||
!pokemon.findTag(t => t instanceof OctolockTag) &&
|
!pokemon.findTag(t => t instanceof OctolockTag) &&
|
||||||
!this.comingFromMirrorArmorUser
|
!this.comingFromMirrorArmorUser
|
||||||
) {
|
) {
|
||||||
applyPreStatStageChangeAbAttrs(
|
applyAbAttrs("ReflectStatStageChangeAbAttr", {
|
||||||
"ReflectStatStageChangeAbAttr",
|
|
||||||
pokemon,
|
pokemon,
|
||||||
stat,
|
stat,
|
||||||
cancelled,
|
cancelled,
|
||||||
simulate,
|
simulated: simulate,
|
||||||
opponentPokemon,
|
source: opponentPokemon,
|
||||||
this.stages,
|
stages: this.stages,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,17 +218,16 @@ export class StatStageChangePhase extends PokemonPhase {
|
|||||||
|
|
||||||
if (stages.value > 0 && this.canBeCopied) {
|
if (stages.value > 0 && this.canBeCopied) {
|
||||||
for (const opponent of pokemon.getOpponents()) {
|
for (const opponent of pokemon.getOpponents()) {
|
||||||
applyAbAttrs("StatStageChangeCopyAbAttr", opponent, null, false, this.stats, stages.value);
|
applyAbAttrs("StatStageChangeCopyAbAttr", { pokemon: opponent, stats: this.stats, numStages: stages.value });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyPostStatStageChangeAbAttrs(
|
applyAbAttrs("PostStatStageChangeAbAttr", {
|
||||||
"PostStatStageChangeAbAttr",
|
|
||||||
pokemon,
|
pokemon,
|
||||||
filteredStats,
|
stats: filteredStats,
|
||||||
this.stages,
|
stages: this.stages,
|
||||||
this.selfTarget,
|
selfTarget: this.selfTarget,
|
||||||
);
|
});
|
||||||
|
|
||||||
// Look for any other stat change phases; if this is the last one, do White Herb check
|
// Look for any other stat change phases; if this is the last one, do White Herb check
|
||||||
const existingPhase = globalScene.phaseManager.findPhase(
|
const existingPhase = globalScene.phaseManager.findPhase(
|
||||||
|
@ -10,7 +10,7 @@ import { getPokemonNameWithAffix } from "#app/messages";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase";
|
import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase";
|
||||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||||
import { applyPreSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
|
||||||
export class SummonPhase extends PartyMemberPokemonPhase {
|
export class SummonPhase extends PartyMemberPokemonPhase {
|
||||||
@ -27,7 +27,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
|
|||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
applyPreSummonAbAttrs("PreSummonAbAttr", this.getPokemon());
|
applyAbAttrs("PreSummonAbAttr", { pokemon: this.getPokemon() });
|
||||||
this.preSummon();
|
this.preSummon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { applyPreSummonAbAttrs, applyPreSwitchOutAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { allMoves } from "#app/data/data-lists";
|
import { allMoves } from "#app/data/data-lists";
|
||||||
import { getPokeballTintColor } from "#app/data/pokeball";
|
import { getPokeballTintColor } from "#app/data/pokeball";
|
||||||
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
|
||||||
@ -124,8 +124,8 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
switchedInPokemon.resetSummonData();
|
switchedInPokemon.resetSummonData();
|
||||||
switchedInPokemon.loadAssets(true);
|
switchedInPokemon.loadAssets(true);
|
||||||
|
|
||||||
applyPreSummonAbAttrs("PreSummonAbAttr", switchedInPokemon);
|
applyAbAttrs("PreSummonAbAttr", { pokemon: switchedInPokemon });
|
||||||
applyPreSwitchOutAbAttrs("PreSwitchOutAbAttr", this.lastPokemon);
|
applyAbAttrs("PreSwitchOutAbAttr", { pokemon: this.lastPokemon });
|
||||||
if (!switchedInPokemon) {
|
if (!switchedInPokemon) {
|
||||||
this.end();
|
this.end();
|
||||||
return;
|
return;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { applyPostTurnAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
|
||||||
import { TerrainType } from "#app/data/terrain";
|
import { TerrainType } from "#app/data/terrain";
|
||||||
import { WeatherType } from "#app/enums/weather-type";
|
import { WeatherType } from "#app/enums/weather-type";
|
||||||
@ -49,7 +49,7 @@ export class TurnEndPhase extends FieldPhase {
|
|||||||
globalScene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon);
|
globalScene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon);
|
||||||
}
|
}
|
||||||
|
|
||||||
applyPostTurnAbAttrs("PostTurnAbAttr", pokemon);
|
applyAbAttrs("PostTurnAbAttr", { pokemon });
|
||||||
}
|
}
|
||||||
|
|
||||||
globalScene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon);
|
globalScene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon);
|
||||||
|
@ -66,8 +66,12 @@ export class TurnStartPhase extends FieldPhase {
|
|||||||
globalScene.getField(true).forEach(p => {
|
globalScene.getField(true).forEach(p => {
|
||||||
const bypassSpeed = new BooleanHolder(false);
|
const bypassSpeed = new BooleanHolder(false);
|
||||||
const canCheckHeldItems = new BooleanHolder(true);
|
const canCheckHeldItems = new BooleanHolder(true);
|
||||||
applyAbAttrs("BypassSpeedChanceAbAttr", p, null, false, bypassSpeed);
|
applyAbAttrs("BypassSpeedChanceAbAttr", { pokemon: p, bypass: bypassSpeed });
|
||||||
applyAbAttrs("PreventBypassSpeedChanceAbAttr", p, null, false, bypassSpeed, canCheckHeldItems);
|
applyAbAttrs("PreventBypassSpeedChanceAbAttr", {
|
||||||
|
pokemon: p,
|
||||||
|
bypass: bypassSpeed,
|
||||||
|
canCheckHeldItems: canCheckHeldItems,
|
||||||
|
});
|
||||||
if (canCheckHeldItems.value) {
|
if (canCheckHeldItems.value) {
|
||||||
globalScene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed);
|
globalScene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import {
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
applyPreWeatherEffectAbAttrs,
|
|
||||||
applyAbAttrs,
|
|
||||||
applyPostWeatherLapseAbAttrs,
|
|
||||||
} from "#app/data/abilities/apply-ab-attrs";
|
|
||||||
import { CommonAnim } from "#enums/move-anims-common";
|
import { CommonAnim } from "#enums/move-anims-common";
|
||||||
import type { Weather } from "#app/data/weather";
|
import type { Weather } from "#app/data/weather";
|
||||||
import { getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather";
|
import { getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather";
|
||||||
@ -41,15 +37,15 @@ export class WeatherEffectPhase extends CommonAnimPhase {
|
|||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
|
|
||||||
this.executeForAll((pokemon: Pokemon) =>
|
this.executeForAll((pokemon: Pokemon) =>
|
||||||
applyPreWeatherEffectAbAttrs("SuppressWeatherEffectAbAttr", pokemon, this.weather, cancelled),
|
applyAbAttrs("SuppressWeatherEffectAbAttr", { pokemon, weather: this.weather, cancelled }),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
const inflictDamage = (pokemon: Pokemon) => {
|
const inflictDamage = (pokemon: Pokemon) => {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
|
|
||||||
applyPreWeatherEffectAbAttrs("PreWeatherDamageAbAttr", pokemon, this.weather, cancelled);
|
applyAbAttrs("PreWeatherDamageAbAttr", { pokemon, weather: this.weather, cancelled });
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
|
|
||||||
if (
|
if (
|
||||||
cancelled.value ||
|
cancelled.value ||
|
||||||
@ -80,7 +76,7 @@ export class WeatherEffectPhase extends CommonAnimPhase {
|
|||||||
globalScene.ui.showText(getWeatherLapseMessage(this.weather.weatherType) ?? "", null, () => {
|
globalScene.ui.showText(getWeatherLapseMessage(this.weather.weatherType) ?? "", null, () => {
|
||||||
this.executeForAll((pokemon: Pokemon) => {
|
this.executeForAll((pokemon: Pokemon) => {
|
||||||
if (!pokemon.switchOutStatus) {
|
if (!pokemon.switchOutStatus) {
|
||||||
applyPostWeatherLapseAbAttrs("PostWeatherLapseAbAttr", pokemon, this.weather);
|
applyAbAttrs("PostWeatherLapseAbAttr", { pokemon, weather: this.weather });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -5,9 +5,13 @@ import type BattleScene from "#app/battle-scene";
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { FixedInt } from "#app/utils/common";
|
import { FixedInt } from "#app/utils/common";
|
||||||
|
|
||||||
|
type TweenManager = typeof Phaser.Tweens.TweenManager.prototype;
|
||||||
|
|
||||||
|
/** The set of properties to mutate */
|
||||||
|
const PROPERTIES = ["delay", "completeDelay", "loopDelay", "duration", "repeatDelay", "hold", "startDelay"];
|
||||||
|
|
||||||
type FadeInType = typeof FadeIn;
|
type FadeInType = typeof FadeIn;
|
||||||
type FadeOutType = typeof FadeOut;
|
type FadeOutType = typeof FadeOut;
|
||||||
|
|
||||||
export function initGameSpeed() {
|
export function initGameSpeed() {
|
||||||
const thisArg = this as BattleScene;
|
const thisArg = this as BattleScene;
|
||||||
|
|
||||||
@ -18,14 +22,44 @@ export function initGameSpeed() {
|
|||||||
return thisArg.gameSpeed === 1 ? value : Math.ceil((value /= thisArg.gameSpeed));
|
return thisArg.gameSpeed === 1 ? value : Math.ceil((value /= thisArg.gameSpeed));
|
||||||
};
|
};
|
||||||
|
|
||||||
const originalAddEvent = this.time.addEvent;
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Complexity is necessary here
|
||||||
|
const mutateProperties = (obj: any, allowArray = false) => {
|
||||||
|
// We do not mutate Tweens or TweenChain objects themselves.
|
||||||
|
if (obj instanceof Phaser.Tweens.Tween || obj instanceof Phaser.Tweens.TweenChain) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If allowArray is true then check if first obj is an array and if so, mutate the tweens inside
|
||||||
|
if (allowArray && Array.isArray(obj)) {
|
||||||
|
for (const tween of obj) {
|
||||||
|
mutateProperties(tween);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const prop of PROPERTIES) {
|
||||||
|
const objProp = obj[prop];
|
||||||
|
if (typeof objProp === "number" || objProp instanceof FixedInt) {
|
||||||
|
obj[prop] = transformValue(objProp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the object has a 'tweens' property that is an array, then it is a tween chain
|
||||||
|
// and we need to mutate its properties as well
|
||||||
|
if (obj.tweens && Array.isArray(obj.tweens)) {
|
||||||
|
for (const tween of obj.tweens) {
|
||||||
|
mutateProperties(tween);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const originalAddEvent: typeof Phaser.Time.Clock.prototype.addEvent = this.time.addEvent;
|
||||||
this.time.addEvent = function (config: Phaser.Time.TimerEvent | Phaser.Types.Time.TimerEventConfig) {
|
this.time.addEvent = function (config: Phaser.Time.TimerEvent | Phaser.Types.Time.TimerEventConfig) {
|
||||||
if (!(config instanceof Phaser.Time.TimerEvent) && config.delay) {
|
if (!(config instanceof Phaser.Time.TimerEvent) && config.delay) {
|
||||||
config.delay = transformValue(config.delay);
|
config.delay = transformValue(config.delay);
|
||||||
}
|
}
|
||||||
return originalAddEvent.apply(this, [config]);
|
return originalAddEvent.apply(this, [config]);
|
||||||
};
|
};
|
||||||
const originalTweensAdd = this.tweens.add;
|
const originalTweensAdd: TweenManager["add"] = this.tweens.add;
|
||||||
|
|
||||||
this.tweens.add = function (
|
this.tweens.add = function (
|
||||||
config:
|
config:
|
||||||
| Phaser.Types.Tweens.TweenBuilderConfig
|
| Phaser.Types.Tweens.TweenBuilderConfig
|
||||||
@ -33,71 +67,33 @@ export function initGameSpeed() {
|
|||||||
| Phaser.Tweens.Tween
|
| Phaser.Tweens.Tween
|
||||||
| Phaser.Tweens.TweenChain,
|
| Phaser.Tweens.TweenChain,
|
||||||
) {
|
) {
|
||||||
if (config.loopDelay) {
|
mutateProperties(config);
|
||||||
config.loopDelay = transformValue(config.loopDelay as number);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(config instanceof Phaser.Tweens.TweenChain)) {
|
|
||||||
if (config.duration) {
|
|
||||||
config.duration = transformValue(config.duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(config instanceof Phaser.Tweens.Tween)) {
|
|
||||||
if (config.delay) {
|
|
||||||
config.delay = transformValue(config.delay as number);
|
|
||||||
}
|
|
||||||
if (config.repeatDelay) {
|
|
||||||
config.repeatDelay = transformValue(config.repeatDelay);
|
|
||||||
}
|
|
||||||
if (config.hold) {
|
|
||||||
config.hold = transformValue(config.hold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return originalTweensAdd.apply(this, [config]);
|
return originalTweensAdd.apply(this, [config]);
|
||||||
};
|
} as typeof originalTweensAdd;
|
||||||
const originalTweensChain = this.tweens.chain;
|
|
||||||
|
const originalTweensChain: TweenManager["chain"] = this.tweens.chain;
|
||||||
this.tweens.chain = function (config: Phaser.Types.Tweens.TweenChainBuilderConfig): Phaser.Tweens.TweenChain {
|
this.tweens.chain = function (config: Phaser.Types.Tweens.TweenChainBuilderConfig): Phaser.Tweens.TweenChain {
|
||||||
if (config.tweens) {
|
mutateProperties(config);
|
||||||
for (const t of config.tweens) {
|
|
||||||
if (t.duration) {
|
|
||||||
t.duration = transformValue(t.duration);
|
|
||||||
}
|
|
||||||
if (t.delay) {
|
|
||||||
t.delay = transformValue(t.delay as number);
|
|
||||||
}
|
|
||||||
if (t.repeatDelay) {
|
|
||||||
t.repeatDelay = transformValue(t.repeatDelay);
|
|
||||||
}
|
|
||||||
if (t.loopDelay) {
|
|
||||||
t.loopDelay = transformValue(t.loopDelay as number);
|
|
||||||
}
|
|
||||||
if (t.hold) {
|
|
||||||
t.hold = transformValue(t.hold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return originalTweensChain.apply(this, [config]);
|
return originalTweensChain.apply(this, [config]);
|
||||||
};
|
} as typeof originalTweensChain;
|
||||||
const originalAddCounter = this.tweens.addCounter;
|
const originalAddCounter: TweenManager["addCounter"] = this.tweens.addCounter;
|
||||||
|
|
||||||
this.tweens.addCounter = function (config: Phaser.Types.Tweens.NumberTweenBuilderConfig) {
|
this.tweens.addCounter = function (config: Phaser.Types.Tweens.NumberTweenBuilderConfig) {
|
||||||
if (config.duration) {
|
mutateProperties(config);
|
||||||
config.duration = transformValue(config.duration);
|
|
||||||
}
|
|
||||||
if (config.delay) {
|
|
||||||
config.delay = transformValue(config.delay);
|
|
||||||
}
|
|
||||||
if (config.repeatDelay) {
|
|
||||||
config.repeatDelay = transformValue(config.repeatDelay);
|
|
||||||
}
|
|
||||||
if (config.loopDelay) {
|
|
||||||
config.loopDelay = transformValue(config.loopDelay as number);
|
|
||||||
}
|
|
||||||
if (config.hold) {
|
|
||||||
config.hold = transformValue(config.hold);
|
|
||||||
}
|
|
||||||
return originalAddCounter.apply(this, [config]);
|
return originalAddCounter.apply(this, [config]);
|
||||||
};
|
} as typeof originalAddCounter;
|
||||||
|
|
||||||
|
const originalCreate: TweenManager["create"] = this.tweens.create;
|
||||||
|
this.tweens.create = function (config: Phaser.Types.Tweens.TweenBuilderConfig) {
|
||||||
|
mutateProperties(config, true);
|
||||||
|
return originalCreate.apply(this, [config]);
|
||||||
|
} as typeof originalCreate;
|
||||||
|
|
||||||
|
const originalAddMultiple: TweenManager["addMultiple"] = this.tweens.addMultiple;
|
||||||
|
this.tweens.addMultiple = function (config: Phaser.Types.Tweens.TweenBuilderConfig[]) {
|
||||||
|
mutateProperties(config, true);
|
||||||
|
return originalAddMultiple.apply(this, [config]);
|
||||||
|
} as typeof originalAddMultiple;
|
||||||
|
|
||||||
const originalFadeOut = SoundFade.fadeOut;
|
const originalFadeOut = SoundFade.fadeOut;
|
||||||
SoundFade.fadeOut = ((_scene: Phaser.Scene, sound: Phaser.Sound.BaseSound, duration: number, destroy?: boolean) =>
|
SoundFade.fadeOut = ((_scene: Phaser.Scene, sound: Phaser.Sound.BaseSound, duration: number, destroy?: boolean) =>
|
||||||
|
@ -201,19 +201,19 @@ export function formatLargeNumber(count: number, threshold: number): string {
|
|||||||
let suffix = "";
|
let suffix = "";
|
||||||
switch (Math.ceil(ret.length / 3) - 1) {
|
switch (Math.ceil(ret.length / 3) - 1) {
|
||||||
case 1:
|
case 1:
|
||||||
suffix = "K";
|
suffix = i18next.t("common:abrThousand");
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
suffix = "M";
|
suffix = i18next.t("common:abrMillion");
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
suffix = "B";
|
suffix = i18next.t("common:abrBillion");
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
suffix = "T";
|
suffix = i18next.t("common:abrTrillion");
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
suffix = "q";
|
suffix = i18next.t("common:abrQuadrillion");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return "?";
|
return "?";
|
||||||
@ -227,15 +227,31 @@ export function formatLargeNumber(count: number, threshold: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Abbreviations from 10^0 to 10^33
|
// Abbreviations from 10^0 to 10^33
|
||||||
const AbbreviationsLargeNumber: string[] = ["", "K", "M", "B", "t", "q", "Q", "s", "S", "o", "n", "d"];
|
function getAbbreviationsLargeNumber(): string[] {
|
||||||
|
return [
|
||||||
|
"",
|
||||||
|
i18next.t("common:abrThousand"),
|
||||||
|
i18next.t("common:abrMillion"),
|
||||||
|
i18next.t("common:abrBillion"),
|
||||||
|
i18next.t("common:abrTrillion"),
|
||||||
|
i18next.t("common:abrQuadrillion"),
|
||||||
|
i18next.t("common:abrQuintillion"),
|
||||||
|
i18next.t("common:abrSextillion"),
|
||||||
|
i18next.t("common:abrSeptillion"),
|
||||||
|
i18next.t("common:abrOctillion"),
|
||||||
|
i18next.t("common:abrNonillion"),
|
||||||
|
i18next.t("common:abrDecillion"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
export function formatFancyLargeNumber(number: number, rounded = 3): string {
|
export function formatFancyLargeNumber(number: number, rounded = 3): string {
|
||||||
|
const abbreviations = getAbbreviationsLargeNumber();
|
||||||
let exponent: number;
|
let exponent: number;
|
||||||
|
|
||||||
if (number < 1000) {
|
if (number < 1000) {
|
||||||
exponent = 0;
|
exponent = 0;
|
||||||
} else {
|
} else {
|
||||||
const maxExp = AbbreviationsLargeNumber.length - 1;
|
const maxExp = abbreviations.length - 1;
|
||||||
|
|
||||||
exponent = Math.floor(Math.log(number) / Math.log(1000));
|
exponent = Math.floor(Math.log(number) / Math.log(1000));
|
||||||
exponent = Math.min(exponent, maxExp);
|
exponent = Math.min(exponent, maxExp);
|
||||||
@ -243,7 +259,7 @@ export function formatFancyLargeNumber(number: number, rounded = 3): string {
|
|||||||
number /= Math.pow(1000, exponent);
|
number /= Math.pow(1000, exponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${(exponent === 0) || number % 1 === 0 ? number : number.toFixed(rounded)}${AbbreviationsLargeNumber[exponent]}`;
|
return `${exponent === 0 || number % 1 === 0 ? number : number.toFixed(rounded)}${abbreviations[exponent]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatMoney(format: MoneyFormat, amount: number) {
|
export function formatMoney(format: MoneyFormat, amount: number) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { RepeatBerryNextTurnAbAttr } from "#app/data/abilities/ability";
|
import { CudChewConsumeBerryAbAttr } from "#app/data/abilities/ability";
|
||||||
import Pokemon from "#app/field/pokemon";
|
import Pokemon from "#app/field/pokemon";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
@ -196,7 +196,7 @@ describe("Abilities - Cud Chew", () => {
|
|||||||
|
|
||||||
describe("regurgiates berries", () => {
|
describe("regurgiates berries", () => {
|
||||||
it("re-triggers effects on eater without pushing to array", async () => {
|
it("re-triggers effects on eater without pushing to array", async () => {
|
||||||
const apply = vi.spyOn(RepeatBerryNextTurnAbAttr.prototype, "apply");
|
const apply = vi.spyOn(CudChewConsumeBerryAbAttr.prototype, "apply");
|
||||||
await game.classicMode.startBattle([SpeciesId.FARIGIRAF]);
|
await game.classicMode.startBattle([SpeciesId.FARIGIRAF]);
|
||||||
|
|
||||||
const farigiraf = game.scene.getPlayerPokemon()!;
|
const farigiraf = game.scene.getPlayerPokemon()!;
|
||||||
|
@ -91,7 +91,7 @@ describe("Abilities - Gorilla Tactics", () => {
|
|||||||
game.move.select(MoveId.METRONOME);
|
game.move.select(MoveId.METRONOME);
|
||||||
await game.phaseInterceptor.to("TurnEndPhase");
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
// Gorilla Tactics should bypass dancer and instruct
|
// Gorilla Tactics should lock into Metronome, not tackle
|
||||||
expect(darmanitan.isMoveRestricted(MoveId.TACKLE)).toBe(true);
|
expect(darmanitan.isMoveRestricted(MoveId.TACKLE)).toBe(true);
|
||||||
expect(darmanitan.isMoveRestricted(MoveId.METRONOME)).toBe(false);
|
expect(darmanitan.isMoveRestricted(MoveId.METRONOME)).toBe(false);
|
||||||
expect(darmanitan.getLastXMoves(-1)).toEqual([
|
expect(darmanitan.getLastXMoves(-1)).toEqual([
|
||||||
|
@ -95,7 +95,7 @@ describe("Abilities - Harvest", () => {
|
|||||||
|
|
||||||
// Give ourselves harvest and disable enemy neut gas,
|
// Give ourselves harvest and disable enemy neut gas,
|
||||||
// but force our roll to fail so we don't accidentally recover anything
|
// but force our roll to fail so we don't accidentally recover anything
|
||||||
vi.spyOn(PostTurnRestoreBerryAbAttr.prototype, "canApplyPostTurn").mockReturnValueOnce(false);
|
vi.spyOn(PostTurnRestoreBerryAbAttr.prototype, "canApply").mockReturnValueOnce(false);
|
||||||
game.override.ability(AbilityId.HARVEST);
|
game.override.ability(AbilityId.HARVEST);
|
||||||
game.move.select(MoveId.GASTRO_ACID);
|
game.move.select(MoveId.GASTRO_ACID);
|
||||||
await game.move.selectEnemyMove(MoveId.NUZZLE);
|
await game.move.selectEnemyMove(MoveId.NUZZLE);
|
||||||
|
@ -42,7 +42,7 @@ describe("Abilities - Healer", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should not queue a message phase for healing if the ally has fainted", async () => {
|
it("should not queue a message phase for healing if the ally has fainted", async () => {
|
||||||
const abSpy = vi.spyOn(PostTurnResetStatusAbAttr.prototype, "canApplyPostTurn");
|
const abSpy = vi.spyOn(PostTurnResetStatusAbAttr.prototype, "canApply");
|
||||||
game.override.moveset([MoveId.SPLASH, MoveId.LUNAR_DANCE]);
|
game.override.moveset([MoveId.SPLASH, MoveId.LUNAR_DANCE]);
|
||||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]);
|
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]);
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ describe("Abilities - Moody", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should only decrease one stat stage by 1 stage if all stat stages are at 6", async () => {
|
it("should only decrease one stat stage by 1 stage if all stat stages are at 6", async () => {
|
||||||
await game.classicMode.startBattle();
|
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
||||||
|
|
||||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ describe("Abilities - Neutralizing Gas", () => {
|
|||||||
|
|
||||||
const enemy = game.scene.getEnemyPokemon()!;
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
const weatherChangeAttr = enemy.getAbilityAttrs("PostSummonWeatherChangeAbAttr", false)[0];
|
const weatherChangeAttr = enemy.getAbilityAttrs("PostSummonWeatherChangeAbAttr", false)[0];
|
||||||
vi.spyOn(weatherChangeAttr, "applyPostSummon");
|
const weatherChangeSpy = vi.spyOn(weatherChangeAttr, "apply");
|
||||||
|
|
||||||
expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeDefined();
|
expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeDefined();
|
||||||
|
|
||||||
@ -187,6 +187,6 @@ describe("Abilities - Neutralizing Gas", () => {
|
|||||||
await game.killPokemon(game.scene.getPlayerPokemon()!);
|
await game.killPokemon(game.scene.getPlayerPokemon()!);
|
||||||
|
|
||||||
expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeUndefined();
|
expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeUndefined();
|
||||||
expect(weatherChangeAttr.applyPostSummon).not.toHaveBeenCalled();
|
expect(weatherChangeSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { StatMultiplierAbAttrParams } from "#app/@types/ability-types";
|
||||||
import { allAbilities } from "#app/data/data-lists";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { CommandPhase } from "#app/phases/command-phase";
|
import { CommandPhase } from "#app/phases/command-phase";
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||||
@ -46,15 +47,13 @@ describe("Abilities - Sand Veil", () => {
|
|||||||
vi.spyOn(leadPokemon[0], "getAbility").mockReturnValue(allAbilities[AbilityId.SAND_VEIL]);
|
vi.spyOn(leadPokemon[0], "getAbility").mockReturnValue(allAbilities[AbilityId.SAND_VEIL]);
|
||||||
|
|
||||||
const sandVeilAttr = allAbilities[AbilityId.SAND_VEIL].getAttrs("StatMultiplierAbAttr")[0];
|
const sandVeilAttr = allAbilities[AbilityId.SAND_VEIL].getAttrs("StatMultiplierAbAttr")[0];
|
||||||
vi.spyOn(sandVeilAttr, "applyStatStage").mockImplementation(
|
vi.spyOn(sandVeilAttr, "apply").mockImplementation(({ stat, statVal }: StatMultiplierAbAttrParams) => {
|
||||||
(_pokemon, _passive, _simulated, stat, statValue, _args) => {
|
if (stat === Stat.EVA && game.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) {
|
||||||
if (stat === Stat.EVA && game.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) {
|
statVal.value *= -1; // will make all attacks miss
|
||||||
statValue.value *= -1; // will make all attacks miss
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
return false;
|
||||||
return false;
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(leadPokemon[0].hasAbility(AbilityId.SAND_VEIL)).toBe(true);
|
expect(leadPokemon[0].hasAbility(AbilityId.SAND_VEIL)).toBe(true);
|
||||||
expect(leadPokemon[1].hasAbility(AbilityId.SAND_VEIL)).toBe(false);
|
expect(leadPokemon[1].hasAbility(AbilityId.SAND_VEIL)).toBe(false);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { BattlerIndex } from "#enums/battler-index";
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
import { applyAbAttrs, applyPreDefendAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||||
import { NumberHolder } from "#app/utils/common";
|
import { NumberHolder } from "#app/utils/common";
|
||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
@ -52,25 +52,16 @@ describe("Abilities - Shield Dust", () => {
|
|||||||
expect(move.id).toBe(MoveId.AIR_SLASH);
|
expect(move.id).toBe(MoveId.AIR_SLASH);
|
||||||
|
|
||||||
const chance = new NumberHolder(move.chance);
|
const chance = new NumberHolder(move.chance);
|
||||||
await applyAbAttrs(
|
applyAbAttrs("MoveEffectChanceMultiplierAbAttr", {
|
||||||
"MoveEffectChanceMultiplierAbAttr",
|
pokemon: phase.getUserPokemon()!,
|
||||||
phase.getUserPokemon()!,
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
chance,
|
chance,
|
||||||
move,
|
move,
|
||||||
phase.getFirstTarget(),
|
});
|
||||||
false,
|
applyAbAttrs("IgnoreMoveEffectsAbAttr", {
|
||||||
);
|
pokemon: phase.getFirstTarget()!,
|
||||||
await applyPreDefendAbAttrs(
|
move,
|
||||||
"IgnoreMoveEffectsAbAttr",
|
|
||||||
phase.getFirstTarget()!,
|
|
||||||
phase.getUserPokemon()!,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
chance,
|
chance,
|
||||||
);
|
});
|
||||||
expect(chance.value).toBe(0);
|
expect(chance.value).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -22,10 +22,7 @@ describe("Abilities - Unburden", () => {
|
|||||||
*/
|
*/
|
||||||
function getHeldItemCount(pokemon: Pokemon): number {
|
function getHeldItemCount(pokemon: Pokemon): number {
|
||||||
const stackCounts = pokemon.getHeldItems().map(m => m.getStackCount());
|
const stackCounts = pokemon.getHeldItems().map(m => m.getStackCount());
|
||||||
if (stackCounts.length) {
|
return stackCounts.reduce((a, b) => a + b, 0);
|
||||||
return stackCounts.reduce((a, b) => a + b);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
@ -277,7 +274,7 @@ describe("Abilities - Unburden", () => {
|
|||||||
const initialTreeckoSpeed = treecko.getStat(Stat.SPD);
|
const initialTreeckoSpeed = treecko.getStat(Stat.SPD);
|
||||||
const initialPurrloinSpeed = purrloin.getStat(Stat.SPD);
|
const initialPurrloinSpeed = purrloin.getStat(Stat.SPD);
|
||||||
const unburdenAttr = treecko.getAbilityAttrs("PostItemLostAbAttr")[0];
|
const unburdenAttr = treecko.getAbilityAttrs("PostItemLostAbAttr")[0];
|
||||||
vi.spyOn(unburdenAttr, "applyPostItemLost");
|
vi.spyOn(unburdenAttr, "apply");
|
||||||
|
|
||||||
// Player uses Baton Pass, which also passes the Baton item
|
// Player uses Baton Pass, which also passes the Baton item
|
||||||
game.move.select(MoveId.BATON_PASS);
|
game.move.select(MoveId.BATON_PASS);
|
||||||
@ -288,7 +285,7 @@ describe("Abilities - Unburden", () => {
|
|||||||
expect(getHeldItemCount(purrloin)).toBe(1);
|
expect(getHeldItemCount(purrloin)).toBe(1);
|
||||||
expect(treecko.getEffectiveStat(Stat.SPD)).toBe(initialTreeckoSpeed);
|
expect(treecko.getEffectiveStat(Stat.SPD)).toBe(initialTreeckoSpeed);
|
||||||
expect(purrloin.getEffectiveStat(Stat.SPD)).toBe(initialPurrloinSpeed);
|
expect(purrloin.getEffectiveStat(Stat.SPD)).toBe(initialPurrloinSpeed);
|
||||||
expect(unburdenAttr.applyPostItemLost).not.toHaveBeenCalled();
|
expect(unburdenAttr.apply).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not speed up a Pokemon after it loses the ability Unburden", async () => {
|
it("should not speed up a Pokemon after it loses the ability Unburden", async () => {
|
||||||
|
79
test/field/pokemon-id-checks.test.ts
Normal file
79
test/field/pokemon-id-checks.test.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import type Pokemon from "#app/field/pokemon";
|
||||||
|
import { MoveId } from "#enums/move-id";
|
||||||
|
import { AbilityId } from "#enums/ability-id";
|
||||||
|
import { SpeciesId } from "#enums/species-id";
|
||||||
|
import { BattleType } from "#enums/battle-type";
|
||||||
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
|
|
||||||
|
describe("Field - Pokemon ID Checks", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.ability(AbilityId.NO_GUARD)
|
||||||
|
.battleStyle("single")
|
||||||
|
.battleType(BattleType.TRAINER)
|
||||||
|
.criticalHits(false)
|
||||||
|
.enemyLevel(100)
|
||||||
|
.enemySpecies(SpeciesId.ARCANINE)
|
||||||
|
.enemyAbility(AbilityId.BALL_FETCH)
|
||||||
|
.enemyMoveset(MoveId.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
function onlyUnique<T>(array: T[]): T[] {
|
||||||
|
return [...new Set<T>(array)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: We currently generate IDs as a pure random integer; enable once unique UUIDs are added
|
||||||
|
it.todo("2 Pokemon should not be able to generate with the same ID during 1 encounter", async () => {
|
||||||
|
game.override.battleType(BattleType.TRAINER); // enemy generates 2 mons
|
||||||
|
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.ABRA]);
|
||||||
|
|
||||||
|
const ids = (game.scene.getPlayerParty() as Pokemon[]).concat(game.scene.getEnemyParty()).map((p: Pokemon) => p.id);
|
||||||
|
const uniqueIds = onlyUnique(ids);
|
||||||
|
|
||||||
|
expect(ids).toHaveLength(uniqueIds.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not prevent Battler Tags from triggering if user has PID of 0", async () => {
|
||||||
|
await game.classicMode.startBattle([SpeciesId.TREECKO, SpeciesId.AERODACTYL]);
|
||||||
|
|
||||||
|
const player = game.field.getPlayerPokemon();
|
||||||
|
const enemy = game.field.getEnemyPokemon();
|
||||||
|
// Override player pokemon PID to be 0
|
||||||
|
player.id = 0;
|
||||||
|
expect(player.getTag(BattlerTagType.DESTINY_BOND)).toBeUndefined();
|
||||||
|
|
||||||
|
game.move.use(MoveId.DESTINY_BOND);
|
||||||
|
game.doSelectPartyPokemon(1);
|
||||||
|
await game.move.forceEnemyMove(MoveId.FLAME_WHEEL);
|
||||||
|
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
|
||||||
|
const dBondTag = player.getTag(BattlerTagType.DESTINY_BOND)!;
|
||||||
|
expect(dBondTag).toBeDefined();
|
||||||
|
expect(dBondTag.sourceId).toBe(0);
|
||||||
|
expect(dBondTag.getSourcePokemon()).toBe(player);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
|
||||||
|
expect(player.isFainted()).toBe(true);
|
||||||
|
expect(enemy.isFainted()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
@ -31,7 +31,7 @@ describe("Spec - Pokemon", () => {
|
|||||||
const pkm = game.scene.getPlayerPokemon()!;
|
const pkm = game.scene.getPlayerPokemon()!;
|
||||||
expect(pkm).toBeDefined();
|
expect(pkm).toBeDefined();
|
||||||
|
|
||||||
expect(pkm.trySetStatus(undefined)).toBe(true);
|
expect(pkm.trySetStatus(undefined)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Add To Party", () => {
|
describe("Add To Party", () => {
|
||||||
|
@ -140,9 +140,8 @@ describe("Moves - Safeguard", () => {
|
|||||||
game.field.mockAbility(player, AbilityId.STATIC);
|
game.field.mockAbility(player, AbilityId.STATIC);
|
||||||
vi.spyOn(
|
vi.spyOn(
|
||||||
allAbilities[AbilityId.STATIC].getAttrs("PostDefendContactApplyStatusEffectAbAttr")[0],
|
allAbilities[AbilityId.STATIC].getAttrs("PostDefendContactApplyStatusEffectAbAttr")[0],
|
||||||
"chance",
|
"canApply",
|
||||||
"get",
|
).mockReturnValue(true);
|
||||||
).mockReturnValue(100);
|
|
||||||
|
|
||||||
game.move.select(MoveId.SPLASH);
|
game.move.select(MoveId.SPLASH);
|
||||||
await game.move.forceEnemyMove(MoveId.SAFEGUARD);
|
await game.move.forceEnemyMove(MoveId.SAFEGUARD);
|
||||||
|
@ -122,15 +122,20 @@ export default class GameWrapper {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Replace this with a proper mock of phaser's TweenManager.
|
||||||
this.scene.tweens = {
|
this.scene.tweens = {
|
||||||
add: data => {
|
add: data => {
|
||||||
if (data.onComplete) {
|
// TODO: our mock of `add` should have the same signature as the real one, which returns the tween
|
||||||
data.onComplete();
|
data.onComplete?.();
|
||||||
}
|
|
||||||
},
|
},
|
||||||
getTweensOf: () => [],
|
getTweensOf: () => [],
|
||||||
killTweensOf: () => [],
|
killTweensOf: () => [],
|
||||||
chain: () => null,
|
|
||||||
|
chain: data => {
|
||||||
|
// TODO: our mock of `chain` should have the same signature as the real one, which returns the chain
|
||||||
|
data?.tweens?.forEach(tween => tween.onComplete?.());
|
||||||
|
data.onComplete?.();
|
||||||
|
},
|
||||||
addCounter: data => {
|
addCounter: data => {
|
||||||
if (data.onComplete) {
|
if (data.onComplete) {
|
||||||
data.onComplete();
|
data.onComplete();
|
||||||
|
@ -5,7 +5,7 @@ export class MockVideoGameObject implements MockGameObject {
|
|||||||
public name: string;
|
public name: string;
|
||||||
public active = true;
|
public active = true;
|
||||||
|
|
||||||
public play = () => null;
|
public play = () => this;
|
||||||
public stop = () => this;
|
public stop = () => this;
|
||||||
public setOrigin = () => this;
|
public setOrigin = () => this;
|
||||||
public setScale = () => this;
|
public setScale = () => this;
|
||||||
|
Loading…
Reference in New Issue
Block a user