This commit is contained in:
Sirz Benjie 2025-06-19 21:02:14 -07:00 committed by GitHub
commit 351ce98efa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 2005 additions and 3899 deletions

View File

@ -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];
};

View 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];
};

View File

@ -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

View File

@ -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,
);
} }

View File

@ -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 }));
} }
} }
} }

View File

@ -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;

View File

@ -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(),

View File

@ -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,6 +88,7 @@ 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}. * A function used to conditionally determine execution of a given {@linkcode MoveAttr}.
@ -347,7 +344,7 @@ export default abstract class Move implements Localizable {
const bypassed = new BooleanHolder(false); const bypassed = new BooleanHolder(false);
// TODO: Allow this to be simulated // TODO: Allow this to be simulated
applyAbAttrs("InfiltratorAbAttr", user, null, false, bypassed); applyAbAttrs("InfiltratorAbAttr", {pokemon: user, bypassed});
return !bypassed.value return !bypassed.value
&& !this.hasFlag(MoveFlags.SOUND_BASED) && !this.hasFlag(MoveFlags.SOUND_BASED)
@ -645,7 +642,7 @@ export default abstract class Move implements Localizable {
case MoveFlags.IGNORE_ABILITIES: case MoveFlags.IGNORE_ABILITIES:
if (user.hasAbilityWithAttr("MoveAbilityBypassAbAttr")) { if (user.hasAbilityWithAttr("MoveAbilityBypassAbAttr")) {
const abilityEffectsIgnored = new BooleanHolder(false); const abilityEffectsIgnored = new BooleanHolder(false);
applyAbAttrs("MoveAbilityBypassAbAttr", user, abilityEffectsIgnored, false, this); applyAbAttrs("MoveAbilityBypassAbAttr", {pokemon: user, cancelled: abilityEffectsIgnored, move: this});
if (abilityEffectsIgnored.value) { if (abilityEffectsIgnored.value) {
return true; return true;
} }
@ -762,7 +759,7 @@ export default abstract class Move implements Localizable {
const moveAccuracy = new NumberHolder(this.accuracy); const moveAccuracy = new NumberHolder(this.accuracy);
applyMoveAttrs("VariableAccuracyAttr", user, target, this, moveAccuracy); applyMoveAttrs("VariableAccuracyAttr", user, target, this, moveAccuracy);
applyPreDefendAbAttrs("WonderSkinAbAttr", target, user, this, { value: false }, simulated, moveAccuracy); applyAbAttrs("WonderSkinAbAttr", {pokemon: target, opponent: user, move: this, simulated, accuracy: moveAccuracy});
if (moveAccuracy.value === -1) { if (moveAccuracy.value === -1) {
return moveAccuracy.value; return moveAccuracy.value;
@ -805,17 +802,25 @@ export default abstract class Move implements Localizable {
const typeChangeMovePowerMultiplier = new NumberHolder(1); const typeChangeMovePowerMultiplier = new NumberHolder(1);
const typeChangeHolder = new NumberHolder(this.type); const typeChangeHolder = new NumberHolder(this.type);
applyPreAttackAbAttrs("MoveTypeChangeAbAttr", source, target, this, true, typeChangeHolder, typeChangeMovePowerMultiplier); applyAbAttrs("MoveTypeChangeAbAttr", {pokemon: source, opponent: target, move: this, simulated: true, moveType: typeChangeHolder, power: typeChangeMovePowerMultiplier});
const sourceTeraType = source.getTeraType(); const sourceTeraType = source.getTeraType();
if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
power.value = 60; power.value = 60;
} }
applyPreAttackAbAttrs("VariableMovePowerAbAttr", source, target, this, simulated, power); const abAttrParams: PreAttackModifyPowerAbAttrParams = {
pokemon: source,
opponent: target,
simulated,
power,
move: this,
}
applyAbAttrs("VariableMovePowerAbAttr", abAttrParams);
const ally = source.getAlly(); const ally = source.getAlly();
if (!isNullOrUndefined(ally)) { if (!isNullOrUndefined(ally)) {
applyPreAttackAbAttrs("AllyMoveCategoryPowerBoostAbAttr", ally, target, this, simulated, power); applyAbAttrs("AllyMoveCategoryPowerBoostAbAttr", {...abAttrParams, pokemon: ally});
} }
const fieldAuras = new Set( const fieldAuras = new Set(
@ -827,11 +832,12 @@ export default abstract class Move implements Localizable {
.flat(), .flat(),
); );
for (const aura of fieldAuras) { for (const aura of fieldAuras) {
aura.applyPreAttack(source, null, simulated, target, this, [ power ]); // TODO: Refactor the fieldAura attribute so that its apply method is not directly called
aura.apply({pokemon: source, simulated, opponent: target, move: this, power});
} }
const alliedField: Pokemon[] = source.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); const alliedField: Pokemon[] = source.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
alliedField.forEach(p => applyPreAttackAbAttrs("UserFieldMoveTypePowerBoostAbAttr", p, target, this, simulated, power)); alliedField.forEach(p => applyAbAttrs("UserFieldMoveTypePowerBoostAbAttr", {pokemon: p, opponent: target, move: this, simulated, power}));
power.value *= typeChangeMovePowerMultiplier.value; power.value *= typeChangeMovePowerMultiplier.value;
@ -858,7 +864,7 @@ export default abstract class Move implements Localizable {
const priority = new NumberHolder(this.priority); const priority = new NumberHolder(this.priority);
applyMoveAttrs("IncrementMovePriorityAttr", user, null, this, priority); applyMoveAttrs("IncrementMovePriorityAttr", user, null, this, priority);
applyAbAttrs("ChangeMovePriorityAbAttr", user, null, simulated, this, priority); applyAbAttrs("ChangeMovePriorityAbAttr", {pokemon: user, simulated, move: this, priority});
return priority.value; return priority.value;
} }
@ -1310,7 +1316,7 @@ export class MoveEffectAttr extends MoveAttr {
getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): number { getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): number {
const moveChance = new NumberHolder(this.effectChanceOverride ?? move.chance); const moveChance = new NumberHolder(this.effectChanceOverride ?? move.chance);
applyAbAttrs("MoveEffectChanceMultiplierAbAttr", user, null, !showAbility, moveChance, move); applyAbAttrs("MoveEffectChanceMultiplierAbAttr", {pokemon: user, simulated: !showAbility, chance: moveChance, move});
if ((!move.hasAttr("FlinchAttr") || moveChance.value <= move.chance) && !move.hasAttr("SecretPowerAttr")) { if ((!move.hasAttr("FlinchAttr") || moveChance.value <= move.chance) && !move.hasAttr("SecretPowerAttr")) {
const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
@ -1318,7 +1324,7 @@ export class MoveEffectAttr extends MoveAttr {
} }
if (!selfEffect) { if (!selfEffect) {
applyPreDefendAbAttrs("IgnoreMoveEffectsAbAttr", target, user, null, null, !showAbility, moveChance); applyAbAttrs("IgnoreMoveEffectsAbAttr", {pokemon: target, move, simulated: !showAbility, chance: moveChance});
} }
return moveChance.value; return moveChance.value;
} }
@ -1709,8 +1715,9 @@ export class RecoilAttr extends MoveEffectAttr {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
if (!this.unblockable) { if (!this.unblockable) {
applyAbAttrs("BlockRecoilDamageAttr", user, cancelled); const abAttrParams: AbAttrParamsWithCancel = {pokemon: user, cancelled};
applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled); applyAbAttrs("BlockRecoilDamageAttr", abAttrParams);
applyAbAttrs("BlockNonDirectDamageAbAttr", abAttrParams);
} }
if (cancelled.value) { if (cancelled.value) {
@ -1843,7 +1850,7 @@ export class HalfSacrificialAttr extends MoveEffectAttr {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
// Check to see if the Pokemon has an ability that blocks non-direct damage // Check to see if the Pokemon has an ability that blocks non-direct damage
applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", {pokemon: user, cancelled});
if (!cancelled.value) { if (!cancelled.value) {
user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT, ignoreSegments: true }); user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT, ignoreSegments: true });
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", { pokemonName: getPokemonNameWithAffix(user) })); // Queue recoil message globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", { pokemonName: getPokemonNameWithAffix(user) })); // Queue recoil message
@ -2042,7 +2049,7 @@ export class FlameBurstAttr extends MoveEffectAttr {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
if (!isNullOrUndefined(targetAlly)) { if (!isNullOrUndefined(targetAlly)) {
applyAbAttrs("BlockNonDirectDamageAbAttr", targetAlly, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", {pokemon: targetAlly, cancelled});
} }
if (cancelled.value || !targetAlly || targetAlly.switchOutStatus) { if (cancelled.value || !targetAlly || targetAlly.switchOutStatus) {
@ -2414,7 +2421,7 @@ export class MultiHitAttr extends MoveAttr {
{ {
const rand = user.randBattleSeedInt(20); const rand = user.randBattleSeedInt(20);
const hitValue = new NumberHolder(rand); const hitValue = new NumberHolder(rand);
applyAbAttrs("MaxMultiHitAbAttr", user, null, false, hitValue); applyAbAttrs("MaxMultiHitAbAttr", {pokemon: user, hits: hitValue});
if (hitValue.value >= 13) { if (hitValue.value >= 13) {
return 2; return 2;
} else if (hitValue.value >= 6) { } else if (hitValue.value >= 6) {
@ -2522,7 +2529,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
} }
if (((!pokemon.status || this.overrideStatus) || (pokemon.status.effect === this.effect && moveChance < 0)) if (((!pokemon.status || this.overrideStatus) || (pokemon.status.effect === this.effect && moveChance < 0))
&& pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining, null, this.overrideStatus, quiet)) { && pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining, null, this.overrideStatus, quiet)) {
applyPostAttackAbAttrs("ConfusionOnStatusEffectAbAttr", user, target, move, null, false, this.effect); applyAbAttrs("ConfusionOnStatusEffectAbAttr", {pokemon: user, opponent: target, move, effect: this.effect});
return true; return true;
} }
} }
@ -2574,7 +2581,7 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : undefined); const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : undefined);
if (target.status) { if (target.status || !statusToApply) {
return false; return false;
} else { } else {
const canSetStatus = target.canSetStatus(statusToApply, true, false, user); const canSetStatus = target.canSetStatus(statusToApply, true, false, user);
@ -2590,7 +2597,8 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
} }
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
return !target.status && target.canSetStatus(user.status?.effect, true, false, user) ? -10 : 0; const statusToApply = user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : undefined);
return !target.status && statusToApply && target.canSetStatus(statusToApply, true, false, user) ? -10 : 0;
} }
} }
@ -2678,7 +2686,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
// Check for abilities that block item theft // Check for abilities that block item theft
// TODO: This should not trigger if the target would faint beforehand // TODO: This should not trigger if the target would faint beforehand
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockItemTheftAbAttr", target, cancelled); applyAbAttrs("BlockItemTheftAbAttr", {pokemon: target, cancelled});
if (cancelled.value) { if (cancelled.value) {
return false; return false;
@ -2795,8 +2803,8 @@ export class EatBerryAttr extends MoveEffectAttr {
protected eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) { protected eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) {
// consumer eats berry, owner triggers unburden and similar effects // consumer eats berry, owner triggers unburden and similar effects
getBerryEffectFunc(this.chosenBerry.berryType)(consumer); getBerryEffectFunc(this.chosenBerry.berryType)(consumer);
applyPostItemLostAbAttrs("PostItemLostAbAttr", berryOwner, false); applyAbAttrs("PostItemLostAbAttr", {pokemon: berryOwner});
applyAbAttrs("HealFromBerryUseAbAttr", consumer, new BooleanHolder(false)); applyAbAttrs("HealFromBerryUseAbAttr", {pokemon: consumer});
consumer.recordEatenBerry(this.chosenBerry.berryType, updateHarvest); consumer.recordEatenBerry(this.chosenBerry.berryType, updateHarvest);
} }
} }
@ -2821,7 +2829,7 @@ export class StealEatBerryAttr extends EatBerryAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
// check for abilities that block item theft // check for abilities that block item theft
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockItemTheftAbAttr", target, cancelled); applyAbAttrs("BlockItemTheftAbAttr", {pokemon: target, cancelled});
if (cancelled.value === true) { if (cancelled.value === true) {
return false; return false;
} }
@ -2835,7 +2843,7 @@ export class StealEatBerryAttr extends EatBerryAttr {
// pick a random berry and eat it // pick a random berry and eat it
this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)]; this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)];
applyPostItemLostAbAttrs("PostItemLostAbAttr", target, false); applyAbAttrs("PostItemLostAbAttr", {pokemon: target});
const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name }); const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name });
globalScene.phaseManager.queueMessage(message); globalScene.phaseManager.queueMessage(message);
this.reduceBerryModifier(target); this.reduceBerryModifier(target);
@ -3026,7 +3034,7 @@ export class OneHitKOAttr extends MoveAttr {
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc {
return (user, target, move) => { return (user, target, move) => {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockOneHitKOAbAttr", target, cancelled); applyAbAttrs("BlockOneHitKOAbAttr", {pokemon: target, cancelled});
return !cancelled.value && user.level >= target.level; return !cancelled.value && user.level >= target.level;
}; };
} }
@ -5438,7 +5446,7 @@ export class NoEffectAttr extends MoveAttr {
const crashDamageFunc = (user: Pokemon, move: Move) => { const crashDamageFunc = (user: Pokemon, move: Move) => {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", {pokemon: user, cancelled});
if (cancelled.value) { if (cancelled.value) {
return false; return false;
} }
@ -6437,9 +6445,9 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
} }
getFailedText(_user: Pokemon, target: Pokemon, _move: Move): string | undefined { getFailedText(_user: Pokemon, target: Pokemon, _move: Move): string | undefined {
const blockedByAbility = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs("ForceSwitchOutImmunityAbAttr", target, blockedByAbility); applyAbAttrs("ForceSwitchOutImmunityAbAttr", {pokemon: target, cancelled});
if (blockedByAbility.value) { if (cancelled.value) {
return i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) }); return i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) });
} }
} }
@ -6478,7 +6486,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
} }
const blockedByAbility = new BooleanHolder(false); const blockedByAbility = new BooleanHolder(false);
applyAbAttrs("ForceSwitchOutImmunityAbAttr", target, blockedByAbility); applyAbAttrs("ForceSwitchOutImmunityAbAttr", {pokemon: target, cancelled: blockedByAbility});
if (blockedByAbility.value) { if (blockedByAbility.value) {
return false; return false;
} }
@ -7987,7 +7995,7 @@ const failIfSingleBattle: MoveConditionFunc = (user, target, move) => globalScen
const failIfDampCondition: MoveConditionFunc = (user, target, move) => { const failIfDampCondition: MoveConditionFunc = (user, target, move) => {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
globalScene.getField(true).map(p=>applyAbAttrs("FieldPreventExplosiveMovesAbAttr", p, cancelled)); globalScene.getField(true).map(p=>applyAbAttrs("FieldPreventExplosiveMovesAbAttr", {pokemon: p, cancelled}));
// Queue a message if an ability prevented usage of the move // Queue a message if an ability prevented usage of the move
if (cancelled.value) { if (cancelled.value) {
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name }));

View File

@ -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");

View File

@ -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;

View File

@ -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;

View File

@ -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;
} }

View File

@ -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;

View File

@ -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) {

View File

@ -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 });
} }
} }

View File

@ -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);

View File

@ -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) {

View File

@ -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")) {

View File

@ -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 });
} }
} }

View File

@ -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 */

View File

@ -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 });
} }
} }
} }

View File

@ -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();
}); });

View File

@ -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();
} }

View File

@ -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();

View File

@ -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 {

View File

@ -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();

View File

@ -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(

View File

@ -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();
} }

View File

@ -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;

View File

@ -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);

View File

@ -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);
} }

View File

@ -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 });
} }
}); });

View File

@ -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()!;

View File

@ -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);

View File

@ -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]);

View File

@ -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()!;

View File

@ -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();
}); });
}); });

View File

@ -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);

View File

@ -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);
}); });

View File

@ -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 () => {

View File

@ -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", () => {

View File

@ -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);