mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-20 16:42:45 +02:00
Compare commits
32 Commits
5b7ad7010d
...
351ce98efa
Author | SHA1 | Date | |
---|---|---|---|
|
351ce98efa | ||
|
a826029a3e | ||
|
4b70fab608 | ||
|
1ff2701964 | ||
|
1e306e25b5 | ||
|
43aa772603 | ||
|
d0f4c4a840 | ||
|
df474da895 | ||
|
7391446dc2 | ||
|
f2bf6f374e | ||
|
8ea30f8ba7 | ||
|
6aef950e76 | ||
|
1fb7eb6891 | ||
|
c6af4acae3 | ||
|
bb8e7adfbe | ||
|
2c2834734d | ||
|
44d4088b35 | ||
|
d7d59723a4 | ||
|
03afffb07f | ||
|
8ff68e894a | ||
|
739c3779a9 | ||
|
bc3970ebf8 | ||
|
8f34ca1d61 | ||
|
eab7d6d1a0 | ||
|
8a96444b8c | ||
|
ef9a410920 | ||
|
f432f8cbf6 | ||
|
1deb74e926 | ||
|
8f66dbe69b | ||
|
b9781bd863 | ||
|
55093ec0af | ||
|
804809bb17 |
@ -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];
|
||||||
|
};
|
||||||
|
32
src/@types/type-helpers.ts
Normal file
32
src/@types/type-helpers.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* 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";
|
||||||
@ -1256,7 +1256,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 +1461,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 +2743,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 +2783,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 +2812,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,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,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 +202,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;
|
||||||
}
|
}
|
||||||
@ -758,7 +758,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;
|
||||||
}
|
}
|
||||||
@ -946,7 +946,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;
|
||||||
}
|
}
|
||||||
@ -1003,7 +1003,12 @@ class StickyWebTag extends ArenaTrapTag {
|
|||||||
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;
|
||||||
@ -1416,7 +1421,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 +1445,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 +1461,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 }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -621,7 +621,7 @@ export class FlinchedTag extends BattlerTag {
|
|||||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
applyAbAttrs("FlinchEffectAbAttr", pokemon, null);
|
applyAbAttrs("FlinchEffectAbAttr", { pokemon });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -916,7 +916,7 @@ export class SeedTag extends BattlerTag {
|
|||||||
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
|
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
|
||||||
if (source) {
|
if (source) {
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
|
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
|
||||||
|
|
||||||
if (!cancelled.value) {
|
if (!cancelled.value) {
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
@ -1006,7 +1006,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 +1056,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 });
|
||||||
@ -1409,7 +1409,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 });
|
||||||
@ -1642,7 +1642,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,
|
||||||
@ -2243,7 +2243,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);
|
||||||
@ -2297,7 +2297,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 +2632,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 });
|
||||||
@ -3021,14 +3021,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;
|
||||||
|
@ -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 {
|
||||||
@ -92,7 +88,12 @@ import { isVirtual, MoveUseMode } from "#enums/move-use-mode";
|
|||||||
import { ChargingMove, MoveAttrMap, MoveAttrString, MoveKindString, MoveClassMap } from "#app/@types/move-types";
|
import { 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}.
|
||||||
|
* Conventionally returns `true` for success and `false` for failure.
|
||||||
|
*/
|
||||||
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
|
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
|
||||||
export type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean;
|
export type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean;
|
||||||
|
|
||||||
@ -343,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)
|
||||||
@ -641,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;
|
||||||
}
|
}
|
||||||
@ -758,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;
|
||||||
@ -801,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(
|
||||||
@ -823,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;
|
||||||
|
|
||||||
@ -854,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;
|
||||||
}
|
}
|
||||||
@ -1306,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;
|
||||||
@ -1314,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;
|
||||||
}
|
}
|
||||||
@ -1390,18 +1400,31 @@ export class BeakBlastHeaderAttr extends AddBattlerTagHeaderAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute to display a message before a move is executed.
|
||||||
|
*/
|
||||||
export class PreMoveMessageAttr extends MoveAttr {
|
export class PreMoveMessageAttr extends MoveAttr {
|
||||||
private message: string | ((user: Pokemon, target: Pokemon, move: Move) => string);
|
/** The message to display or a function returning one */
|
||||||
|
private message: string | ((user: Pokemon, target: Pokemon, move: Move) => string | undefined);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@linkcode PreMoveMessageAttr} to display a message before move execution.
|
||||||
|
* @param message - The message to display before move use, either as a string or a function producing one.
|
||||||
|
* @remarks
|
||||||
|
* If {@linkcode message} evaluates to an empty string (`''`), no message will be displayed
|
||||||
|
* (though the move will still succeed).
|
||||||
|
*/
|
||||||
constructor(message: string | ((user: Pokemon, target: Pokemon, move: Move) => string)) {
|
constructor(message: string | ((user: Pokemon, target: Pokemon, move: Move) => string)) {
|
||||||
super();
|
super();
|
||||||
this.message = message;
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
apply(user: Pokemon, target: Pokemon, move: Move, _args: any[]): boolean {
|
||||||
const message = typeof this.message === "string"
|
const message = typeof this.message === "function"
|
||||||
? this.message as string
|
? this.message(user, target, move)
|
||||||
: this.message(user, target, move);
|
: this.message;
|
||||||
|
|
||||||
|
// TODO: Consider changing if/when MoveAttr `apply` return values become significant
|
||||||
if (message) {
|
if (message) {
|
||||||
globalScene.phaseManager.queueMessage(message, 500);
|
globalScene.phaseManager.queueMessage(message, 500);
|
||||||
return true;
|
return true;
|
||||||
@ -1692,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) {
|
||||||
@ -1826,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
|
||||||
@ -2025,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) {
|
||||||
@ -2397,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) {
|
||||||
@ -2505,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2557,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);
|
||||||
@ -2573,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2661,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;
|
||||||
@ -2778,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2804,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;
|
||||||
}
|
}
|
||||||
@ -2818,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);
|
||||||
@ -3009,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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -5421,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;
|
||||||
}
|
}
|
||||||
@ -6420,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) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6461,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;
|
||||||
}
|
}
|
||||||
@ -7970,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 }));
|
||||||
@ -11299,7 +11324,11 @@ export function initMoves() {
|
|||||||
.attr(ForceSwitchOutAttr, true, SwitchType.SHED_TAIL)
|
.attr(ForceSwitchOutAttr, true, SwitchType.SHED_TAIL)
|
||||||
.condition(failIfLastInPartyCondition),
|
.condition(failIfLastInPartyCondition),
|
||||||
new SelfStatusMove(MoveId.CHILLY_RECEPTION, PokemonType.ICE, -1, 10, -1, 0, 9)
|
new SelfStatusMove(MoveId.CHILLY_RECEPTION, PokemonType.ICE, -1, 10, -1, 0, 9)
|
||||||
.attr(PreMoveMessageAttr, (user, move) => i18next.t("moveTriggers:chillyReception", { pokemonName: getPokemonNameWithAffix(user) }))
|
.attr(PreMoveMessageAttr, (user, _target, _move) =>
|
||||||
|
// Don't display text if current move phase is follow up (ie move called indirectly)
|
||||||
|
isVirtual((globalScene.phaseManager.getCurrentPhase() as MovePhase).useMode)
|
||||||
|
? ""
|
||||||
|
: i18next.t("moveTriggers:chillyReception", { pokemonName: getPokemonNameWithAffix(user) }))
|
||||||
.attr(ChillyReceptionAttr, true),
|
.attr(ChillyReceptionAttr, true),
|
||||||
new SelfStatusMove(MoveId.TIDY_UP, PokemonType.NORMAL, -1, 10, -1, 0, 9)
|
new SelfStatusMove(MoveId.TIDY_UP, PokemonType.NORMAL, -1, 10, -1, 0, 9)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true)
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true)
|
||||||
|
@ -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");
|
||||||
|
@ -751,7 +751,7 @@ export async function catchPokemon(
|
|||||||
UiMode.POKEDEX_PAGE,
|
UiMode.POKEDEX_PAGE,
|
||||||
pokemon.species,
|
pokemon.species,
|
||||||
pokemon.formIndex,
|
pokemon.formIndex,
|
||||||
attributes,
|
[attributes],
|
||||||
null,
|
null,
|
||||||
() => {
|
() => {
|
||||||
globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
|
globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
|
||||||
|
@ -764,7 +764,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
|||||||
readonly subLegendary: boolean;
|
readonly subLegendary: boolean;
|
||||||
readonly legendary: boolean;
|
readonly legendary: boolean;
|
||||||
readonly mythical: boolean;
|
readonly mythical: boolean;
|
||||||
readonly species: string;
|
public category: string;
|
||||||
readonly growthRate: GrowthRate;
|
readonly growthRate: GrowthRate;
|
||||||
/** The chance (as a decimal) for this Species to be male, or `null` for genderless species */
|
/** The chance (as a decimal) for this Species to be male, or `null` for genderless species */
|
||||||
readonly malePercent: number | null;
|
readonly malePercent: number | null;
|
||||||
@ -778,7 +778,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
|||||||
subLegendary: boolean,
|
subLegendary: boolean,
|
||||||
legendary: boolean,
|
legendary: boolean,
|
||||||
mythical: boolean,
|
mythical: boolean,
|
||||||
species: string,
|
category: string,
|
||||||
type1: PokemonType,
|
type1: PokemonType,
|
||||||
type2: PokemonType | null,
|
type2: PokemonType | null,
|
||||||
height: number,
|
height: number,
|
||||||
@ -829,7 +829,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
|||||||
this.subLegendary = subLegendary;
|
this.subLegendary = subLegendary;
|
||||||
this.legendary = legendary;
|
this.legendary = legendary;
|
||||||
this.mythical = mythical;
|
this.mythical = mythical;
|
||||||
this.species = species;
|
this.category = category;
|
||||||
this.growthRate = growthRate;
|
this.growthRate = growthRate;
|
||||||
this.malePercent = malePercent;
|
this.malePercent = malePercent;
|
||||||
this.genderDiffs = genderDiffs;
|
this.genderDiffs = genderDiffs;
|
||||||
@ -968,6 +968,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
|||||||
|
|
||||||
localize(): void {
|
localize(): void {
|
||||||
this.name = i18next.t(`pokemon:${SpeciesId[this.speciesId].toLowerCase()}`);
|
this.name = i18next.t(`pokemon:${SpeciesId[this.speciesId].toLowerCase()}`);
|
||||||
|
this.category = i18next.t(`pokemonCategory:${SpeciesId[this.speciesId].toLowerCase()}_category`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getWildSpeciesForLevel(level: number, allowEvolving: boolean, isBoss: boolean, gameMode: GameMode): SpeciesId {
|
getWildSpeciesForLevel(level: number, allowEvolving: boolean, isBoss: boolean, gameMode: GameMode): SpeciesId {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
@ -3358,7 +3377,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);
|
||||||
@ -3397,8 +3421,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);
|
||||||
@ -3418,33 +3443,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;
|
||||||
@ -3559,7 +3591,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) {
|
||||||
@ -3706,16 +3738,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 */
|
||||||
@ -3726,7 +3756,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]
|
||||||
@ -3747,7 +3777,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;
|
||||||
@ -3811,7 +3845,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 */
|
||||||
@ -3822,14 +3862,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,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3837,7 +3888,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)
|
||||||
@ -3875,7 +3926,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)];
|
||||||
|
|
||||||
@ -3886,7 +3937,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,
|
||||||
@ -3998,7 +4049,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;
|
||||||
}
|
}
|
||||||
@ -4046,11 +4097,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;
|
||||||
@ -4066,13 +4123,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;
|
||||||
}
|
}
|
||||||
@ -4597,7 +4654,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,
|
||||||
@ -4628,8 +4685,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;
|
||||||
}
|
}
|
||||||
@ -4678,21 +4741,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;
|
||||||
}
|
}
|
||||||
@ -4723,6 +4785,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;
|
||||||
}
|
}
|
||||||
@ -4781,7 +4846,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;
|
||||||
@ -4842,7 +4906,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;
|
||||||
}
|
}
|
||||||
@ -5391,7 +5455,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);
|
||||||
@ -5451,7 +5515,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";
|
||||||
|
|
||||||
@ -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);
|
||||||
@ -38,14 +38,11 @@ export class AttemptRunPhase extends PokemonPhase {
|
|||||||
alpha: 0,
|
alpha: 0,
|
||||||
duration: 250,
|
duration: 250,
|
||||||
ease: "Sine.easeIn",
|
ease: "Sine.easeIn",
|
||||||
onComplete: () =>
|
onComplete: () => enemyField.forEach(enemyPokemon => enemyPokemon.destroy()),
|
||||||
// biome-ignore lint/complexity/noForEach: TODO
|
|
||||||
enemyField.forEach(enemyPokemon => enemyPokemon.destroy()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
globalScene.clearEnemyHeldItemModifiers();
|
globalScene.clearEnemyHeldItemModifiers();
|
||||||
|
|
||||||
// biome-ignore lint/complexity/noForEach: TODO
|
|
||||||
enemyField.forEach(enemyPokemon => {
|
enemyField.forEach(enemyPokemon => {
|
||||||
enemyPokemon.hideInfo().then(() => enemyPokemon.destroy());
|
enemyPokemon.hideInfo().then(() => enemyPokemon.destroy());
|
||||||
enemyPokemon.hp = 0;
|
enemyPokemon.hp = 0;
|
||||||
|
@ -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);
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -808,7 +802,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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1002,7 +998,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 */
|
||||||
@ -668,6 +679,9 @@ export class MovePhase extends BattlePhase {
|
|||||||
}),
|
}),
|
||||||
500,
|
500,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Moves with pre-use messages (Magnitude, Chilly Reception, Fickle Beam, etc.) always display their messages even on failure
|
||||||
|
// TODO: This assumes single target for message funcs - is this sustainable?
|
||||||
applyMoveAttrs("PreMoveMessageAttr", this.pokemon, this.pokemon.getOpponents(false)[0], this.move.getMove());
|
applyMoveAttrs("PreMoveMessageAttr", this.pokemon, this.pokemon.getOpponents(false)[0], this.move.getMove());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -245,6 +245,7 @@ export async function initI18n(): Promise<void> {
|
|||||||
"pokeball",
|
"pokeball",
|
||||||
"pokedexUiHandler",
|
"pokedexUiHandler",
|
||||||
"pokemon",
|
"pokemon",
|
||||||
|
"pokemonCategory",
|
||||||
"pokemonEvolutions",
|
"pokemonEvolutions",
|
||||||
"pokemonForm",
|
"pokemonForm",
|
||||||
"pokemonInfo",
|
"pokemonInfo",
|
||||||
|
@ -174,6 +174,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
|||||||
private pokemonCaughtHatchedContainer: Phaser.GameObjects.Container;
|
private pokemonCaughtHatchedContainer: Phaser.GameObjects.Container;
|
||||||
private pokemonCaughtCountText: Phaser.GameObjects.Text;
|
private pokemonCaughtCountText: Phaser.GameObjects.Text;
|
||||||
private pokemonFormText: Phaser.GameObjects.Text;
|
private pokemonFormText: Phaser.GameObjects.Text;
|
||||||
|
private pokemonCategoryText: Phaser.GameObjects.Text;
|
||||||
private pokemonHatchedIcon: Phaser.GameObjects.Sprite;
|
private pokemonHatchedIcon: Phaser.GameObjects.Sprite;
|
||||||
private pokemonHatchedCountText: Phaser.GameObjects.Text;
|
private pokemonHatchedCountText: Phaser.GameObjects.Text;
|
||||||
private pokemonShinyIcons: Phaser.GameObjects.Sprite[];
|
private pokemonShinyIcons: Phaser.GameObjects.Sprite[];
|
||||||
@ -409,6 +410,12 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
|||||||
this.pokemonFormText.setOrigin(0, 0);
|
this.pokemonFormText.setOrigin(0, 0);
|
||||||
this.starterSelectContainer.add(this.pokemonFormText);
|
this.starterSelectContainer.add(this.pokemonFormText);
|
||||||
|
|
||||||
|
this.pokemonCategoryText = addTextObject(100, 18, "Category", TextStyle.WINDOW_ALT, {
|
||||||
|
fontSize: "42px",
|
||||||
|
});
|
||||||
|
this.pokemonCategoryText.setOrigin(1, 0);
|
||||||
|
this.starterSelectContainer.add(this.pokemonCategoryText);
|
||||||
|
|
||||||
this.pokemonCaughtHatchedContainer = globalScene.add.container(2, 25);
|
this.pokemonCaughtHatchedContainer = globalScene.add.container(2, 25);
|
||||||
this.pokemonCaughtHatchedContainer.setScale(0.5);
|
this.pokemonCaughtHatchedContainer.setScale(0.5);
|
||||||
this.starterSelectContainer.add(this.pokemonCaughtHatchedContainer);
|
this.starterSelectContainer.add(this.pokemonCaughtHatchedContainer);
|
||||||
@ -2354,6 +2361,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
|||||||
this.pokemonCaughtHatchedContainer.setVisible(true);
|
this.pokemonCaughtHatchedContainer.setVisible(true);
|
||||||
this.pokemonCandyContainer.setVisible(false);
|
this.pokemonCandyContainer.setVisible(false);
|
||||||
this.pokemonFormText.setVisible(false);
|
this.pokemonFormText.setVisible(false);
|
||||||
|
this.pokemonCategoryText.setVisible(false);
|
||||||
|
|
||||||
const defaultDexAttr = globalScene.gameData.getSpeciesDefaultDexAttr(species, true, true);
|
const defaultDexAttr = globalScene.gameData.getSpeciesDefaultDexAttr(species, true, true);
|
||||||
const props = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
|
const props = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
|
||||||
@ -2382,6 +2390,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
|||||||
this.pokemonCaughtHatchedContainer.setVisible(false);
|
this.pokemonCaughtHatchedContainer.setVisible(false);
|
||||||
this.pokemonCandyContainer.setVisible(false);
|
this.pokemonCandyContainer.setVisible(false);
|
||||||
this.pokemonFormText.setVisible(false);
|
this.pokemonFormText.setVisible(false);
|
||||||
|
this.pokemonCategoryText.setVisible(false);
|
||||||
|
|
||||||
this.setSpeciesDetails(species!, {
|
this.setSpeciesDetails(species!, {
|
||||||
// TODO: is this bang correct?
|
// TODO: is this bang correct?
|
||||||
@ -2534,6 +2543,13 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
|||||||
this.pokemonNameText.setText(species ? "???" : "");
|
this.pokemonNameText.setText(species ? "???" : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setting the category
|
||||||
|
if (isFormCaught) {
|
||||||
|
this.pokemonCategoryText.setText(species.category);
|
||||||
|
} else {
|
||||||
|
this.pokemonCategoryText.setText("");
|
||||||
|
}
|
||||||
|
|
||||||
// Setting tint of the sprite
|
// Setting tint of the sprite
|
||||||
if (isFormCaught) {
|
if (isFormCaught) {
|
||||||
this.species.loadAssets(female!, formIndex, shiny, variant as Variant, true).then(() => {
|
this.species.loadAssets(female!, formIndex, shiny, variant as Variant, true).then(() => {
|
||||||
|
@ -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()!;
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -277,7 +277,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 +288,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 () => {
|
||||||
|
@ -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", () => {
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import { AbilityId } from "#enums/ability-id";
|
import { RandomMoveAttr } from "#app/data/moves/move";
|
||||||
|
import { MoveResult } from "#enums/move-result";
|
||||||
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
|
import { AbilityId } from "#app/enums/ability-id";
|
||||||
import { WeatherType } from "#enums/weather-type";
|
import { WeatherType } from "#enums/weather-type";
|
||||||
import GameManager from "#test/testUtils/gameManager";
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
|
import i18next from "i18next";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
//import { TurnInitPhase } from "#app/phases/turn-init-phase";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
||||||
|
|
||||||
describe("Moves - Chilly Reception", () => {
|
describe("Moves - Chilly Reception", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -25,95 +28,121 @@ describe("Moves - Chilly Reception", () => {
|
|||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
game.override
|
game.override
|
||||||
.battleStyle("single")
|
.battleStyle("single")
|
||||||
.moveset([MoveId.CHILLY_RECEPTION, MoveId.SNOWSCAPE])
|
.moveset([MoveId.CHILLY_RECEPTION, MoveId.SNOWSCAPE, MoveId.SPLASH, MoveId.METRONOME])
|
||||||
.enemyMoveset(MoveId.SPLASH)
|
.enemyMoveset(MoveId.SPLASH)
|
||||||
.enemyAbility(AbilityId.BALL_FETCH)
|
.enemyAbility(AbilityId.BALL_FETCH)
|
||||||
.ability(AbilityId.BALL_FETCH);
|
.ability(AbilityId.BALL_FETCH);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should still change the weather if user can't switch out", async () => {
|
it("should display message before use, switch the user out and change the weather to snow", async () => {
|
||||||
|
await game.classicMode.startBattle([SpeciesId.SLOWKING, SpeciesId.MEOWTH]);
|
||||||
|
|
||||||
|
const [slowking, meowth] = game.scene.getPlayerParty();
|
||||||
|
|
||||||
|
game.move.select(MoveId.CHILLY_RECEPTION);
|
||||||
|
game.doSelectPartyPokemon(1);
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
||||||
|
expect(game.scene.getPlayerPokemon()).toBe(meowth);
|
||||||
|
expect(slowking.isOnField()).toBe(false);
|
||||||
|
expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase");
|
||||||
|
expect(game.textInterceptor.logs).toContain(
|
||||||
|
i18next.t("moveTriggers:chillyReception", { pokemonName: getPokemonNameWithAffix(slowking) }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should still change weather if user can't switch out", async () => {
|
||||||
await game.classicMode.startBattle([SpeciesId.SLOWKING]);
|
await game.classicMode.startBattle([SpeciesId.SLOWKING]);
|
||||||
|
|
||||||
game.move.select(MoveId.CHILLY_RECEPTION);
|
game.move.select(MoveId.CHILLY_RECEPTION);
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
||||||
|
expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase");
|
||||||
|
expect(game.scene.getPlayerPokemon()?.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should switch out even if it's snowing", async () => {
|
it("should still switch out even if weather cannot be changed", async () => {
|
||||||
await game.classicMode.startBattle([SpeciesId.SLOWKING, SpeciesId.MEOWTH]);
|
await game.classicMode.startBattle([SpeciesId.SLOWKING, SpeciesId.MEOWTH]);
|
||||||
// first turn set up snow with snowscape, try chilly reception on second turn
|
|
||||||
|
expect(game.scene.arena.weather?.weatherType).not.toBe(WeatherType.SNOW);
|
||||||
|
|
||||||
|
const [slowking, meowth] = game.scene.getPlayerParty();
|
||||||
|
|
||||||
game.move.select(MoveId.SNOWSCAPE);
|
game.move.select(MoveId.SNOWSCAPE);
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
await game.toNextTurn();
|
||||||
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
||||||
|
|
||||||
await game.phaseInterceptor.to("TurnInitPhase", false);
|
|
||||||
game.move.select(MoveId.CHILLY_RECEPTION);
|
game.move.select(MoveId.CHILLY_RECEPTION);
|
||||||
game.doSelectPartyPokemon(1);
|
game.doSelectPartyPokemon(1);
|
||||||
|
// TODO: Uncomment lines once wimp out PR fixes force switches to not reset summon data immediately
|
||||||
|
// await game.phaseInterceptor.to("SwitchSummonPhase", false);
|
||||||
|
// expect(slowking.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
||||||
|
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
||||||
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MEOWTH);
|
expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase");
|
||||||
|
expect(game.scene.getPlayerPokemon()).toBe(meowth);
|
||||||
|
expect(slowking.isOnField()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("happy case - switch out and weather changes", async () => {
|
// Source: https://replay.pokemonshowdown.com/gen9ou-2367532550
|
||||||
|
it("should fail (while still displaying message) if neither weather change nor switch out succeeds", async () => {
|
||||||
|
await game.classicMode.startBattle([SpeciesId.SLOWKING]);
|
||||||
|
|
||||||
|
expect(game.scene.arena.weather?.weatherType).not.toBe(WeatherType.SNOW);
|
||||||
|
|
||||||
|
const slowking = game.scene.getPlayerPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(MoveId.SNOWSCAPE);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
||||||
|
|
||||||
|
game.move.select(MoveId.CHILLY_RECEPTION);
|
||||||
|
game.doSelectPartyPokemon(1);
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
||||||
|
expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase");
|
||||||
|
expect(game.scene.getPlayerPokemon()).toBe(slowking);
|
||||||
|
expect(slowking.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||||
|
expect(game.textInterceptor.logs).toContain(
|
||||||
|
i18next.t("moveTriggers:chillyReception", { pokemonName: getPokemonNameWithAffix(slowking) }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should succeed without message if called indirectly", async () => {
|
||||||
|
vi.spyOn(RandomMoveAttr.prototype, "getMoveOverride").mockReturnValue(MoveId.CHILLY_RECEPTION);
|
||||||
await game.classicMode.startBattle([SpeciesId.SLOWKING, SpeciesId.MEOWTH]);
|
await game.classicMode.startBattle([SpeciesId.SLOWKING, SpeciesId.MEOWTH]);
|
||||||
|
|
||||||
game.move.select(MoveId.CHILLY_RECEPTION);
|
const [slowking, meowth] = game.scene.getPlayerParty();
|
||||||
game.doSelectPartyPokemon(1);
|
|
||||||
|
game.move.select(MoveId.METRONOME);
|
||||||
|
game.doSelectPartyPokemon(1);
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
||||||
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MEOWTH);
|
expect(game.scene.getPlayerPokemon()).toBe(meowth);
|
||||||
|
expect(slowking.isOnField()).toBe(false);
|
||||||
|
expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase");
|
||||||
|
expect(game.textInterceptor.logs).not.toContain(
|
||||||
|
i18next.t("moveTriggers:chillyReception", { pokemonName: getPokemonNameWithAffix(slowking) }),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// enemy uses another move and weather doesn't change
|
// Bugcheck test for enemy AI bug
|
||||||
it("check case - enemy not selecting chilly reception doesn't change weather ", async () => {
|
it("check case - enemy not selecting chilly reception doesn't change weather", async () => {
|
||||||
game.override.battleStyle("single").enemyMoveset([MoveId.CHILLY_RECEPTION, MoveId.TACKLE]).moveset(MoveId.SPLASH);
|
game.override.enemyMoveset([MoveId.CHILLY_RECEPTION, MoveId.TACKLE]);
|
||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.SLOWKING, SpeciesId.MEOWTH]);
|
await game.classicMode.startBattle([SpeciesId.SLOWKING, SpeciesId.MEOWTH]);
|
||||||
|
|
||||||
game.move.select(MoveId.SPLASH);
|
game.move.select(MoveId.SPLASH);
|
||||||
await game.move.selectEnemyMove(MoveId.TACKLE);
|
await game.move.selectEnemyMove(MoveId.TACKLE);
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
expect(game.scene.arena.weather?.weatherType).toBeUndefined();
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("enemy trainer - expected behavior ", async () => {
|
|
||||||
game.override
|
|
||||||
.battleStyle("single")
|
|
||||||
.startingWave(8)
|
|
||||||
.enemyMoveset(MoveId.CHILLY_RECEPTION)
|
|
||||||
.enemySpecies(SpeciesId.MAGIKARP)
|
|
||||||
.moveset([MoveId.SPLASH, MoveId.THUNDERBOLT]);
|
|
||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.JOLTEON]);
|
|
||||||
const RIVAL_MAGIKARP1 = game.scene.getEnemyPokemon()?.id;
|
|
||||||
|
|
||||||
game.move.select(MoveId.SPLASH);
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
|
||||||
expect(game.scene.getEnemyPokemon()?.id !== RIVAL_MAGIKARP1);
|
|
||||||
|
|
||||||
await game.phaseInterceptor.to("TurnInitPhase", false);
|
|
||||||
game.move.select(MoveId.SPLASH);
|
|
||||||
|
|
||||||
// second chilly reception should still switch out
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
|
||||||
await game.phaseInterceptor.to("TurnInitPhase", false);
|
|
||||||
expect(game.scene.getEnemyPokemon()?.id === RIVAL_MAGIKARP1);
|
|
||||||
game.move.select(MoveId.THUNDERBOLT);
|
|
||||||
|
|
||||||
// enemy chilly recep move should fail: it's snowing and no option to switch out
|
|
||||||
// no crashing
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
|
||||||
await game.phaseInterceptor.to("TurnInitPhase", false);
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
|
||||||
game.move.select(MoveId.SPLASH);
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user