From b9781bd8636acf6ad2dd4a70c2d58eea36214f6f Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Sat, 14 Jun 2025 12:40:26 -0500 Subject: [PATCH] Update callsites in pokemon.ts --- src/@types/ab-attr-types.ts | 7 - src/@types/ability-types.ts | 19 +- src/@types/type-helpers.ts | 8 + src/data/abilities/ability.ts | 236 +++++++++------------ src/data/abilities/apply-ab-attrs.ts | 70 ++++--- src/data/arena-tag.ts | 11 +- src/data/moves/move.ts | 5 +- src/field/arena.ts | 6 +- src/field/pokemon.ts | 295 +++++++++++++++++---------- src/phases/move-end-phase.ts | 4 +- test/abilities/unburden.test.ts | 2 +- 11 files changed, 357 insertions(+), 306 deletions(-) diff --git a/src/@types/ab-attr-types.ts b/src/@types/ab-attr-types.ts index 62999f46db6..e69de29bb2d 100644 --- a/src/@types/ab-attr-types.ts +++ b/src/@types/ab-attr-types.ts @@ -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[0]; -} \ No newline at end of file diff --git a/src/@types/ability-types.ts b/src/@types/ability-types.ts index 853c9d4e79d..8922aa818bd 100644 --- a/src/@types/ability-types.ts +++ b/src/@types/ability-types.ts @@ -1,17 +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 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 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 = (attr: TAttr, passive: boolean, ...args: any[]) => void; -export type AbAttrSuccessFunc = (attr: TAttr, passive: boolean, ...args: any[]) => boolean; 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; @@ -33,11 +30,17 @@ export type AbAttrMap = { * Subset of ability attribute classes that may be passed to {@linkcode applyAbAttrs } method * * @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 * the base apply method. * * Such attributes may not be passed to the {@linkcode applyAbAttrs} method */ -export type CallableAbAttrString = Exclude; +export type CallableAbAttrString = + | Exclude + | "PreApplyBattlerTagAbAttr"; + +export type AbAttrParamMap = { + [K in keyof AbAttrMap]: Parameters[0]; +}; diff --git a/src/@types/type-helpers.ts b/src/@types/type-helpers.ts index e67d38518f0..39a3a0b9c49 100644 --- a/src/@types/type-helpers.ts +++ b/src/@types/type-helpers.ts @@ -22,3 +22,11 @@ export type Exact = { * */ export type Closed = X; + +/** + * Remove `readonly` from all properties of the provided type + * @typeParam T - The type to make mutable + */ +export type Mutable = { + -readonly [P in keyof T]: T[P]; +}; diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index c983b5c7c47..37271933701 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -238,7 +238,7 @@ export interface AbAttrBaseParams { readonly simulated?: boolean; /** Whether the ability is the passive ability. Default false */ - readonly passive?: boolean; + passive?: boolean; } export interface AbAttrParamsWithCancel extends AbAttrBaseParams { @@ -306,6 +306,7 @@ export abstract class AbAttr { } export class BlockRecoilDamageAttr extends AbAttr { + private declare readonly _: never; constructor() { super(false); } @@ -314,7 +315,8 @@ export class BlockRecoilDamageAttr extends AbAttr { 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", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName: abilityName, @@ -333,6 +335,7 @@ export interface DoubleBattleChanceAbAttrParams extends AbAttrBaseParams { * @see {@linkcode apply} */ export class DoubleBattleChanceAbAttr extends AbAttr { + private declare readonly _: never; constructor() { 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 { /** 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 { /** The move being used by the attacker */ @@ -1628,9 +1635,9 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr { */ export interface AddSecondStrikeAbAttrParams extends AugmentMoveInteractionAbAttrParams { /** 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_ */ - multiplier: NumberHolder; + multiplier?: NumberHolder; } /** @@ -1660,11 +1667,11 @@ export class AddSecondStrikeAbAttr extends PreAttackAbAttr { * to the damage multiplier of this ability. */ override apply({ hitCount, multiplier, pokemon }: AddSecondStrikeAbAttrParams): void { - if (hitCount.value) { + if (hitCount?.value) { hitCount.value += 1; } - if (multiplier.value && pokemon.turnData.hitsLeft === 1) { + if (multiplier?.value && pokemon.turnData.hitsLeft === 1) { multiplier.value = this.damageMultiplier; } } @@ -1806,27 +1813,13 @@ export class FieldMovePowerBoostAbAttr extends AbAttr { this.powerMultiplier = powerMultiplier; } - canApplyPreAttack( - _pokemon: Pokemon | null, - _passive: boolean | null, - _simulated: boolean, - _defender: Pokemon | null, - _move: Move, - _args: any[], - ): boolean { + canApply(_params: PreAttackModifyPowerAbAttrParams): boolean { return true; // logic for this attr is handled in move.ts instead of normally } - applyPreAttack( - pokemon: Pokemon | null, - _passive: boolean | null, - _simulated: boolean, - defender: Pokemon | null, - move: Move, - args: any[], - ): void { - if (this.condition(pokemon, defender, move)) { - (args[0] as NumberHolder).value *= this.powerMultiplier; + apply({ pokemon, opponent, move, power }: PreAttackModifyPowerAbAttrParams): void { + if (this.condition(pokemon, opponent, move)) { + power.value *= this.powerMultiplier; } } } @@ -1880,6 +1873,7 @@ export interface StatMultiplierAbAttrParams extends AbAttrBaseParams { } export class StatMultiplierAbAttr extends AbAttr { + private declare readonly _: never; private stat: BattleStat; private multiplier: number; /** 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; } - canApplyStatStage({ pokemon, move, stat }: StatMultiplierAbAttrParams): boolean { + override canApply({ pokemon, move, stat }: StatMultiplierAbAttrParams): boolean { return stat === this.stat && (!this.condition || this.condition(pokemon, null, move)); } - applyStatStage({ statVal }: StatMultiplierAbAttrParams): void { + override apply({ statVal }: StatMultiplierAbAttrParams): void { 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 */ stat: BattleStat; /** 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 */ - override canApply({ stat }: IgnoreOpponentStatStagesAbAttrParam): boolean { + override canApply({ stat }: IgnoreOpponentStatStagesAbAttrParams): boolean { return this.stats.includes(stat); } /** * Sets the ignored holder to true. */ - override apply({ ignored }: IgnoreOpponentStatStagesAbAttrParam): void { + override apply({ ignored }: IgnoreOpponentStatStagesAbAttrParams): void { 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 */ export class PreLeaveFieldAbAttr extends AbAttr { - canApplyPreLeaveField(_params: Closed): boolean { + canApply(_params: Closed): boolean { return true; } - applyPreLeaveField(_params: Closed): void {} + apply(_params: Closed): void {} } /** @@ -3408,11 +3402,11 @@ export interface PreStatStageChangeAbAttrParams extends AbAttrBaseParams { * Base class for ability attributes that apply their effect before a stat stage change. */ export abstract class PreStatStageChangeAbAttr extends AbAttr { - canApplyPreStatStageChange(_params: Closed): boolean { + canApply(_params: Closed): boolean { return true; } - applyPreStatStageChange(_params: Closed): void {} + apply(_params: Closed): void {} } /** @@ -3672,8 +3666,9 @@ export interface ConditionalUserFieldProtectStatAbAttrParams extends AbAttrBaseP stat: BattleStat; /** Holds whether the stat stage change is prevented by the ability */ cancelled: BooleanHolder; + // TODO: consider making this required and not inherit from PreStatStageChangeAbAttr /** 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. + * Subclasses violate Liskov Substitution Principle, so this class must not be provided to {@linkcode applyAbAttrs} */ export abstract class PreApplyBattlerTagAbAttr extends AbAttr { - canApplyPreApplyBattlerTag(_params: Closed): boolean { + canApply(_params: PreApplyBattlerTagAbAttrParams): boolean { return true; } - applyPreApplyBattlerTag(_params: Closed): void {} + apply(_params: PreApplyBattlerTagAbAttrParams): void {} } -/** - * 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 PreApplyBattlerTagAbAttr { +// Intentionally not exported because this shouldn't be able to be passed to `applyAbAttrs`. It only exists so that +// PreApplyBattlerTagImmunityAbAttr and UserFieldPreApplyBattlerTagImmunityAbAttr can avoid code duplication +// while preserving type safety. (Since the UserField version require an additional parameter, target, in its apply methods) +abstract class BaseBattlerTagImmunityAbAttr

extends PreApplyBattlerTagAbAttr { protected immuneTagTypes: BattlerTagType[]; constructor(immuneTagTypes: BattlerTagType | BattlerTagType[]) { @@ -3747,15 +3741,15 @@ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr { this.immuneTagTypes = coerceArray(immuneTagTypes); } - override canApply({ cancelled, tag }: PreApplyBattlerTagAbAttrParams): boolean { + override canApply({ cancelled, tag }: P): boolean { return !cancelled.value && this.immuneTagTypes.includes(tag.tagType); } - override apply({ cancelled }: PreApplyBattlerTagAbAttrParams): void { + override apply({ cancelled }: P): void { cancelled.value = true; } - override getTriggerMessage({ pokemon, tag }: PreApplyBattlerTagAbAttrParams, abilityName: string): string { + override getTriggerMessage({ pokemon, tag }: P, abilityName: string): string { return i18next.t("abilityTriggers:battlerTagImmunity", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), 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 {} + /** * Provides immunity to BattlerTags {@linkcode BattlerTag} to the user. */ 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. - * @extends PreApplyBattlerTagImmunityAbAttr */ -export class UserFieldBattlerTagImmunityAbAttr extends PreApplyBattlerTagImmunityAbAttr {} +export class UserFieldBattlerTagImmunityAbAttr extends BaseBattlerTagImmunityAbAttr {} -// NOTE: We are inheriting from `PreApplyBattlerTagImmunityAbAttr` which has a different signature export class ConditionalUserFieldBattlerTagImmunityAbAttr extends UserFieldBattlerTagImmunityAbAttr { 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. * @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 // to guard against the case where return !!params.target && super.canApply(params) && this.condition(params.target ?? params.pokemon); } + override apply(_params: UserFieldBattlerTagImmunityAbAttrParams) {} + constructor(condition: (target: Pokemon) => boolean, immuneTagTypes: BattlerTagType | BattlerTagType[]) { super(immuneTagTypes); @@ -3798,9 +3807,9 @@ export class ConditionalUserFieldBattlerTagImmunityAbAttr extends UserFieldBattl 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 { @@ -3863,7 +3872,7 @@ export interface ConditionalCritAbAttrParams extends AbAttrBaseParams { /** The move being used */ move: Move; /** Holds whether the attack will critically hit */ - crit: BooleanHolder; + isCritical: BooleanHolder; } /** @@ -3878,12 +3887,12 @@ export class ConditionalCritAbAttr extends AbAttr { this.condition = condition; } - override canApply({ crit, pokemon, target, move }: ConditionalCritAbAttrParams): boolean { - return !crit.value && this.condition(pokemon, target, move); + override canApply({ isCritical, pokemon, target, move }: ConditionalCritAbAttrParams): boolean { + return !isCritical.value && this.condition(pokemon, target, move); } - override apply({ crit }: ConditionalCritAbAttrParams): void { - crit.value = true; + override apply({ isCritical }: ConditionalCritAbAttrParams): void { + isCritical.value = true; } } @@ -3904,7 +3913,7 @@ export class BlockStatusDamageAbAttr extends AbAttr { 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[]) { super(false); @@ -3916,8 +3925,6 @@ export class BlockStatusDamageAbAttr extends AbAttr { return !!pokemon.status?.effect && this.effects.includes(pokemon.status.effect); } - /** - */ override apply({ cancelled }: AbAttrParamsWithCancel): void { 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. @@ -4321,23 +4330,11 @@ export class PostWeatherLapseAbAttr extends AbAttr { this.weatherTypes = weatherTypes; } - canApplyPostWeatherLapse( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _weather: Weather | null, - _args: any[], - ): boolean { + canApply(_params: Closed): boolean { return true; } - applyPostWeatherLapse( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _weather: Weather | null, - _args: any[], - ): void {} + apply(_params: Closed): void {} getCondition(): AbAttrCondition { return getWeatherCondition(...this.weatherTypes); @@ -4353,23 +4350,11 @@ export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr { this.healFactor = healFactor; } - override canApplyPostWeatherLapse( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _weather: Weather | null, - _args: any[], - ): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { return !pokemon.isFullHp(); } - override applyPostWeatherLapse( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - _weather: Weather, - _args: any[], - ): void { + override apply({ pokemon, passive, simulated }: PreWeatherEffectAbAttrParams): void { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; if (!simulated) { globalScene.phaseManager.unshiftNew( @@ -4395,23 +4380,11 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr { this.damageFactor = damageFactor; } - override canApplyPostWeatherLapse( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _weather: Weather | null, - _args: any[], - ): boolean { + override canApply({ pokemon }: PreWeatherEffectAbAttrParams): boolean { return !pokemon.hasAbilityWithAttr("BlockNonDirectDamageAbAttr"); } - override applyPostWeatherLapse( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - _weather: Weather, - _args: any[], - ): void { + override apply({ simulated, pokemon, passive }: PreWeatherEffectAbAttrParams): void { if (!simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; globalScene.phaseManager.queueMessage( @@ -4430,8 +4403,6 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr { export interface PostTerrainChangeAbAttrParams extends AbAttrBaseParams { /** The terrain type that is being changed to */ terrain: TerrainType; - /** Holds whether the terrain change is prevented by the ability */ - cancelled: BooleanHolder; } 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 { 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 */ export class PostMoveUsedAbAttr extends AbAttr { - canApplyPostMoveUsed(_params: Closed): boolean { + canApply(_params: Closed): boolean { return true; } - applyPostMoveUsed(_params: Closed): void {} + apply(_params: Closed): void {} } /** @@ -4926,7 +4899,7 @@ export class PostMoveUsedAbAttr extends AbAttr { * @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 const forbiddenTags = [ BattlerTagType.FLYING, @@ -4985,11 +4958,11 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { * @extends AbAttr */ export class PostItemLostAbAttr extends AbAttr { - canApplyPostItemLost(_pokemon: Pokemon, _simulated: boolean, _args: any[]): boolean { + canApply(_params: Closed): boolean { return true; } - applyPostItemLost(_pokemon: Pokemon, _simulated: boolean, _args: any[]): void {} + apply(_params: Closed): void {} } /** @@ -5002,7 +4975,7 @@ export class PostItemLostApplyBattlerTagAbAttr extends PostItemLostAbAttr { this.tagType = tagType; } - override canApplyPostItemLost(pokemon: Pokemon, simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon, simulated }: AbAttrBaseParams): boolean { return !pokemon.getTag(this.tagType) && !simulated; } @@ -5011,7 +4984,7 @@ export class PostItemLostApplyBattlerTagAbAttr extends PostItemLostAbAttr { * @param pokemon {@linkcode Pokemon} with this ability * @param _args N/A */ - override applyPostItemLost(pokemon: Pokemon, _simulated: boolean, _args: any[]): void { + override apply({ pokemon }: AbAttrBaseParams): void { pokemon.addTag(this.tagType); } } @@ -5176,25 +5149,11 @@ export class CheckTrappedAbAttr extends AbAttr { this.arenaTrapCondition = condition; } - canApplyCheckTrapped( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _trapped: BooleanHolder, - _otherPokemon: Pokemon, - _args: any[], - ): boolean { + override canApply(_params: Closed): boolean { return true; } - applyCheckTrapped( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _trapped: BooleanHolder, - _otherPokemon: Pokemon, - _args: any[], - ): void {} + override apply(_params: Closed): void {} } export interface CheckTrappedAbAttrParams extends AbAttrBaseParams { @@ -5232,7 +5191,7 @@ export class ArenaTrapAbAttr extends CheckTrappedAbAttr { trapped.value = true; } - getTriggerMessage({ pokemon }: CheckTrappedAbAttrParams, abilityName: string): string { + override getTriggerMessage({ pokemon }: CheckTrappedAbAttrParams, abilityName: string): string { return i18next.t("abilityTriggers:arenaTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), 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 */ 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}. * @sealed */ -export class ReflectStatusMoveAbAttr extends AbAttr {} +export class ReflectStatusMoveAbAttr extends AbAttr { + private declare readonly _: never; +} /** @sealed */ export class NoTransformAbilityAbAttr extends AbAttr { + private declare readonly _: never; constructor() { super(false); } @@ -5693,6 +5657,7 @@ export class NoTransformAbilityAbAttr extends AbAttr { /** @sealed */ export class NoFusionAbilityAbAttr extends AbAttr { + private declare readonly _: never; constructor() { super(false); } @@ -5917,6 +5882,7 @@ export class IllusionPreSummonAbAttr extends PreSummonAbAttr { /** @sealed */ export class IllusionBreakAbAttr extends AbAttr { + private declare readonly _: never; // TODO: Consider adding a `canApply` method that checks if the pokemon has an active illusion override apply({ pokemon }: AbAttrBaseParams): void { pokemon.breakIllusion(); @@ -6294,11 +6260,11 @@ export interface PostDamageAbAttrParams extends AbAttrBaseParams { * Triggers after the Pokemon takes any damage */ export class PostDamageAbAttr extends AbAttr { - public canApplyPostDamage(_params: PostDamageAbAttrParams): boolean { + override canApply(_params: PostDamageAbAttrParams): boolean { return true; } - public applyPostDamage(_params: PostDamageAbAttrParams): void {} + override apply(_params: PostDamageAbAttrParams): void {} } /** diff --git a/src/data/abilities/apply-ab-attrs.ts b/src/data/abilities/apply-ab-attrs.ts index 56a22f2c2b0..2f4a2f446ab 100644 --- a/src/data/abilities/apply-ab-attrs.ts +++ b/src/data/abilities/apply-ab-attrs.ts @@ -1,8 +1,8 @@ -import type { AbAttrParamMap } from "#app/@types/ab-attr-types"; -import type { AbAttr, AbAttrBaseParams, AbAttrMap, CallableAbAttrString } from "#app/@types/ability-types"; +import type { AbAttrParamMap } from "#app/@types/ability-types"; +import type { AbAttr, AbAttrBaseParams, AbAttrString, CallableAbAttrString } from "#app/@types/ability-types"; import { globalScene } from "#app/global-scene"; -function applySingleAbAttrs( +function applySingleAbAttrs( attrType: T, params: AbAttrParamMap[T], gainedMidTurn = false, @@ -15,11 +15,10 @@ function applySingleAbAttrs( const attr = 1 as unknown as AbAttr; - if (attr.is("BlockRedirectAbAttr")) { - attr + if (attr.is("BypassSpeedChanceAbAttr")) { + attr; } - const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility(); if ( gainedMidTurn && @@ -30,12 +29,15 @@ function applySingleAbAttrs( return; } - - // typescript assert + // typescript assert for (const attr of ability.getAttrs(attrType)) { const condition = attr.getCondition(); 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; } @@ -45,6 +47,7 @@ function applySingleAbAttrs( globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, 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); if (message) { if (!simulated) { @@ -53,7 +56,8 @@ function applySingleAbAttrs( 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) { globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false); @@ -69,31 +73,42 @@ function applySingleAbAttrs( function applyAbAttrsInternal( attrType: T, - params: Parameters[0], + params: AbAttrParamMap[T], messages: string[] = [], gainedMidTurn = false, ) { - const { pokemon } = params; + // 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]) { - if (pokemon) { - applySingleAbAttrs(attrType, { ...params, passive }, gainedMidTurn, messages); - globalScene.phaseManager.clearPhaseQueueSplice(); - } + 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 messages - An optional array to which ability trigger messges will be added */ export function applyAbAttrs( attrType: T, - params: Parameters[0], + params: AbAttrParamMap[T], + messages?: string[], ): void { - applyAbAttrsInternal(attrType, params); + applyAbAttrsInternal(attrType, params, messages); } - // 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) */ -export function applyOnLoseAbAttrs(params): void { - applySingleAbAttrs("PreLeaveFieldAbAttr"); +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); } diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 494a0438b18..fe9a6be06f4 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -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; } @@ -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; } @@ -1438,7 +1438,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 +1454,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 })); } } } diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index c03022eff22..93b59780f6e 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -2553,7 +2553,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); @@ -2569,7 +2569,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; } } diff --git a/src/field/arena.ts b/src/field/arena.ts index 2bb00bd00f7..6893678d4a8 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -368,7 +368,7 @@ export class Arena { pokemon.findAndRemoveTags( t => "weatherTypes" in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather), ); - applyAbAttrs("PostWeatherChangeAbAttr", {pokemon, weather}); + applyAbAttrs("PostWeatherChangeAbAttr", { pokemon, weather }); }); return true; @@ -457,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; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index e2a498ebb46..b918580d31e 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -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,18 @@ 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 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, // 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); + // 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 +2391,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 +2435,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 +2459,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 +3379,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 +3423,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 +3445,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 +3593,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 +3740,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { null, multiStrikeEnhancementMultiplier, ); + if (!ignoreSourceAbility) { - applyPreAttackAbAttrs( - "AddSecondStrikeAbAttr", - source, - this, + applyAbAttrs("AddSecondStrikeAbAttr", { + pokemon: source, + opponent: this, move, simulated, - null, - multiStrikeEnhancementMultiplier, - ); + cancelled: new BooleanHolder(false), + multiplier: multiStrikeEnhancementMultiplier, + }); } /** 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 */ 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 +3781,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 +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 */ 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 */ @@ -3822,14 +3868,26 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { 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) */ 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 +3895,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 +3933,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 +3944,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 +4056,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 }); } return damage; } @@ -4046,11 +4104,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const stubTag = new BattlerTag(tagType, 0, 0); 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(); 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 +4130,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 +4661,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 +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 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 +4748,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 +4792,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 +4853,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 +4913,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 +5462,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 +5522,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; diff --git a/src/phases/move-end-phase.ts b/src/phases/move-end-phase.ts index 8c8f2ac5239..7e1006c74e8 100644 --- a/src/phases/move-end-phase.ts +++ b/src/phases/move-end-phase.ts @@ -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 }); } } diff --git a/test/abilities/unburden.test.ts b/test/abilities/unburden.test.ts index 6e24e10d168..fd7f8e9c662 100644 --- a/test/abilities/unburden.test.ts +++ b/test/abilities/unburden.test.ts @@ -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 () => {