Update callsites in pokemon.ts

This commit is contained in:
Sirz Benjie 2025-06-14 12:40:26 -05:00
parent 55093ec0af
commit b9781bd863
No known key found for this signature in database
GPG Key ID: 38AC42D68CF5E138
11 changed files with 357 additions and 306 deletions

View File

@ -1,7 +0,0 @@
export type * from "#app/data/abilities/ability";
import type { AbAttrMap } from "./ability-types";
export type AbAttrParamMap = {
[K in keyof AbAttrMap]: Parameters<AbAttrMap[K]["apply"]>[0];
}

View File

@ -1,17 +1,14 @@
import type { AbAttr } from "#app/data/abilities/ability";
import type Move from "#app/data/moves/move"; import type Move from "#app/data/moves/move";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import type { BattleStat } from "#enums/stat"; import type { BattleStat } from "#enums/stat";
import type { AbAttrConstructorMap } from "#app/data/abilities/ability"; import type { AbAttrConstructorMap } from "#app/data/abilities/ability";
// intentionally re-export all types from abilities to have this be the centralized place to import ability types
export type * from "#app/data/abilities/ability";
// biome-ignore lint/correctness/noUnusedImports: Used in a tsdoc comment // biome-ignore lint/correctness/noUnusedImports: Used in a tsdoc comment
import type { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import type { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
// Intentionally re-export all types from the ability attributes module
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;
export type AbAttrCondition = (pokemon: Pokemon) => boolean; export type AbAttrCondition = (pokemon: Pokemon) => boolean;
export type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean; export type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
export type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean; export type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
@ -33,11 +30,17 @@ export type AbAttrMap = {
* Subset of ability attribute classes that may be passed to {@linkcode applyAbAttrs } method * Subset of ability attribute classes that may be passed to {@linkcode applyAbAttrs } method
* *
* @remarks * @remarks
* Our AbAttr classes violate Liskov Substitution Principal. * Our AbAttr classes violate Liskov Substitution Principle.
* *
* AbAttrs that are not in this have subclasses with apply methods requiring different parameters than * AbAttrs that are not in this have subclasses with apply methods requiring different parameters than
* the base apply method. * the base apply method.
* *
* Such attributes may not be passed to the {@linkcode applyAbAttrs} method * Such attributes may not be passed to the {@linkcode applyAbAttrs} method
*/ */
export type CallableAbAttrString = Exclude<AbAttrString, "PreDefendAbAttr" | "PreAttackAbAttr">; export type CallableAbAttrString =
| Exclude<AbAttrString, "PreDefendAbAttr" | "PreAttackAbAttr">
| "PreApplyBattlerTagAbAttr";
export type AbAttrParamMap = {
[K in keyof AbAttrMap]: Parameters<AbAttrMap[K]["apply"]>[0];
};

View File

@ -22,3 +22,11 @@ export type Exact<T> = {
* *
*/ */
export type Closed<X> = 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

@ -238,7 +238,7 @@ export interface AbAttrBaseParams {
readonly simulated?: boolean; readonly simulated?: boolean;
/** Whether the ability is the passive ability. Default false */ /** Whether the ability is the passive ability. Default false */
readonly passive?: boolean; passive?: boolean;
} }
export interface AbAttrParamsWithCancel extends AbAttrBaseParams { export interface AbAttrParamsWithCancel extends AbAttrBaseParams {
@ -306,6 +306,7 @@ export abstract class AbAttr {
} }
export class BlockRecoilDamageAttr extends AbAttr { export class BlockRecoilDamageAttr extends AbAttr {
private declare readonly _: never;
constructor() { constructor() {
super(false); super(false);
} }
@ -314,7 +315,8 @@ export class BlockRecoilDamageAttr extends AbAttr {
cancelled.value = true; cancelled.value = true;
} }
getTriggerMessage({ pokemon }: AbAttrParamsWithCancel, abilityName: string) { override getTriggerMessage({ pokemon }: AbAttrParamsWithCancel, abilityName: string) {
// TODO: remove this because this does not exist on cartridge
return i18next.t("abilityTriggers:blockRecoilDamage", { return i18next.t("abilityTriggers:blockRecoilDamage", {
pokemonName: getPokemonNameWithAffix(pokemon), pokemonName: getPokemonNameWithAffix(pokemon),
abilityName: abilityName, abilityName: abilityName,
@ -333,6 +335,7 @@ export interface DoubleBattleChanceAbAttrParams extends AbAttrBaseParams {
* @see {@linkcode apply} * @see {@linkcode apply}
*/ */
export class DoubleBattleChanceAbAttr extends AbAttr { export class DoubleBattleChanceAbAttr extends AbAttr {
private declare readonly _: never;
constructor() { constructor() {
super(false); super(false);
} }
@ -1380,7 +1383,9 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
} }
} }
export class PostStatStageChangeAbAttr extends AbAttr {} export class PostStatStageChangeAbAttr extends AbAttr {
private declare readonly _: never;
}
export interface PostStatStageChangeAbAttrParams extends AbAttrBaseParams { export interface PostStatStageChangeAbAttrParams extends AbAttrBaseParams {
/** The stats that were changed */ /** The stats that were changed */
@ -1424,7 +1429,9 @@ export class PostStatStageChangeStatStageChangeAbAttr extends PostStatStageChang
} }
} }
export abstract class PreAttackAbAttr extends AbAttr {} export abstract class PreAttackAbAttr extends AbAttr {
private declare readonly _: never;
}
export interface ModifyMoveEffectChanceAbAttrParams extends AbAttrBaseParams { export interface ModifyMoveEffectChanceAbAttrParams extends AbAttrBaseParams {
/** The move being used by the attacker */ /** The move being used by the attacker */
@ -1628,9 +1635,9 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr {
*/ */
export interface AddSecondStrikeAbAttrParams extends AugmentMoveInteractionAbAttrParams { export interface AddSecondStrikeAbAttrParams extends AugmentMoveInteractionAbAttrParams {
/** Holder for the number of hits. May be modified by ability application */ /** Holder for the number of hits. May be modified by ability application */
hitCount: NumberHolder; hitCount?: NumberHolder;
/** Holder for the damage multiplier _of the current hit_ */ /** Holder for the damage multiplier _of the current hit_ */
multiplier: NumberHolder; multiplier?: NumberHolder;
} }
/** /**
@ -1660,11 +1667,11 @@ export class AddSecondStrikeAbAttr extends PreAttackAbAttr {
* to the damage multiplier of this ability. * to the damage multiplier of this ability.
*/ */
override apply({ hitCount, multiplier, pokemon }: AddSecondStrikeAbAttrParams): void { override apply({ hitCount, multiplier, pokemon }: AddSecondStrikeAbAttrParams): void {
if (hitCount.value) { if (hitCount?.value) {
hitCount.value += 1; hitCount.value += 1;
} }
if (multiplier.value && pokemon.turnData.hitsLeft === 1) { if (multiplier?.value && pokemon.turnData.hitsLeft === 1) {
multiplier.value = this.damageMultiplier; multiplier.value = this.damageMultiplier;
} }
} }
@ -1806,27 +1813,13 @@ export class FieldMovePowerBoostAbAttr extends AbAttr {
this.powerMultiplier = powerMultiplier; this.powerMultiplier = powerMultiplier;
} }
canApplyPreAttack( canApply(_params: PreAttackModifyPowerAbAttrParams): boolean {
_pokemon: Pokemon | null,
_passive: boolean | null,
_simulated: boolean,
_defender: Pokemon | null,
_move: Move,
_args: any[],
): boolean {
return true; // logic for this attr is handled in move.ts instead of normally return true; // logic for this attr is handled in move.ts instead of normally
} }
applyPreAttack( apply({ pokemon, opponent, move, power }: PreAttackModifyPowerAbAttrParams): void {
pokemon: Pokemon | null, if (this.condition(pokemon, opponent, move)) {
_passive: boolean | null, power.value *= this.powerMultiplier;
_simulated: boolean,
defender: Pokemon | null,
move: Move,
args: any[],
): void {
if (this.condition(pokemon, defender, move)) {
(args[0] as NumberHolder).value *= this.powerMultiplier;
} }
} }
} }
@ -1880,6 +1873,7 @@ export interface StatMultiplierAbAttrParams extends AbAttrBaseParams {
} }
export class StatMultiplierAbAttr extends AbAttr { export class StatMultiplierAbAttr extends AbAttr {
private declare readonly _: never;
private stat: BattleStat; private stat: BattleStat;
private multiplier: number; private multiplier: number;
/** Function determining if the stat multiplier is able to be applied to the move. /** Function determining if the stat multiplier is able to be applied to the move.
@ -1897,11 +1891,11 @@ export class StatMultiplierAbAttr extends AbAttr {
this.condition = condition ?? null; this.condition = condition ?? null;
} }
canApplyStatStage({ pokemon, move, stat }: StatMultiplierAbAttrParams): boolean { override canApply({ pokemon, move, stat }: StatMultiplierAbAttrParams): boolean {
return stat === this.stat && (!this.condition || this.condition(pokemon, null, move)); return stat === this.stat && (!this.condition || this.condition(pokemon, null, move));
} }
applyStatStage({ statVal }: StatMultiplierAbAttrParams): void { override apply({ statVal }: StatMultiplierAbAttrParams): void {
statVal.value *= this.multiplier; statVal.value *= this.multiplier;
} }
} }
@ -2380,7 +2374,7 @@ export class CopyFaintedAllyAbilityAbAttr extends PostKnockOutAbAttr {
} }
} }
interface IgnoreOpponentStatStagesAbAttrParam extends AbAttrBaseParams { export interface IgnoreOpponentStatStagesAbAttrParams extends AbAttrBaseParams {
/** The to check for ignorability */ /** The to check for ignorability */
stat: BattleStat; stat: BattleStat;
/** Holds whether the stat is ignored by the ability */ /** Holds whether the stat is ignored by the ability */
@ -2402,14 +2396,14 @@ export class IgnoreOpponentStatStagesAbAttr extends AbAttr {
/** /**
* @returns Whether `stat` is one of the stats ignored by the ability * @returns Whether `stat` is one of the stats ignored by the ability
*/ */
override canApply({ stat }: IgnoreOpponentStatStagesAbAttrParam): boolean { override canApply({ stat }: IgnoreOpponentStatStagesAbAttrParams): boolean {
return this.stats.includes(stat); return this.stats.includes(stat);
} }
/** /**
* Sets the ignored holder to true. * Sets the ignored holder to true.
*/ */
override apply({ ignored }: IgnoreOpponentStatStagesAbAttrParam): void { override apply({ ignored }: IgnoreOpponentStatStagesAbAttrParams): void {
ignored.value = true; ignored.value = true;
} }
} }
@ -3314,11 +3308,11 @@ export class PreSwitchOutFormChangeAbAttr extends PreSwitchOutAbAttr {
* Base class for ability attributes that apply their effect just before the user leaves the field * Base class for ability attributes that apply their effect just before the user leaves the field
*/ */
export class PreLeaveFieldAbAttr extends AbAttr { export class PreLeaveFieldAbAttr extends AbAttr {
canApplyPreLeaveField(_params: Closed<AbAttrBaseParams>): boolean { canApply(_params: Closed<AbAttrBaseParams>): boolean {
return true; return true;
} }
applyPreLeaveField(_params: Closed<AbAttrBaseParams>): void {} apply(_params: Closed<AbAttrBaseParams>): void {}
} }
/** /**
@ -3408,11 +3402,11 @@ export interface PreStatStageChangeAbAttrParams extends AbAttrBaseParams {
* Base class for ability attributes that apply their effect before a stat stage change. * Base class for ability attributes that apply their effect before a stat stage change.
*/ */
export abstract class PreStatStageChangeAbAttr extends AbAttr { export abstract class PreStatStageChangeAbAttr extends AbAttr {
canApplyPreStatStageChange(_params: Closed<PreStatStageChangeAbAttrParams>): boolean { canApply(_params: Closed<PreStatStageChangeAbAttrParams>): boolean {
return true; return true;
} }
applyPreStatStageChange(_params: Closed<PreStatStageChangeAbAttrParams>): void {} apply(_params: Closed<PreStatStageChangeAbAttrParams>): void {}
} }
/** /**
@ -3672,8 +3666,9 @@ export interface ConditionalUserFieldProtectStatAbAttrParams extends AbAttrBaseP
stat: BattleStat; stat: BattleStat;
/** Holds whether the stat stage change is prevented by the ability */ /** Holds whether the stat stage change is prevented by the ability */
cancelled: BooleanHolder; cancelled: BooleanHolder;
// TODO: consider making this required and not inherit from PreStatStageChangeAbAttr
/** The target of the stat stage change */ /** The target of the stat stage change */
target: Pokemon; target?: Pokemon;
} }
/** /**
@ -3724,21 +3719,20 @@ export interface PreApplyBattlerTagAbAttrParams extends AbAttrBaseParams {
/** /**
* Base class for ability attributes that apply their effect before a BattlerTag {@linkcode BattlerTag} is applied. * Base class for ability attributes that apply their effect before a BattlerTag {@linkcode BattlerTag} is applied.
* Subclasses violate Liskov Substitution Principle, so this class must not be provided to {@linkcode applyAbAttrs}
*/ */
export abstract class PreApplyBattlerTagAbAttr extends AbAttr { export abstract class PreApplyBattlerTagAbAttr extends AbAttr {
canApplyPreApplyBattlerTag(_params: Closed<PreApplyBattlerTagAbAttrParams>): boolean { canApply(_params: PreApplyBattlerTagAbAttrParams): boolean {
return true; return true;
} }
applyPreApplyBattlerTag(_params: Closed<PreApplyBattlerTagAbAttrParams>): void {} apply(_params: PreApplyBattlerTagAbAttrParams): void {}
} }
/** // Intentionally not exported because this shouldn't be able to be passed to `applyAbAttrs`. It only exists so that
* Provides immunity to BattlerTags {@linkcode BattlerTag} to specified targets. // PreApplyBattlerTagImmunityAbAttr and UserFieldPreApplyBattlerTagImmunityAbAttr can avoid code duplication
* // while preserving type safety. (Since the UserField version require an additional parameter, target, in its apply methods)
* This does not check whether the tag is already applied; that check should happen in the caller. abstract class BaseBattlerTagImmunityAbAttr<P extends PreApplyBattlerTagAbAttrParams> extends PreApplyBattlerTagAbAttr {
*/
export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr {
protected immuneTagTypes: BattlerTagType[]; protected immuneTagTypes: BattlerTagType[];
constructor(immuneTagTypes: BattlerTagType | BattlerTagType[]) { constructor(immuneTagTypes: BattlerTagType | BattlerTagType[]) {
@ -3747,15 +3741,15 @@ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr {
this.immuneTagTypes = coerceArray(immuneTagTypes); this.immuneTagTypes = coerceArray(immuneTagTypes);
} }
override canApply({ cancelled, tag }: PreApplyBattlerTagAbAttrParams): boolean { override canApply({ cancelled, tag }: P): boolean {
return !cancelled.value && this.immuneTagTypes.includes(tag.tagType); return !cancelled.value && this.immuneTagTypes.includes(tag.tagType);
} }
override apply({ cancelled }: PreApplyBattlerTagAbAttrParams): void { override apply({ cancelled }: P): void {
cancelled.value = true; cancelled.value = true;
} }
override getTriggerMessage({ pokemon, tag }: PreApplyBattlerTagAbAttrParams, abilityName: string): string { override getTriggerMessage({ pokemon, tag }: P, abilityName: string): string {
return i18next.t("abilityTriggers:battlerTagImmunity", { return i18next.t("abilityTriggers:battlerTagImmunity", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
abilityName, abilityName,
@ -3764,18 +3758,31 @@ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr {
} }
} }
// TODO: The battler tag ability attributes are in dire need of improvement
// It is unclear why there is a `PreApplyBattlerTagImmunityAbAttr` class that isn't used,
// and then why there's a BattlerTagImmunityAbAttr class as well.
/**
* Provides immunity to BattlerTags {@linkcode BattlerTag} to specified targets.
*
* This does not check whether the tag is already applied; that check should happen in the caller.
*/
export class PreApplyBattlerTagImmunityAbAttr extends BaseBattlerTagImmunityAbAttr<PreApplyBattlerTagAbAttrParams> {}
/** /**
* Provides immunity to BattlerTags {@linkcode BattlerTag} to the user. * Provides immunity to BattlerTags {@linkcode BattlerTag} to the user.
*/ */
export class BattlerTagImmunityAbAttr extends PreApplyBattlerTagImmunityAbAttr {} export class BattlerTagImmunityAbAttr extends PreApplyBattlerTagImmunityAbAttr {}
export interface UserFieldBattlerTagImmunityAbAttrParams extends PreApplyBattlerTagAbAttrParams {
/** The pokemon that the battler tag is being applied to */
target: Pokemon;
}
/** /**
* Provides immunity to BattlerTags {@linkcode BattlerTag} to the user's field. * Provides immunity to BattlerTags {@linkcode BattlerTag} to the user's field.
* @extends PreApplyBattlerTagImmunityAbAttr
*/ */
export class UserFieldBattlerTagImmunityAbAttr extends PreApplyBattlerTagImmunityAbAttr {} export class UserFieldBattlerTagImmunityAbAttr extends BaseBattlerTagImmunityAbAttr<UserFieldBattlerTagImmunityAbAttrParams> {}
// NOTE: We are inheriting from `PreApplyBattlerTagImmunityAbAttr` which has a different signature
export class ConditionalUserFieldBattlerTagImmunityAbAttr extends UserFieldBattlerTagImmunityAbAttr { export class ConditionalUserFieldBattlerTagImmunityAbAttr extends UserFieldBattlerTagImmunityAbAttr {
private condition: (target: Pokemon) => boolean; private condition: (target: Pokemon) => boolean;
@ -3783,12 +3790,14 @@ export class ConditionalUserFieldBattlerTagImmunityAbAttr extends UserFieldBattl
* Determine whether the {@linkcode ConditionalUserFieldBattlerTagImmunityAbAttr} can be applied by passing the target pokemon to the condition. * Determine whether the {@linkcode ConditionalUserFieldBattlerTagImmunityAbAttr} can be applied by passing the target pokemon to the condition.
* @returns Whether the ability can be used to cancel the battler tag * @returns Whether the ability can be used to cancel the battler tag
*/ */
override canApply(params: PreApplyBattlerTagAbAttrParams & { target: Pokemon }): boolean { override canApply(params: UserFieldBattlerTagImmunityAbAttrParams): boolean {
// the `!!params` here is to ensure the target is not null or undefined. This is defensive programming // the `!!params` here is to ensure the target is not null or undefined. This is defensive programming
// to guard against the case where // to guard against the case where
return !!params.target && super.canApply(params) && this.condition(params.target ?? params.pokemon); return !!params.target && super.canApply(params) && this.condition(params.target ?? params.pokemon);
} }
override apply(_params: UserFieldBattlerTagImmunityAbAttrParams) {}
constructor(condition: (target: Pokemon) => boolean, immuneTagTypes: BattlerTagType | BattlerTagType[]) { constructor(condition: (target: Pokemon) => boolean, immuneTagTypes: BattlerTagType | BattlerTagType[]) {
super(immuneTagTypes); super(immuneTagTypes);
@ -3798,9 +3807,9 @@ export class ConditionalUserFieldBattlerTagImmunityAbAttr extends UserFieldBattl
export interface BlockCritAbAttrParams extends AbAttrBaseParams { export interface BlockCritAbAttrParams extends AbAttrBaseParams {
/** /**
* Holds a boolean that will be set to false if the owner may not be crit * Holds a boolean that will be set to true if the user's ability prevents the attack from being critical
*/ */
readonly canCrit: BooleanHolder; readonly blockCrit: BooleanHolder;
} }
export class BlockCritAbAttr extends AbAttr { export class BlockCritAbAttr extends AbAttr {
@ -3863,7 +3872,7 @@ export interface ConditionalCritAbAttrParams extends AbAttrBaseParams {
/** The move being used */ /** The move being used */
move: Move; move: Move;
/** Holds whether the attack will critically hit */ /** Holds whether the attack will critically hit */
crit: BooleanHolder; isCritical: BooleanHolder;
} }
/** /**
@ -3878,12 +3887,12 @@ export class ConditionalCritAbAttr extends AbAttr {
this.condition = condition; this.condition = condition;
} }
override canApply({ crit, pokemon, target, move }: ConditionalCritAbAttrParams): boolean { override canApply({ isCritical, pokemon, target, move }: ConditionalCritAbAttrParams): boolean {
return !crit.value && this.condition(pokemon, target, move); return !isCritical.value && this.condition(pokemon, target, move);
} }
override apply({ crit }: ConditionalCritAbAttrParams): void { override apply({ isCritical }: ConditionalCritAbAttrParams): void {
crit.value = true; isCritical.value = true;
} }
} }
@ -3904,7 +3913,7 @@ export class BlockStatusDamageAbAttr extends AbAttr {
private effects: StatusEffect[]; private effects: StatusEffect[];
/** /**
* @param {StatusEffect[]} effects The status effect(s) that will be blocked from damaging the ability pokemon * @param effects - The status effect(s) that will be blocked from damaging the ability pokemon
*/ */
constructor(...effects: StatusEffect[]) { constructor(...effects: StatusEffect[]) {
super(false); super(false);
@ -3916,8 +3925,6 @@ export class BlockStatusDamageAbAttr extends AbAttr {
return !!pokemon.status?.effect && this.effects.includes(pokemon.status.effect); return !!pokemon.status?.effect && this.effects.includes(pokemon.status.effect);
} }
/**
*/
override apply({ cancelled }: AbAttrParamsWithCancel): void { override apply({ cancelled }: AbAttrParamsWithCancel): void {
cancelled.value = true; cancelled.value = true;
} }
@ -3967,7 +3974,9 @@ export class ChangeMovePriorityAbAttr extends AbAttr {
} }
} }
export class IgnoreContactAbAttr extends AbAttr {} export class IgnoreContactAbAttr extends AbAttr {
private declare readonly _: never;
}
/** /**
* Shared interface for attributes that respond to a weather. * Shared interface for attributes that respond to a weather.
@ -4321,23 +4330,11 @@ export class PostWeatherLapseAbAttr extends AbAttr {
this.weatherTypes = weatherTypes; this.weatherTypes = weatherTypes;
} }
canApplyPostWeatherLapse( canApply(_params: Closed<PreWeatherEffectAbAttrParams>): boolean {
_pokemon: Pokemon,
_passive: boolean,
_simulated: boolean,
_weather: Weather | null,
_args: any[],
): boolean {
return true; return true;
} }
applyPostWeatherLapse( apply(_params: Closed<PreWeatherEffectAbAttrParams>): void {}
_pokemon: Pokemon,
_passive: boolean,
_simulated: boolean,
_weather: Weather | null,
_args: any[],
): void {}
getCondition(): AbAttrCondition { getCondition(): AbAttrCondition {
return getWeatherCondition(...this.weatherTypes); return getWeatherCondition(...this.weatherTypes);
@ -4353,23 +4350,11 @@ export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr {
this.healFactor = healFactor; this.healFactor = healFactor;
} }
override canApplyPostWeatherLapse( override canApply({ pokemon }: AbAttrBaseParams): boolean {
pokemon: Pokemon,
_passive: boolean,
_simulated: boolean,
_weather: Weather | null,
_args: any[],
): boolean {
return !pokemon.isFullHp(); return !pokemon.isFullHp();
} }
override applyPostWeatherLapse( override apply({ pokemon, passive, simulated }: PreWeatherEffectAbAttrParams): void {
pokemon: Pokemon,
passive: boolean,
simulated: boolean,
_weather: Weather,
_args: any[],
): void {
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
if (!simulated) { if (!simulated) {
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
@ -4395,23 +4380,11 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr {
this.damageFactor = damageFactor; this.damageFactor = damageFactor;
} }
override canApplyPostWeatherLapse( override canApply({ pokemon }: PreWeatherEffectAbAttrParams): boolean {
pokemon: Pokemon,
_passive: boolean,
_simulated: boolean,
_weather: Weather | null,
_args: any[],
): boolean {
return !pokemon.hasAbilityWithAttr("BlockNonDirectDamageAbAttr"); return !pokemon.hasAbilityWithAttr("BlockNonDirectDamageAbAttr");
} }
override applyPostWeatherLapse( override apply({ simulated, pokemon, passive }: PreWeatherEffectAbAttrParams): void {
pokemon: Pokemon,
passive: boolean,
simulated: boolean,
_weather: Weather,
_args: any[],
): void {
if (!simulated) { if (!simulated) {
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
globalScene.phaseManager.queueMessage( globalScene.phaseManager.queueMessage(
@ -4430,8 +4403,6 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr {
export interface PostTerrainChangeAbAttrParams extends AbAttrBaseParams { export interface PostTerrainChangeAbAttrParams extends AbAttrBaseParams {
/** The terrain type that is being changed to */ /** The terrain type that is being changed to */
terrain: TerrainType; terrain: TerrainType;
/** Holds whether the terrain change is prevented by the ability */
cancelled: BooleanHolder;
} }
export class PostTerrainChangeAbAttr extends AbAttr { export class PostTerrainChangeAbAttr extends AbAttr {
@ -4858,7 +4829,9 @@ export class FetchBallAbAttr extends PostTurnAbAttr {
} }
} }
export class PostBiomeChangeAbAttr extends AbAttr {} export class PostBiomeChangeAbAttr extends AbAttr {
private declare readonly _: never;
}
export class PostBiomeChangeWeatherChangeAbAttr extends PostBiomeChangeAbAttr { export class PostBiomeChangeWeatherChangeAbAttr extends PostBiomeChangeAbAttr {
private weatherType: WeatherType; private weatherType: WeatherType;
@ -4914,11 +4887,11 @@ export interface PostMoveUsedAbAttrParams extends AbAttrBaseParams {
* Triggers just after a move is used either by the opponent or the player * Triggers just after a move is used either by the opponent or the player
*/ */
export class PostMoveUsedAbAttr extends AbAttr { export class PostMoveUsedAbAttr extends AbAttr {
canApplyPostMoveUsed(_params: Closed<PostMoveUsedAbAttrParams>): boolean { canApply(_params: Closed<PostMoveUsedAbAttrParams>): boolean {
return true; return true;
} }
applyPostMoveUsed(_params: Closed<PostMoveUsedAbAttrParams>): void {} apply(_params: Closed<PostMoveUsedAbAttrParams>): void {}
} }
/** /**
@ -4926,7 +4899,7 @@ export class PostMoveUsedAbAttr extends AbAttr {
* @extends PostMoveUsedAbAttr * @extends PostMoveUsedAbAttr
*/ */
export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr {
override canApplyPostMoveUsed({ source, pokemon }: PostMoveUsedAbAttrParams): boolean { override canApply({ source, pokemon }: PostMoveUsedAbAttrParams): boolean {
// List of tags that prevent the Dancer from replicating the move // List of tags that prevent the Dancer from replicating the move
const forbiddenTags = [ const forbiddenTags = [
BattlerTagType.FLYING, BattlerTagType.FLYING,
@ -4985,11 +4958,11 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr {
* @extends AbAttr * @extends AbAttr
*/ */
export class PostItemLostAbAttr extends AbAttr { export class PostItemLostAbAttr extends AbAttr {
canApplyPostItemLost(_pokemon: Pokemon, _simulated: boolean, _args: any[]): boolean { canApply(_params: Closed<AbAttrBaseParams>): boolean {
return true; return true;
} }
applyPostItemLost(_pokemon: Pokemon, _simulated: boolean, _args: any[]): void {} apply(_params: Closed<AbAttrBaseParams>): void {}
} }
/** /**
@ -5002,7 +4975,7 @@ export class PostItemLostApplyBattlerTagAbAttr extends PostItemLostAbAttr {
this.tagType = tagType; this.tagType = tagType;
} }
override canApplyPostItemLost(pokemon: Pokemon, simulated: boolean, _args: any[]): boolean { override canApply({ pokemon, simulated }: AbAttrBaseParams): boolean {
return !pokemon.getTag(this.tagType) && !simulated; return !pokemon.getTag(this.tagType) && !simulated;
} }
@ -5011,7 +4984,7 @@ export class PostItemLostApplyBattlerTagAbAttr extends PostItemLostAbAttr {
* @param pokemon {@linkcode Pokemon} with this ability * @param pokemon {@linkcode Pokemon} with this ability
* @param _args N/A * @param _args N/A
*/ */
override applyPostItemLost(pokemon: Pokemon, _simulated: boolean, _args: any[]): void { override apply({ pokemon }: AbAttrBaseParams): void {
pokemon.addTag(this.tagType); pokemon.addTag(this.tagType);
} }
} }
@ -5176,25 +5149,11 @@ export class CheckTrappedAbAttr extends AbAttr {
this.arenaTrapCondition = condition; this.arenaTrapCondition = condition;
} }
canApplyCheckTrapped( override canApply(_params: Closed<CheckTrappedAbAttrParams>): boolean {
_pokemon: Pokemon,
_passive: boolean,
_simulated: boolean,
_trapped: BooleanHolder,
_otherPokemon: Pokemon,
_args: any[],
): boolean {
return true; return true;
} }
applyCheckTrapped( override apply(_params: Closed<CheckTrappedAbAttrParams>): void {}
_pokemon: Pokemon,
_passive: boolean,
_simulated: boolean,
_trapped: BooleanHolder,
_otherPokemon: Pokemon,
_args: any[],
): void {}
} }
export interface CheckTrappedAbAttrParams extends AbAttrBaseParams { export interface CheckTrappedAbAttrParams extends AbAttrBaseParams {
@ -5232,7 +5191,7 @@ export class ArenaTrapAbAttr extends CheckTrappedAbAttr {
trapped.value = true; trapped.value = true;
} }
getTriggerMessage({ pokemon }: CheckTrappedAbAttrParams, abilityName: string): string { override getTriggerMessage({ pokemon }: CheckTrappedAbAttrParams, abilityName: string): string {
return i18next.t("abilityTriggers:arenaTrap", { return i18next.t("abilityTriggers:arenaTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
abilityName, abilityName,
@ -5543,7 +5502,9 @@ export class FlinchStatStageChangeAbAttr extends FlinchEffectAbAttr {
} }
} }
export class IncreasePpAbAttr extends AbAttr {} export class IncreasePpAbAttr extends AbAttr {
private declare readonly _: never;
}
/** @sealed */ /** @sealed */
export class ForceSwitchOutImmunityAbAttr extends AbAttr { export class ForceSwitchOutImmunityAbAttr extends AbAttr {
@ -5682,10 +5643,13 @@ export class InfiltratorAbAttr extends AbAttr {
* moves as if the user had used {@linkcode MoveId.MAGIC_COAT | Magic Coat}. * moves as if the user had used {@linkcode MoveId.MAGIC_COAT | Magic Coat}.
* @sealed * @sealed
*/ */
export class ReflectStatusMoveAbAttr extends AbAttr {} export class ReflectStatusMoveAbAttr extends AbAttr {
private declare readonly _: never;
}
/** @sealed */ /** @sealed */
export class NoTransformAbilityAbAttr extends AbAttr { export class NoTransformAbilityAbAttr extends AbAttr {
private declare readonly _: never;
constructor() { constructor() {
super(false); super(false);
} }
@ -5693,6 +5657,7 @@ export class NoTransformAbilityAbAttr extends AbAttr {
/** @sealed */ /** @sealed */
export class NoFusionAbilityAbAttr extends AbAttr { export class NoFusionAbilityAbAttr extends AbAttr {
private declare readonly _: never;
constructor() { constructor() {
super(false); super(false);
} }
@ -5917,6 +5882,7 @@ export class IllusionPreSummonAbAttr extends PreSummonAbAttr {
/** @sealed */ /** @sealed */
export class IllusionBreakAbAttr extends AbAttr { export class IllusionBreakAbAttr extends AbAttr {
private declare readonly _: never;
// TODO: Consider adding a `canApply` method that checks if the pokemon has an active illusion // TODO: Consider adding a `canApply` method that checks if the pokemon has an active illusion
override apply({ pokemon }: AbAttrBaseParams): void { override apply({ pokemon }: AbAttrBaseParams): void {
pokemon.breakIllusion(); pokemon.breakIllusion();
@ -6294,11 +6260,11 @@ export interface PostDamageAbAttrParams extends AbAttrBaseParams {
* Triggers after the Pokemon takes any damage * Triggers after the Pokemon takes any damage
*/ */
export class PostDamageAbAttr extends AbAttr { export class PostDamageAbAttr extends AbAttr {
public canApplyPostDamage(_params: PostDamageAbAttrParams): boolean { override canApply(_params: PostDamageAbAttrParams): boolean {
return true; return true;
} }
public applyPostDamage(_params: PostDamageAbAttrParams): void {} override apply(_params: PostDamageAbAttrParams): void {}
} }
/** /**

View File

@ -1,8 +1,8 @@
import type { AbAttrParamMap } from "#app/@types/ab-attr-types"; import type { AbAttrParamMap } from "#app/@types/ability-types";
import type { AbAttr, AbAttrBaseParams, AbAttrMap, CallableAbAttrString } from "#app/@types/ability-types"; import type { AbAttr, AbAttrBaseParams, AbAttrString, CallableAbAttrString } from "#app/@types/ability-types";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
function applySingleAbAttrs<T extends CallableAbAttrString>( function applySingleAbAttrs<T extends AbAttrString>(
attrType: T, attrType: T,
params: AbAttrParamMap[T], params: AbAttrParamMap[T],
gainedMidTurn = false, gainedMidTurn = false,
@ -15,11 +15,10 @@ function applySingleAbAttrs<T extends CallableAbAttrString>(
const attr = 1 as unknown as AbAttr; const attr = 1 as unknown as AbAttr;
if (attr.is("BlockRedirectAbAttr")) { if (attr.is("BypassSpeedChanceAbAttr")) {
attr attr;
} }
const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility(); const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility();
if ( if (
gainedMidTurn && gainedMidTurn &&
@ -30,12 +29,15 @@ function applySingleAbAttrs<T extends CallableAbAttrString>(
return; return;
} }
// typescript assert // typescript assert
for (const attr of ability.getAttrs(attrType)) { for (const attr of ability.getAttrs(attrType)) {
const condition = attr.getCondition(); const condition = attr.getCondition();
let abShown = false; let abShown = false;
if ((condition && !condition(pokemon)) || !attr.canApply(params)) { if (
(condition && !condition(pokemon)) ||
// @ts-ignore: typescript can't unify the type of params with the generic type that was passed
!attr.canApply(params)
) {
continue; continue;
} }
@ -45,6 +47,7 @@ function applySingleAbAttrs<T extends CallableAbAttrString>(
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true); globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true);
abShown = true; abShown = true;
} }
// @ts-expect-error - typescript can't unify the type of params with the generic type that was passed
const message = attr.getTriggerMessage(params, ability.name); const message = attr.getTriggerMessage(params, ability.name);
if (message) { if (message) {
if (!simulated) { if (!simulated) {
@ -53,7 +56,8 @@ function applySingleAbAttrs<T extends CallableAbAttrString>(
messages.push(message); messages.push(message);
} }
// @ts-ignore: typescript can't unify the type of params with the generic type that was passed
attr.apply(params);
if (abShown) { if (abShown) {
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false); globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false);
@ -69,31 +73,42 @@ function applySingleAbAttrs<T extends CallableAbAttrString>(
function applyAbAttrsInternal<T extends CallableAbAttrString>( function applyAbAttrsInternal<T extends CallableAbAttrString>(
attrType: T, attrType: T,
params: Parameters<AbAttrMap[T]["apply"]>[0], params: AbAttrParamMap[T],
messages: string[] = [], messages: string[] = [],
gainedMidTurn = false, gainedMidTurn = false,
) { ) {
const { pokemon } = params; // If the pokemon is not defined, no ability attributes to be applied.
for (const passive of [false, true]) { // TODO: Evaluate whether this check is even necessary anymore
if (pokemon) { if (!params.pokemon) {
applySingleAbAttrs(attrType, { ...params, passive }, gainedMidTurn, messages); return;
globalScene.phaseManager.clearPhaseQueueSplice();
} }
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 case it was undefined earlier
// this is necessary in case this method is called with an object that is reused.
params.passive = undefined;
} }
} }
/** /**
* @param attrType - The type of the ability attribute to apply * @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 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>( export function applyAbAttrs<T extends CallableAbAttrString>(
attrType: T, attrType: T,
params: Parameters<AbAttrMap[T]["apply"]>[0], params: AbAttrParamMap[T],
messages?: string[],
): void { ): void {
applyAbAttrsInternal(attrType, params); applyAbAttrsInternal(attrType, params, messages);
} }
// TODO: Improve the type signatures of the following methods / refactor the apply methods // TODO: Improve the type signatures of the following methods / refactor the apply methods
/** /**
@ -108,17 +123,8 @@ export function applyOnGainAbAttrs(params: AbAttrBaseParams): void {
/** /**
* Applies ability attributes which activate when the ability is lost or suppressed (i.e. primal weather) * Applies ability attributes which activate when the ability is lost or suppressed (i.e. primal weather)
*/ */
export function applyOnLoseAbAttrs(params): void { export function applyOnLoseAbAttrs(params: AbAttrBaseParams): void {
applySingleAbAttrs("PreLeaveFieldAbAttr"); applySingleAbAttrs("PreLeaveFieldAbAttr", params, true);
applySingleAbAttrs( applySingleAbAttrs("IllusionBreakAbAttr", params, true);
pokemon,
passive,
"IllusionBreakAbAttr",
(attr, passive) => attr.apply(pokemon, passive, simulated, null, args),
(attr, passive) => attr.canApply(pokemon, passive, simulated, args),
args,
true,
simulated,
);
} }

View File

@ -137,7 +137,7 @@ export class MistTag extends ArenaTag {
if (attacker) { if (attacker) {
const bypassed = new BooleanHolder(false); const bypassed = new BooleanHolder(false);
// TODO: Allow this to be simulated // TODO: Allow this to be simulated
applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed); applyAbAttrs("InfiltratorAbAttr", { pokemon: attacker, simulated: false, bypassed });
if (bypassed.value) { if (bypassed.value) {
return false; return false;
} }
@ -758,7 +758,7 @@ class SpikesTag extends ArenaTrapTag {
} }
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
if (simulated || cancelled.value) { if (simulated || cancelled.value) {
return !cancelled.value; return !cancelled.value;
} }
@ -1438,7 +1438,10 @@ export class SuppressAbilitiesTag extends ArenaTag {
const setter = globalScene const setter = globalScene
.getField() .getField()
.filter(p => p?.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false))[0]; .filter(p => p?.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false))[0];
applyOnGainAbAttrs(setter, setter.getAbility().hasAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr")); applyOnGainAbAttrs({
pokemon: setter,
passive: setter.getAbility().hasAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr"),
});
} }
} }
@ -1451,7 +1454,7 @@ export class SuppressAbilitiesTag extends ArenaTag {
for (const pokemon of globalScene.getField(true)) { for (const pokemon of globalScene.getField(true)) {
// There is only one pokemon with this attr on the field on removal, so its abilities are already active // There is only one pokemon with this attr on the field on removal, so its abilities are already active
if (pokemon && !pokemon.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false)) { if (pokemon && !pokemon.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false)) {
[true, false].forEach(passive => applyOnGainAbAttrs(pokemon, passive)); [true, false].forEach(passive => applyOnGainAbAttrs({ pokemon, passive }));
} }
} }
} }

View File

@ -2553,7 +2553,7 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : undefined); const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : undefined);
if (target.status) { if (target.status || !statusToApply) {
return false; return false;
} else { } else {
const canSetStatus = target.canSetStatus(statusToApply, true, false, user); const canSetStatus = target.canSetStatus(statusToApply, true, false, user);
@ -2569,7 +2569,8 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
} }
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
return !target.status && target.canSetStatus(user.status?.effect, true, false, user) ? -10 : 0; const statusToApply = user.status?.effect ?? (user.hasAbility(AbilityId.COMATOSE) ? StatusEffect.SLEEP : undefined);
return !target.status && statusToApply && target.canSetStatus(statusToApply, true, false, user) ? -10 : 0;
} }
} }

View File

@ -457,8 +457,8 @@ export class Arena {
pokemon.findAndRemoveTags( pokemon.findAndRemoveTags(
t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain), t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain),
); );
applyPostTerrainChangeAbAttrs("PostTerrainChangeAbAttr", pokemon, terrain); applyAbAttrs("PostTerrainChangeAbAttr", { pokemon, terrain });
applyAbAttrs("TerrainEventTypeChangeAbAttr", pokemon, null, false); applyAbAttrs("TerrainEventTypeChangeAbAttr", { pokemon });
}); });
return true; return true;

View File

@ -108,23 +108,8 @@ import { WeatherType } from "#enums/weather-type";
import { NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; import { NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagSide } from "#enums/arena-tag-side";
import type { SuppressAbilitiesTag } from "#app/data/arena-tag"; import type { SuppressAbilitiesTag } from "#app/data/arena-tag";
import type { Ability } from "#app/data/abilities/ability"; import type { Ability, PreAttackModifyDamageAbAttrParams } from "#app/data/abilities/ability";
import { import { applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "#app/data/abilities/apply-ab-attrs";
applyAbAttrs,
applyStatMultiplierAbAttrs,
applyPreApplyBattlerTagAbAttrs,
applyPreAttackAbAttrs,
applyPreDefendAbAttrs,
applyPreSetStatusAbAttrs,
applyFieldStatMultiplierAbAttrs,
applyCheckTrappedAbAttrs,
applyPostDamageAbAttrs,
applyPostItemLostAbAttrs,
applyOnGainAbAttrs,
applyPreLeaveFieldAbAttrs,
applyOnLoseAbAttrs,
applyAllyStatMultiplierAbAttrs,
} from "#app/data/abilities/apply-ab-attrs";
import { allAbilities } from "#app/data/data-lists"; import { allAbilities } from "#app/data/data-lists";
import type PokemonData from "#app/system/pokemon-data"; import type PokemonData from "#app/system/pokemon-data";
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
@ -189,7 +174,7 @@ import { HitResult } from "#enums/hit-result";
import { AiType } from "#enums/ai-type"; import { AiType } from "#enums/ai-type";
import type { MoveResult } from "#enums/move-result"; import type { MoveResult } from "#enums/move-result";
import { PokemonMove } from "#app/data/moves/pokemon-move"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import type { AbAttrMap, AbAttrString } from "#app/@types/ability-types"; import type { AbAttrMap, AbAttrString, TypeMultiplierAbAttrParams } from "#app/@types/ability-types";
/** Base typeclass for damage parameter methods, used for DRY */ /** Base typeclass for damage parameter methods, used for DRY */
type damageParams = { type damageParams = {
@ -1364,7 +1349,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs("HighCritAttr", source, this, move, critStage); applyMoveAttrs("HighCritAttr", source, this, move, critStage);
globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage); globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage);
globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage); globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
applyAbAttrs("BonusCritAbAttr", source, null, false, critStage); applyAbAttrs("BonusCritAbAttr", { pokemon: source, critStage });
const critBoostTag = source.getTag(CritBoostTag); const critBoostTag = source.getTag(CritBoostTag);
if (critBoostTag) { if (critBoostTag) {
// Dragon cheer only gives +1 crit stage to non-dragon types // Dragon cheer only gives +1 crit stage to non-dragon types
@ -1415,46 +1400,52 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
simulated = true, simulated = true,
ignoreHeldItems = false, ignoreHeldItems = false,
): number { ): number {
const statValue = new NumberHolder(this.getStat(stat, false)); const statVal = new NumberHolder(this.getStat(stat, false));
if (!ignoreHeldItems) { if (!ignoreHeldItems) {
globalScene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statValue); globalScene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statVal);
} }
// The Ruin abilities here are never ignored, but they reveal themselves on summon anyway // The Ruin abilities here are never ignored, but they reveal themselves on summon anyway
const fieldApplied = new BooleanHolder(false); const fieldApplied = new BooleanHolder(false);
for (const pokemon of globalScene.getField(true)) { for (const pokemon of globalScene.getField(true)) {
applyFieldStatMultiplierAbAttrs( applyAbAttrs("FieldMultiplyStatAbAttr", {
"FieldMultiplyStatAbAttr",
pokemon, pokemon,
stat, stat,
statValue, statVal,
this, target: this,
fieldApplied, hasApplied: fieldApplied,
simulated, simulated,
); });
if (fieldApplied.value) { if (fieldApplied.value) {
break; break;
} }
} }
if (!ignoreAbility) { if (!ignoreAbility) {
applyStatMultiplierAbAttrs("StatMultiplierAbAttr", this, stat, statValue, simulated); applyAbAttrs("StatMultiplierAbAttr", {
pokemon: this,
stat,
statVal,
simulated,
// TODO: maybe just don't call this if the move is none?
move: move ?? allMoves[MoveId.NONE],
});
} }
const ally = this.getAlly(); const ally = this.getAlly();
if (!isNullOrUndefined(ally)) { if (!isNullOrUndefined(ally)) {
applyAllyStatMultiplierAbAttrs( applyAbAttrs("AllyStatMultiplierAbAttr", {
"AllyStatMultiplierAbAttr", pokemon: ally,
ally,
stat, stat,
statValue, statVal,
simulated, simulated,
this, // TODO: maybe just don't call this if the move is none?
move?.hasFlag(MoveFlags.IGNORE_ABILITIES) || ignoreAllyAbility, move: move ?? allMoves[MoveId.NONE],
); ignoreAbility: move?.hasFlag(MoveFlags.IGNORE_ABILITIES) || ignoreAllyAbility,
});
} }
let ret = let ret =
statValue.value * statVal.value *
this.getStatStageMultiplier(stat, opponent, move, ignoreOppAbility, isCritical, simulated, ignoreHeldItems); this.getStatStageMultiplier(stat, opponent, move, ignoreOppAbility, isCritical, simulated, ignoreHeldItems);
switch (stat) { switch (stat) {
@ -2045,20 +2036,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param ability New Ability * @param ability New Ability
*/ */
public setTempAbility(ability: Ability, passive = false): void { public setTempAbility(ability: Ability, passive = false): void {
applyOnLoseAbAttrs(this, passive); applyOnLoseAbAttrs({ pokemon: this, passive });
if (passive) { if (passive) {
this.summonData.passiveAbility = ability.id; this.summonData.passiveAbility = ability.id;
} else { } else {
this.summonData.ability = ability.id; this.summonData.ability = ability.id;
} }
applyOnGainAbAttrs(this, passive); applyOnGainAbAttrs({ pokemon: this, passive });
} }
/** /**
* Suppresses an ability and calls its onlose attributes * Suppresses an ability and calls its onlose attributes
*/ */
public suppressAbility() { public suppressAbility() {
[true, false].forEach(passive => applyOnLoseAbAttrs(this, passive)); [true, false].forEach(passive => applyOnLoseAbAttrs({ pokemon: this, passive }));
this.summonData.abilitySuppressed = true; this.summonData.abilitySuppressed = true;
} }
@ -2194,7 +2185,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const weight = new NumberHolder(this.species.weight - weightRemoved); const weight = new NumberHolder(this.species.weight - weightRemoved);
// This will trigger the ability overlay so only call this function when necessary // This will trigger the ability overlay so only call this function when necessary
applyAbAttrs("WeightMultiplierAbAttr", this, null, false, weight); applyAbAttrs("WeightMultiplierAbAttr", { pokemon: this, weight });
return Math.max(minWeight, weight.value); return Math.max(minWeight, weight.value);
} }
@ -2256,7 +2247,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return false; return false;
} }
const trappedByAbility = new BooleanHolder(false); /** Holds whether the pokemon is trapped due to an ability */
const trapped = new BooleanHolder(false);
/** /**
* Contains opposing Pokemon (Enemy/Player Pokemon) depending on perspective * Contains opposing Pokemon (Enemy/Player Pokemon) depending on perspective
* Afterwards, it filters out Pokemon that have been switched out of the field so trapped abilities/moves do not trigger * Afterwards, it filters out Pokemon that have been switched out of the field so trapped abilities/moves do not trigger
@ -2265,14 +2257,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const opposingField = opposingFieldUnfiltered.filter(enemyPkm => enemyPkm.switchOutStatus === false); const opposingField = opposingFieldUnfiltered.filter(enemyPkm => enemyPkm.switchOutStatus === false);
for (const opponent of opposingField) { for (const opponent of opposingField) {
applyCheckTrappedAbAttrs("CheckTrappedAbAttr", opponent, trappedByAbility, this, trappedAbMessages, simulated); applyAbAttrs("CheckTrappedAbAttr", { pokemon: opponent, trapped, opponent: this, simulated }, trappedAbMessages);
} }
const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
return ( return (
trappedByAbility.value || trapped.value || !!this.getTag(TrappedTag) || !!globalScene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, side)
!!this.getTag(TrappedTag) ||
!!globalScene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, side)
); );
} }
@ -2287,7 +2277,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const moveTypeHolder = new NumberHolder(move.type); const moveTypeHolder = new NumberHolder(move.type);
applyMoveAttrs("VariableMoveTypeAttr", this, null, move, moveTypeHolder); applyMoveAttrs("VariableMoveTypeAttr", this, null, move, moveTypeHolder);
applyPreAttackAbAttrs("MoveTypeChangeAbAttr", this, null, move, simulated, moveTypeHolder);
const cancelled = new BooleanHolder(false);
const power = new NumberHolder(move.power);
applyAbAttrs("MoveTypeChangeAbAttr", {
pokemon: this,
move,
simulated,
moveType: moveTypeHolder,
cancelled,
power,
opponent: this,
});
// If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type, // If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type,
// then bypass the check for ion deluge and electrify // then bypass the check for ion deluge and electrify
@ -2351,17 +2352,31 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
const cancelledHolder = cancelled ?? new BooleanHolder(false); const cancelledHolder = cancelled ?? new BooleanHolder(false);
// TypeMultiplierAbAttrParams is shared amongst the type of AbAttrs we will be invoking
const commonAbAttrParams: TypeMultiplierAbAttrParams = {
pokemon: this,
opponent: source,
move,
cancelled: cancelledHolder,
simulated,
typeMultiplier,
};
if (!ignoreAbility) { if (!ignoreAbility) {
applyPreDefendAbAttrs("TypeImmunityAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier); applyAbAttrs("TypeImmunityAbAttr", commonAbAttrParams);
if (!cancelledHolder.value) { if (!cancelledHolder.value) {
applyPreDefendAbAttrs("MoveImmunityAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier); applyAbAttrs("MoveImmunityAbAttr", commonAbAttrParams);
} }
if (!cancelledHolder.value) { if (!cancelledHolder.value) {
const defendingSidePlayField = this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); const defendingSidePlayField = this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
defendingSidePlayField.forEach(p => defendingSidePlayField.forEach(p =>
applyPreDefendAbAttrs("FieldPriorityMoveImmunityAbAttr", p, source, move, cancelledHolder), applyAbAttrs("FieldPriorityMoveImmunityAbAttr", {
pokemon: p,
opponent: source,
move,
cancelled: cancelledHolder,
}),
); );
} }
} }
@ -2376,7 +2391,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// Apply Tera Shell's effect to attacks after all immunities are accounted for // Apply Tera Shell's effect to attacks after all immunities are accounted for
if (!ignoreAbility && move.category !== MoveCategory.STATUS) { if (!ignoreAbility && move.category !== MoveCategory.STATUS) {
applyPreDefendAbAttrs("FullHpResistTypeAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier); applyAbAttrs("FullHpResistTypeAbAttr", commonAbAttrParams);
} }
if (move.category === MoveCategory.STATUS && move.hitsSubstitute(source, this)) { if (move.category === MoveCategory.STATUS && move.hitsSubstitute(source, this)) {
@ -2420,16 +2435,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
let multiplier = types let multiplier = types
.map(defType => { .map(defenderType => {
const multiplier = new NumberHolder(getTypeDamageMultiplier(moveType, defType)); const multiplier = new NumberHolder(getTypeDamageMultiplier(moveType, defenderType));
applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multiplier); applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multiplier);
if (move) { if (move) {
applyMoveAttrs("VariableMoveTypeChartAttr", null, this, move, multiplier, defType); applyMoveAttrs("VariableMoveTypeChartAttr", null, this, move, multiplier, defenderType);
} }
if (source) { if (source) {
const ignoreImmunity = new BooleanHolder(false); const ignoreImmunity = new BooleanHolder(false);
if (source.isActive(true) && source.hasAbilityWithAttr("IgnoreTypeImmunityAbAttr")) { if (source.isActive(true) && source.hasAbilityWithAttr("IgnoreTypeImmunityAbAttr")) {
applyAbAttrs("IgnoreTypeImmunityAbAttr", source, ignoreImmunity, simulated, moveType, defType); applyAbAttrs("IgnoreTypeImmunityAbAttr", {
pokemon: source,
cancelled: ignoreImmunity,
simulated,
moveType,
defenderType,
});
} }
if (ignoreImmunity.value) { if (ignoreImmunity.value) {
if (multiplier.value === 0) { if (multiplier.value === 0) {
@ -2438,7 +2459,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
const exposedTags = this.findTags(tag => tag instanceof ExposedTag) as ExposedTag[]; const exposedTags = this.findTags(tag => tag instanceof ExposedTag) as ExposedTag[];
if (exposedTags.some(t => t.ignoreImmunity(defType, moveType))) { if (exposedTags.some(t => t.ignoreImmunity(defenderType, moveType))) {
if (multiplier.value === 0) { if (multiplier.value === 0) {
return 1; return 1;
} }
@ -3358,7 +3379,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
if (!ignoreOppAbility) { if (!ignoreOppAbility) {
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", opponent, null, simulated, stat, ignoreStatStage); applyAbAttrs("IgnoreOpponentStatStagesAbAttr", {
pokemon: opponent,
ignored: ignoreStatStage,
stat,
simulated,
});
} }
if (move) { if (move) {
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, opponent, move, ignoreStatStage); applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, opponent, move, ignoreStatStage);
@ -3397,8 +3423,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const ignoreAccStatStage = new BooleanHolder(false); const ignoreAccStatStage = new BooleanHolder(false);
const ignoreEvaStatStage = new BooleanHolder(false); const ignoreEvaStatStage = new BooleanHolder(false);
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", target, null, false, Stat.ACC, ignoreAccStatStage); // TODO: consider refactoring this method to accept `simulated` and then pass simulated to these applyAbAttrs
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", this, null, false, Stat.EVA, ignoreEvaStatStage); applyAbAttrs("IgnoreOpponentStatStagesAbAttr", { pokemon: target, stat: Stat.ACC, ignored: ignoreAccStatStage });
applyAbAttrs("IgnoreOpponentStatStagesAbAttr", { pokemon: this, stat: Stat.EVA, ignored: ignoreEvaStatStage });
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage); applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage);
globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage); globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage);
@ -3418,33 +3445,40 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
: 3 / (3 + Math.min(targetEvaStage.value - userAccStage.value, 6)); : 3 / (3 + Math.min(targetEvaStage.value - userAccStage.value, 6));
} }
applyStatMultiplierAbAttrs("StatMultiplierAbAttr", this, Stat.ACC, accuracyMultiplier, false, sourceMove); applyAbAttrs("StatMultiplierAbAttr", {
pokemon: this,
stat: Stat.ACC,
statVal: accuracyMultiplier,
move: sourceMove,
});
const evasionMultiplier = new NumberHolder(1); const evasionMultiplier = new NumberHolder(1);
applyStatMultiplierAbAttrs("StatMultiplierAbAttr", target, Stat.EVA, evasionMultiplier); applyAbAttrs("StatMultiplierAbAttr", {
pokemon: target,
stat: Stat.EVA,
statVal: evasionMultiplier,
move: sourceMove,
});
const ally = this.getAlly(); const ally = this.getAlly();
if (!isNullOrUndefined(ally)) { if (!isNullOrUndefined(ally)) {
const ignore = const ignore =
this.hasAbilityWithAttr("MoveAbilityBypassAbAttr") || sourceMove.hasFlag(MoveFlags.IGNORE_ABILITIES); this.hasAbilityWithAttr("MoveAbilityBypassAbAttr") || sourceMove.hasFlag(MoveFlags.IGNORE_ABILITIES);
applyAllyStatMultiplierAbAttrs( applyAbAttrs("AllyStatMultiplierAbAttr", {
"AllyStatMultiplierAbAttr", pokemon: ally,
ally, stat: Stat.ACC,
Stat.ACC, statVal: accuracyMultiplier,
accuracyMultiplier, ignoreAbility: ignore,
false, move: sourceMove,
this, });
ignore,
); applyAbAttrs("AllyStatMultiplierAbAttr", {
applyAllyStatMultiplierAbAttrs( pokemon: ally,
"AllyStatMultiplierAbAttr", stat: Stat.EVA,
ally, statVal: evasionMultiplier,
Stat.EVA, ignoreAbility: ignore,
evasionMultiplier, move: sourceMove,
false, });
this,
ignore,
);
} }
return accuracyMultiplier.value / evasionMultiplier.value; return accuracyMultiplier.value / evasionMultiplier.value;
@ -3559,7 +3593,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs("CombinedPledgeStabBoostAttr", source, this, move, stabMultiplier); applyMoveAttrs("CombinedPledgeStabBoostAttr", source, this, move, stabMultiplier);
if (!ignoreSourceAbility) { if (!ignoreSourceAbility) {
applyAbAttrs("StabBoostAbAttr", source, null, simulated, stabMultiplier); applyAbAttrs("StabBoostAbAttr", { pokemon: source, simulated, multiplier: stabMultiplier });
} }
if (source.isTerastallized && sourceTeraType === moveType && moveType !== PokemonType.STELLAR) { if (source.isTerastallized && sourceTeraType === moveType && moveType !== PokemonType.STELLAR) {
@ -3706,16 +3740,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
null, null,
multiStrikeEnhancementMultiplier, multiStrikeEnhancementMultiplier,
); );
if (!ignoreSourceAbility) { if (!ignoreSourceAbility) {
applyPreAttackAbAttrs( applyAbAttrs("AddSecondStrikeAbAttr", {
"AddSecondStrikeAbAttr", pokemon: source,
source, opponent: this,
this,
move, move,
simulated, simulated,
null, cancelled: new BooleanHolder(false),
multiStrikeEnhancementMultiplier, multiplier: multiStrikeEnhancementMultiplier,
); });
} }
/** Doubles damage if this Pokemon's last move was Glaive Rush */ /** Doubles damage if this Pokemon's last move was Glaive Rush */
@ -3726,7 +3760,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** The damage multiplier when the given move critically hits */ /** The damage multiplier when the given move critically hits */
const criticalMultiplier = new NumberHolder(isCritical ? 1.5 : 1); const criticalMultiplier = new NumberHolder(isCritical ? 1.5 : 1);
applyAbAttrs("MultCritAbAttr", source, null, simulated, criticalMultiplier); applyAbAttrs("MultCritAbAttr", { pokemon: source, simulated, critMult: criticalMultiplier });
/** /**
* A multiplier for random damage spread in the range [0.85, 1] * A multiplier for random damage spread in the range [0.85, 1]
@ -3747,7 +3781,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
) { ) {
const burnDamageReductionCancelled = new BooleanHolder(false); const burnDamageReductionCancelled = new BooleanHolder(false);
if (!ignoreSourceAbility) { if (!ignoreSourceAbility) {
applyAbAttrs("BypassBurnDamageReductionAbAttr", source, burnDamageReductionCancelled, simulated); applyAbAttrs("BypassBurnDamageReductionAbAttr", {
pokemon: source,
cancelled: burnDamageReductionCancelled,
simulated,
});
} }
if (!burnDamageReductionCancelled.value) { if (!burnDamageReductionCancelled.value) {
burnMultiplier = 0.5; burnMultiplier = 0.5;
@ -3811,7 +3849,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** Doubles damage if the attacker has Tinted Lens and is using a resisted move */ /** Doubles damage if the attacker has Tinted Lens and is using a resisted move */
if (!ignoreSourceAbility) { if (!ignoreSourceAbility) {
applyPreAttackAbAttrs("DamageBoostAbAttr", source, this, move, simulated, damage); applyAbAttrs("DamageBoostAbAttr", {
pokemon: source,
opponent: this,
move,
simulated,
damage,
// cancelled isn't necessary for this ability attribute, but is required by the interface
cancelled: new BooleanHolder(false),
});
} }
/** Apply the enemy's Damage and Resistance tokens */ /** Apply the enemy's Damage and Resistance tokens */
@ -3822,14 +3868,26 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
globalScene.applyModifiers(EnemyDamageReducerModifier, false, damage); globalScene.applyModifiers(EnemyDamageReducerModifier, false, damage);
} }
const abAttrParams: PreAttackModifyDamageAbAttrParams = {
pokemon: this,
opponent: source,
move,
cancelled,
simulated,
damage,
};
/** Apply this Pokemon's post-calc defensive modifiers (e.g. Fur Coat) */ /** Apply this Pokemon's post-calc defensive modifiers (e.g. Fur Coat) */
if (!ignoreAbility) { if (!ignoreAbility) {
applyPreDefendAbAttrs("ReceivedMoveDamageMultiplierAbAttr", this, source, move, cancelled, simulated, damage); applyAbAttrs("ReceivedMoveDamageMultiplierAbAttr", abAttrParams);
const ally = this.getAlly(); const ally = this.getAlly();
/** Additionally apply friend guard damage reduction if ally has it. */ /** Additionally apply friend guard damage reduction if ally has it. */
if (globalScene.currentBattle.double && !isNullOrUndefined(ally) && ally.isActive(true)) { if (globalScene.currentBattle.double && !isNullOrUndefined(ally) && ally.isActive(true)) {
applyPreDefendAbAttrs("AlliedFieldDamageReductionAbAttr", ally, source, move, cancelled, simulated, damage); applyAbAttrs("AlliedFieldDamageReductionAbAttr", {
...abAttrParams,
// Same parameters as before, except we are applying the ally's ability
pokemon: ally,
});
} }
} }
@ -3837,7 +3895,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs("ModifiedDamageAttr", source, this, move, damage); applyMoveAttrs("ModifiedDamageAttr", source, this, move, damage);
if (this.isFullHp() && !ignoreAbility) { if (this.isFullHp() && !ignoreAbility) {
applyPreDefendAbAttrs("PreDefendFullHpEndureAbAttr", this, source, move, cancelled, false, damage); applyAbAttrs("PreDefendFullHpEndureAbAttr", abAttrParams);
} }
// debug message for when damage is applied (i.e. not simulated) // debug message for when damage is applied (i.e. not simulated)
@ -3875,7 +3933,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const alwaysCrit = new BooleanHolder(false); const alwaysCrit = new BooleanHolder(false);
applyMoveAttrs("CritOnlyAttr", source, this, move, alwaysCrit); applyMoveAttrs("CritOnlyAttr", source, this, move, alwaysCrit);
applyAbAttrs("ConditionalCritAbAttr", source, null, false, alwaysCrit, this, move); applyAbAttrs("ConditionalCritAbAttr", { pokemon: source, isCritical: alwaysCrit, target: this, move });
const alwaysCritTag = !!source.getTag(BattlerTagType.ALWAYS_CRIT); const alwaysCritTag = !!source.getTag(BattlerTagType.ALWAYS_CRIT);
const critChance = [24, 8, 2, 1][Phaser.Math.Clamp(this.getCritStage(source, move), 0, 3)]; const critChance = [24, 8, 2, 1][Phaser.Math.Clamp(this.getCritStage(source, move), 0, 3)];
@ -3886,7 +3944,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// apply crit block effects from lucky chant & co., overriding previous effects // apply crit block effects from lucky chant & co., overriding previous effects
const blockCrit = new BooleanHolder(false); const blockCrit = new BooleanHolder(false);
applyAbAttrs("BlockCritAbAttr", this, null, false, blockCrit); applyAbAttrs("BlockCritAbAttr", { pokemon: this, blockCrit });
const blockCritTag = globalScene.arena.getTagOnSide( const blockCritTag = globalScene.arena.getTagOnSide(
NoCritTag, NoCritTag,
this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY,
@ -3998,7 +4056,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* Multi-hits are handled in move-effect-phase.ts for PostDamageAbAttr * Multi-hits are handled in move-effect-phase.ts for PostDamageAbAttr
*/ */
if (!source || source.turnData.hitCount <= 1) { if (!source || source.turnData.hitCount <= 1) {
applyPostDamageAbAttrs("PostDamageAbAttr", this, damage, this.hasPassive(), false, [], source); applyAbAttrs("PostDamageAbAttr", { pokemon: this, damage });
} }
return damage; return damage;
} }
@ -4046,11 +4104,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const stubTag = new BattlerTag(tagType, 0, 0); const stubTag = new BattlerTag(tagType, 0, 0);
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs("BattlerTagImmunityAbAttr", this, stubTag, cancelled, true, this); applyAbAttrs("BattlerTagImmunityAbAttr", { pokemon: this, tag: stubTag, cancelled, simulated: true });
const userField = this.getAlliedField(); const userField = this.getAlliedField();
userField.forEach(pokemon => userField.forEach(pokemon =>
applyPreApplyBattlerTagAbAttrs("UserFieldBattlerTagImmunityAbAttr", pokemon, stubTag, cancelled, true, this), applyAbAttrs("UserFieldBattlerTagImmunityAbAttr", {
pokemon,
tag: stubTag,
cancelled,
simulated: true,
target: this,
}),
); );
return !cancelled.value; return !cancelled.value;
@ -4066,13 +4130,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const newTag = getBattlerTag(tagType, turnCount, sourceMove!, sourceId!); // TODO: are the bangs correct? const newTag = getBattlerTag(tagType, turnCount, sourceMove!, sourceId!); // TODO: are the bangs correct?
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyPreApplyBattlerTagAbAttrs("BattlerTagImmunityAbAttr", this, newTag, cancelled); applyAbAttrs("BattlerTagImmunityAbAttr", { pokemon: this, tag: newTag, cancelled });
if (cancelled.value) { if (cancelled.value) {
return false; return false;
} }
for (const pokemon of this.getAlliedField()) { for (const pokemon of this.getAlliedField()) {
applyPreApplyBattlerTagAbAttrs("UserFieldBattlerTagImmunityAbAttr", pokemon, newTag, cancelled, false, this); applyAbAttrs("UserFieldBattlerTagImmunityAbAttr", { pokemon, tag: newTag, cancelled, target: this });
if (cancelled.value) { if (cancelled.value) {
return false; return false;
} }
@ -4597,7 +4661,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param ignoreField Whether any field effects (weather, terrain, etc.) should be considered * @param ignoreField Whether any field effects (weather, terrain, etc.) should be considered
*/ */
canSetStatus( canSetStatus(
effect: StatusEffect | undefined, effect: StatusEffect,
quiet = false, quiet = false,
overrideStatus = false, overrideStatus = false,
sourcePokemon: Pokemon | null = null, sourcePokemon: Pokemon | null = null,
@ -4628,8 +4692,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity // Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity
const cancelImmunity = new BooleanHolder(false); const cancelImmunity = new BooleanHolder(false);
// TODO: Determine if we need to pass `quiet` as the value for simulated in this call
if (sourcePokemon) { if (sourcePokemon) {
applyAbAttrs("IgnoreTypeStatusEffectImmunityAbAttr", sourcePokemon, cancelImmunity, false, effect, defType); applyAbAttrs("IgnoreTypeStatusEffectImmunityAbAttr", {
pokemon: sourcePokemon,
cancelled: cancelImmunity,
statusEffect: effect,
defenderType: defType,
});
if (cancelImmunity.value) { if (cancelImmunity.value) {
return false; return false;
} }
@ -4678,21 +4748,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyPreSetStatusAbAttrs("StatusEffectImmunityAbAttr", this, effect, cancelled, quiet); applyAbAttrs("StatusEffectImmunityAbAttr", { pokemon: this, effect, cancelled, simulated: quiet });
if (cancelled.value) { if (cancelled.value) {
return false; return false;
} }
for (const pokemon of this.getAlliedField()) { for (const pokemon of this.getAlliedField()) {
applyPreSetStatusAbAttrs( applyAbAttrs("UserFieldStatusEffectImmunityAbAttr", {
"UserFieldStatusEffectImmunityAbAttr",
pokemon, pokemon,
effect, effect,
cancelled, cancelled,
quiet, simulated: quiet,
this, target: this,
sourcePokemon, source: sourcePokemon,
); });
if (cancelled.value) { if (cancelled.value) {
break; break;
} }
@ -4723,6 +4792,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
overrideStatus?: boolean, overrideStatus?: boolean,
quiet = true, quiet = true,
): boolean { ): boolean {
if (!effect) {
return false;
}
if (!this.canSetStatus(effect, quiet, overrideStatus, sourcePokemon)) { if (!this.canSetStatus(effect, quiet, overrideStatus, sourcePokemon)) {
return false; return false;
} }
@ -4781,7 +4853,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
sleepTurnsRemaining = sleepTurnsRemaining!; // tell TS compiler it's defined sleepTurnsRemaining = sleepTurnsRemaining!; // tell TS compiler it's defined
effect = effect!; // If `effect` is undefined then `trySetStatus()` will have already returned early via the `canSetStatus()` call
this.status = new Status(effect, 0, sleepTurnsRemaining?.value); this.status = new Status(effect, 0, sleepTurnsRemaining?.value);
return true; return true;
@ -4842,7 +4913,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (globalScene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) { if (globalScene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) {
const bypassed = new BooleanHolder(false); const bypassed = new BooleanHolder(false);
if (attacker) { if (attacker) {
applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed); applyAbAttrs("InfiltratorAbAttr", { pokemon: attacker, bypassed });
} }
return !bypassed.value; return !bypassed.value;
} }
@ -5391,7 +5462,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.hideInfo(); this.hideInfo();
} }
// Trigger abilities that activate upon leaving the field // Trigger abilities that activate upon leaving the field
applyPreLeaveFieldAbAttrs("PreLeaveFieldAbAttr", this); applyAbAttrs("PreLeaveFieldAbAttr", { pokemon: this });
this.setSwitchOutStatus(true); this.setSwitchOutStatus(true);
globalScene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true); globalScene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true);
globalScene.field.remove(this, destroy); globalScene.field.remove(this, destroy);
@ -5451,7 +5522,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
globalScene.removeModifier(heldItem, this.isEnemy()); globalScene.removeModifier(heldItem, this.isEnemy());
} }
if (forBattle) { if (forBattle) {
applyPostItemLostAbAttrs("PostItemLostAbAttr", this, false); applyAbAttrs("PostItemLostAbAttr", { pokemon: this });
} }
return true; return true;

View File

@ -2,8 +2,8 @@ import { globalScene } from "#app/global-scene";
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
import { PokemonPhase } from "./pokemon-phase"; import { PokemonPhase } from "./pokemon-phase";
import type { BattlerIndex } from "#enums/battler-index"; import type { BattlerIndex } from "#enums/battler-index";
import { applyPostSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
export class MoveEndPhase extends PokemonPhase { export class MoveEndPhase extends PokemonPhase {
public readonly phaseName = "MoveEndPhase"; public readonly phaseName = "MoveEndPhase";
@ -30,7 +30,7 @@ export class MoveEndPhase extends PokemonPhase {
globalScene.arena.setIgnoreAbilities(false); globalScene.arena.setIgnoreAbilities(false);
for (const target of this.targets) { for (const target of this.targets) {
if (target) { if (target) {
applyPostSummonAbAttrs("PostSummonRemoveEffectAbAttr", target); applyAbAttrs("PostSummonRemoveEffectAbAttr", { pokemon: target });
} }
} }

View File

@ -288,7 +288,7 @@ describe("Abilities - Unburden", () => {
expect(getHeldItemCount(purrloin)).toBe(1); expect(getHeldItemCount(purrloin)).toBe(1);
expect(treecko.getEffectiveStat(Stat.SPD)).toBe(initialTreeckoSpeed); expect(treecko.getEffectiveStat(Stat.SPD)).toBe(initialTreeckoSpeed);
expect(purrloin.getEffectiveStat(Stat.SPD)).toBe(initialPurrloinSpeed); expect(purrloin.getEffectiveStat(Stat.SPD)).toBe(initialPurrloinSpeed);
expect(unburdenAttr.applyPostItemLost).not.toHaveBeenCalled(); expect(unburdenAttr.apply).not.toHaveBeenCalled();
}); });
it("should not speed up a Pokemon after it loses the ability Unburden", async () => { it("should not speed up a Pokemon after it loses the ability Unburden", async () => {