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 Pokemon from "#app/field/pokemon";
import type { BattleStat } from "#enums/stat";
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 AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean, ...args: any[]) => void;
export type AbAttrSuccessFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean, ...args: any[]) => boolean;
// biome-ignore lint/correctness/noUnusedImports: Used in a tsdoc comment
import type { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
export type AbAttrCondition = (pokemon: Pokemon) => boolean;
export type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, 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 = {
[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 { ModifierPoolType } from "#enums/modifier-pool-type";
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 type { FixedBattleConfig } 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);
this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance);
for (const p of playerField) {
applyAbAttrs("DoubleBattleChanceAbAttr", p, null, false, doubleChance);
applyAbAttrs("DoubleBattleChanceAbAttr", { pokemon: p, chance: doubleChance });
}
return Math.max(doubleChance.value, 1);
}
@ -1461,7 +1461,7 @@ export default class BattleScene extends SceneBase {
for (const pokemon of this.getPlayerParty()) {
pokemon.resetBattleAndWaveData();
pokemon.resetTera();
applyPostBattleInitAbAttrs("PostBattleInitAbAttr", pokemon);
applyAbAttrs("PostBattleInitAbAttr", { pokemon });
if (
pokemon.hasSpecies(SpeciesId.TERAPAGOS) ||
(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);
if (source && source.isPlayer() !== target.isPlayer()) {
applyAbAttrs("BlockItemTheftAbAttr", source, cancelled);
applyAbAttrs("BlockItemTheftAbAttr", { pokemon: source, cancelled });
}
if (cancelled.value) {
@ -2783,13 +2783,13 @@ export default class BattleScene extends SceneBase {
if (target.isPlayer()) {
this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant);
if (source && itemLost) {
applyPostItemLostAbAttrs("PostItemLostAbAttr", source, false);
applyAbAttrs("PostItemLostAbAttr", { pokemon: source });
}
return true;
}
this.addEnemyModifier(newItemModifier, ignoreUpdate, instant);
if (source && itemLost) {
applyPostItemLostAbAttrs("PostItemLostAbAttr", source, false);
applyAbAttrs("PostItemLostAbAttr", { pokemon: source });
}
return true;
}
@ -2812,7 +2812,7 @@ export default class BattleScene extends SceneBase {
const cancelled = new BooleanHolder(false);
if (source && source.isPlayer() !== target.isPlayer()) {
applyAbAttrs("BlockItemTheftAbAttr", source, cancelled);
applyAbAttrs("BlockItemTheftAbAttr", { pokemon: source, cancelled });
}
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 Pokemon from "#app/field/pokemon";
import type { AbAttrParamMap } from "#app/@types/ability-types";
import type { AbAttrBaseParams, AbAttrString, CallableAbAttrString } from "#app/@types/ability-types";
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>(
pokemon: Pokemon,
passive: boolean,
attrType: T,
applyFunc: AbAttrApplyFunc<AbAttrMap[T]>,
successFunc: AbAttrSuccessFunc<AbAttrMap[T]>,
args: any[],
params: AbAttrParamMap[T],
gainedMidTurn = false,
simulated = false,
messages: string[] = [],
) {
const { simulated = false, passive = false, pokemon } = params;
if (!pokemon?.canApplyAbility(passive) || (passive && pokemon.getPassiveAbility().id === pokemon.getAbility().id)) {
return;
}
@ -75,7 +26,11 @@ function applySingleAbAttrs<T extends AbAttrString>(
for (const attr of ability.getAttrs(attrType)) {
const condition = attr.getCondition();
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;
}
@ -85,15 +40,16 @@ function applySingleAbAttrs<T extends AbAttrString>(
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true);
abShown = true;
}
const message = attr.getTriggerMessage(pokemon, ability.name, args);
const message = attr.getTriggerMessage(params as any, ability.name);
if (message) {
if (!simulated) {
globalScene.phaseManager.queueMessage(message);
}
messages.push(message);
}
applyFunc(attr, passive);
// The `as any` cast here uses the same reasoning as above.
attr.apply(params as any);
if (abShown) {
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,
pokemon: Pokemon | null,
applyFunc: AbAttrApplyFunc<AbAttrMap[T]>,
successFunc: AbAttrSuccessFunc<AbAttrMap[T]>,
args: any[],
simulated = false,
params: AbAttrParamMap[T],
messages: string[] = [],
gainedMidTurn = false,
) {
for (const passive of [false, true]) {
if (pokemon) {
applySingleAbAttrs(pokemon, passive, attrType, applyFunc, successFunc, args, gainedMidTurn, simulated, messages);
globalScene.phaseManager.clearPhaseQueueSplice();
}
// If the pokemon is not defined, no ability attributes to be applied.
// TODO: Evaluate whether this check is even necessary anymore
if (!params.pokemon) {
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,
pokemon: Pokemon,
cancelled: BooleanHolder | null,
simulated = false,
...args: any[]
params: AbAttrParamMap[T],
messages?: string[],
): void {
applyAbAttrsInternal<T>(
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,
);
applyAbAttrsInternal(attrType, params, messages);
}
// 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)
*
* 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 {
applySingleAbAttrs(
pokemon,
passive,
"PostSummonAbAttr",
(attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args),
(attr, passive) => attr.canApplyPostSummon(pokemon, passive, simulated, args),
args,
true,
simulated,
);
export function applyOnGainAbAttrs(params: AbAttrBaseParams): void {
applySingleAbAttrs("PostSummonAbAttr", params, true);
}
/**
* 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 {
applySingleAbAttrs(
pokemon,
passive,
"PreLeaveFieldAbAttr",
(attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [...args, true]),
(attr, passive) => attr.canApplyPreLeaveField(pokemon, passive, simulated, [...args, true]),
args,
true,
simulated,
);
export function applyOnLoseAbAttrs(params: AbAttrBaseParams): void {
applySingleAbAttrs("PreLeaveFieldAbAttr", params, true);
applySingleAbAttrs(
pokemon,
passive,
"IllusionBreakAbAttr",
(attr, passive) => attr.apply(pokemon, passive, simulated, null, args),
(attr, passive) => attr.canApply(pokemon, passive, simulated, args),
args,
true,
simulated,
);
applySingleAbAttrs("IllusionBreakAbAttr", params, true);
}

View File

@ -137,7 +137,7 @@ export class MistTag extends ArenaTag {
if (attacker) {
const bypassed = new BooleanHolder(false);
// TODO: Allow this to be simulated
applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
applyAbAttrs("InfiltratorAbAttr", { pokemon: attacker, simulated: false, bypassed });
if (bypassed.value) {
return false;
}
@ -202,7 +202,7 @@ export class WeakenMoveScreenTag extends ArenaTag {
): boolean {
if (this.weakenedCategories.includes(moveCategory)) {
const bypassed = new BooleanHolder(false);
applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
applyAbAttrs("InfiltratorAbAttr", { pokemon: attacker, bypassed });
if (bypassed.value) {
return false;
}
@ -758,7 +758,7 @@ class SpikesTag extends ArenaTrapTag {
}
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
if (simulated || cancelled.value) {
return !cancelled.value;
}
@ -946,7 +946,7 @@ class StealthRockTag extends ArenaTrapTag {
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
if (cancelled.value) {
return false;
}
@ -1003,7 +1003,12 @@ class StickyWebTag extends ArenaTrapTag {
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
if (pokemon.isGrounded()) {
const cancelled = new BooleanHolder(false);
applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled);
applyAbAttrs("ProtectStatAbAttr", {
pokemon,
cancelled,
stat: Stat.SPD,
stages: -1,
});
if (simulated) {
return !cancelled.value;
@ -1416,7 +1421,9 @@ export class SuppressAbilitiesTag extends ArenaTag {
for (const fieldPokemon of globalScene.getField(true)) {
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
.getField()
.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)) {
// 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)) {
[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),
}),
);
applyAbAttrs("FlinchEffectAbAttr", pokemon, null);
applyAbAttrs("FlinchEffectAbAttr", { pokemon });
return true;
}
@ -916,7 +916,7 @@ export class SeedTag extends BattlerTag {
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
if (source) {
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
if (!cancelled.value) {
globalScene.phaseManager.unshiftNew(
@ -1006,7 +1006,7 @@ export class PowderTag extends BattlerTag {
globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER);
const cancelDamage = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelDamage);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled: cancelDamage });
if (!cancelDamage.value) {
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
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
if (!cancelled.value) {
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);
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
if (!cancelled.value) {
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 {
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon: user, cancelled });
if (!cancelled.value) {
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), {
result: HitResult.INDIRECT,
@ -2243,7 +2243,7 @@ export class SaltCuredTag extends BattlerTag {
);
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
if (!cancelled.value) {
const pokemonSteelOrWater = pokemon.isOfType(PokemonType.STEEL) || pokemon.isOfType(PokemonType.WATER);
@ -2297,7 +2297,7 @@ export class CursedTag extends BattlerTag {
);
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
if (!cancelled.value) {
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
@ -2632,7 +2632,7 @@ export class GulpMissileTag extends BattlerTag {
}
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", attacker, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon: attacker, cancelled });
if (!cancelled.value) {
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);
if (lapseType === BattlerTagLapseType.CUSTOM) {
const cancelled = new BooleanHolder(false);
applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled);
applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", pokemon, cancelled, false, pokemon);
if (!cancelled.value) {
if (pokemon.mysteryEncounterBattleEffects) {
pokemon.mysteryEncounterBattleEffects(pokemon);
}
}
pokemon.mysteryEncounterBattleEffects?.(pokemon);
}
return ret;

View File

@ -35,28 +35,28 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
case BerryType.APICOT:
case BerryType.SALAC:
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
const stat: BattleStat = berryType - BerryType.ENIGMA;
applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6;
applyAbAttrs("ReduceBerryUseThresholdAbAttr", { pokemon, hpRatioReq });
return pokemon.getHpRatio() < hpRatioReq.value && pokemon.getStatStage(stat) < 6;
};
case BerryType.LANSAT:
return (pokemon: Pokemon) => {
const threshold = new NumberHolder(0.25);
applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
const hpRatioReq = new NumberHolder(0.25);
applyAbAttrs("ReduceBerryUseThresholdAbAttr", { pokemon, hpRatioReq });
return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST);
};
case BerryType.STARF:
return (pokemon: Pokemon) => {
const threshold = new NumberHolder(0.25);
applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
const hpRatioReq = new NumberHolder(0.25);
applyAbAttrs("ReduceBerryUseThresholdAbAttr", { pokemon, hpRatioReq });
return pokemon.getHpRatio() < 0.25;
};
case BerryType.LEPPA:
return (pokemon: Pokemon) => {
const threshold = new NumberHolder(0.25);
applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
const hpRatioReq = new NumberHolder(0.25);
applyAbAttrs("ReduceBerryUseThresholdAbAttr", { pokemon, hpRatioReq });
return !!pokemon.getMoveset().find(m => !m.getPpRatio());
};
}
@ -72,7 +72,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
case BerryType.ENIGMA:
{
const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4));
applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, hpHealed);
applyAbAttrs("DoubleBerryEffectAbAttr", { pokemon: consumer, effectValue: hpHealed });
globalScene.phaseManager.unshiftNew(
"PokemonHealPhase",
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.
const stat: BattleStat = berryType - BerryType.ENIGMA;
const statStages = new NumberHolder(1);
applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, statStages);
applyAbAttrs("DoubleBerryEffectAbAttr", { pokemon: consumer, effectValue: statStages });
globalScene.phaseManager.unshiftNew(
"StatStageChangePhase",
consumer.getBattlerIndex(),
@ -126,7 +126,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
{
const randStat = randSeedInt(Stat.SPD, Stat.ATK);
const stages = new NumberHolder(2);
applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, stages);
applyAbAttrs("DoubleBerryEffectAbAttr", { pokemon: consumer, effectValue: stages });
globalScene.phaseManager.unshiftNew(
"StatStageChangePhase",
consumer.getBattlerIndex(),

View File

@ -33,11 +33,7 @@ import type { ArenaTrapTag } from "../arena-tag";
import { WeakenMoveTypeTag } from "../arena-tag";
import { ArenaTagSide } from "#enums/arena-tag-side";
import {
applyAbAttrs,
applyPostAttackAbAttrs,
applyPostItemLostAbAttrs,
applyPreAttackAbAttrs,
applyPreDefendAbAttrs
applyAbAttrs
} from "../abilities/apply-ab-attrs";
import { allAbilities, allMoves } from "../data-lists";
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 { applyMoveAttrs } from "./apply-attrs";
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}.
@ -347,7 +344,7 @@ export default abstract class Move implements Localizable {
const bypassed = new BooleanHolder(false);
// TODO: Allow this to be simulated
applyAbAttrs("InfiltratorAbAttr", user, null, false, bypassed);
applyAbAttrs("InfiltratorAbAttr", {pokemon: user, bypassed});
return !bypassed.value
&& !this.hasFlag(MoveFlags.SOUND_BASED)
@ -645,7 +642,7 @@ export default abstract class Move implements Localizable {
case MoveFlags.IGNORE_ABILITIES:
if (user.hasAbilityWithAttr("MoveAbilityBypassAbAttr")) {
const abilityEffectsIgnored = new BooleanHolder(false);
applyAbAttrs("MoveAbilityBypassAbAttr", user, abilityEffectsIgnored, false, this);
applyAbAttrs("MoveAbilityBypassAbAttr", {pokemon: user, cancelled: abilityEffectsIgnored, move: this});
if (abilityEffectsIgnored.value) {
return true;
}
@ -762,7 +759,7 @@ export default abstract class Move implements Localizable {
const moveAccuracy = new NumberHolder(this.accuracy);
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) {
return moveAccuracy.value;
@ -805,17 +802,25 @@ export default abstract class Move implements Localizable {
const typeChangeMovePowerMultiplier = new NumberHolder(1);
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();
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;
}
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();
if (!isNullOrUndefined(ally)) {
applyPreAttackAbAttrs("AllyMoveCategoryPowerBoostAbAttr", ally, target, this, simulated, power);
applyAbAttrs("AllyMoveCategoryPowerBoostAbAttr", {...abAttrParams, pokemon: ally});
}
const fieldAuras = new Set(
@ -827,11 +832,12 @@ export default abstract class Move implements Localizable {
.flat(),
);
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();
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;
@ -858,7 +864,7 @@ export default abstract class Move implements Localizable {
const priority = new NumberHolder(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;
}
@ -1310,7 +1316,7 @@ export class MoveEffectAttr extends MoveAttr {
getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): number {
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")) {
const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
@ -1318,7 +1324,7 @@ export class MoveEffectAttr extends MoveAttr {
}
if (!selfEffect) {
applyPreDefendAbAttrs("IgnoreMoveEffectsAbAttr", target, user, null, null, !showAbility, moveChance);
applyAbAttrs("IgnoreMoveEffectsAbAttr", {pokemon: target, move, simulated: !showAbility, chance: moveChance});
}
return moveChance.value;
}
@ -1709,8 +1715,9 @@ export class RecoilAttr extends MoveEffectAttr {
const cancelled = new BooleanHolder(false);
if (!this.unblockable) {
applyAbAttrs("BlockRecoilDamageAttr", user, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
const abAttrParams: AbAttrParamsWithCancel = {pokemon: user, cancelled};
applyAbAttrs("BlockRecoilDamageAttr", abAttrParams);
applyAbAttrs("BlockNonDirectDamageAbAttr", abAttrParams);
}
if (cancelled.value) {
@ -1843,7 +1850,7 @@ export class HalfSacrificialAttr extends MoveEffectAttr {
const cancelled = new BooleanHolder(false);
// 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) {
user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT, ignoreSegments: true });
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);
if (!isNullOrUndefined(targetAlly)) {
applyAbAttrs("BlockNonDirectDamageAbAttr", targetAlly, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", {pokemon: targetAlly, cancelled});
}
if (cancelled.value || !targetAlly || targetAlly.switchOutStatus) {
@ -2414,7 +2421,7 @@ export class MultiHitAttr extends MoveAttr {
{
const rand = user.randBattleSeedInt(20);
const hitValue = new NumberHolder(rand);
applyAbAttrs("MaxMultiHitAbAttr", user, null, false, hitValue);
applyAbAttrs("MaxMultiHitAbAttr", {pokemon: user, hits: hitValue});
if (hitValue.value >= 13) {
return 2;
} 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))
&& 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;
}
}
@ -2574,7 +2581,7 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : undefined);
if (target.status) {
if (target.status || !statusToApply) {
return false;
} else {
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 {
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
// TODO: This should not trigger if the target would faint beforehand
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockItemTheftAbAttr", target, cancelled);
applyAbAttrs("BlockItemTheftAbAttr", {pokemon: target, cancelled});
if (cancelled.value) {
return false;
@ -2795,8 +2803,8 @@ export class EatBerryAttr extends MoveEffectAttr {
protected eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) {
// consumer eats berry, owner triggers unburden and similar effects
getBerryEffectFunc(this.chosenBerry.berryType)(consumer);
applyPostItemLostAbAttrs("PostItemLostAbAttr", berryOwner, false);
applyAbAttrs("HealFromBerryUseAbAttr", consumer, new BooleanHolder(false));
applyAbAttrs("PostItemLostAbAttr", {pokemon: berryOwner});
applyAbAttrs("HealFromBerryUseAbAttr", {pokemon: consumer});
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 {
// check for abilities that block item theft
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockItemTheftAbAttr", target, cancelled);
applyAbAttrs("BlockItemTheftAbAttr", {pokemon: target, cancelled});
if (cancelled.value === true) {
return false;
}
@ -2835,7 +2843,7 @@ export class StealEatBerryAttr extends EatBerryAttr {
// pick a random berry and eat it
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 });
globalScene.phaseManager.queueMessage(message);
this.reduceBerryModifier(target);
@ -3026,7 +3034,7 @@ export class OneHitKOAttr extends MoveAttr {
getCondition(): MoveConditionFunc {
return (user, target, move) => {
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockOneHitKOAbAttr", target, cancelled);
applyAbAttrs("BlockOneHitKOAbAttr", {pokemon: target, cancelled});
return !cancelled.value && user.level >= target.level;
};
}
@ -5438,7 +5446,7 @@ export class NoEffectAttr extends MoveAttr {
const crashDamageFunc = (user: Pokemon, move: Move) => {
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", {pokemon: user, cancelled});
if (cancelled.value) {
return false;
}
@ -6437,9 +6445,9 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
}
getFailedText(_user: Pokemon, target: Pokemon, _move: Move): string | undefined {
const blockedByAbility = new BooleanHolder(false);
applyAbAttrs("ForceSwitchOutImmunityAbAttr", target, blockedByAbility);
if (blockedByAbility.value) {
const cancelled = new BooleanHolder(false);
applyAbAttrs("ForceSwitchOutImmunityAbAttr", {pokemon: target, cancelled});
if (cancelled.value) {
return i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) });
}
}
@ -6478,7 +6486,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
}
const blockedByAbility = new BooleanHolder(false);
applyAbAttrs("ForceSwitchOutImmunityAbAttr", target, blockedByAbility);
applyAbAttrs("ForceSwitchOutImmunityAbAttr", {pokemon: target, cancelled: blockedByAbility});
if (blockedByAbility.value) {
return false;
}
@ -7987,7 +7995,7 @@ const failIfSingleBattle: MoveConditionFunc = (user, target, move) => globalScen
const failIfDampCondition: MoveConditionFunc = (user, target, move) => {
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
if (cancelled.value) {
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 { Stat } from "#enums/stat";
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 { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
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
pokemon.resetBattleAndWaveData();
applyPostBattleInitAbAttrs("PostBattleInitAbAttr", pokemon);
applyAbAttrs("PostBattleInitAbAttr", { pokemon });
}
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 type { BattlerIndex } from "#enums/battler-index";
import { Terrain, TerrainType } from "#app/data/terrain";
import {
applyAbAttrs,
applyPostTerrainChangeAbAttrs,
applyPostWeatherChangeAbAttrs,
} from "#app/data/abilities/apply-ab-attrs";
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import type Pokemon from "#app/field/pokemon";
import Overrides from "#app/overrides";
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
@ -372,7 +368,7 @@ export class Arena {
pokemon.findAndRemoveTags(
t => "weatherTypes" in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather),
);
applyPostWeatherChangeAbAttrs("PostWeatherChangeAbAttr", pokemon, weather);
applyAbAttrs("PostWeatherChangeAbAttr", { pokemon, weather });
});
return true;
@ -461,8 +457,8 @@ export class Arena {
pokemon.findAndRemoveTags(
t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain),
);
applyPostTerrainChangeAbAttrs("PostTerrainChangeAbAttr", pokemon, terrain);
applyAbAttrs("TerrainEventTypeChangeAbAttr", pokemon, null, false);
applyAbAttrs("PostTerrainChangeAbAttr", { pokemon, terrain });
applyAbAttrs("TerrainEventTypeChangeAbAttr", { pokemon });
});
return true;

View File

@ -108,23 +108,8 @@ import { WeatherType } from "#enums/weather-type";
import { NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
import { ArenaTagSide } from "#enums/arena-tag-side";
import type { SuppressAbilitiesTag } from "#app/data/arena-tag";
import type { Ability } from "#app/data/abilities/ability";
import {
applyAbAttrs,
applyStatMultiplierAbAttrs,
applyPreApplyBattlerTagAbAttrs,
applyPreAttackAbAttrs,
applyPreDefendAbAttrs,
applyPreSetStatusAbAttrs,
applyFieldStatMultiplierAbAttrs,
applyCheckTrappedAbAttrs,
applyPostDamageAbAttrs,
applyPostItemLostAbAttrs,
applyOnGainAbAttrs,
applyPreLeaveFieldAbAttrs,
applyOnLoseAbAttrs,
applyAllyStatMultiplierAbAttrs,
} from "#app/data/abilities/apply-ab-attrs";
import type { Ability, PreAttackModifyDamageAbAttrParams } from "#app/data/abilities/ability";
import { applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { allAbilities } from "#app/data/data-lists";
import type PokemonData from "#app/system/pokemon-data";
import { BattlerIndex } from "#enums/battler-index";
@ -189,7 +174,7 @@ import { HitResult } from "#enums/hit-result";
import { AiType } from "#enums/ai-type";
import type { MoveResult } from "#enums/move-result";
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 */
type damageParams = {
@ -1364,7 +1349,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs("HighCritAttr", source, this, move, critStage);
globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage);
globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
applyAbAttrs("BonusCritAbAttr", source, null, false, critStage);
applyAbAttrs("BonusCritAbAttr", { pokemon: source, critStage });
const critBoostTag = source.getTag(CritBoostTag);
if (critBoostTag) {
// 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,
ignoreHeldItems = false,
): number {
const statValue = new NumberHolder(this.getStat(stat, false));
const statVal = new NumberHolder(this.getStat(stat, false));
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
const fieldApplied = new BooleanHolder(false);
for (const pokemon of globalScene.getField(true)) {
applyFieldStatMultiplierAbAttrs(
"FieldMultiplyStatAbAttr",
applyAbAttrs("FieldMultiplyStatAbAttr", {
pokemon,
stat,
statValue,
this,
fieldApplied,
statVal,
target: this,
hasApplied: fieldApplied,
simulated,
);
});
if (fieldApplied.value) {
break;
}
}
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();
if (!isNullOrUndefined(ally)) {
applyAllyStatMultiplierAbAttrs(
"AllyStatMultiplierAbAttr",
ally,
applyAbAttrs("AllyStatMultiplierAbAttr", {
pokemon: ally,
stat,
statValue,
statVal,
simulated,
this,
move?.hasFlag(MoveFlags.IGNORE_ABILITIES) || ignoreAllyAbility,
);
// TODO: maybe just don't call this if the move is none?
move: move ?? allMoves[MoveId.NONE],
ignoreAbility: move?.hasFlag(MoveFlags.IGNORE_ABILITIES) || ignoreAllyAbility,
});
}
let ret =
statValue.value *
statVal.value *
this.getStatStageMultiplier(stat, opponent, move, ignoreOppAbility, isCritical, simulated, ignoreHeldItems);
switch (stat) {
@ -2045,20 +2036,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param ability New Ability
*/
public setTempAbility(ability: Ability, passive = false): void {
applyOnLoseAbAttrs(this, passive);
applyOnLoseAbAttrs({ pokemon: this, passive });
if (passive) {
this.summonData.passiveAbility = ability.id;
} else {
this.summonData.ability = ability.id;
}
applyOnGainAbAttrs(this, passive);
applyOnGainAbAttrs({ pokemon: this, passive });
}
/**
* Suppresses an ability and calls its onlose attributes
*/
public suppressAbility() {
[true, false].forEach(passive => applyOnLoseAbAttrs(this, passive));
[true, false].forEach(passive => applyOnLoseAbAttrs({ pokemon: this, passive }));
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);
// 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);
}
@ -2256,7 +2247,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
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
* 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);
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;
return (
trappedByAbility.value ||
!!this.getTag(TrappedTag) ||
!!globalScene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, side)
trapped.value || !!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);
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,
// 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);
// 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) {
applyPreDefendAbAttrs("TypeImmunityAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier);
applyAbAttrs("TypeImmunityAbAttr", commonAbAttrParams);
if (!cancelledHolder.value) {
applyPreDefendAbAttrs("MoveImmunityAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier);
applyAbAttrs("MoveImmunityAbAttr", commonAbAttrParams);
}
if (!cancelledHolder.value) {
const defendingSidePlayField = this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
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
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)) {
@ -2420,16 +2433,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
let multiplier = types
.map(defType => {
const multiplier = new NumberHolder(getTypeDamageMultiplier(moveType, defType));
.map(defenderType => {
const multiplier = new NumberHolder(getTypeDamageMultiplier(moveType, defenderType));
applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multiplier);
if (move) {
applyMoveAttrs("VariableMoveTypeChartAttr", null, this, move, multiplier, defType);
applyMoveAttrs("VariableMoveTypeChartAttr", null, this, move, multiplier, defenderType);
}
if (source) {
const ignoreImmunity = new BooleanHolder(false);
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 (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[];
if (exposedTags.some(t => t.ignoreImmunity(defType, moveType))) {
if (exposedTags.some(t => t.ignoreImmunity(defenderType, moveType))) {
if (multiplier.value === 0) {
return 1;
}
@ -3358,7 +3377,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
if (!ignoreOppAbility) {
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", opponent, null, simulated, stat, ignoreStatStage);
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", {
pokemon: opponent,
ignored: ignoreStatStage,
stat,
simulated,
});
}
if (move) {
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 ignoreEvaStatStage = new BooleanHolder(false);
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", target, null, false, Stat.ACC, ignoreAccStatStage);
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", this, null, false, Stat.EVA, ignoreEvaStatStage);
// TODO: consider refactoring this method to accept `simulated` and then pass simulated to these applyAbAttrs
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", { pokemon: target, stat: Stat.ACC, ignored: ignoreAccStatStage });
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", { pokemon: this, stat: Stat.EVA, ignored: ignoreEvaStatStage });
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage);
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));
}
applyStatMultiplierAbAttrs("StatMultiplierAbAttr", this, Stat.ACC, accuracyMultiplier, false, sourceMove);
applyAbAttrs("StatMultiplierAbAttr", {
pokemon: this,
stat: Stat.ACC,
statVal: accuracyMultiplier,
move: sourceMove,
});
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();
if (!isNullOrUndefined(ally)) {
const ignore =
this.hasAbilityWithAttr("MoveAbilityBypassAbAttr") || sourceMove.hasFlag(MoveFlags.IGNORE_ABILITIES);
applyAllyStatMultiplierAbAttrs(
"AllyStatMultiplierAbAttr",
ally,
Stat.ACC,
accuracyMultiplier,
false,
this,
ignore,
);
applyAllyStatMultiplierAbAttrs(
"AllyStatMultiplierAbAttr",
ally,
Stat.EVA,
evasionMultiplier,
false,
this,
ignore,
);
applyAbAttrs("AllyStatMultiplierAbAttr", {
pokemon: ally,
stat: Stat.ACC,
statVal: accuracyMultiplier,
ignoreAbility: ignore,
move: sourceMove,
});
applyAbAttrs("AllyStatMultiplierAbAttr", {
pokemon: ally,
stat: Stat.EVA,
statVal: evasionMultiplier,
ignoreAbility: ignore,
move: sourceMove,
});
}
return accuracyMultiplier.value / evasionMultiplier.value;
@ -3559,7 +3591,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs("CombinedPledgeStabBoostAttr", source, this, move, stabMultiplier);
if (!ignoreSourceAbility) {
applyAbAttrs("StabBoostAbAttr", source, null, simulated, stabMultiplier);
applyAbAttrs("StabBoostAbAttr", { pokemon: source, simulated, multiplier: stabMultiplier });
}
if (source.isTerastallized && sourceTeraType === moveType && moveType !== PokemonType.STELLAR) {
@ -3706,16 +3738,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
null,
multiStrikeEnhancementMultiplier,
);
if (!ignoreSourceAbility) {
applyPreAttackAbAttrs(
"AddSecondStrikeAbAttr",
source,
this,
applyAbAttrs("AddSecondStrikeAbAttr", {
pokemon: source,
move,
simulated,
null,
multiStrikeEnhancementMultiplier,
);
multiplier: multiStrikeEnhancementMultiplier,
});
}
/** 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 */
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]
@ -3747,7 +3777,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
) {
const burnDamageReductionCancelled = new BooleanHolder(false);
if (!ignoreSourceAbility) {
applyAbAttrs("BypassBurnDamageReductionAbAttr", source, burnDamageReductionCancelled, simulated);
applyAbAttrs("BypassBurnDamageReductionAbAttr", {
pokemon: source,
cancelled: burnDamageReductionCancelled,
simulated,
});
}
if (!burnDamageReductionCancelled.value) {
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 */
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 */
@ -3822,14 +3862,25 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
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) */
if (!ignoreAbility) {
applyPreDefendAbAttrs("ReceivedMoveDamageMultiplierAbAttr", this, source, move, cancelled, simulated, damage);
applyAbAttrs("ReceivedMoveDamageMultiplierAbAttr", abAttrParams);
const ally = this.getAlly();
/** Additionally apply friend guard damage reduction if ally has it. */
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);
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)
@ -3875,7 +3926,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const alwaysCrit = new BooleanHolder(false);
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 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
const blockCrit = new BooleanHolder(false);
applyAbAttrs("BlockCritAbAttr", this, null, false, blockCrit);
applyAbAttrs("BlockCritAbAttr", { pokemon: this, blockCrit });
const blockCritTag = globalScene.arena.getTagOnSide(
NoCritTag,
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
*/
if (!source || source.turnData.hitCount <= 1) {
applyPostDamageAbAttrs("PostDamageAbAttr", this, damage, this.hasPassive(), false, [], source);
applyAbAttrs("PostDamageAbAttr", { pokemon: this, damage, source });
}
return damage;
}
@ -4046,11 +4097,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const stubTag = new BattlerTag(tagType, 0, 0);
const cancelled = new BooleanHolder(false);
applyPreApplyBattlerTagAbAttrs("BattlerTagImmunityAbAttr", this, stubTag, cancelled, true);
applyAbAttrs("BattlerTagImmunityAbAttr", { pokemon: this, tag: stubTag, cancelled, simulated: true });
const userField = this.getAlliedField();
userField.forEach(pokemon =>
applyPreApplyBattlerTagAbAttrs("UserFieldBattlerTagImmunityAbAttr", pokemon, stubTag, cancelled, true, this),
applyAbAttrs("UserFieldBattlerTagImmunityAbAttr", {
pokemon,
tag: stubTag,
cancelled,
simulated: true,
target: this,
}),
);
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 cancelled = new BooleanHolder(false);
applyPreApplyBattlerTagAbAttrs("BattlerTagImmunityAbAttr", this, newTag, cancelled);
applyAbAttrs("BattlerTagImmunityAbAttr", { pokemon: this, tag: newTag, cancelled });
if (cancelled.value) {
return false;
}
for (const pokemon of this.getAlliedField()) {
applyPreApplyBattlerTagAbAttrs("UserFieldBattlerTagImmunityAbAttr", pokemon, newTag, cancelled, false, this);
applyAbAttrs("UserFieldBattlerTagImmunityAbAttr", { pokemon, tag: newTag, cancelled, target: this });
if (cancelled.value) {
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
*/
canSetStatus(
effect: StatusEffect | undefined,
effect: StatusEffect,
quiet = false,
overrideStatus = false,
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
const cancelImmunity = new BooleanHolder(false);
// TODO: Determine if we need to pass `quiet` as the value for simulated in this call
if (sourcePokemon) {
applyAbAttrs("IgnoreTypeStatusEffectImmunityAbAttr", sourcePokemon, cancelImmunity, false, effect, defType);
applyAbAttrs("IgnoreTypeStatusEffectImmunityAbAttr", {
pokemon: sourcePokemon,
cancelled: cancelImmunity,
statusEffect: effect,
defenderType: defType,
});
if (cancelImmunity.value) {
return false;
}
@ -4678,21 +4741,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
const cancelled = new BooleanHolder(false);
applyPreSetStatusAbAttrs("StatusEffectImmunityAbAttr", this, effect, cancelled, quiet);
applyAbAttrs("StatusEffectImmunityAbAttr", { pokemon: this, effect, cancelled, simulated: quiet });
if (cancelled.value) {
return false;
}
for (const pokemon of this.getAlliedField()) {
applyPreSetStatusAbAttrs(
"UserFieldStatusEffectImmunityAbAttr",
applyAbAttrs("UserFieldStatusEffectImmunityAbAttr", {
pokemon,
effect,
cancelled,
quiet,
this,
sourcePokemon,
);
simulated: quiet,
target: this,
source: sourcePokemon,
});
if (cancelled.value) {
break;
}
@ -4723,6 +4785,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
overrideStatus?: boolean,
quiet = true,
): boolean {
if (!effect) {
return false;
}
if (!this.canSetStatus(effect, quiet, overrideStatus, sourcePokemon)) {
return false;
}
@ -4781,7 +4846,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
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);
return true;
@ -4842,7 +4906,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (globalScene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) {
const bypassed = new BooleanHolder(false);
if (attacker) {
applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
applyAbAttrs("InfiltratorAbAttr", { pokemon: attacker, bypassed });
}
return !bypassed.value;
}
@ -5391,7 +5455,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.hideInfo();
}
// Trigger abilities that activate upon leaving the field
applyPreLeaveFieldAbAttrs("PreLeaveFieldAbAttr", this);
applyAbAttrs("PreLeaveFieldAbAttr", { pokemon: this });
this.setSwitchOutStatus(true);
globalScene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true);
globalScene.field.remove(this, destroy);
@ -5451,7 +5515,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
globalScene.removeModifier(heldItem, this.isEnemy());
}
if (forBattle) {
applyPostItemLostAbAttrs("PostItemLostAbAttr", this, false);
applyAbAttrs("PostItemLostAbAttr", { pokemon: this });
}
return true;

View File

@ -42,7 +42,7 @@ import type {
import { getModifierType } from "#app/utils/modifier-utils";
import { Color, ShadowColor } from "#enums/color";
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 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
getBerryEffectFunc(this.berryType)(pokemon);
applyPostItemLostAbAttrs("PostItemLostAbAttr", pokemon, false);
applyAbAttrs("PostItemLostAbAttr", { pokemon });
// Update berry eaten trackers for Belch, Harvest, Cud Chew, etc.
// 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
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
for (const p of field) {
applyAbAttrs("CommanderAbAttr", p, null, false);
applyAbAttrs("CommanderAbAttr", { pokemon: p });
}
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 { StatusEffect } from "#enums/status-effect";
import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
@ -25,10 +25,10 @@ export class AttemptRunPhase extends PokemonPhase {
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) {
enemyField.forEach(enemyPokemon => applyPreLeaveFieldAbAttrs("PreLeaveFieldAbAttr", enemyPokemon));
enemyField.forEach(enemyPokemon => applyAbAttrs("PreLeaveFieldAbAttr", { pokemon: enemyPokemon }));
globalScene.playSound("se/flee");
globalScene.phaseManager.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
@ -38,14 +38,11 @@ export class AttemptRunPhase extends PokemonPhase {
alpha: 0,
duration: 250,
ease: "Sine.easeIn",
onComplete: () =>
// biome-ignore lint/complexity/noForEach: TODO
enemyField.forEach(enemyPokemon => enemyPokemon.destroy()),
onComplete: () => enemyField.forEach(enemyPokemon => enemyPokemon.destroy()),
});
globalScene.clearEnemyHeldItemModifiers();
// biome-ignore lint/complexity/noForEach: TODO
enemyField.forEach(enemyPokemon => {
enemyPokemon.hideInfo().then(() => enemyPokemon.destroy());
enemyPokemon.hp = 0;

View File

@ -1,5 +1,5 @@
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 { BattlePhase } from "./battle-phase";
@ -65,7 +65,7 @@ export class BattleEndPhase extends BattlePhase {
}
for (const pokemon of globalScene.getPokemonAllowedInBattle()) {
applyPostBattleAbAttrs("PostBattleAbAttr", pokemon, false, this.isVictory);
applyAbAttrs("PostBattleAbAttr", { pokemon, victory: this.isVictory });
}
if (globalScene.currentBattle.moneyScattered) {

View File

@ -20,7 +20,7 @@ export class BerryPhase extends FieldPhase {
this.executeForAll(pokemon => {
this.eatBerries(pokemon);
applyAbAttrs("RepeatBerryNextTurnAbAttr", pokemon, null);
applyAbAttrs("CudChewConsumeBerryAbAttr", { pokemon });
});
this.end();
@ -42,7 +42,7 @@ export class BerryPhase extends FieldPhase {
// TODO: If both opponents on field have unnerve, which one displays its message?
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) {
globalScene.phaseManager.queueMessage(
i18next.t("abilityTriggers:preventBerryUse", {
@ -70,6 +70,6 @@ export class BerryPhase extends FieldPhase {
globalScene.updateModifiers(pokemon.isPlayer());
// 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 { globalScene } from "#app/global-scene";
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 { getCharVariantFromDialogue } from "#app/data/dialogue";
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)
.reverse()
.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 (battle.battleType === BattleType.WILD) {
for (const pokemon of globalScene.getField()) {
applyPreSummonAbAttrs("PreSummonAbAttr", pokemon, []);
applyAbAttrs("PreSummonAbAttr", { pokemon });
}
globalScene.field.add(enemyPokemon);
battle.seenEnemyPartyMemberIds.add(enemyPokemon.id);

View File

@ -1,11 +1,7 @@
import type { BattlerIndex } from "#enums/battler-index";
import { BattleType } from "#enums/battle-type";
import { globalScene } from "#app/global-scene";
import {
applyPostFaintAbAttrs,
applyPostKnockOutAbAttrs,
applyPostVictoryAbAttrs,
} 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 { battleSpecDialogue } from "#app/data/dialogue";
import { allMoves } from "#app/data/data-lists";
@ -117,29 +113,31 @@ export class FaintPhase extends PokemonPhase {
pokemon.resetTera();
// TODO: this can be simplified by just checking whether lastAttack is defined
if (pokemon.turnData.attacksReceived?.length) {
const lastAttack = pokemon.turnData.attacksReceived[0];
applyPostFaintAbAttrs(
"PostFaintAbAttr",
pokemon,
globalScene.getPokemonById(lastAttack.sourceId)!,
new PokemonMove(lastAttack.move).getMove(),
lastAttack.result,
); // TODO: is this bang correct?
applyAbAttrs("PostFaintAbAttr", {
pokemon: pokemon,
// TODO: We should refactor lastAttack's sourceId to forbid null and just use undefined
attacker: globalScene.getPokemonById(lastAttack.sourceId) ?? undefined,
// TODO: improve the way that we provide the move that knocked out the pokemon...
move: new PokemonMove(lastAttack.move).getMove(),
hitResult: lastAttack.result,
}); // TODO: is this bang correct?
} else {
//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);
for (const p of alivePlayField) {
applyPostKnockOutAbAttrs("PostKnockOutAbAttr", p, pokemon);
applyAbAttrs("PostKnockOutAbAttr", { pokemon: p, victim: pokemon });
}
if (pokemon.turnData.attacksReceived?.length) {
const defeatSource = this.source;
if (defeatSource?.isOnField()) {
applyPostVictoryAbAttrs("PostVictoryAbAttr", defeatSource);
applyAbAttrs("PostVictoryAbAttr", { pokemon: defeatSource });
const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move];
const pvattrs = pvmove.getAttrs("PostVictoryStatStageChangeAttr");
if (pvattrs.length) {

View File

@ -1,12 +1,6 @@
import { BattlerIndex } from "#enums/battler-index";
import { globalScene } from "#app/global-scene";
import {
applyExecutedMoveAbAttrs,
applyPostAttackAbAttrs,
applyPostDamageAbAttrs,
applyPostDefendAbAttrs,
applyPreAttackAbAttrs,
} from "#app/data/abilities/apply-ab-attrs";
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { ConditionalProtectTag } from "#app/data/arena-tag";
import { ArenaTagSide } from "#enums/arena-tag-side";
import { MoveAnim } from "#app/data/battle-anims";
@ -322,7 +316,7 @@ export class MoveEffectPhase extends PokemonPhase {
// Assume single target for multi hit
applyMoveAttrs("MultiHitAttr", user, this.getFirstTarget() ?? null, move, hitCount);
// 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
globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount);
// 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
if (this.firstHit) {
user.pushMoveHistory(this.moveHistoryEntry);
applyExecutedMoveAbAttrs("ExecutedMoveAbAttr", user);
applyAbAttrs("ExecutedMoveAbAttr", { pokemon: user });
}
try {
@ -439,7 +433,7 @@ export class MoveEffectPhase extends PokemonPhase {
* @param hitResult - The {@linkcode HitResult} of the attempted move
*/
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);
}
@ -808,7 +802,9 @@ export class MoveEffectPhase extends PokemonPhase {
// Multi-hit check for Wimp Out/Emergency Exit
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.applyHeldItemFlinchCheck(user, target, dealsDamage);
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
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 { PokemonPhase } from "./pokemon-phase";
import type { BattlerIndex } from "#enums/battler-index";
import { applyPostSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import type Pokemon from "#app/field/pokemon";
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
export class MoveEndPhase extends PokemonPhase {
public readonly phaseName = "MoveEndPhase";
@ -30,7 +30,7 @@ export class MoveEndPhase extends PokemonPhase {
globalScene.arena.setIgnoreAbilities(false);
for (const target of this.targets) {
if (target) {
applyPostSummonAbAttrs("PostSummonRemoveEffectAbAttr", target);
applyAbAttrs("PostSummonRemoveEffectAbAttr", { pokemon: target });
}
}

View File

@ -1,6 +1,6 @@
import { BattlerIndex } from "#enums/battler-index";
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 { CommonAnim } from "#enums/move-anims-common";
import { CenterOfAttentionTag } from "#app/data/battler-tags";
@ -228,14 +228,11 @@ export class MovePhase extends BattlePhase {
case StatusEffect.SLEEP: {
applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove());
const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0);
applyAbAttrs(
"ReduceStatusEffectDurationAbAttr",
this.pokemon,
null,
false,
this.pokemon.status.effect,
turnsRemaining,
);
applyAbAttrs("ReduceStatusEffectDurationAbAttr", {
pokemon: this.pokemon,
statusEffect: this.pokemon.status.effect,
duration: turnsRemaining,
});
this.pokemon.status.sleepTurnsRemaining = turnsRemaining.value;
healed = this.pokemon.status.sleepTurnsRemaining <= 0;
activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP);
@ -396,7 +393,8 @@ export class MovePhase extends BattlePhase {
*/
if (success) {
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(
"MoveEffectPhase",
this.pokemon.getBattlerIndex(),
@ -406,7 +404,11 @@ export class MovePhase extends BattlePhase {
);
} else {
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({
@ -438,7 +440,7 @@ export class MovePhase extends BattlePhase {
if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !dancerModes.includes(this.useMode)) {
// TODO: Fix in dancer PR to move to MEP for hit checks
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
applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, this.move.getMove());
applyAbAttrs("PokemonTypeChangeAbAttr", {
pokemon: this.pokemon,
move: this.move.getMove(),
opponent: targets[0],
});
globalScene.phaseManager.unshiftNew(
"MoveChargePhase",
@ -523,7 +529,12 @@ export class MovePhase extends BattlePhase {
.getField(true)
.filter(p => p !== this.pokemon)
.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 */

View File

@ -14,7 +14,7 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase {
if (pokemon) {
pokemon.resetBattleAndWaveData();
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 { PokemonPhase } from "./pokemon-phase";
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";
export class ObtainStatusEffectPhase extends PokemonPhase {
@ -53,7 +53,11 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeStatusEffectTrigger, true);
// If mold breaker etc was used to set this status, it shouldn't apply to abilities activated afterwards
globalScene.arena.setIgnoreAbilities(false);
applyPostSetStatusAbAttrs("PostSetStatusAbAttr", pokemon, this.statusEffect, this.sourcePokemon);
applyAbAttrs("PostSetStatusAbAttr", {
pokemon,
effect: this.statusEffect,
sourcePokemon: this.sourcePokemon ?? undefined,
});
}
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 type { BattlerIndex } from "#enums/battler-index";
@ -16,7 +16,8 @@ export class PostSummonActivateAbilityPhase extends PostSummonPhase {
}
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();
}

View File

@ -28,7 +28,7 @@ export class PostSummonPhase extends PokemonPhase {
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
for (const p of field) {
applyAbAttrs("CommanderAbAttr", p, null, false);
applyAbAttrs("CommanderAbAttr", { pokemon: p });
}
this.end();

View File

@ -1,6 +1,6 @@
import { globalScene } from "#app/global-scene";
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 { CommonAnim } from "#enums/move-anims-common";
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) {
pokemon.status.incrementTurn();
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
applyAbAttrs("BlockStatusDamageAbAttr", pokemon, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
applyAbAttrs("BlockStatusDamageAbAttr", { pokemon, cancelled });
if (!cancelled.value) {
globalScene.phaseManager.queueMessage(
@ -39,14 +39,14 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
break;
case StatusEffect.BURN:
damage.value = Math.max(pokemon.getMaxHp() >> 4, 1);
applyAbAttrs("ReduceBurnDamageAbAttr", pokemon, null, false, damage);
applyAbAttrs("ReduceBurnDamageAbAttr", { pokemon, burnDamage: damage });
break;
}
if (damage.value) {
// Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ...
globalScene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true));
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());
} else {

View File

@ -181,9 +181,10 @@ export class QuietFormChangePhase extends BattlePhase {
}
}
if (this.formChange.trigger instanceof SpeciesFormChangeTeraTrigger) {
applyAbAttrs("PostTeraFormChangeStatChangeAbAttr", this.pokemon, null);
applyAbAttrs("ClearWeatherAbAttr", this.pokemon, null);
applyAbAttrs("ClearTerrainAbAttr", this.pokemon, null);
const params = { pokemon: this.pokemon };
applyAbAttrs("PostTeraFormChangeStatChangeAbAttr", params);
applyAbAttrs("ClearWeatherAbAttr", params);
applyAbAttrs("ClearTerrainAbAttr", params);
}
super.end();

View File

@ -1,10 +1,6 @@
import { globalScene } from "#app/global-scene";
import type { BattlerIndex } from "#enums/battler-index";
import {
applyAbAttrs,
applyPostStatStageChangeAbAttrs,
applyPreStatStageChangeAbAttrs,
} from "#app/data/abilities/apply-ab-attrs";
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { MistTag } from "#app/data/arena-tag";
import { ArenaTagSide } from "#enums/arena-tag-side";
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 { OctolockTag } from "#app/data/battler-tags";
import { ArenaTagType } from "#app/enums/arena-tag-type";
import type {
ConditionalUserFieldProtectStatAbAttrParams,
PreStatStageChangeAbAttrParams,
} from "#app/@types/ability-types";
export type StatStageChangeCallback = (
target: Pokemon | null,
@ -126,7 +126,7 @@ export class StatStageChangePhase extends PokemonPhase {
const stages = new NumberHolder(this.stages);
if (!this.ignoreAbilities) {
applyAbAttrs("StatStageChangeMultiplierAbAttr", pokemon, null, false, stages);
applyAbAttrs("StatStageChangeMultiplierAbAttr", { pokemon, numStages: stages });
}
let simulate = false;
@ -146,42 +146,38 @@ export class StatStageChangePhase extends PokemonPhase {
}
if (!cancelled.value && !this.selfTarget && stages.value < 0) {
applyPreStatStageChangeAbAttrs("ProtectStatAbAttr", pokemon, stat, cancelled, simulate);
applyPreStatStageChangeAbAttrs(
"ConditionalUserFieldProtectStatAbAttr",
const abAttrParams: PreStatStageChangeAbAttrParams & ConditionalUserFieldProtectStatAbAttrParams = {
pokemon,
stat,
cancelled,
simulate,
pokemon,
);
simulated: simulate,
target: pokemon,
stages: this.stages,
};
applyAbAttrs("ProtectStatAbAttr", abAttrParams);
applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", abAttrParams);
// TODO: Consider skipping this call if `cancelled` is false.
const ally = pokemon.getAlly();
if (!isNullOrUndefined(ally)) {
applyPreStatStageChangeAbAttrs(
"ConditionalUserFieldProtectStatAbAttr",
ally,
stat,
cancelled,
simulate,
pokemon,
);
applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", { ...abAttrParams, pokemon: ally });
}
/** Potential stat reflection due to Mirror Armor, does not apply to Octolock end of turn effect */
if (
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) &&
!this.comingFromMirrorArmorUser
) {
applyPreStatStageChangeAbAttrs(
"ReflectStatStageChangeAbAttr",
applyAbAttrs("ReflectStatStageChangeAbAttr", {
pokemon,
stat,
cancelled,
simulate,
opponentPokemon,
this.stages,
);
simulated: simulate,
source: opponentPokemon,
stages: this.stages,
});
}
}
@ -222,17 +218,16 @@ export class StatStageChangePhase extends PokemonPhase {
if (stages.value > 0 && this.canBeCopied) {
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(
"PostStatStageChangeAbAttr",
applyAbAttrs("PostStatStageChangeAbAttr", {
pokemon,
filteredStats,
this.stages,
this.selfTarget,
);
stats: filteredStats,
stages: this.stages,
selfTarget: this.selfTarget,
});
// Look for any other stat change phases; if this is the last one, do White Herb check
const existingPhase = globalScene.phaseManager.findPhase(

View File

@ -10,7 +10,7 @@ import { getPokemonNameWithAffix } from "#app/messages";
import i18next from "i18next";
import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase";
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";
export class SummonPhase extends PartyMemberPokemonPhase {
@ -27,7 +27,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
start() {
super.start();
applyPreSummonAbAttrs("PreSummonAbAttr", this.getPokemon());
applyAbAttrs("PreSummonAbAttr", { pokemon: this.getPokemon() });
this.preSummon();
}

View File

@ -1,5 +1,5 @@
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 { getPokeballTintColor } from "#app/data/pokeball";
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
@ -124,8 +124,8 @@ export class SwitchSummonPhase extends SummonPhase {
switchedInPokemon.resetSummonData();
switchedInPokemon.loadAssets(true);
applyPreSummonAbAttrs("PreSummonAbAttr", switchedInPokemon);
applyPreSwitchOutAbAttrs("PreSwitchOutAbAttr", this.lastPokemon);
applyAbAttrs("PreSummonAbAttr", { pokemon: switchedInPokemon });
applyAbAttrs("PreSwitchOutAbAttr", { pokemon: this.lastPokemon });
if (!switchedInPokemon) {
this.end();
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 { TerrainType } from "#app/data/terrain";
import { WeatherType } from "#app/enums/weather-type";
@ -49,7 +49,7 @@ export class TurnEndPhase extends FieldPhase {
globalScene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon);
}
applyPostTurnAbAttrs("PostTurnAbAttr", pokemon);
applyAbAttrs("PostTurnAbAttr", { pokemon });
}
globalScene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon);

View File

@ -66,8 +66,12 @@ export class TurnStartPhase extends FieldPhase {
globalScene.getField(true).forEach(p => {
const bypassSpeed = new BooleanHolder(false);
const canCheckHeldItems = new BooleanHolder(true);
applyAbAttrs("BypassSpeedChanceAbAttr", p, null, false, bypassSpeed);
applyAbAttrs("PreventBypassSpeedChanceAbAttr", p, null, false, bypassSpeed, canCheckHeldItems);
applyAbAttrs("BypassSpeedChanceAbAttr", { pokemon: p, bypass: bypassSpeed });
applyAbAttrs("PreventBypassSpeedChanceAbAttr", {
pokemon: p,
bypass: bypassSpeed,
canCheckHeldItems: canCheckHeldItems,
});
if (canCheckHeldItems.value) {
globalScene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed);
}

View File

@ -1,9 +1,5 @@
import { globalScene } from "#app/global-scene";
import {
applyPreWeatherEffectAbAttrs,
applyAbAttrs,
applyPostWeatherLapseAbAttrs,
} from "#app/data/abilities/apply-ab-attrs";
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { CommonAnim } from "#enums/move-anims-common";
import type { Weather } 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);
this.executeForAll((pokemon: Pokemon) =>
applyPreWeatherEffectAbAttrs("SuppressWeatherEffectAbAttr", pokemon, this.weather, cancelled),
applyAbAttrs("SuppressWeatherEffectAbAttr", { pokemon, weather: this.weather, cancelled }),
);
if (!cancelled.value) {
const inflictDamage = (pokemon: Pokemon) => {
const cancelled = new BooleanHolder(false);
applyPreWeatherEffectAbAttrs("PreWeatherDamageAbAttr", pokemon, this.weather, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
applyAbAttrs("PreWeatherDamageAbAttr", { pokemon, weather: this.weather, cancelled });
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
if (
cancelled.value ||
@ -80,7 +76,7 @@ export class WeatherEffectPhase extends CommonAnimPhase {
globalScene.ui.showText(getWeatherLapseMessage(this.weather.weatherType) ?? "", null, () => {
this.executeForAll((pokemon: Pokemon) => {
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 { globalScene } from "#app/global-scene";
import { getPokemonNameWithAffix } from "#app/messages";
@ -196,7 +196,7 @@ describe("Abilities - Cud Chew", () => {
describe("regurgiates berries", () => {
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]);
const farigiraf = game.scene.getPlayerPokemon()!;

View File

@ -95,7 +95,7 @@ describe("Abilities - Harvest", () => {
// Give ourselves harvest and disable enemy neut gas,
// 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.move.select(MoveId.GASTRO_ACID);
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 () => {
const abSpy = vi.spyOn(PostTurnResetStatusAbAttr.prototype, "canApplyPostTurn");
const abSpy = vi.spyOn(PostTurnResetStatusAbAttr.prototype, "canApply");
game.override.moveset([MoveId.SPLASH, MoveId.LUNAR_DANCE]);
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 () => {
await game.classicMode.startBattle();
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
const playerPokemon = game.scene.getPlayerPokemon()!;

View File

@ -178,7 +178,7 @@ describe("Abilities - Neutralizing Gas", () => {
const enemy = game.scene.getEnemyPokemon()!;
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();
@ -187,6 +187,6 @@ describe("Abilities - Neutralizing Gas", () => {
await game.killPokemon(game.scene.getPlayerPokemon()!);
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 { CommandPhase } from "#app/phases/command-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]);
const sandVeilAttr = allAbilities[AbilityId.SAND_VEIL].getAttrs("StatMultiplierAbAttr")[0];
vi.spyOn(sandVeilAttr, "applyStatStage").mockImplementation(
(_pokemon, _passive, _simulated, stat, statValue, _args) => {
if (stat === Stat.EVA && game.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) {
statValue.value *= -1; // will make all attacks miss
return true;
}
return false;
},
);
vi.spyOn(sandVeilAttr, "apply").mockImplementation(({ stat, statVal }: StatMultiplierAbAttrParams) => {
if (stat === Stat.EVA && game.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) {
statVal.value *= -1; // will make all attacks miss
return true;
}
return false;
});
expect(leadPokemon[0].hasAbility(AbilityId.SAND_VEIL)).toBe(true);
expect(leadPokemon[1].hasAbility(AbilityId.SAND_VEIL)).toBe(false);

View File

@ -1,5 +1,5 @@
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 { NumberHolder } from "#app/utils/common";
import { AbilityId } from "#enums/ability-id";
@ -52,25 +52,16 @@ describe("Abilities - Shield Dust", () => {
expect(move.id).toBe(MoveId.AIR_SLASH);
const chance = new NumberHolder(move.chance);
await applyAbAttrs(
"MoveEffectChanceMultiplierAbAttr",
phase.getUserPokemon()!,
null,
false,
applyAbAttrs("MoveEffectChanceMultiplierAbAttr", {
pokemon: phase.getUserPokemon()!,
chance,
move,
phase.getFirstTarget(),
false,
);
await applyPreDefendAbAttrs(
"IgnoreMoveEffectsAbAttr",
phase.getFirstTarget()!,
phase.getUserPokemon()!,
null,
null,
false,
});
applyAbAttrs("IgnoreMoveEffectsAbAttr", {
pokemon: phase.getFirstTarget()!,
move,
chance,
);
});
expect(chance.value).toBe(0);
});

View File

@ -277,7 +277,7 @@ describe("Abilities - Unburden", () => {
const initialTreeckoSpeed = treecko.getStat(Stat.SPD);
const initialPurrloinSpeed = purrloin.getStat(Stat.SPD);
const unburdenAttr = treecko.getAbilityAttrs("PostItemLostAbAttr")[0];
vi.spyOn(unburdenAttr, "applyPostItemLost");
vi.spyOn(unburdenAttr, "apply");
// Player uses Baton Pass, which also passes the Baton item
game.move.select(MoveId.BATON_PASS);
@ -288,7 +288,7 @@ describe("Abilities - Unburden", () => {
expect(getHeldItemCount(purrloin)).toBe(1);
expect(treecko.getEffectiveStat(Stat.SPD)).toBe(initialTreeckoSpeed);
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 () => {

View File

@ -31,7 +31,7 @@ describe("Spec - Pokemon", () => {
const pkm = game.scene.getPlayerPokemon()!;
expect(pkm).toBeDefined();
expect(pkm.trySetStatus(undefined)).toBe(true);
expect(pkm.trySetStatus(undefined)).toBe(false);
});
describe("Add To Party", () => {

View File

@ -140,9 +140,8 @@ describe("Moves - Safeguard", () => {
game.field.mockAbility(player, AbilityId.STATIC);
vi.spyOn(
allAbilities[AbilityId.STATIC].getAttrs("PostDefendContactApplyStatusEffectAbAttr")[0],
"chance",
"get",
).mockReturnValue(100);
"canApply",
).mockReturnValue(true);
game.move.select(MoveId.SPLASH);
await game.move.forceEnemyMove(MoveId.SAFEGUARD);