diff --git a/src/@types/ab-attr-types.ts b/src/@types/ab-attr-types.ts new file mode 100644 index 00000000000..45f8a6d44d4 --- /dev/null +++ b/src/@types/ab-attr-types.ts @@ -0,0 +1 @@ +export type * from "#app/data/abilities/ability"; diff --git a/src/@types/ability-types.ts b/src/@types/ability-types.ts index 6f21a012b64..853c9d4e79d 100644 --- a/src/@types/ability-types.ts +++ b/src/@types/ability-types.ts @@ -4,6 +4,9 @@ import type Pokemon from "#app/field/pokemon"; import type { BattleStat } from "#enums/stat"; import type { AbAttrConstructorMap } 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"; @@ -25,3 +28,16 @@ export type AbAttrString = keyof AbAttrConstructorMap; export type AbAttrMap = { [K in keyof AbAttrConstructorMap]: InstanceType; }; + +/** + * Subset of ability attribute classes that may be passed to {@linkcode applyAbAttrs } method + * + * @remarks + * Our AbAttr classes violate Liskov Substitution Principal. + * + * 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; diff --git a/src/@types/type-helpers.ts b/src/@types/type-helpers.ts new file mode 100644 index 00000000000..0249f88919a --- /dev/null +++ b/src/@types/type-helpers.ts @@ -0,0 +1,34 @@ +/* + * A collection of custom utility types that aid in type checking and ensuring strict type conformity + */ + +// biome-ignore lint/correctness/noUnusedImports: Used in a tsdoc comment +import type { AbAttr } from "./ability-types"; + +/** Exactly matches the type of the argument, preventing adding additional properties. + * ⚠️ Should never be used with `extends`, as this will nullify the exactness of the type. + * As an example, used to ensure that the parameters of {@linkcode AbAttr#canApply} and {@linkcode AbAttr#getTriggerMessage} are compatible with + * the type of the apply method + * + * @typeParam T - The type to match exactly + */ +export type Exact = { + [K in keyof T]: T[K]; +}; + +/** + * Type hint that indicates that the type is intended to be closed to a specific shape. + * + * @remarks + * Can be used to ensure that a particular parameter does not change when subclassed, **But will only do so for one level of inheritance** + * ```ts + * type foo = { } // whatever + * class Foo { + * method(param: Closed) + * } + * ``` + * + * @typeParam T - The type to check + * @typeParam Shape - The shape to match against + */ +export type Closed = X extends Shape ? (Shape extends X ? X : never) : never; diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 70195d6a152..8e49855b477 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -81,6 +81,7 @@ import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag"; import type { Constructor } from "#app/utils/common"; import type { Localizable } from "#app/@types/locales"; import { applyAbAttrs } from "./apply-ab-attrs"; +import type { Closed, Exact } from "#app/@types/type-helpers"; export class Ability implements Localizable { public id: AbilityId; @@ -135,7 +136,8 @@ export class Ability implements Localizable { if (!targetAttr) { return []; } - return this.attrs.filter((a): a is AbAttrMap[T] => a instanceof targetAttr); + // TODO: figure out how to remove the `as AbAttrMap[T][]` cast + return this.attrs.filter((a): a is AbAttrMap[T] => a instanceof targetAttr) as AbAttrMap[T][]; } /** @@ -220,6 +222,30 @@ export class Ability implements Localizable { } } +/** + * Base set of parameters passed to every ability attribute's apply method + */ +export interface AbAttrBaseParams { + /** + * The pokemon that has the ability being applied + */ + readonly pokemon: Pokemon; + + /** + * Whether the ability application results in the interaction being cancelled + */ + readonly cancelled: BooleanHolder; + + /** Whether the ability's effects are being simulated. + * Used to prevent, for instance, messages flyouts from being displayed. + * Defaults to false. + */ + readonly simulated?: boolean; + + /** Whether the ability is the passive ability. Default false */ + readonly passive?: boolean; +} + export abstract class AbAttr { public showAbility: boolean; private extraCondition: AbAttrCondition; @@ -250,25 +276,23 @@ export abstract class AbAttr { } /** - * Applies ability effects without checking conditions - * @param _pokemon - The pokemon to apply this ability to - * @param _passive - Whether or not the ability is a passive - * @param _simulated - Whether the call is simulated - * @param _args - Extra args passed to the function. Handled by child classes. - * @see {@linkcode canApply} + * Apply ability effects without checking conditions. + * + * For a description of parameters, see {@linkcode AbAttrBaseParams} + * @see {@linkcode AbAttrBaseParams} */ - apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder | null, - _args: any[], - ): void {} + apply(_params: AbAttrBaseParams): void {} - getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null { + // The NoInfer in the next two signatures enforces that the type of the _params operand + // is always compatible with the type of apply. This allows fewer fields, but never a parameter with more. + getTriggerMessage(_params: Exact[0]>, _abilityName: string): string | null { return null; } + canApply(_params: Exact[0]>): boolean { + return true; + } + getCondition(): AbAttrCondition | null { return this.extraCondition || null; } @@ -277,19 +301,6 @@ export abstract class AbAttr { this.extraCondition = condition; return this; } - - /** - * Returns a boolean describing whether the ability can be applied under current conditions - * @param _pokemon - The pokemon to apply this ability to - * @param _passive - Whether or not the ability is a passive - * @param _simulated - Whether the call is simulated - * @param _args - Extra args passed to the function. Handled by child classes. - * @returns `true` if the ability can be applied, `false` otherwise - * @see {@linkcode apply} - */ - canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { - return true; - } } export class BlockRecoilDamageAttr extends AbAttr { @@ -297,17 +308,11 @@ export class BlockRecoilDamageAttr extends AbAttr { super(false); } - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: AbAttrBaseParams): void { cancelled.value = true; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]) { + getTriggerMessage({ pokemon }: AbAttrBaseParams, abilityName: string) { return i18next.t("abilityTriggers:blockRecoilDamage", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName: abilityName, @@ -315,6 +320,11 @@ export class BlockRecoilDamageAttr extends AbAttr { } } +export interface DoubleBattleChanceAbAttrParams extends AbAttrBaseParams { + /** Holder for the chance of a double battle that may be modified by the ability */ + chance: NumberHolder; +} + /** * Attribute for abilities that increase the chance of a double battle * occurring. @@ -326,30 +336,16 @@ export class DoubleBattleChanceAbAttr extends AbAttr { } /** - * Increases the chance of a double battle occurring - * @param args [0] {@linkcode NumberHolder} for double battle chance + * Increase the chance of a double battle occurring, storing the result in `chance` */ - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - const doubleBattleChance = args[0] as NumberHolder; - // This is divided because the chance is generated as a number from 0 to doubleBattleChance.value using Utils.randSeedInt - // A double battle will initiate if the generated number is 0 - doubleBattleChance.value = doubleBattleChance.value / 4; + override apply({ chance }: DoubleBattleChanceAbAttrParams): void { + // This is divided by 4 as the chance is generated as a number from 0 to chance.value using Utils.randSeedInt + // A double battle will initiate if the generated number is 0. + chance.value /= 4; } } -export class PostBattleInitAbAttr extends AbAttr { - canApplyPostBattleInit(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args?: any[]): boolean { - return true; - } - - applyPostBattleInit(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args?: any[]): void {} -} +export class PostBattleInitAbAttr extends AbAttr {} export class PostBattleInitFormChangeAbAttr extends PostBattleInitAbAttr { private formFunc: (p: Pokemon) => number; @@ -360,12 +356,12 @@ export class PostBattleInitFormChangeAbAttr extends PostBattleInitAbAttr { this.formFunc = formFunc; } - override canApplyPostBattleInit(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: never[]): boolean { + override canApply({ pokemon, simulated }: AbAttrBaseParams): boolean { const formIndex = this.formFunc(pokemon); return formIndex !== pokemon.formIndex && !simulated; } - override applyPostBattleInit(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { + override apply({ pokemon }: AbAttrBaseParams): void { globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); } } @@ -381,13 +377,7 @@ export class PostTeraFormChangeStatChangeAbAttr extends AbAttr { this.stages = stages; } - override apply( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _cancelled: BooleanHolder | null, - _args: any[], - ): void { + override apply({ pokemon, simulated }: AbAttrBaseParams): void { const statStageChangePhases: StatStageChangePhase[] = []; if (!simulated) { @@ -418,17 +408,14 @@ export class ClearWeatherAbAttr extends AbAttr { this.weather = weather; } - public override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + /** + * @param _params - No parameters are used for this attribute. + */ + override canApply(_params: AbAttrBaseParams): boolean { return globalScene.arena.canSetWeather(WeatherType.NONE); } - public override apply( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ pokemon, simulated }: AbAttrBaseParams): void { if (!simulated) { globalScene.arena.trySetWeather(WeatherType.NONE, pokemon); } @@ -450,17 +437,11 @@ export class ClearTerrainAbAttr extends AbAttr { this.terrain = terrain; } - public override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply(_: AbAttrBaseParams): boolean { return globalScene.arena.canSetTerrain(TerrainType.NONE); } - public override apply( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _cancelled: BooleanHolder, - _args: any[], - ): void { + public override apply({ pokemon, simulated }: AbAttrBaseParams): void { if (!simulated) { globalScene.arena.trySetTerrain(TerrainType.NONE, true, pokemon); } @@ -469,58 +450,48 @@ export class ClearTerrainAbAttr extends AbAttr { type PreDefendAbAttrCondition = (pokemon: Pokemon, attacker: Pokemon, move: Move) => boolean; -export class PreDefendAbAttr extends AbAttr { - canApplyPreDefend( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - _move: Move | null, - _cancelled: BooleanHolder | null, - _args: any[], - ): boolean { - return true; - } - - applyPreDefend( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - _move: Move | null, - _cancelled: BooleanHolder | null, - _args: any[], - ): void {} +/** + * Shared interface for AbAttrs that interact with a move that is being used by or against the user. + * + * Often extended by other interfaces to add more parameters. + * Used, e.g. by {@linkcode PreDefendAbAttr} and {@linkcode PostAttackAbAttr} + */ +export interface AugmentMoveInteractionAbAttrParams extends AbAttrBaseParams { + /** The move used by (or against, for defend attributes) */ + move: Move; + /** The pokemon on the other side of the interaction*/ + opponent: Pokemon; } +/** + * Shared interface for parameters of several {@linkcode PreDefendAbAttr} ability attributes that modify damage. + */ +export interface PreDefendModifyDamageAbAttrParams extends AugmentMoveInteractionAbAttrParams { + /** Holder for the amount of damage that will be dealt by a move */ + damage: NumberHolder; +} + +/** + * Class for abilities that apply effects before the defending Pokemon takes damage. + * + * ⚠️ This attribute must not be called via `applyAbAttrs` as its subclasses violate the Liskov Substitution Principle. + */ +export abstract class PreDefendAbAttr extends AbAttr {} + export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr { - override canApplyPreDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - _move: Move | null, - _cancelled: BooleanHolder | null, - args: any[], - ): boolean { + override canApply({ pokemon, damage }: PreDefendModifyDamageAbAttrParams): boolean { return ( pokemon.isFullHp() && // Checks if pokemon has wonder_guard (which forces 1hp) pokemon.getMaxHp() > 1 && // Damage >= hp - (args[0] as NumberHolder).value >= pokemon.hp + damage.value >= pokemon.hp && + // Cannot apply if the pokemon already has sturdy from some other source + !pokemon.getTag(BattlerTagType.STURDY) ); } - override applyPreDefend( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _attacker: Pokemon, - _move: Move, - _cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ pokemon, simulated }: PreDefendModifyDamageAbAttrParams): void { if (!simulated) { pokemon.addTag(BattlerTagType.STURDY, 1); } @@ -528,17 +499,11 @@ export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr { } export class BlockItemTheftAbAttr extends AbAttr { - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: AbAttrBaseParams): void { cancelled.value = true; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]) { + getTriggerMessage({ pokemon }: AbAttrBaseParams, abilityName: string) { return i18next.t("abilityTriggers:blockItemTheft", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, @@ -546,23 +511,22 @@ export class BlockItemTheftAbAttr extends AbAttr { } } +export interface StabBoostAbAttrParams extends AbAttrBaseParams { + /** Holds the resolved STAB multiplier after ability application */ + multiplier: NumberHolder; +} + export class StabBoostAbAttr extends AbAttr { constructor() { super(false); } - override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { - return (args[0] as NumberHolder).value > 1; + override canApply({ multiplier }: StabBoostAbAttrParams): boolean { + return multiplier.value > 1; } - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - (args[0] as NumberHolder).value += 0.5; + override apply({ multiplier }: StabBoostAbAttrParams): void { + multiplier.value += 0.5; } } @@ -577,28 +541,12 @@ export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr { this.damageMultiplier = damageMultiplier; } - override canApplyPreDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - move: Move, - _cancelled: BooleanHolder | null, - _args: any[], - ): boolean { + override canApply({ pokemon, opponent: attacker, move }: PreDefendModifyDamageAbAttrParams): boolean { return this.condition(pokemon, attacker, move); } - override applyPreDefend( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - _move: Move, - _cancelled: BooleanHolder, - args: any[], - ): void { - (args[0] as NumberHolder).value = toDmgValue((args[0] as NumberHolder).value * this.damageMultiplier); + override apply({ damage }: PreDefendModifyDamageAbAttrParams): void { + damage.value = toDmgValue(damage.value * this.damageMultiplier); } } @@ -615,20 +563,9 @@ export class AlliedFieldDamageReductionAbAttr extends PreDefendAbAttr { } /** - * Handles the damage reduction - * @param args - * - `[0]` {@linkcode NumberHolder} - The damage being dealt + * Apply the damage reduction multiplier to the damage value. */ - override applyPreDefend( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - _move: Move, - _cancelled: BooleanHolder, - args: any[], - ): void { - const damage = args[0] as NumberHolder; + override apply({ damage }: PreDefendModifyDamageAbAttrParams): void { damage.value = toDmgValue(damage.value * this.damageMultiplier); } } @@ -639,9 +576,16 @@ export class ReceivedTypeDamageMultiplierAbAttr extends ReceivedMoveDamageMultip } } +/** + * Shared interface used by several {@linkcode PreDefendAbAttr} abilities that influence the computed type effectiveness + */ +export interface TypeMultiplierAbAttrParams extends AugmentMoveInteractionAbAttrParams { + /** Holds the type multiplier of an attack. In the case of an immunity, this value will be set to 0. */ + typeMultiplier: NumberHolder; +} + /** * Determines whether a Pokemon is immune to a move because of an ability. - * @extends PreDefendAbAttr * @see {@linkcode applyPreDefend} * @see {@linkcode getCondition} */ @@ -657,15 +601,7 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr { this.condition = condition ?? null; } - override canApplyPreDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - move: Move, - _cancelled: BooleanHolder | null, - _args: any[], - ): boolean { + override canApply({ move, opponent: attacker, pokemon }: TypeMultiplierAbAttrParams): boolean { return ( ![MoveTarget.BOTH_SIDES, MoveTarget.ENEMY_SIDE, MoveTarget.USER_SIDE].includes(move.moveTarget) && attacker !== pokemon && @@ -673,26 +609,8 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr { ); } - /** - * Applies immunity if this ability grants immunity to the type of the given move. - * @param _pokemon {@linkcode Pokemon} The defending Pokemon. - * @param _passive - Whether the ability is passive. - * @param _attacker {@linkcode Pokemon} The attacking Pokemon. - * @param _move {@linkcode Move} The attacking move. - * @param _cancelled {@linkcode BooleanHolder} - A holder for a boolean value indicating if the move was cancelled. - * @param args [0] {@linkcode NumberHolder} gets set to 0 if move is immuned by an ability. - * @param args [1] - Whether the move is simulated. - */ - override applyPreDefend( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - _move: Move, - _cancelled: BooleanHolder, - args: any[], - ): void { - (args[0] as NumberHolder).value = 0; + override apply({ typeMultiplier }: TypeMultiplierAbAttrParams): void { + typeMultiplier.value = 0; } getImmuneType(): PokemonType | null { @@ -710,39 +628,14 @@ export class AttackTypeImmunityAbAttr extends TypeImmunityAbAttr { super(immuneType, condition); } - override canApplyPreDefend( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - cancelled: BooleanHolder | null, - args: any[], - ): boolean { + override canApply(params: TypeMultiplierAbAttrParams): boolean { + const { move } = params; return ( move.category !== MoveCategory.STATUS && !move.hasAttr("NeutralDamageAgainstFlyingTypeMultiplierAttr") && - super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args) + super.canApply(params) ); } - - /** - * Applies immunity if the move used is not a status move. - * Type immunity abilities that do not give additional benefits (HP recovery, stat boosts, etc) are not immune to status moves of the type - * Example: Levitate - */ - override applyPreDefend( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - cancelled: BooleanHolder, - args: any[], - ): void { - // this is a hacky way to fix the Levitate/Thousand Arrows interaction, but it works for now... - super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); - } } export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr { @@ -751,28 +644,9 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr { super(immuneType); } - override canApplyPreDefend( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - cancelled: BooleanHolder | null, - args: any[], - ): boolean { - return super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); - } - - override applyPreDefend( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - cancelled: BooleanHolder, - args: any[], - ): void { - super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); + override apply(params: TypeMultiplierAbAttrParams): void { + super.apply(params); + const { pokemon, cancelled, simulated, passive } = params; if (!pokemon.isFullHp() && !simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; globalScene.phaseManager.unshiftNew( @@ -801,28 +675,9 @@ class TypeImmunityStatStageChangeAbAttr extends TypeImmunityAbAttr { this.stages = stages; } - override canApplyPreDefend( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - cancelled: BooleanHolder | null, - args: any[], - ): boolean { - return super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); - } - - override applyPreDefend( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - cancelled: BooleanHolder, - args: any[], - ): void { - super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); + override apply(params: TypeMultiplierAbAttrParams): void { + const { cancelled, simulated, pokemon } = params; + super.apply(params); cancelled.value = true; // Suppresses "No Effect" message if (!simulated) { globalScene.phaseManager.unshiftNew( @@ -847,28 +702,9 @@ class TypeImmunityAddBattlerTagAbAttr extends TypeImmunityAbAttr { this.turnCount = turnCount; } - override canApplyPreDefend( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - cancelled: BooleanHolder | null, - args: any[], - ): boolean { - return super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); - } - - override applyPreDefend( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - cancelled: BooleanHolder, - args: any[], - ): void { - super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); + override apply(params: TypeMultiplierAbAttrParams): void { + const { cancelled, simulated, pokemon } = params; + super.apply(params); cancelled.value = true; // Suppresses "No Effect" message if (!simulated) { pokemon.addTag(this.tagType, this.turnCount, undefined, pokemon.id); @@ -881,36 +717,16 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr { super(null, condition); } - override canApplyPreDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - move: Move, - _cancelled: BooleanHolder | null, - args: any[], - ): boolean { - const modifierValue = - args.length > 0 - ? (args[0] as NumberHolder).value - : pokemon.getAttackTypeEffectiveness(attacker.getMoveType(move), attacker, undefined, undefined, move); - return move.is("AttackMove") && modifierValue < 2; + override canApply({ move, typeMultiplier }: TypeMultiplierAbAttrParams): boolean { + return move.is("AttackMove") && typeMultiplier.value < 2; } - override applyPreDefend( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - _move: Move, - cancelled: BooleanHolder, - args: any[], - ): void { + override apply({ typeMultiplier, cancelled }: TypeMultiplierAbAttrParams): void { cancelled.value = true; // Suppresses "No Effect" message - (args[0] as NumberHolder).value = 0; + typeMultiplier.value = 0; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + getTriggerMessage({ pokemon }: TypeMultiplierAbAttrParams, abilityName: string): string { return i18next.t("abilityTriggers:nonSuperEffectiveImmunity", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, @@ -924,16 +740,10 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr { * @extends PreDefendAbAttr */ export class FullHpResistTypeAbAttr extends PreDefendAbAttr { - override canApplyPreDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - move: Move | null, - _cancelled: BooleanHolder | null, - args: any[], - ): boolean { - const typeMultiplier = args[0]; + /** + * Allow application if the pokemon with the ability is at full hp and the mvoe is not fixed damage + */ + override canApply({ typeMultiplier, move, pokemon }: TypeMultiplierAbAttrParams): boolean { return ( typeMultiplier instanceof NumberHolder && !move?.hasAttr("FixedDamageAttr") && @@ -943,70 +753,22 @@ export class FullHpResistTypeAbAttr extends PreDefendAbAttr { } /** - * Reduces a type multiplier to 0.5 if the source is at full HP. - * @param pokemon {@linkcode Pokemon} the Pokemon with this ability - * @param _passive n/a - * @param _simulated n/a (this doesn't change game state) - * @param _attacker n/a - * @param _move {@linkcode Move} the move being used on the source - * @param _cancelled n/a - * @param args `[0]` a container for the move's current type effectiveness multiplier + * Reduce the type multiplier to 0.5 if the source is at full HP. */ - override applyPreDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - _move: Move | null, - _cancelled: BooleanHolder | null, - args: any[], - ): void { - const typeMultiplier = args[0]; + override apply({ typeMultiplier, pokemon }: TypeMultiplierAbAttrParams): void { typeMultiplier.value = 0.5; pokemon.turnData.moveEffectiveness = 0.5; } - getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string { + getTriggerMessage({ pokemon }: TypeMultiplierAbAttrParams, _abilityName: string): string { return i18next.t("abilityTriggers:fullHpResistType", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }); } } -export class PostDefendAbAttr extends AbAttr { - canApplyPostDefend( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - _move: Move, - _hitResult: HitResult | null, - _args: any[], - ): boolean { - return true; - } - - applyPostDefend( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - _move: Move, - _hitResult: HitResult | null, - _args: any[], - ): void {} -} - export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr { - override canApplyPreDefend( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - move: Move, - _cancelled: BooleanHolder | null, - _args: any[], - ): boolean { + override canApply({ move, opponent: attacker }: AugmentMoveInteractionAbAttrParams): boolean { return ( !(move.moveTarget === MoveTarget.USER || move.moveTarget === MoveTarget.NEAR_ALLY) && move.getPriority(attacker) > 0 && @@ -1014,41 +776,11 @@ export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr { ); } - override applyPreDefend( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - _move: Move, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: AugmentMoveInteractionAbAttrParams): void { cancelled.value = true; } } -export class PostStatStageChangeAbAttr extends AbAttr { - canApplyPostStatStageChange( - _pokemon: Pokemon, - _simulated: boolean, - _statsChanged: BattleStat[], - _stagesChanged: number, - _selfTarget: boolean, - _args: any[], - ): boolean { - return true; - } - - applyPostStatStageChange( - _pokemon: Pokemon, - _simulated: boolean, - _statsChanged: BattleStat[], - _stagesChanged: number, - _selfTarget: boolean, - _args: any[], - ): void {} -} - export class MoveImmunityAbAttr extends PreDefendAbAttr { private immuneCondition: PreDefendAbAttrCondition; @@ -1058,70 +790,39 @@ export class MoveImmunityAbAttr extends PreDefendAbAttr { this.immuneCondition = immuneCondition; } - override canApplyPreDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - move: Move, - _cancelled: BooleanHolder | null, - _args: any[], - ): boolean { + override canApply({ pokemon, opponent: attacker, move }: AugmentMoveInteractionAbAttrParams): boolean { return this.immuneCondition(pokemon, attacker, move); } - override applyPreDefend( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - _move: Move, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: AugmentMoveInteractionAbAttrParams): void { cancelled.value = true; } - getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string { + override getTriggerMessage({ pokemon }: AugmentMoveInteractionAbAttrParams, _abilityName: string): string { return i18next.t("abilityTriggers:moveImmunity", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); } } +export interface PreDefendModifyAccAbAttrParams extends AugmentMoveInteractionAbAttrParams { + /** Holds the accuracy of the move after the ability is applied */ + accuracy: NumberHolder; +} + /** * Reduces the accuracy of status moves used against the Pokémon with this ability to 50%. * Used by Wonder Skin. - * - * @extends PreDefendAbAttr */ export class WonderSkinAbAttr extends PreDefendAbAttr { constructor() { super(false); } - override canApplyPreDefend( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - move: Move, - _cancelled: BooleanHolder | null, - args: any[], - ): boolean { - const moveAccuracy = args[0] as NumberHolder; - return move.category === MoveCategory.STATUS && moveAccuracy.value >= 50; + override canApply({ move, accuracy }: PreDefendModifyAccAbAttrParams): boolean { + return move.category === MoveCategory.STATUS && accuracy.value >= 50; } - override applyPreDefend( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - _move: Move, - _cancelled: BooleanHolder, - args: any[], - ): void { - const moveAccuracy = args[0] as NumberHolder; - moveAccuracy.value = 50; + override apply({ accuracy }: PreDefendModifyAccAbAttrParams): void { + accuracy.value = 50; } } @@ -1135,52 +836,40 @@ export class MoveImmunityStatStageChangeAbAttr extends MoveImmunityAbAttr { this.stages = stages; } - override canApplyPreDefend( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - cancelled: BooleanHolder | null, - args: any[], - ): boolean { - return !simulated && super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); + override canApply(params: AugmentMoveInteractionAbAttrParams): boolean { + // TODO: Evaluate whether it makes sense to check against simulated here. + // We likely want to check 'simulated' when the apply method enqueues the phase + return !params.simulated && super.canApply(params); } - override applyPreDefend( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - cancelled: BooleanHolder, - args: any[], - ): void { - super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); + override apply(params: AugmentMoveInteractionAbAttrParams): void { + super.apply(params); + // TODO: We probably should not unshift the phase if this is simulated globalScene.phaseManager.unshiftNew( "StatStageChangePhase", - pokemon.getBattlerIndex(), + params.pokemon.getBattlerIndex(), true, [this.stat], this.stages, ); } } + +/** + * Shared parameters for ability attributes that apply an effect after move was used by or against the the user. + */ +export interface PostMoveInteractionAbAttrParams extends AugmentMoveInteractionAbAttrParams { + /** Stores the hit result of the move used in the interaction */ + readonly hitResult: HitResult; +} + +export class PostDefendAbAttr extends AbAttr {} + /** * Class for abilities that make drain moves deal damage to user instead of healing them. - * @extends PostDefendAbAttr - * @see {@linkcode applyPostDefend} */ export class ReverseDrainAbAttr extends PostDefendAbAttr { - override canApplyPostDefend( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - move: Move, - _hitResult: HitResult | null, - _args: any[], - ): boolean { + override canApply({ move }: PostMoveInteractionAbAttrParams): boolean { return move.hasAttr("HitHealAttr"); } @@ -1188,22 +877,8 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr { * Determines if a damage and draining move was used to check if this ability should stop the healing. * Examples include: Absorb, Draining Kiss, Bitter Blade, etc. * Also displays a message to show this ability was activated. - * @param _pokemon {@linkcode Pokemon} with this ability - * @param _passive N/A - * @param attacker {@linkcode Pokemon} that is attacking this Pokemon - * @param _move {@linkcode PokemonMove} that is being used - * @param _hitResult N/A - * @param _args N/A */ - override applyPostDefend( - _pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - attacker: Pokemon, - _move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ simulated, opponent: attacker }: PostMoveInteractionAbAttrParams): void { if (!simulated) { globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) }), @@ -1235,27 +910,11 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr { this.allOthers = allOthers; } - override canApplyPostDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - move: Move, - _hitResult: HitResult | null, - _args: any[], - ): boolean { + override canApply({ pokemon, opponent: attacker, move }: PostMoveInteractionAbAttrParams): boolean { return this.condition(pokemon, attacker, move); } - override applyPostDefend( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - attacker: Pokemon, - _move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ simulated, pokemon, opponent: attacker }: PostMoveInteractionAbAttrParams): void { if (simulated) { return; } @@ -1307,15 +966,7 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr { this.selfTarget = selfTarget; } - override canApplyPostDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - move: Move, - _hitResult: HitResult | null, - _args: any[], - ): boolean { + override canApply({ pokemon, opponent: attacker, move }: PostMoveInteractionAbAttrParams): boolean { const hpGateFlat: number = Math.ceil(pokemon.getMaxHp() * this.hpGate); const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1]; const damageReceived = lastAttackReceived?.damage || 0; @@ -1324,15 +975,7 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr { ); } - override applyPostDefend( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - attacker: Pokemon, - _move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ simulated, pokemon, opponent: attacker }: PostMoveInteractionAbAttrParams): void { if (!simulated) { globalScene.phaseManager.unshiftNew( "StatStageChangePhase", @@ -1356,30 +999,14 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr { this.tagType = tagType; } - override canApplyPostDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - move: Move, - _hitResult: HitResult | null, - _args: any[], - ): boolean { + override canApply({ pokemon, opponent: attacker, move }: PostMoveInteractionAbAttrParams): boolean { const tag = globalScene.arena.getTag(this.tagType) as ArenaTrapTag; return ( this.condition(pokemon, attacker, move) && (!globalScene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) ); } - override applyPostDefend( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _attacker: Pokemon, - _move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ simulated, pokemon }: PostMoveInteractionAbAttrParams): void { if (!simulated) { globalScene.arena.addTag( this.tagType, @@ -1402,27 +1029,11 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { this.tagType = tagType; } - override canApplyPostDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - move: Move, - _hitResult: HitResult | null, - _args: any[], - ): boolean { + override canApply({ pokemon, opponent: attacker, move }: PostMoveInteractionAbAttrParams): boolean { return this.condition(pokemon, attacker, move); } - override applyPostDefend( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _attacker: Pokemon, - move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ simulated, pokemon, move }: PostMoveInteractionAbAttrParams): void { if (!pokemon.getTag(this.tagType) && !simulated) { pokemon.addTag(this.tagType, undefined, undefined, pokemon.id); globalScene.phaseManager.queueMessage( @@ -1438,34 +1049,24 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr { private type: PokemonType; - override canApplyPostDefend( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - hitResult: HitResult, - _args: any[], - ): boolean { + override canApply({ + opponent: attacker, + move, + pokemon, + hitResult, + simulated, + }: PostMoveInteractionAbAttrParams): boolean { this.type = attacker.getMoveType(move); const pokemonTypes = pokemon.getTypes(true); return hitResult < HitResult.NO_EFFECT && (simulated || pokemonTypes.length !== 1 || pokemonTypes[0] !== this.type); } - override applyPostDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ pokemon, opponent: attacker, move }: PostMoveInteractionAbAttrParams): void { const type = attacker.getMoveType(move); pokemon.summonData.types = [type]; } - override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + override getTriggerMessage({ pokemon }: PostMoveInteractionAbAttrParams, abilityName: string): string { return i18next.t("abilityTriggers:postDefendTypeChange", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, @@ -1483,27 +1084,11 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr { this.terrainType = terrainType; } - override canApplyPostDefend( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - _move: Move, - hitResult: HitResult, - _args: any[], - ): boolean { + override canApply({ hitResult }: PostMoveInteractionAbAttrParams): boolean { return hitResult < HitResult.NO_EFFECT && globalScene.arena.canSetTerrain(this.terrainType); } - override applyPostDefend( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _attacker: Pokemon, - _move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ simulated, pokemon }: PostMoveInteractionAbAttrParams): void { if (!simulated) { globalScene.arena.trySetTerrain(this.terrainType, false, pokemon); } @@ -1521,15 +1106,7 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr { this.effects = effects; } - override canApplyPostDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - move: Move, - _hitResult: HitResult | null, - _args: any[], - ): boolean { + override canApply({ pokemon, move, opponent: attacker }: PostMoveInteractionAbAttrParams): boolean { const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)]; return ( @@ -1540,15 +1117,8 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr { ); } - override applyPostDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - _move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ opponent: attacker, pokemon }: PostMoveInteractionAbAttrParams): void { + // TODO: Probably want to check against simulated here const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)]; attacker.trySetStatus(effect, true, pokemon); @@ -1560,31 +1130,9 @@ export class EffectSporeAbAttr extends PostDefendContactApplyStatusEffectAbAttr super(10, StatusEffect.POISON, StatusEffect.PARALYSIS, StatusEffect.SLEEP); } - override canApplyPostDefend( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - hitResult: HitResult | null, - args: any[], - ): boolean { - return ( - !(attacker.hasAbility(AbilityId.OVERCOAT) || attacker.isOfType(PokemonType.GRASS)) && - super.canApplyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args) - ); - } - - override applyPostDefend( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - hitResult: HitResult, - args: any[], - ): void { - super.applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args); + override canApply(params: PostMoveInteractionAbAttrParams): boolean { + const attacker = params.opponent; + return !(attacker.hasAbility(AbilityId.OVERCOAT) || attacker.isOfType(PokemonType.GRASS)) && super.canApply(params); } } @@ -1601,15 +1149,7 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr { this.turnCount = turnCount; } - override canApplyPostDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - move: Move, - _hitResult: HitResult | null, - _args: any[], - ): boolean { + override canApply({ move, pokemon, opponent: attacker }: PostMoveInteractionAbAttrParams): boolean { return ( move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) && pokemon.randBattleSeedInt(100) < this.chance && @@ -1617,15 +1157,7 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr { ); } - override applyPostDefend( - _pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ simulated, opponent: attacker, move }: PostMoveInteractionAbAttrParams): void { if (!simulated) { attacker.addTag(this.tagType, this.turnCount, move.id, attacker.id); } @@ -1643,15 +1175,7 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr { this.stages = stages; } - override applyPostDefend( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _attacker: Pokemon, - _move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ simulated, pokemon }: PostMoveInteractionAbAttrParams): void { if (!simulated) { globalScene.phaseManager.unshiftNew( "StatStageChangePhase", @@ -1679,15 +1203,7 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { this.damageRatio = damageRatio; } - override canApplyPostDefend( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - _hitResult: HitResult | null, - _args: any[], - ): boolean { + override canApply({ simulated, move, opponent: attacker, pokemon }: PostMoveInteractionAbAttrParams): boolean { return ( !simulated && move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) && @@ -1695,20 +1211,12 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { ); } - override applyPostDefend( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - _move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ opponent: attacker }: PostMoveInteractionAbAttrParams): void { attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), { result: HitResult.INDIRECT }); attacker.turnData.damageTaken += toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)); } - override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + override getTriggerMessage({ pokemon }: PostMoveInteractionAbAttrParams, abilityName: string): string { return i18next.t("abilityTriggers:postDefendContactDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, @@ -1731,37 +1239,21 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr { this.turns = turns; } - override canApplyPostDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - move: Move, - _hitResult: HitResult | null, - _args: any[], - ): boolean { + override canApply({ move, opponent: attacker, pokemon }: PostMoveInteractionAbAttrParams): boolean { return ( move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) && !attacker.getTag(BattlerTagType.PERISH_SONG) ); } - override applyPostDefend( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - attacker: Pokemon, - _move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ simulated, opponent: attacker, pokemon }: PostMoveInteractionAbAttrParams): void { if (!simulated) { attacker.addTag(BattlerTagType.PERISH_SONG, this.turns); pokemon.addTag(BattlerTagType.PERISH_SONG, this.turns); } } - override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + override getTriggerMessage({ pokemon }: PostMoveInteractionAbAttrParams, abilityName: string): string { return i18next.t("abilityTriggers:perishBody", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName: abilityName, @@ -1780,15 +1272,7 @@ export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr { this.condition = condition; } - override canApplyPostDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - move: Move, - _hitResult: HitResult | null, - _args: any[], - ): boolean { + override canApply({ pokemon, opponent: attacker, move }: PostMoveInteractionAbAttrParams): boolean { return ( !(this.condition && !this.condition(pokemon, attacker, move)) && !globalScene.arena.weather?.isImmutable() && @@ -1796,15 +1280,7 @@ export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr { ); } - override applyPostDefend( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _attacker: Pokemon, - _move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ simulated, pokemon }: PostMoveInteractionAbAttrParams): void { if (!simulated) { globalScene.arena.trySetWeather(this.weatherType, pokemon); } @@ -1812,30 +1288,14 @@ export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr { } export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr { - override canApplyPostDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - move: Move, - _hitResult: HitResult | null, - _args: any[], - ): boolean { + override canApply({ move, opponent: attacker, pokemon }: PostMoveInteractionAbAttrParams): boolean { return ( move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) && attacker.getAbility().isSwappable ); } - override applyPostDefend( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - attacker: Pokemon, - _move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ simulated, opponent: attacker, pokemon }: PostMoveInteractionAbAttrParams): void { if (!simulated) { const tempAbility = attacker.getAbility(); attacker.setTempAbility(pokemon.getAbility()); @@ -1843,7 +1303,7 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr { } } - override getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string { + override getTriggerMessage({ pokemon }: PostMoveInteractionAbAttrParams, _abilityName: string): string { return i18next.t("abilityTriggers:postDefendAbilitySwap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), }); @@ -1858,15 +1318,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { this.ability = ability; } - override canApplyPostDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - move: Move, - _hitResult: HitResult | null, - _args: any[], - ): boolean { + override canApply({ move, opponent: attacker, pokemon }: PostMoveInteractionAbAttrParams): boolean { return ( move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) && attacker.getAbility().isSuppressable && @@ -1874,21 +1326,13 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { ); } - override applyPostDefend( - _pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - attacker: Pokemon, - _move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ simulated, opponent: attacker }: PostMoveInteractionAbAttrParams): void { if (!simulated) { attacker.setTempAbility(allAbilities[this.ability]); } } - override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + override getTriggerMessage({ pokemon }: PostMoveInteractionAbAttrParams, abilityName: string): string { return i18next.t("abilityTriggers:postDefendAbilityGive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, @@ -1907,15 +1351,7 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { this.chance = chance; } - override canApplyPostDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - move: Move, - _hitResult: HitResult | null, - _args: any[], - ): boolean { + override canApply({ move, opponent: attacker, pokemon }: PostMoveInteractionAbAttrParams): boolean { return ( isNullOrUndefined(attacker.getTag(BattlerTagType.DISABLED)) && move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) && @@ -1923,15 +1359,8 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { ); } - override applyPostDefend( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ simulated, opponent: attacker, move, pokemon }: PostMoveInteractionAbAttrParams): void { + // TODO: investigate why this is setting properties if (!simulated) { this.attacker = attacker; this.move = move; @@ -1940,6 +1369,17 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { } } +export class PostStatStageChangeAbAttr extends AbAttr {} + +export interface PostStatStageChangeAbAttrParams extends AbAttrBaseParams { + /** The stats that were changed */ + stats: BattleStat[]; + /** The amount of stages that the stats changed by */ + stages: number; + /**Whether the source of the stat stages were from the user's own move */ + selfTarget: boolean; +} + export class PostStatStageChangeStatStageChangeAbAttr extends PostStatStageChangeAbAttr { private condition: PokemonStatStageChangeCondition; private statsToChange: BattleStat[]; @@ -1953,25 +1393,14 @@ export class PostStatStageChangeStatStageChangeAbAttr extends PostStatStageChang this.stages = stages; } - override canApplyPostStatStageChange( - pokemon: Pokemon, - _simulated: boolean, - statStagesChanged: BattleStat[], - stagesChanged: number, - selfTarget: boolean, - _args: any[], - ): boolean { - return this.condition(pokemon, statStagesChanged, stagesChanged) && !selfTarget; + override canApply({ pokemon, stats, stages, selfTarget }: PostStatStageChangeAbAttrParams): boolean { + return this.condition(pokemon, stats, stages) && !selfTarget; } - override applyPostStatStageChange( - pokemon: Pokemon, - simulated: boolean, - _statStagesChanged: BattleStat[], - _stagesChanged: number, - _selfTarget: boolean, - _args: any[], - ): void { + /** + * Add additional stat changes when one of the pokemon's own stats change + */ + override apply({ simulated, pokemon }: PostStatStageChangeAbAttrParams): void { if (!simulated) { globalScene.phaseManager.unshiftNew( "StatStageChangePhase", @@ -1984,32 +1413,17 @@ export class PostStatStageChangeStatStageChangeAbAttr extends PostStatStageChang } } -export class PreAttackAbAttr extends AbAttr { - canApplyPreAttack( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _defender: Pokemon | null, - _move: Move, - _args: any[], - ): boolean { - return true; - } +export abstract class PreAttackAbAttr extends AbAttr {} - applyPreAttack( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _defender: Pokemon | null, - _move: Move, - _args: any[], - ): void {} +export interface ModifyMoveEffectChanceAbAttrParams extends AbAttrBaseParams { + /** The move being used by the attacker */ + move: Move; + /** Holds the additional effect chance. Must be between 0 and 1*/ + chance: NumberHolder; } /** * Modifies moves additional effects with multipliers, ie. Sheer Force, Serene Grace. - * @extends AbAttr - * @see {@linkcode apply} */ export class MoveEffectChanceMultiplierAbAttr extends AbAttr { private chanceMultiplier: number; @@ -2019,96 +1433,58 @@ export class MoveEffectChanceMultiplierAbAttr extends AbAttr { this.chanceMultiplier = chanceMultiplier; } - override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { + override canApply({ chance, move }: ModifyMoveEffectChanceAbAttrParams): boolean { const exceptMoves = [MoveId.ORDER_UP, MoveId.ELECTRO_SHOT]; - return !((args[0] as NumberHolder).value <= 0 || exceptMoves.includes((args[1] as Move).id)); + return !(chance.value <= 0 || exceptMoves.includes(move.id)); } - /** - * @param args [0]: {@linkcode NumberHolder} Move additional effect chance. Has to be higher than or equal to 0. - * [1]: {@linkcode MoveId } Move used by the ability user. - */ - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - (args[0] as NumberHolder).value *= this.chanceMultiplier; - (args[0] as NumberHolder).value = Math.min((args[0] as NumberHolder).value, 100); + override apply({ chance }: ModifyMoveEffectChanceAbAttrParams): void { + chance.value *= this.chanceMultiplier; + chance.value = Math.min(chance.value, 100); } } /** * Sets incoming moves additional effect chance to zero, ignoring all effects from moves. ie. Shield Dust. - * @extends PreDefendAbAttr - * @see {@linkcode applyPreDefend} */ export class IgnoreMoveEffectsAbAttr extends PreDefendAbAttr { constructor(showAbility = false) { super(showAbility); } - override canApplyPreDefend( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - _move: Move | null, - _cancelled: BooleanHolder | null, - args: any[], - ): boolean { - return (args[0] as NumberHolder).value > 0; + override canApply({ chance }: ModifyMoveEffectChanceAbAttrParams): boolean { + return chance.value > 0; } - /** - * @param args [0]: {@linkcode NumberHolder} Move additional effect chance. - */ - override applyPreDefend( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - _move: Move, - _cancelled: BooleanHolder, - args: any[], - ): void { - (args[0] as NumberHolder).value = 0; - } -} - -export class VariableMovePowerAbAttr extends PreAttackAbAttr { - override canApplyPreAttack( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _defender: Pokemon, - _move: Move, - _args: any[], - ): boolean { - return true; + override apply({ chance }: ModifyMoveEffectChanceAbAttrParams): void { + chance.value = 0; } } export class FieldPreventExplosiveMovesAbAttr extends AbAttr { - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: AugmentMoveInteractionAbAttrParams): void { cancelled.value = true; } } +export interface FieldMultiplyStatAbAttrParams extends AbAttrBaseParams { + /** The kind of stat that is being checked for modification */ + stat: Stat; + /** Holds the value of the stat after multipliers */ + statVal: NumberHolder; + /** The target of the stat multiplier */ + target: Pokemon; + /** Holds whether another multiplier has already been applied to the stat. + * + * @remarks + * Intended to be used to prevent the multiplier from stacking + * with other instances of the ability */ + hasApplied: BooleanHolder; +} + /** * Multiplies a Stat if the checked Pokemon lacks this ability. * If this ability cannot stack, a BooleanHolder can be used to prevent this from stacking. - * @see {@link applyFieldStatMultiplierAbAttrs} - * @see {@link applyFieldStat} - * @see {@link BooleanHolder} */ export class FieldMultiplyStatAbAttr extends AbAttr { private stat: Stat; @@ -2123,49 +1499,32 @@ export class FieldMultiplyStatAbAttr extends AbAttr { this.canStack = canStack; } - canApplyFieldStat( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - stat: Stat, - _statValue: NumberHolder, - checkedPokemon: Pokemon, - hasApplied: BooleanHolder, - _args: any[], - ): boolean { + canApply({ hasApplied, target, stat }: FieldMultiplyStatAbAttrParams): boolean { return ( this.canStack || (!hasApplied.value && this.stat === stat && - checkedPokemon.getAbilityAttrs("FieldMultiplyStatAbAttr").every(attr => attr.stat !== stat)) + target.getAbilityAttrs("FieldMultiplyStatAbAttr").every(attr => attr.stat !== stat)) ); } /** * applyFieldStat: Tries to multiply a Pokemon's Stat - * @param _pokemon {@linkcode Pokemon} the Pokemon using this ability - * @param _passive {@linkcode boolean} unused - * @param _stat {@linkcode Stat} the type of the checked stat - * @param statValue {@linkcode NumberHolder} the value of the checked stat - * @param _checkedPokemon {@linkcode Pokemon} the Pokemon this ability is targeting - * @param hasApplied {@linkcode BooleanHolder} whether or not another multiplier has been applied to this stat - * @param _args {any[]} unused */ - applyFieldStat( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _stat: Stat, - statValue: NumberHolder, - _checkedPokemon: Pokemon, - hasApplied: BooleanHolder, - _args: any[], - ): void { - statValue.value *= this.multiplier; + apply({ statVal, hasApplied }: FieldMultiplyStatAbAttrParams): void { + statVal.value *= this.multiplier; hasApplied.value = true; } } +export interface MoveTypeChangeAbAttrParams extends AugmentMoveInteractionAbAttrParams { + // TODO: Replace the number holder with a holder for the type. + /** Holds the type of the move, which may change after ability application */ + moveType: NumberHolder; + /** Holds the power of the move, which may change after ability application */ + power: NumberHolder; +} + export class MoveTypeChangeAbAttr extends PreAttackAbAttr { constructor( private newType: PokemonType, @@ -2183,24 +1542,10 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr { * - The move is not forbidden from having its type changed by an ability, e.g. {@linkcode MoveId.MULTI_ATTACK} * - The user is not terastallized and using tera blast * - The user is not a terastallized terapagos with tera stellar using tera starstorm - * @param pokemon - The pokemon that has the move type changing ability and is using the attacking move - * @param _passive - Unused - * @param _simulated - Unused - * @param _defender - The pokemon being attacked (unused) - * @param move - The move being used - * @param _args - args[0] holds the type that the move is changed to, args[1] holds the multiplier - * @returns whether the move type change attribute can be applied */ - override canApplyPreAttack( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _defender: Pokemon | null, - move: Move, - _args: [NumberHolder?, NumberHolder?, ...any], - ): boolean { + override canApply({ pokemon, opponent: target, move }: MoveTypeChangeAbAttrParams): boolean { return ( - (!this.condition || this.condition(pokemon, _defender, move)) && + (!this.condition || this.condition(pokemon, target, move)) && !noAbilityTypeOverrideMoves.has(move.id) && (!pokemon.isTerastallized || (move.id !== MoveId.TERA_BLAST && @@ -2210,28 +1555,9 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr { ); } - /** - * @param _pokemon - The pokemon that has the move type changing ability and is using the attacking move - * @param _passive - Unused - * @param _simulated - Unused - * @param _defender - The pokemon being attacked (unused) - * @param _move - The move being used - * @param args - args[0] holds the type that the move is changed to, args[1] holds the multiplier - */ - override applyPreAttack( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _defender: Pokemon, - _move: Move, - args: [NumberHolder?, NumberHolder?, ...any], - ): void { - if (args[0] && args[0] instanceof NumberHolder) { - args[0].value = this.newType; - } - if (args[1] && args[1] instanceof NumberHolder) { - args[1].value *= this.powerMultiplier; - } + override apply({ moveType, power }: MoveTypeChangeAbAttrParams): void { + moveType.value = this.newType; + power.value *= this.powerMultiplier; } } @@ -2243,14 +1569,7 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr { super(true); } - override canApplyPreAttack( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _defender: Pokemon | null, - move: Move, - _args: any[], - ): boolean { + override canApply({ move, pokemon }: AugmentMoveInteractionAbAttrParams): boolean { if ( !pokemon.isTerastallized && move.id !== MoveId.STRUGGLE && @@ -2275,14 +1594,7 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr { return false; } - override applyPreAttack( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _defender: Pokemon, - move: Move, - _args: any[], - ): void { + override apply({ simulated, pokemon, move }: AugmentMoveInteractionAbAttrParams): void { const moveType = pokemon.getMoveType(move); if (!simulated) { @@ -2292,7 +1604,7 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr { } } - getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string { + getTriggerMessage({ pokemon }: AugmentMoveInteractionAbAttrParams, _abilityName: string): string { return i18next.t("abilityTriggers:pokemonTypeChange", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveType: i18next.t(`pokemonInfo:Type.${PokemonType[this.moveType]}`), @@ -2300,61 +1612,63 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr { } } +/** + * Parameters for abilities that modify the hit count and damage of a move + */ +export interface AddSecondStrikeAbAttrParams extends AugmentMoveInteractionAbAttrParams { + /** Holder for the number of hits. May be modified by ability application */ + hitCount: NumberHolder; + /** Holder for the damage multiplier _of the current hit_ */ + multiplier: NumberHolder; +} + /** * Class for abilities that convert single-strike moves to two-strike moves (i.e. Parental Bond). - * @param damageMultiplier the damage multiplier for the second strike, relative to the first. */ export class AddSecondStrikeAbAttr extends PreAttackAbAttr { private damageMultiplier: number; + /** + * @param damageMultiplier - The damage multiplier for the second strike, relative to the first + */ constructor(damageMultiplier: number) { super(false); this.damageMultiplier = damageMultiplier; } - override canApplyPreAttack( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _defender: Pokemon | null, - move: Move, - _args: any[], - ): boolean { + /** + * Return whether the move can be multi-strike enhanced + */ + override canApply({ pokemon, move }: AddSecondStrikeAbAttrParams): boolean { return move.canBeMultiStrikeEnhanced(pokemon, true); } /** - * If conditions are met, this doubles the move's hit count (via args[1]) - * or multiplies the damage of secondary strikes (via args[2]) - * @param pokemon the {@linkcode Pokemon} using the move - * @param _passive n/a - * @param _defender n/a - * @param _move the {@linkcode Move} used by the ability source - * @param args Additional arguments: - * - `[0]` the number of strikes this move currently has ({@linkcode NumberHolder}) - * - `[1]` the damage multiplier for the current strike ({@linkcode NumberHolder}) + * Add one to the move's hit count, and, if the pokemon has only one hit left, sets the damage multiplier + * to the damage multiplier of this ability. */ - override applyPreAttack( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _defender: Pokemon, - _move: Move, - args: any[], - ): void { - const hitCount = args[0] as NumberHolder; - const multiplier = args[1] as NumberHolder; - if (hitCount?.value) { + override apply({ hitCount, multiplier, pokemon }: AddSecondStrikeAbAttrParams): void { + if (hitCount.value) { hitCount.value += 1; } - if (multiplier?.value && pokemon.turnData.hitsLeft === 1) { + if (multiplier.value && pokemon.turnData.hitsLeft === 1) { multiplier.value = this.damageMultiplier; } } } +/** + * Common interface for parameters used by abilities that modify damage/power of a move before an attack + */ +export interface PreAttackModifyDamageAbAttrParams extends AugmentMoveInteractionAbAttrParams { + /** + * The power of the move or amount of damage. May be modified by ability application. + */ + damage: NumberHolder; +} + /** * Class for abilities that boost the damage of moves * For abilities that boost the base power of moves, see VariableMovePowerAbAttr @@ -2371,38 +1685,37 @@ export class DamageBoostAbAttr extends PreAttackAbAttr { this.condition = condition; } - override canApplyPreAttack( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - defender: Pokemon | null, - move: Move, - _args: any[], - ): boolean { - return this.condition(pokemon, defender, move); + override canApply({ pokemon, opponent: target, move }: PreAttackModifyDamageAbAttrParams): boolean { + return this.condition(pokemon, target, move); } /** - * - * @param _pokemon the attacker pokemon - * @param _passive N/A - * @param _defender the target pokemon - * @param _move the move used by the attacker pokemon - * @param args Utils.NumberHolder as damage + * Adjust the power by the damage multiplier. */ - override applyPreAttack( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _defender: Pokemon, - _move: Move, - args: any[], - ): void { - const power = args[0] as NumberHolder; + override apply({ damage: power }: PreAttackModifyDamageAbAttrParams): void { power.value = toDmgValue(power.value * this.damageMultiplier); } } +export interface PreAttackModifyPowerAbAttrParams extends AugmentMoveInteractionAbAttrParams { + /** Holds the base power of the move, which may be modified after ability application */ + power: NumberHolder; +} + +/* +This base class *is* allowed to be invoked directly by `abAttrApply`. +As such, we require that all subclasses have compatible `apply` parameters. +To do this, we use the `Closed` type. This ensures that any subclass of `VariableMovePowerAbAttr` +may not modify the type of apply's parameter to an interface that introduces new fields +or changes the type of existing fields. +*/ +export abstract class VariableMovePowerAbAttr extends PreAttackAbAttr { + override canApply(_params: Closed): boolean { + return true; + } + override apply(_params: Closed): void {} +} + export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr { private condition: PokemonAttackCondition; private powerMultiplier: number; @@ -2413,26 +1726,12 @@ export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr { this.powerMultiplier = powerMultiplier; } - override canApplyPreAttack( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - defender: Pokemon | null, - move: Move, - _args: any[], - ): boolean { - return this.condition(pokemon, defender, move); + override canApply({ pokemon, opponent, move }: PreAttackModifyPowerAbAttrParams): boolean { + return this.condition(pokemon, opponent, move); } - override applyPreAttack( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _defender: Pokemon, - _move: Move, - args: any[], - ): void { - (args[0] as NumberHolder).value *= this.powerMultiplier; + override apply({ power }: PreAttackModifyPowerAbAttrParams): void { + power.value *= this.powerMultiplier; } } @@ -2455,48 +1754,31 @@ export class LowHpMoveTypePowerBoostAbAttr extends MoveTypePowerBoostAbAttr { /** * Abilities which cause a variable amount of power increase. - * @extends VariableMovePowerAbAttr - * @see {@link applyPreAttack} */ export class VariableMovePowerBoostAbAttr extends VariableMovePowerAbAttr { private mult: (user: Pokemon, target: Pokemon, move: Move) => number; /** - * @param mult A function which takes the user, target, and move, and returns the power multiplier. 1 means no multiplier. - * @param {boolean} showAbility Whether to show the ability when it activates. + * @param mult - A function which takes the user, target, and move, and returns the power multiplier. 1 means no multiplier. + * @param showAbility - Whether to show the ability when it activates. */ constructor(mult: (user: Pokemon, target: Pokemon, move: Move) => number, showAbility = true) { super(showAbility); this.mult = mult; } - override canApplyPreAttack( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - defender: Pokemon, - move: Move, - _args: any[], - ): boolean { - return this.mult(pokemon, defender, move) !== 1; + override canApply({ pokemon, opponent, move }: PreAttackModifyPowerAbAttrParams): boolean { + return this.mult(pokemon, opponent, move) !== 1; } - override applyPreAttack( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - defender: Pokemon, - move: Move, - args: any[], - ): void { - const multiplier = this.mult(pokemon, defender, move); - (args[0] as NumberHolder).value *= multiplier; + override apply({ pokemon, opponent, move, power }: PreAttackModifyPowerAbAttrParams): void { + const multiplier = this.mult(pokemon, opponent, move); + power.value *= multiplier; } } /** * Boosts the power of a Pokémon's move under certain conditions. - * @extends AbAttr */ export class FieldMovePowerBoostAbAttr extends AbAttr { // TODO: Refactor this class? It extends from base AbAttr but has preAttack methods and gets called directly instead of going through applyAbAttrsInternal @@ -2540,7 +1822,6 @@ export class FieldMovePowerBoostAbAttr extends AbAttr { /** * Boosts the power of a specific type of move. - * @extends FieldMovePowerBoostAbAttr */ export class PreAttackFieldMoveTypePowerBoostAbAttr extends FieldMovePowerBoostAbAttr { /** @@ -2578,9 +1859,23 @@ export class AllyMoveCategoryPowerBoostAbAttr extends FieldMovePowerBoostAbAttr } } +export interface StatMultiplierAbAttrParams extends AbAttrBaseParams { + /** The move being used by the user in the interaction*/ + move: Move; + /** The stat to determine modification for*/ + stat: BattleStat; + /** Holds the value of the stat, which may change after ability application. */ + statVal: NumberHolder; +} + export class StatMultiplierAbAttr extends AbAttr { private stat: BattleStat; private multiplier: number; + /** Function determining if the stat multiplier is able to be applied to the move. + * + * @remarks + * Currently only used by Hustle. + */ private condition: PokemonAttackCondition | null; constructor(stat: BattleStat, multiplier: number, condition?: PokemonAttackCondition) { @@ -2591,71 +1886,20 @@ export class StatMultiplierAbAttr extends AbAttr { this.condition = condition ?? null; } - canApplyStatStage( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - stat: BattleStat, - _statValue: NumberHolder, - args: any[], - ): boolean { - const move = args[0] as Move; + canApplyStatStage({ pokemon, move, stat }: StatMultiplierAbAttrParams): boolean { return stat === this.stat && (!this.condition || this.condition(pokemon, null, move)); } - applyStatStage( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _stat: BattleStat, - statValue: NumberHolder, - _args: any[], - ): void { - statValue.value *= this.multiplier; + applyStatStage({ statVal }: StatMultiplierAbAttrParams): void { + statVal.value *= this.multiplier; } } -export class PostAttackAbAttr extends AbAttr { - private attackCondition: PokemonAttackCondition; - - /** The default attackCondition requires that the selected move is a damaging move */ - constructor( - attackCondition: PokemonAttackCondition = (_user, _target, move) => move.category !== MoveCategory.STATUS, - showAbility = true, - ) { - super(showAbility); - - this.attackCondition = attackCondition; - } - - /** - * By default, this method checks that the move used is a damaging attack before - * applying the effect of any inherited class. This can be changed by providing a different {@link attackCondition} to the constructor. See {@link ConfusionOnStatusEffectAbAttr} - * for an example of an effect that does not require a damaging move. +export interface AllyStatMultiplierAbAttrParams extends StatMultiplierAbAttrParams { + /** Whether abilities are being ignored during the interaction (e.g. due to a Mold-Breaker like effect). + * Note that some abilities that provide stat multipliers to allies apply their boosts regardless of this flag. */ - canApplyPostAttack( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - defender: Pokemon, - move: Move, - _hitResult: HitResult | null, - _args: any[], - ): boolean { - // When attackRequired is true, we require the move to be an attack move and to deal damage before checking secondary requirements. - // If attackRequired is false, we always defer to the secondary requirements. - return this.attackCondition(pokemon, defender, move); - } - - applyPostAttack( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _defender: Pokemon, - _move: Move, - _hitResult: HitResult | null, - _args: any[], - ): void {} + ignoreAbility: boolean; } /** @@ -2683,65 +1927,29 @@ export class AllyStatMultiplierAbAttr extends AbAttr { /** * Multiply a Pokemon's Stat due to an Ally's ability. - * @param _pokemon - The ally {@linkcode Pokemon} with the ability (unused) - * @param passive - unused - * @param _simulated - Whether the ability is being simulated (unused) - * @param _stat - The type of the checked {@linkcode Stat} (unused) - * @param statValue - {@linkcode NumberHolder} containing the value of the checked stat - * @param _checkedPokemon - The {@linkcode Pokemon} this ability is targeting (unused) - * @param _ignoreAbility - Whether the ability should be ignored if possible - * @param _args - unused - * @returns `true` if this changed the checked stat, `false` otherwise. */ - applyAllyStat( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _stat: BattleStat, - statValue: NumberHolder, - _checkedPokemon: Pokemon, - _ignoreAbility: boolean, - _args: any[], - ) { - statValue.value *= this.multiplier; + apply({ statVal }: AllyStatMultiplierAbAttrParams) { + statVal.value *= this.multiplier; } /** - * Check if this ability can apply to the checked stat. - * @param _pokemon - The ally {@linkcode Pokemon} with the ability (unused) - * @param passive - unused - * @param _simulated - Whether the ability is being simulated (unused) - * @param stat - The type of the checked {@linkcode Stat} - * @param _statValue - {@linkcode NumberHolder} containing the value of the checked stat - * @param _checkedPokemon - The {@linkcode Pokemon} this ability is targeting (unused) - * @param ignoreAbility - Whether the ability should be ignored if possible - * @param _args - unused - * @returns `true` if this can apply to the checked stat, `false` otherwise. + * @returns Whether the ability with this attribute can apply to the checked stat */ - canApplyAllyStat( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - stat: BattleStat, - _statValue: NumberHolder, - _checkedPokemon: Pokemon, - ignoreAbility: boolean, - _args: any[], - ): boolean { + canApply({ stat, ignoreAbility }: AllyStatMultiplierAbAttrParams): boolean { return stat === this.stat && !(ignoreAbility && this.ignorable); } } /** - * Takes effect whenever a move succesfully executes, such as gorilla tactics' move-locking. + * Takes effect whenever the user's move succesfully executes, such as gorilla tactics' move-locking. * (More specifically, whenever a move is pushed to the move history) */ export class ExecutedMoveAbAttr extends AbAttr { - canApplyExecutedMove(_pokemon: Pokemon, _simulated: boolean): boolean { + canApply(_params: Closed): boolean { return true; } - applyExecutedMove(_pokemon: Pokemon, _simulated: boolean): void {} + apply(_params: Closed): void {} } /** @@ -2753,17 +1961,50 @@ export class GorillaTacticsAbAttr extends ExecutedMoveAbAttr { super(showAbility); } - override canApplyExecutedMove(pokemon: Pokemon, _simulated: boolean): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { + // TODO: Consider whether checking against simulated makes sense here return !pokemon.getTag(BattlerTagType.GORILLA_TACTICS); } - override applyExecutedMove(pokemon: Pokemon, simulated: boolean): void { + override apply({ pokemon, simulated }: AbAttrBaseParams): void { if (!simulated) { pokemon.addTag(BattlerTagType.GORILLA_TACTICS); } } } +/* +Subclasses that override the `canApply` and `apply` are not allowed to change the type of their parameters. +This is enforced via the {@linkcode Closed} type. +*/ +/** + * Base class for abilities that apply some effect after the user's move successfully executes. + */ +export abstract class PostAttackAbAttr extends AbAttr { + private attackCondition: PokemonAttackCondition; + + /** The default attackCondition requires that the selected move is a damaging move */ + constructor( + attackCondition: PokemonAttackCondition = (_user, _target, move) => move.category !== MoveCategory.STATUS, + showAbility = true, + ) { + super(showAbility); + + this.attackCondition = attackCondition; + } + + /** + * By default, this method checks that the move used is a damaging attack. + * This can be changed by providing a different {@link attackCondition} to the constructor. See {@link ConfusionOnStatusEffectAbAttr} + * for an example of an effect that does not require a damaging move. + */ + override canApply({ pokemon, opponent, move }: Closed): boolean { + return this.attackCondition(pokemon, opponent, move); + } + + override apply(_params: Closed): void {} +} + export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { private stealCondition: PokemonAttackCondition | null; private stolenItem?: PokemonHeldItemModifier; @@ -2774,22 +2015,18 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { this.stealCondition = stealCondition ?? null; } - override canApplyPostAttack( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - defender: Pokemon, - move: Move, - hitResult: HitResult, - args: any[], - ): boolean { + override canApply(params: PostMoveInteractionAbAttrParams): boolean { + const { simulated, pokemon, opponent, move, hitResult } = params; + // TODO: Revisit the hitResult check here. + // The PostAttackAbAttr should should only be invoked in cases where the move successfully connected, + // calling `super.canApply` already checks that the move was a damage move and not a status move. if ( - super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args) && + super.canApply(params) && !simulated && hitResult < HitResult.NO_EFFECT && - (!this.stealCondition || this.stealCondition(pokemon, defender, move)) + (!this.stealCondition || this.stealCondition(pokemon, opponent, move)) ) { - const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferable); + const heldItems = this.getTargetHeldItems(opponent).filter(i => i.isTransferable); if (heldItems.length) { // Ensure that the stolen item in testing is the same as when the effect is applied this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; @@ -2802,16 +2039,8 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { return false; } - override applyPostAttack( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - defender: Pokemon, - _move: Move, - _hitResult: HitResult, - _args: any[], - ): void { - const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferable); + override apply({ opponent, pokemon }: PostMoveInteractionAbAttrParams): void { + const heldItems = this.getTargetHeldItems(opponent).filter(i => i.isTransferable); if (!this.stolenItem) { this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; } @@ -2819,7 +2048,7 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:postAttackStealHeldItem", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - defenderName: defender.name, + defenderName: opponent.name, stolenItemType: this.stolenItem.type.name, }), ); @@ -2848,45 +2077,30 @@ export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr { this.effects = effects; } - override canApplyPostAttack( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - hitResult: HitResult | null, - args: any[], - ): boolean { + override canApply(params: PostMoveInteractionAbAttrParams): boolean { + const { simulated, pokemon, move, opponent } = params; if ( - super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args) && + super.canApply(params) && (simulated || - (!attacker.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") && - pokemon !== attacker && + (!opponent.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") && + pokemon !== opponent && (!this.contactRequired || - move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon })) && + move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: pokemon, target: opponent })) && pokemon.randBattleSeedInt(100) < this.chance && !pokemon.status)) ) { const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)]; - return simulated || attacker.canSetStatus(effect, true, false, pokemon); + return simulated || opponent.canSetStatus(effect, true, false, pokemon); } return false; } - applyPostAttack( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - _move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + apply({ pokemon, opponent }: PostMoveInteractionAbAttrParams): void { const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)]; - attacker.trySetStatus(effect, true, pokemon); + opponent.trySetStatus(effect, true, pokemon); } } @@ -2913,40 +2127,25 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr { this.effects = effects; } - override canApplyPostAttack( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - hitResult: HitResult | null, - args: any[], - ): boolean { + override canApply(params: PostMoveInteractionAbAttrParams): boolean { + const { pokemon, move, opponent } = params; /**Battler tags inflicted by abilities post attacking are also considered additional effects.*/ return ( - super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args) && - !attacker.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") && - pokemon !== attacker && + super.canApply(params) && + !opponent.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") && + pokemon !== opponent && (!this.contactRequired || - move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon })) && - pokemon.randBattleSeedInt(100) < this.chance(attacker, pokemon, move) && + move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: opponent, target: pokemon })) && + pokemon.randBattleSeedInt(100) < this.chance(opponent, pokemon, move) && !pokemon.status ); } - override applyPostAttack( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - attacker: Pokemon, - _move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ pokemon, simulated, opponent }: PostMoveInteractionAbAttrParams): void { if (!simulated) { const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randBattleSeedInt(this.effects.length)]; - attacker.addTag(effect); + opponent.addTag(effect); } } } @@ -2961,17 +2160,9 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { this.condition = condition; } - override canApplyPostDefend( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - attacker: Pokemon, - move: Move, - hitResult: HitResult, - _args: any[], - ): boolean { - if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) { - const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable); + override canApply({ simulated, pokemon, opponent, move, hitResult }: PostMoveInteractionAbAttrParams): boolean { + if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, opponent, move))) { + const heldItems = this.getTargetHeldItems(opponent).filter(i => i.isTransferable); if (heldItems.length) { this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; if (globalScene.canTransferHeldItemModifier(this.stolenItem, pokemon)) { @@ -2982,16 +2173,8 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { return false; } - override applyPostDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - _move: Move, - _hitResult: HitResult, - _args: any[], - ): void { - const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable); + override apply({ pokemon, opponent }: PostMoveInteractionAbAttrParams): void { + const heldItems = this.getTargetHeldItems(opponent).filter(i => i.isTransferable); if (!this.stolenItem) { this.stolenItem = heldItems[pokemon.randBattleSeedInt(heldItems.length)]; } @@ -2999,7 +2182,7 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:postDefendStealHeldItem", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - attackerName: attacker.name, + attackerName: opponent.name, stolenItemType: this.stolenItem.type.name, }), ); @@ -3015,38 +2198,29 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { } } +/** + * Shared parameters used for abilities that apply an effect after the user is inflicted with a status condition. + */ +export interface PostSetStatusAbAttrParams extends AbAttrBaseParams { + /** The pokemon that set the status condition, or undefined if not set by a pokemon */ + sourcePokemon?: Pokemon; + /** The status effect that was set*/ + effect: StatusEffect; +} + +/* +Subclasses that override the `canApply` and `apply` methods of PostSetStatusAbAttr are not allowed to change the +type of their parameters. This is enforced via the Closed type. +*/ /** * Base class for defining all {@linkcode Ability} Attributes after a status effect has been set. - * @see {@linkcode applyPostSetStatus()}. */ export class PostSetStatusAbAttr extends AbAttr { - canApplyPostSetStatus( - _pokemon: Pokemon, - _sourcePokemon: Pokemon | null = null, - _passive: boolean, - _effect: StatusEffect, - _simulated: boolean, - _rgs: any[], - ): boolean { + canApply(_params: Closed): boolean { return true; } - /** - * Does nothing after a status condition is set. - * @param _pokemon {@linkcode Pokemon} that status condition was set on. - * @param _sourcePokemon {@linkcode Pokemon} that that set the status condition. Is `null` if status was not set by a Pokemon. - * @param _passive Whether this ability is a passive. - * @param _effect {@linkcode StatusEffect} that was set. - * @param _args Set of unique arguments needed by this attribute. - */ - applyPostSetStatus( - _pokemon: Pokemon, - _sourcePokemon: Pokemon | null = null, - _passive: boolean, - _effect: StatusEffect, - _simulated: boolean, - _args: any[], - ): void {} + apply(_params: Closed): void {} } /** @@ -3055,14 +2229,14 @@ export class PostSetStatusAbAttr extends AbAttr { * ability attribute. For Synchronize ability. */ export class SynchronizeStatusAbAttr extends PostSetStatusAbAttr { - override canApplyPostSetStatus( - _pokemon: Pokemon, - sourcePokemon: (Pokemon | null) | undefined, - _passive: boolean, - effect: StatusEffect, - _simulated: boolean, - _args: any[], - ): boolean { + /** + * @returns Whether the status effect that was set is one of the synchronizable statuses: + * - {@linkcode StatusEffect.BURN | Burn} + * - {@linkcode StatusEffect.PARALYSIS | Paralysis} + * - {@linkcode StatusEffect.POISON | Poison} + * - {@linkcode StatusEffect.TOXIC | Toxic} + */ + override canApply({ sourcePokemon, effect }: PostSetStatusAbAttrParams): boolean { /** Synchronizable statuses */ const syncStatuses = new Set([ StatusEffect.BURN, @@ -3078,32 +2252,24 @@ export class SynchronizeStatusAbAttr extends PostSetStatusAbAttr { /** * If the `StatusEffect` that was set is Burn, Paralysis, Poison, or Toxic, and the status * was set by a source Pokemon, set the source Pokemon's status to the same `StatusEffect`. - * @param pokemon {@linkcode Pokemon} that status condition was set on. - * @param sourcePokemon {@linkcode Pokemon} that that set the status condition. Is null if status was not set by a Pokemon. - * @param _passive Whether this ability is a passive. - * @param effect {@linkcode StatusEffect} that was set. - * @param _args Set of unique arguments needed by this attribute. */ - override applyPostSetStatus( - pokemon: Pokemon, - sourcePokemon: Pokemon | null = null, - _passive: boolean, - effect: StatusEffect, - simulated: boolean, - _args: any[], - ): void { + override apply({ simulated, effect, sourcePokemon, pokemon }: PostSetStatusAbAttrParams): void { if (!simulated && sourcePokemon) { sourcePokemon.trySetStatus(effect, true, pokemon); } } } +/** + * Base class for abilities that apply an effect after the user knocks out an opponent in battle. + * Not to be confused with {@link PostKnockOutAbAttr}, which applies after any pokemon is knocked out in battle. + */ export class PostVictoryAbAttr extends AbAttr { - canApplyPostVictory(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + canApply(_params: Closed): boolean { return true; } - applyPostVictory(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {} + apply(_params: Closed): void {} } class PostVictoryStatStageChangeAbAttr extends PostVictoryAbAttr { @@ -3117,7 +2283,7 @@ class PostVictoryStatStageChangeAbAttr extends PostVictoryAbAttr { this.stages = stages; } - override applyPostVictory(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ pokemon, simulated }: AbAttrBaseParams): void { const stat = typeof this.stat === "function" ? this.stat(pokemon) : this.stat; if (!simulated) { globalScene.phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), true, [stat], this.stages); @@ -3134,36 +2300,36 @@ export class PostVictoryFormChangeAbAttr extends PostVictoryAbAttr { this.formFunc = formFunc; } - override canApplyPostVictory(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { const formIndex = this.formFunc(pokemon); return formIndex !== pokemon.formIndex; } - override applyPostVictory(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ simulated, pokemon }: AbAttrBaseParams): void { if (!simulated) { globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); } } } -export class PostKnockOutAbAttr extends AbAttr { - canApplyPostKnockOut( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _knockedOut: Pokemon, - _args: any[], - ): boolean { +/** + * Shared parameters used for abilities that apply an effect after a Pokemon (other than the user) is knocked out. + */ +export interface PostKnockOutAbAttrParams extends AbAttrBaseParams { + /** The Pokemon that was knocked out */ + victim: Pokemon; +} + +/** + * Base class for ability attributes that apply after a Pokemon (other than the user) is knocked out, including indirectly. + * Not to be confused with {@linkcode PostVictoryAbAttr}, which applies after the user directly knocks out an opponent. + */ +export abstract class PostKnockOutAbAttr extends AbAttr { + canApply(_params: Closed): boolean { return true; } - applyPostKnockOut( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _knockedOut: Pokemon, - _args: any[], - ): void {} + apply(_params: Closed): void {} } export class PostKnockOutStatStageChangeAbAttr extends PostKnockOutAbAttr { @@ -3177,13 +2343,7 @@ export class PostKnockOutStatStageChangeAbAttr extends PostKnockOutAbAttr { this.stages = stages; } - override applyPostKnockOut( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _knockedOut: Pokemon, - _args: any[], - ): void { + override apply({ pokemon, simulated }: PostKnockOutAbAttrParams): void { const stat = typeof this.stat === "function" ? this.stat(pokemon) : this.stat; if (!simulated) { globalScene.phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), true, [stat], this.stages); @@ -3192,35 +2352,29 @@ export class PostKnockOutStatStageChangeAbAttr extends PostKnockOutAbAttr { } export class CopyFaintedAllyAbilityAbAttr extends PostKnockOutAbAttr { - override canApplyPostKnockOut( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - knockedOut: Pokemon, - _args: any[], - ): boolean { - return pokemon.isPlayer() === knockedOut.isPlayer() && knockedOut.getAbility().isCopiable; + override canApply({ pokemon, victim }: PostKnockOutAbAttrParams): boolean { + return pokemon.isPlayer() === victim.isPlayer() && victim.getAbility().isCopiable; } - override applyPostKnockOut( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - knockedOut: Pokemon, - _args: any[], - ): void { + override apply({ pokemon, simulated, victim }: PostKnockOutAbAttrParams): void { if (!simulated) { - pokemon.setTempAbility(knockedOut.getAbility()); + pokemon.setTempAbility(victim.getAbility()); globalScene.phaseManager.queueMessage( i18next.t("abilityTriggers:copyFaintedAllyAbility", { - pokemonNameWithAffix: getPokemonNameWithAffix(knockedOut), - abilityName: allAbilities[knockedOut.getAbility().id].name, + pokemonNameWithAffix: getPokemonNameWithAffix(victim), + abilityName: allAbilities[victim.getAbility().id].name, }), ); } } } +interface IgnoreOpponentStatStagesAbAttrParam extends AbAttrBaseParams { + /** The to check for ignorability */ + stat: BattleStat; + /** Holds whether the stat is ignored by the ability */ + ignored: BooleanHolder; +} /** * Ability attribute for ignoring the opponent's stat changes * @param stats the stats that should be ignored @@ -3234,45 +2388,35 @@ export class IgnoreOpponentStatStagesAbAttr extends AbAttr { this.stats = stats ?? BATTLE_STATS; } - override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { - return this.stats.includes(args[0]); + /** + * @returns Whether `stat` is one of the stats ignored by the ability + */ + override canApply({ stat }: IgnoreOpponentStatStagesAbAttrParam): boolean { + return this.stats.includes(stat); } /** - * Modifies a BooleanHolder and returns the result to see if a stat is ignored or not - * @param _pokemon n/a - * @param _passive n/a - * @param _simulated n/a - * @param _cancelled n/a - * @param args A BooleanHolder that represents whether or not to ignore a stat's stat changes + * Sets the ignored holder to true. */ - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - (args[1] as BooleanHolder).value = true; + override apply({ ignored }: IgnoreOpponentStatStagesAbAttrParam): void { + ignored.value = true; } } +/** + * Abilities with this attribute prevent the user from being affected by Intimidate. + * @sealed + */ export class IntimidateImmunityAbAttr extends AbAttr { constructor() { super(false); } - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: AbAttrBaseParams): void { cancelled.value = true; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + getTriggerMessage({ pokemon }: AbAttrBaseParams, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:intimidateImmunity", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, @@ -3292,13 +2436,7 @@ export class PostIntimidateStatStageChangeAbAttr extends AbAttr { this.overwrites = !!overwrites; } - override apply( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ pokemon, simulated, cancelled }: AbAttrBaseParams): void { if (!simulated) { globalScene.phaseManager.pushNew( "StatStageChangePhase", @@ -3316,7 +2454,7 @@ export class PostIntimidateStatStageChangeAbAttr extends AbAttr { * Base class for defining all {@linkcode Ability} Attributes post summon * @see {@linkcode applyPostSummon()} */ -export class PostSummonAbAttr extends AbAttr { +export abstract class PostSummonAbAttr extends AbAttr { /** Should the ability activate when gained in battle? This will almost always be true */ private activateOnGain: boolean; @@ -3332,23 +2470,20 @@ export class PostSummonAbAttr extends AbAttr { return this.activateOnGain; } - canApplyPostSummon(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + canApply(_params: Closed): boolean { return true; } /** * Applies ability post summon (after switching in) - * @param _pokemon {@linkcode Pokemon} with this ability - * @param _passive Whether this ability is a passive - * @param _args Set of unique arguments needed by this attribute */ - applyPostSummon(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {} + apply(_params: Closed): void {} } /** * Base class for ability attributes which remove an effect on summon */ -export class PostSummonRemoveEffectAbAttr extends PostSummonAbAttr {} +export abstract class PostSummonRemoveEffectAbAttr extends PostSummonAbAttr {} /** * Removes specified arena tags when a Pokemon is summoned. @@ -3365,11 +2500,11 @@ export class PostSummonRemoveArenaTagAbAttr extends PostSummonAbAttr { this.arenaTags = arenaTags; } - override canApplyPostSummon(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply(_params: AbAttrBaseParams): boolean { return globalScene.arena.tags.some(tag => this.arenaTags.includes(tag.tagType)); } - override applyPostSummon(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ simulated }: AbAttrBaseParams): void { if (!simulated) { for (const arenaTag of this.arenaTags) { globalScene.arena.removeTag(arenaTag); @@ -3396,7 +2531,7 @@ export class PostSummonAddArenaTagAbAttr extends PostSummonAbAttr { this.quiet = quiet; } - public override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + public override apply({ pokemon, simulated }: AbAttrBaseParams): void { this.sourceId = pokemon.id; if (!simulated) { globalScene.arena.addTag(this.tagType, this.turnCount, undefined, this.sourceId, this.side, this.quiet); @@ -3413,7 +2548,7 @@ export class PostSummonMessageAbAttr extends PostSummonAbAttr { this.messageFunc = messageFunc; } - override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ simulated, pokemon }: AbAttrBaseParams): void { if (!simulated) { globalScene.phaseManager.queueMessage(this.messageFunc(pokemon)); } @@ -3430,7 +2565,7 @@ export class PostSummonUnnamedMessageAbAttr extends PostSummonAbAttr { this.message = message; } - override applyPostSummon(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ simulated }: AbAttrBaseParams): void { if (!simulated) { globalScene.phaseManager.queueMessage(this.message); } @@ -3448,11 +2583,11 @@ export class PostSummonAddBattlerTagAbAttr extends PostSummonAbAttr { this.turnCount = turnCount; } - override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { return pokemon.canAddTag(this.tagType); } - override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ simulated, pokemon }: AbAttrBaseParams): void { if (!simulated) { pokemon.addTag(this.tagType, this.turnCount); } @@ -3475,11 +2610,11 @@ export class PostSummonRemoveBattlerTagAbAttr extends PostSummonRemoveEffectAbAt this.immuneTags = immuneTags; } - public override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + public override canApply({ pokemon }: AbAttrBaseParams): boolean { return this.immuneTags.some(tagType => !!pokemon.getTag(tagType)); } - public override applyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { + public override apply({ pokemon }: AbAttrBaseParams): void { this.immuneTags.forEach(tagType => pokemon.removeTag(tagType)); } } @@ -3499,7 +2634,7 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { this.intimidate = !!intimidate; } - override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ pokemon, simulated }: AbAttrBaseParams): void { if (simulated) { return; } @@ -3514,27 +2649,29 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { this.stats, this.stages, ); - } else { - for (const opponent of pokemon.getOpponents()) { - const cancelled = new BooleanHolder(false); - if (this.intimidate) { - applyAbAttrs("IntimidateImmunityAbAttr", opponent, cancelled, simulated); - applyAbAttrs("PostIntimidateStatStageChangeAbAttr", opponent, cancelled, simulated); + return; + } - if (opponent.getTag(BattlerTagType.SUBSTITUTE)) { - cancelled.value = true; - } - } - if (!cancelled.value) { - globalScene.phaseManager.unshiftNew( - "StatStageChangePhase", - opponent.getBattlerIndex(), - false, - this.stats, - this.stages, - ); + for (const opponent of pokemon.getOpponents()) { + const cancelled = new BooleanHolder(false); + if (this.intimidate) { + const params: AbAttrBaseParams = { pokemon: opponent, cancelled, simulated }; + applyAbAttrs("IntimidateImmunityAbAttr", params); + applyAbAttrs("PostIntimidateStatStageChangeAbAttr", params); + + if (opponent.getTag(BattlerTagType.SUBSTITUTE)) { + cancelled.value = true; } } + if (!cancelled.value) { + globalScene.phaseManager.unshiftNew( + "StatStageChangePhase", + opponent.getBattlerIndex(), + false, + this.stats, + this.stages, + ); + } } } } @@ -3550,11 +2687,11 @@ export class PostSummonAllyHealAbAttr extends PostSummonAbAttr { this.showAnim = showAnim; } - override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { return pokemon.getAlly()?.isActive(true) ?? false; } - override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ pokemon, simulated }: AbAttrBaseParams): void { const target = pokemon.getAlly(); if (!simulated && !isNullOrUndefined(target)) { globalScene.phaseManager.unshiftNew( @@ -3581,11 +2718,11 @@ export class PostSummonAllyHealAbAttr extends PostSummonAbAttr { * @returns if the move was successful */ export class PostSummonClearAllyStatStagesAbAttr extends PostSummonAbAttr { - override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { return pokemon.getAlly()?.isActive(true) ?? false; } - override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ pokemon, simulated }: AbAttrBaseParams): void { const target = pokemon.getAlly(); if (!simulated && !isNullOrUndefined(target)) { for (const s of BATTLE_STATS) { @@ -3605,7 +2742,6 @@ export class PostSummonClearAllyStatStagesAbAttr extends PostSummonAbAttr { * Download raises either the Attack stat or Special Attack stat by one stage depending on the foe's currently lowest defensive stat: * it will raise Attack if the foe's current Defense is lower than its current Special Defense stat; * otherwise, it will raise Special Attack. - * @extends PostSummonAbAttr * @see {applyPostSummon} */ export class DownloadAbAttr extends PostSummonAbAttr { @@ -3614,7 +2750,7 @@ export class DownloadAbAttr extends PostSummonAbAttr { private enemyCountTally: number; private stats: BattleStat[]; - override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { this.enemyDef = 0; this.enemySpDef = 0; this.enemyCountTally = 0; @@ -3632,11 +2768,8 @@ export class DownloadAbAttr extends PostSummonAbAttr { /** * Checks to see if it is the opening turn (starting a new game), if so, Download won't work. This is because Download takes into account * vitamins and items, so it needs to use the Stat and the stat alone. - * @param {Pokemon} pokemon Pokemon that is using the move, as well as seeing the opposing pokemon. - * @param {boolean} _passive N/A - * @param {any[]} _args N/A */ - override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ pokemon, simulated }: AbAttrBaseParams): void { if (this.enemyDef < this.enemySpDef) { this.stats = [Stat.ATK]; } else { @@ -3658,7 +2791,7 @@ export class PostSummonWeatherChangeAbAttr extends PostSummonAbAttr { this.weatherType = weatherType; } - override canApplyPostSummon(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply(_params: AbAttrBaseParams): boolean { const weatherReplaceable = this.weatherType === WeatherType.HEAVY_RAIN || this.weatherType === WeatherType.HARSH_SUN || @@ -3667,7 +2800,7 @@ export class PostSummonWeatherChangeAbAttr extends PostSummonAbAttr { return weatherReplaceable && globalScene.arena.canSetWeather(this.weatherType); } - override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ pokemon, simulated }: AbAttrBaseParams): void { if (!simulated) { globalScene.arena.trySetWeather(this.weatherType, pokemon); } @@ -3683,11 +2816,11 @@ export class PostSummonTerrainChangeAbAttr extends PostSummonAbAttr { this.terrainType = terrainType; } - override canApplyPostSummon(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply(_params: AbAttrBaseParams): boolean { return globalScene.arena.canSetTerrain(this.terrainType); } - override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ simulated, pokemon }: AbAttrBaseParams): void { if (!simulated) { globalScene.arena.trySetTerrain(this.terrainType, false, pokemon); } @@ -3709,12 +2842,13 @@ export class PostSummonHealStatusAbAttr extends PostSummonRemoveEffectAbAttr { this.immuneEffects = immuneEffects; } - public override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + public override canApply({ pokemon }: AbAttrBaseParams): boolean { const status = pokemon.status?.effect; return !isNullOrUndefined(status) && (this.immuneEffects.length < 1 || this.immuneEffects.includes(status)); } - public override applyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { + public override apply({ pokemon }: AbAttrBaseParams): void { + // TODO: should probably check against simulated... const status = pokemon.status?.effect; if (!isNullOrUndefined(status)) { this.statusHealed = status; @@ -3723,9 +2857,9 @@ export class PostSummonHealStatusAbAttr extends PostSummonRemoveEffectAbAttr { } } - public override getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null { + public override getTriggerMessage({ pokemon }: AbAttrBaseParams): string | null { if (this.statusHealed) { - return getStatusEffectHealText(this.statusHealed, getPokemonNameWithAffix(_pokemon)); + return getStatusEffectHealText(this.statusHealed, getPokemonNameWithAffix(pokemon)); } return null; } @@ -3740,11 +2874,11 @@ export class PostSummonFormChangeAbAttr extends PostSummonAbAttr { this.formFunc = formFunc; } - override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { return this.formFunc(pokemon) !== pokemon.formIndex; } - override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ pokemon, simulated }: AbAttrBaseParams): void { if (!simulated) { globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); } @@ -3756,7 +2890,7 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr { private target: Pokemon; private targetAbilityName: string; - override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { const targets = pokemon.getOpponents(); if (!targets.length) { return false; @@ -3782,7 +2916,7 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr { return true; } - override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ pokemon, simulated }: AbAttrBaseParams): void { if (!simulated) { pokemon.setTempAbility(this.target!.getAbility()); setAbilityRevealed(this.target!); @@ -3790,7 +2924,7 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr { } } - getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string { + getTriggerMessage({ pokemon }, _abilityName: string): string { return i18next.t("abilityTriggers:trace", { pokemonName: getPokemonNameWithAffix(pokemon), targetName: getPokemonNameWithAffix(this.target), @@ -3814,31 +2948,28 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt this.statusEffect = statusEffect; } - override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { const party = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); return party.filter(p => p.isAllowedInBattle()).length > 0; } /** * Removes supplied status effect from the user's field when user of the ability is summoned. - * - * @param pokemon - The Pokémon that triggered the ability. - * @param _passive - n/a - * @param _args - n/a */ - override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ pokemon, simulated }: AbAttrBaseParams): void { + if (simulated) { + return; + } const party = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); const allowedParty = party.filter(p => p.isAllowedInBattle()); - if (!simulated) { - for (const pokemon of allowedParty) { - if (pokemon.status && this.statusEffect.includes(pokemon.status.effect)) { - globalScene.phaseManager.queueMessage( - getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)), - ); - pokemon.resetStatus(false); - pokemon.updateInfo(); - } + for (const pokemon of allowedParty) { + if (pokemon.status && this.statusEffect.includes(pokemon.status.effect)) { + globalScene.phaseManager.queueMessage( + getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)), + ); + pokemon.resetStatus(false); + pokemon.updateInfo(); } } } @@ -3846,7 +2977,7 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt /** Attempt to copy the stat changes on an ally pokemon */ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { - override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { if (!globalScene.currentBattle.double) { return false; } @@ -3855,9 +2986,12 @@ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { return !(isNullOrUndefined(ally) || ally.getStatStages().every(s => s === 0)); } - override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ pokemon, simulated }: AbAttrBaseParams): void { + if (simulated) { + return; + } const ally = pokemon.getAlly(); - if (!simulated && !isNullOrUndefined(ally)) { + if (!isNullOrUndefined(ally)) { for (const s of BATTLE_STATS) { pokemon.setStatStage(s, ally.getStatStage(s)); } @@ -3865,7 +2999,7 @@ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { } } - getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string { + getTriggerMessage({ pokemon }: AbAttrBaseParams, _abilityName: string): string { return i18next.t("abilityTriggers:costar", { pokemonName: getPokemonNameWithAffix(pokemon), allyName: getPokemonNameWithAffix(pokemon.getAlly()), @@ -3906,7 +3040,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { return target; } - override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon, simulated }: AbAttrBaseParams): boolean { const targets = pokemon.getOpponents(); const target = this.getTarget(targets); @@ -3914,15 +3048,16 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { return false; } + // TODO: Consider moving the simulated check to the apply method if (simulated || !targets.length) { - return simulated; + return !!simulated; } // transforming from or into fusion pokemon causes various problems (including crashes and save corruption) return !(this.getTarget(targets).fusionSpecies || pokemon.fusionSpecies); } - override applyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { + override apply({ pokemon }: AbAttrBaseParams): void { const target = this.getTarget(pokemon.getOpponents()); globalScene.phaseManager.unshiftNew( @@ -3940,17 +3075,14 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { * @extends PostSummonAbAttr */ export class PostSummonWeatherSuppressedFormChangeAbAttr extends PostSummonAbAttr { - override canApplyPostSummon(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply(_params: AbAttrBaseParams): boolean { return getPokemonWithWeatherBasedForms().length > 0; } /** * Triggers {@linkcode Arena.triggerWeatherBasedFormChangesToNormal | triggerWeatherBasedFormChangesToNormal} - * @param {Pokemon} _pokemon the Pokemon with this ability - * @param _passive n/a - * @param _args n/a */ - override applyPostSummon(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ simulated }: AbAttrBaseParams): void { if (!simulated) { globalScene.arena.triggerWeatherBasedFormChangesToNormal(); } @@ -3973,28 +3105,20 @@ export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr { /** * Determine if the pokemon has a forme change that is triggered by the weather - * - * @param pokemon - The pokemon with the forme change ability - * @param _passive - unused - * @param _simulated - unused - * @param _args - unused */ - override canApplyPostSummon(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { return !!pokemonFormChanges[pokemon.species.speciesId]?.some( fc => fc.findTrigger(SpeciesFormChangeWeatherTrigger) && fc.canChange(pokemon), ); } /** - * Trigger the pokemon's forme change by invoking - * {@linkcode BattleScene.triggerPokemonFormChange | triggerPokemonFormChange} - * - * @param pokemon - The Pokemon with this ability - * @param _passive - unused - * @param simulated - unused - * @param _args - unused + * Calls the {@linkcode BattleScene.triggerPokemonFormChange | triggerPokemonFormChange} for both + * {@linkcode SpeciesFormChange.SpeciesFormChangeWeatherTrigger | SpeciesFormChangeWeatherTrigger} and + * {@linkcode SpeciesFormChange.SpeciesFormChangeWeatherTrigger | SpeciesFormChangeRevertWeatherFormTrigger} if it + * is the specific Pokemon and ability */ - override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ pokemon, simulated }: AbAttrBaseParams): void { if (!simulated) { globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeWeatherTrigger); } @@ -4012,7 +3136,7 @@ export class CommanderAbAttr extends AbAttr { super(true); } - override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { // If the ally Dondozo is fainted or was previously "commanded" by // another Pokemon, this effect cannot apply. @@ -4026,7 +3150,7 @@ export class CommanderAbAttr extends AbAttr { ); } - override apply(pokemon: Pokemon, _passive: boolean, simulated: boolean, _cancelled: null, _args: any[]): void { + override apply({ pokemon, simulated }: AbAttrBaseParams): void { if (!simulated) { // Lapse the source's semi-invulnerable tags (to avoid visual inconsistencies) pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); @@ -4040,28 +3164,34 @@ export class CommanderAbAttr extends AbAttr { } } -export class PreSwitchOutAbAttr extends AbAttr { +/** + * Base class for ability attributes that apply their effect when their user switches out. + */ +export abstract class PreSwitchOutAbAttr extends AbAttr { constructor(showAbility = true) { super(showAbility); } - canApplyPreSwitchOut(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + canApply(_params: Closed): boolean { return true; } - applyPreSwitchOut(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {} + apply(_params: Closed): void {} } +/** + * Resets all status effects on the user when it switches out. + */ export class PreSwitchOutResetStatusAbAttr extends PreSwitchOutAbAttr { constructor() { super(false); } - override canApplyPreSwitchOut(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { return !isNullOrUndefined(pokemon.status); } - override applyPreSwitchOut(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ pokemon, simulated }: AbAttrBaseParams): void { if (!simulated) { pokemon.resetStatus(); pokemon.updateInfo(); @@ -4073,13 +3203,8 @@ export class PreSwitchOutResetStatusAbAttr extends PreSwitchOutAbAttr { * Clears Desolate Land/Primordial Sea/Delta Stream upon the Pokemon switching out. */ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { - /** - * @param pokemon The {@linkcode Pokemon} with the ability - * @param _passive N/A - * @param _args N/A - * @returns {boolean} Returns true if the weather clears, otherwise false. - */ - override applyPreSwitchOut(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): boolean { + override apply({ pokemon, simulated }: AbAttrBaseParams): boolean { + // TODO: Evaluate why this is returning a boolean rather than relay const weatherType = globalScene.arena.weather?.weatherType; let turnOffWeather = false; @@ -4134,11 +3259,11 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { } export class PreSwitchOutHealAbAttr extends PreSwitchOutAbAttr { - override canApplyPreSwitchOut(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { return !pokemon.isFullHp(); } - override applyPreSwitchOut(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ pokemon, simulated }: AbAttrBaseParams): void { if (!simulated) { const healAmount = toDmgValue(pokemon.getMaxHp() * 0.33); pokemon.heal(healAmount); @@ -4149,7 +3274,6 @@ export class PreSwitchOutHealAbAttr extends PreSwitchOutAbAttr { /** * Attribute for form changes that occur on switching out - * @extends PreSwitchOutAbAttr * @see {@linkcode applyPreSwitchOut} */ export class PreSwitchOutFormChangeAbAttr extends PreSwitchOutAbAttr { @@ -4161,36 +3285,36 @@ export class PreSwitchOutFormChangeAbAttr extends PreSwitchOutAbAttr { this.formFunc = formFunc; } - override canApplyPreSwitchOut(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { return this.formFunc(pokemon) !== pokemon.formIndex; } /** * On switch out, trigger the form change to the one defined in the ability - * @param pokemon The pokemon switching out and changing form {@linkcode Pokemon} - * @param _passive N/A - * @param _args N/A */ - override applyPreSwitchOut(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ simulated, pokemon }: AbAttrBaseParams): void { if (!simulated) { globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); } } } +/** + * Base class for ability attributes that apply their effect just before the user leaves the field + */ export class PreLeaveFieldAbAttr extends AbAttr { - canApplyPreLeaveField(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + canApplyPreLeaveField(_params: Closed): boolean { return true; } - applyPreLeaveField(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {} + applyPreLeaveField(_params: Closed): void {} } /** * Clears Desolate Land/Primordial Sea/Delta Stream upon the Pokemon switching out. */ export class PreLeaveFieldClearWeatherAbAttr extends PreLeaveFieldAbAttr { - override canApplyPreLeaveField(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { const weatherType = globalScene.arena.weather?.weatherType; // Clear weather only if user's ability matches the weather and no other pokemon has the ability. switch (weatherType) { @@ -4231,12 +3355,7 @@ export class PreLeaveFieldClearWeatherAbAttr extends PreLeaveFieldAbAttr { return false; } - /** - * @param _pokemon The {@linkcode Pokemon} with the ability - * @param _passive N/A - * @param _args N/A - */ - override applyPreLeaveField(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ simulated }: AbAttrBaseParams): void { if (!simulated) { globalScene.arena.trySetWeather(WeatherType.NONE); } @@ -4245,47 +3364,44 @@ export class PreLeaveFieldClearWeatherAbAttr extends PreLeaveFieldAbAttr { /** * Updates the active {@linkcode SuppressAbilitiesTag} when a pokemon with {@linkcode AbilityId.NEUTRALIZING_GAS} leaves the field + * + * @sealed */ export class PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr extends PreLeaveFieldAbAttr { constructor() { super(false); } - public override canApplyPreLeaveField( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _args: any[], - ): boolean { + public override canApply(_params: AbAttrBaseParams): boolean { return !!globalScene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS); } - public override applyPreLeaveField(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { + public override apply(_params: AbAttrBaseParams): void { const suppressTag = globalScene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag; suppressTag.onSourceLeave(globalScene.arena); } } -export class PreStatStageChangeAbAttr extends AbAttr { - canApplyPreStatStageChange( - _pokemon: Pokemon | null, - _passive: boolean, - _simulated: boolean, - _stat: BattleStat, - _cancelled: BooleanHolder, - _args: any[], - ): boolean { +export interface PreStatStageChangeAbAttrParams extends AbAttrBaseParams { + /* The stat being affected by the stat stage change */ + stat: BattleStat; + /** The amount of stages to change by (negative if the stat is being decreased) */ + stages: number; + /** The source of the stat stage drop */ + source: Pokemon; + // Note: `cancelled` already exists in `AbAttrBaseParams`, though we redefine it here to change its tsdoc + /** Holder that will be set to true if the stat stage change should be cancelled due to the ability */ + cancelled: BooleanHolder; +} +/** + * Base class for ability attributes that apply their effect before a stat stage change. + */ +export abstract class PreStatStageChangeAbAttr extends AbAttr { + canApplyPreStatStageChange(_params: Closed): boolean { return true; } - applyPreStatStageChange( - _pokemon: Pokemon | null, - _passive: boolean, - _simulated: boolean, - _stat: BattleStat, - _cancelled: BooleanHolder, - _args: any[], - ): void {} + applyPreStatStageChange(_params: Closed): void {} } /** @@ -4298,28 +3414,13 @@ export class ReflectStatStageChangeAbAttr extends PreStatStageChangeAbAttr { /** * Apply the {@linkcode ReflectStatStageChangeAbAttr} to an interaction - * @param _pokemon The user pokemon - * @param _passive N/A - * @param simulated `true` if the ability is being simulated by the AI - * @param stat the {@linkcode BattleStat} being affected - * @param cancelled The {@linkcode BooleanHolder} that will be set to true due to reflection - * @param args */ - override applyPreStatStageChange( - _pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - stat: BattleStat, - cancelled: BooleanHolder, - args: any[], - ): void { - const attacker: Pokemon = args[0]; - const stages = args[1]; + override apply({ source, cancelled, stat, simulated, stages }: PreStatStageChangeAbAttrParams): void { this.reflectedStat = stat; if (!simulated) { globalScene.phaseManager.unshiftNew( "StatStageChangePhase", - attacker.getBattlerIndex(), + source.getBattlerIndex(), false, [stat], stages, @@ -4333,7 +3434,7 @@ export class ReflectStatStageChangeAbAttr extends PreStatStageChangeAbAttr { cancelled.value = true; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + getTriggerMessage({ pokemon }: PreStatStageChangeAbAttrParams, abilityName: string): string { return i18next.t("abilityTriggers:protectStat", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, @@ -4355,38 +3456,18 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr { this.protectedStat = protectedStat; } - override canApplyPreStatStageChange( - _pokemon: Pokemon | null, - _passive: boolean, - _simulated: boolean, - stat: BattleStat, - _cancelled: BooleanHolder, - _args: any[], - ): boolean { + override canApply({ stat }: PreStatStageChangeAbAttrParams): boolean { return isNullOrUndefined(this.protectedStat) || stat === this.protectedStat; } /** * Apply the {@linkcode ProtectedStatAbAttr} to an interaction - * @param _pokemon - * @param _passive - * @param simulated - * @param _stat the {@linkcode BattleStat} being affected - * @param cancelled The {@linkcode BooleanHolder} that will be set to true if the stat is protected - * @param _args */ - override applyPreStatStageChange( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _stat: BattleStat, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: PreStatStageChangeAbAttrParams): void { cancelled.value = true; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + override getTriggerMessage({ pokemon }: PreStatStageChangeAbAttrParams, abilityName: string): string { return i18next.t("abilityTriggers:protectStat", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, @@ -4395,85 +3476,60 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr { } } +export interface ConfusionOnStatusEffectAbAttrParams extends AbAttrBaseParams { + /** The status effect that was applied */ + effect: StatusEffect; + /** The move that applied the status effect */ + move: Move; + /** The opponent that was inflicted with the status effect */ + opponent: Pokemon; +} + /** * This attribute applies confusion to the target whenever the user * directly poisons them with a move, e.g. Poison Puppeteer. * Called in {@linkcode StatusEffectAttr}. - * @extends PostAttackAbAttr - * @see {@linkcode applyPostAttack} */ -export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr { +export class ConfusionOnStatusEffectAbAttr extends AbAttr { /** List of effects to apply confusion after */ private effects: StatusEffect[]; constructor(...effects: StatusEffect[]) { - /** This effect does not require a damaging move */ - super((_user, _target, _move) => true); + super(); this.effects = effects; } - override canApplyPostAttack( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - defender: Pokemon, - move: Move, - hitResult: HitResult | null, - args: any[], - ): boolean { - return ( - super.canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args) && - this.effects.indexOf(args[0]) > -1 && - !defender.isFainted() && - defender.canAddTag(BattlerTagType.CONFUSED) - ); + /** + * @return Whether the ability can apply confusion to the opponent + */ + override canApply({ opponent, effect }: ConfusionOnStatusEffectAbAttrParams): boolean { + return this.effects.includes(effect) && !opponent.isFainted() && opponent.canAddTag(BattlerTagType.CONFUSED); } /** * Applies confusion to the target pokemon. - * @param pokemon {@link Pokemon} attacking - * @param _passive N/A - * @param defender {@link Pokemon} defending - * @param move {@link Move} used to apply status effect and confusion - * @param _hitResult N/A - * @param _args [0] {@linkcode StatusEffect} applied by move */ - override applyPostAttack( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - defender: Pokemon, - move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ opponent, simulated, pokemon, move }: ConfusionOnStatusEffectAbAttrParams): void { if (!simulated) { - defender.addTag(BattlerTagType.CONFUSED, pokemon.randBattleSeedIntRange(2, 5), move.id, defender.id); + opponent.addTag(BattlerTagType.CONFUSED, pokemon.randBattleSeedIntRange(2, 5), move.id, opponent.id); } } } +export interface PreSetStatusAbAttrParams extends AbAttrBaseParams { + /** The status effect being applied */ + effect: StatusEffect; + /** Holds whether the status effect is prevented by the ability */ + cancelled: BooleanHolder; +} + export class PreSetStatusAbAttr extends AbAttr { /** Return whether the ability attribute can be applied */ - canApplyPreSetStatus( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _effect: StatusEffect | undefined, - _cancelled: BooleanHolder, - _args: any[], - ): boolean { + canApply(_params: Closed): boolean { return true; } - applyPreSetStatus( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _effect: StatusEffect | undefined, - _cancelled: BooleanHolder, - _args: any[], - ): void {} + apply(_params: Closed): void {} } /** @@ -4481,7 +3537,6 @@ export class PreSetStatusAbAttr extends AbAttr { */ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr { protected immuneEffects: StatusEffect[]; - private lastEffect: StatusEffect; /** * @param immuneEffects - The status effects to which the Pokémon is immune. @@ -4492,44 +3547,23 @@ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr { this.immuneEffects = immuneEffects; } - override canApplyPreSetStatus( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - effect: StatusEffect, - _cancelled: BooleanHolder, - _args: any[], - ): boolean { + override canApply({ effect }: PreSetStatusAbAttrParams): boolean { return (effect !== StatusEffect.FAINT && this.immuneEffects.length < 1) || this.immuneEffects.includes(effect); } /** * Applies immunity to supplied status effects. - * - * @param _pokemon - The Pokémon to which the status is being applied. - * @param _passive - n/a - * @param effect - The status effect being applied. - * @param cancelled - A holder for a boolean value indicating if the status application was cancelled. - * @param _args - n/a */ - override applyPreSetStatus( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - effect: StatusEffect, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: PreSetStatusAbAttrParams): void { cancelled.value = true; - this.lastEffect = effect; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + override getTriggerMessage({ pokemon, effect }: PreSetStatusAbAttrParams, abilityName: string): string { return this.immuneEffects.length ? i18next.t("abilityTriggers:statusEffectImmunityWithName", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, - statusEffectName: getStatusEffectDescriptor(this.lastEffect), + statusEffectName: getStatusEffectDescriptor(effect), }) : i18next.t("abilityTriggers:statusEffectImmunity", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), @@ -4538,63 +3572,97 @@ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr { } } +// NOTE: There is a good amount of overlapping code between this +// and PreSetStatusEffectImmunity. However, we need these classes to be distinct +// as this one's apply method requires additional parameters +// TODO: Find away to avoid the code duplication without sacrificing +// the subclass split /** * Provides immunity to status effects to the user. - * @extends PreSetStatusEffectImmunityAbAttr */ export class StatusEffectImmunityAbAttr extends PreSetStatusEffectImmunityAbAttr {} -/** - * Provides immunity to status effects to the user's field. - * @extends PreSetStatusEffectImmunityAbAttr - */ -export class UserFieldStatusEffectImmunityAbAttr extends PreSetStatusEffectImmunityAbAttr {} +export interface UserFieldStatusEffectImmunityAbAttrParams extends AbAttrBaseParams { + /** The status effect being applied */ + effect: StatusEffect; + /** Holds whether the status effect is prevented by the ability */ + cancelled: BooleanHolder; + /** The target of the status effect */ + target: Pokemon; + // TODO: It may be the case that callers are passing `null` in the case that the pokemon setting the status is the same as the target. + // Evaluate this and update the tsdoc accordingly. + /** The source of the status effect, or null if it is not coming from a pokemon */ + source: Pokemon | null; +} /** - * Conditionally provides immunity to status effects to the user's field. + * Provides immunity to status effects to the user's field. + */ +export class UserFieldStatusEffectImmunityAbAttr extends AbAttr { + protected immuneEffects: StatusEffect[]; + constructor(...immuneEffects: StatusEffect[]) { + super(); + + this.immuneEffects = immuneEffects; + } + + override canApply({ effect, cancelled }: UserFieldStatusEffectImmunityAbAttrParams): boolean { + return ( + (!cancelled.value && effect !== StatusEffect.FAINT && this.immuneEffects.length < 1) || + this.immuneEffects.includes(effect) + ); + } + + /** + * Set the `cancelled` value to true, indicating that the status effect is prevented. + */ + override apply({ cancelled }: UserFieldStatusEffectImmunityAbAttrParams): void { + cancelled.value = true; + } +} + +/** + * Conditionally provides immunity to status effects for the user's field. * * Used by {@linkcode AbilityId.FLOWER_VEIL | Flower Veil}. - * @extends UserFieldStatusEffectImmunityAbAttr - * */ export class ConditionalUserFieldStatusEffectImmunityAbAttr extends UserFieldStatusEffectImmunityAbAttr { /** * The condition for the field immunity to be applied. - * @param target The target of the status effect - * @param source The source of the status effect + * @param target - The target of the status effect + * @param source - The source of the status effect */ - protected condition: (target: Pokemon, source: Pokemon | null) => boolean; - - /** - * Evaluate the condition to determine if the {@linkcode ConditionalUserFieldStatusEffectImmunityAbAttr} can be applied. - * @param _pokemon The pokemon with the ability - * @param _passive unused - * @param _simulated Whether the ability is being simulated - * @param effect The status effect being applied - * @param cancelled Holds whether the status effect was cancelled by a prior effect - * @param args `Args[0]` is the target of the status effect, `Args[1]` is the source. - * @returns Whether the ability can be applied to cancel the status effect. - */ - override canApplyPreSetStatus( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - effect: StatusEffect, - cancelled: BooleanHolder, - args: [Pokemon, Pokemon | null, ...any], - ): boolean { - return ( - ((!cancelled.value && effect !== StatusEffect.FAINT && this.immuneEffects.length < 1) || - this.immuneEffects.includes(effect)) && - this.condition(args[0], args[1]) - ); - } + private condition: (target: Pokemon, source: Pokemon | null) => boolean; constructor(condition: (target: Pokemon, source: Pokemon | null) => boolean, ...immuneEffects: StatusEffect[]) { super(...immuneEffects); this.condition = condition; } + + /** + * Evaluate the condition to determine if the {@linkcode ConditionalUserFieldStatusEffectImmunityAbAttr} can be applied. + * @returns Whether the ability can be applied to cancel the status effect. + */ + override canApply(params: UserFieldStatusEffectImmunityAbAttrParams): boolean { + return this.condition(params.target, params.source) && super.canApply(params); + } + + /** + * Set the `cancelled` value to true, indicating that the status effect is prevented. + */ + override apply({ cancelled }: UserFieldStatusEffectImmunityAbAttrParams): void { + cancelled.value = true; + } +} + +export interface ConditionalUserFieldProtectStatAbAttrParams extends AbAttrBaseParams { + /** The stat being affected by the stat stage change */ + stat: BattleStat; + /** Holds whether the stat stage change is prevented by the ability */ + cancelled: BooleanHolder; + /** The target of the stat stage change */ + target: Pokemon; } /** @@ -4615,24 +3683,9 @@ export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbA } /** - * Determine whether the {@linkcode ConditionalUserFieldProtectStatAbAttr} can be applied. - * @param _pokemon The pokemon with the ability - * @param _passive unused - * @param _simulated Unused - * @param stat The stat being affected - * @param cancelled Holds whether the stat change was already prevented. - * @param args Args[0] is the target pokemon of the stat change. - * @returns `true` if the ability can be applied + * @returns Whether the ability can be used to cancel the stat stage change. */ - override canApplyPreStatStageChange( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - stat: BattleStat, - cancelled: BooleanHolder, - args: [Pokemon, ...any], - ): boolean { - const target = args[0]; + override canApply({ stat, cancelled, target }: ConditionalUserFieldProtectStatAbAttrParams): boolean { if (!target) { return false; } @@ -4645,53 +3698,37 @@ export class ConditionalUserFieldProtectStatAbAttr extends PreStatStageChangeAbA /** * Apply the {@linkcode ConditionalUserFieldStatusEffectImmunityAbAttr} to an interaction - * @param _pokemon The pokemon the stat change is affecting (unused) - * @param _passive unused - * @param _simulated unused - * @param stat The stat being affected - * @param cancelled Will be set to true if the stat change is prevented - * @param _args unused */ - override applyPreStatStageChange( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _stat: BattleStat, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: ConditionalUserFieldProtectStatAbAttrParams): void { cancelled.value = true; } } -export class PreApplyBattlerTagAbAttr extends AbAttr { - canApplyPreApplyBattlerTag( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _tag: BattlerTag, - _cancelled: BooleanHolder, - _args: any[], - ): boolean { +export interface PreApplyBattlerTagAbAttrParams extends AbAttrBaseParams { + /** The tag being applied */ + tag: BattlerTag; + /** Holds whether the tag is prevented by the ability */ + cancelled: BooleanHolder; +} + +/** + * Base class for ability attributes that apply their effect before a BattlerTag {@linkcode BattlerTag} is applied. + */ +export abstract class PreApplyBattlerTagAbAttr extends AbAttr { + canApplyPreApplyBattlerTag(_params: Closed): boolean { return true; } - applyPreApplyBattlerTag( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _tag: BattlerTag, - _cancelled: BooleanHolder, - _args: any[], - ): void {} + applyPreApplyBattlerTag(_params: Closed): 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 { protected immuneTagTypes: BattlerTagType[]; - protected battlerTag: BattlerTag; constructor(immuneTagTypes: BattlerTagType | BattlerTagType[]) { super(true); @@ -4699,42 +3736,25 @@ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr { this.immuneTagTypes = coerceArray(immuneTagTypes); } - override canApplyPreApplyBattlerTag( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - tag: BattlerTag, - cancelled: BooleanHolder, - _args: any[], - ): boolean { - this.battlerTag = tag; - + override canApply({ cancelled, tag }: PreApplyBattlerTagAbAttrParams): boolean { return !cancelled.value && this.immuneTagTypes.includes(tag.tagType); } - override applyPreApplyBattlerTag( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _tag: BattlerTag, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: PreApplyBattlerTagAbAttrParams): void { cancelled.value = true; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + override getTriggerMessage({ pokemon, tag }: PreApplyBattlerTagAbAttrParams, abilityName: string): string { return i18next.t("abilityTriggers:battlerTagImmunity", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, - battlerTagName: this.battlerTag.getDescriptor(), + battlerTagName: tag.getDescriptor(), }); } } /** * Provides immunity to BattlerTags {@linkcode BattlerTag} to the user. - * @extends PreApplyBattlerTagImmunityAbAttr */ export class BattlerTagImmunityAbAttr extends PreApplyBattlerTagImmunityAbAttr {} @@ -4744,30 +3764,18 @@ export class BattlerTagImmunityAbAttr extends PreApplyBattlerTagImmunityAbAttr { */ export class UserFieldBattlerTagImmunityAbAttr extends PreApplyBattlerTagImmunityAbAttr {} +// NOTE: We are inheriting from `PreApplyBattlerTagImmunityAbAttr` which has a different signature export class ConditionalUserFieldBattlerTagImmunityAbAttr extends UserFieldBattlerTagImmunityAbAttr { private condition: (target: Pokemon) => boolean; /** * Determine whether the {@linkcode ConditionalUserFieldBattlerTagImmunityAbAttr} can be applied by passing the target pokemon to the condition. - * @param pokemon The pokemon owning the ability - * @param passive unused - * @param simulated whether the ability is being simulated (unused) - * @param tag The {@linkcode BattlerTag} being applied - * @param cancelled Holds whether the tag was previously cancelled (unused) - * @param args Args[0] is the target that the tag is attempting to be applied to * @returns Whether the ability can be used to cancel the battler tag */ - override canApplyPreApplyBattlerTag( - pokemon: Pokemon, - passive: boolean, - simulated: boolean, - tag: BattlerTag, - cancelled: BooleanHolder, - args: [Pokemon, ...any], - ): boolean { - return ( - super.canApplyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args) && this.condition(args[0]) - ); + override canApply(params: PreApplyBattlerTagAbAttrParams & { target: Pokemon }): 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); } constructor(condition: (target: Pokemon) => boolean, immuneTagTypes: BattlerTagType | BattlerTagType[]) { @@ -4777,6 +3785,13 @@ 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 + */ + readonly canCrit: BooleanHolder; +} + export class BlockCritAbAttr extends AbAttr { constructor() { super(false); @@ -4784,19 +3799,17 @@ export class BlockCritAbAttr extends AbAttr { /** * Apply the block crit ability by setting the value in the provided boolean holder to `true`. - * @param args - `[0]`: A {@linkcode BooleanHolder} containing whether the attack is prevented from critting. */ - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: [BooleanHolder], - ): void { - args[0].value = true; + override apply({ canCrit: preventCrit }: BlockCritAbAttrParams): void { + preventCrit.value = false; } } +export interface BonusCritAbAttrParams extends AbAttrBaseParams { + /** Holds the crit stage that may be modified by ability application */ + critStage: NumberHolder; +} + export class BonusCritAbAttr extends AbAttr { constructor() { super(false); @@ -4804,24 +3817,17 @@ export class BonusCritAbAttr extends AbAttr { /** * Apply the bonus crit ability by increasing the value in the provided number holder by 1 - * - * @param _pokemon The pokemon with the BonusCrit ability (unused) - * @param _passive Unused - * @param _simulated Unused - * @param _cancelled Unused - * @param args Args[0] is a number holder containing the crit stage. */ - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: [NumberHolder, ...any], - ): void { - (args[0] as NumberHolder).value += 1; + override apply({ critStage }: BonusCritAbAttrParams): void { + critStage.value += 1; } } +export interface MultCritAbAttrParams extends AbAttrBaseParams { + /** The critical hit multiplier that may be modified by ability application */ + critMult: NumberHolder; +} + export class MultCritAbAttr extends AbAttr { public multAmount: number; @@ -4831,27 +3837,26 @@ export class MultCritAbAttr extends AbAttr { this.multAmount = multAmount; } - override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { - const critMult = args[0] as NumberHolder; + override canApply({ critMult }: MultCritAbAttrParams): boolean { return critMult.value > 1; } - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - const critMult = args[0] as NumberHolder; + override apply({ critMult }: MultCritAbAttrParams): void { critMult.value *= this.multAmount; } } +export interface ConditionalCritAbAttrParams extends AbAttrBaseParams { + /** Holds a boolean that will be set to true if the attack is guaranteed to crit */ + target: Pokemon; + /** The move being used */ + move: Move; + /** Holds whether the attack will critically hit */ + crit: BooleanHolder; +} + /** * Guarantees a critical hit according to the given condition, except if target prevents critical hits. ie. Merciless - * @extends AbAttr - * @see {@linkcode apply} */ export class ConditionalCritAbAttr extends AbAttr { private condition: PokemonAttackCondition; @@ -4862,26 +3867,12 @@ export class ConditionalCritAbAttr extends AbAttr { this.condition = condition; } - override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { - const target = args[1] as Pokemon; - const move = args[2] as Move; - return this.condition(pokemon, target, move); + override canApply({ crit, pokemon, target, move }: ConditionalCritAbAttrParams): boolean { + return !crit.value && this.condition(pokemon, target, move); } - /** - * @param _pokemon {@linkcode Pokemon} user. - * @param args [0] {@linkcode BooleanHolder} If true critical hit is guaranteed. - * [1] {@linkcode Pokemon} Target. - * [2] {@linkcode Move} used by ability user. - */ - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - (args[0] as BooleanHolder).value = true; + override apply({ crit }: ConditionalCritAbAttrParams): void { + crit.value = true; } } @@ -4890,13 +3881,7 @@ export class BlockNonDirectDamageAbAttr extends AbAttr { super(false); } - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: AbAttrBaseParams): void { cancelled.value = true; } } @@ -4916,51 +3901,44 @@ export class BlockStatusDamageAbAttr extends AbAttr { this.effects = effects; } - override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { return !!pokemon.status?.effect && this.effects.includes(pokemon.status.effect); } /** - * @param {Pokemon} _pokemon The pokemon with the ability - * @param {boolean} _passive N/A - * @param {BooleanHolder} cancelled Whether to cancel the status damage - * @param {any[]} _args N/A */ - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: AbAttrBaseParams): void { cancelled.value = true; } } export class BlockOneHitKOAbAttr extends AbAttr { - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: AbAttrBaseParams): void { cancelled.value = true; } } +export interface ChangeMovePriorityAbAttrParams extends AbAttrBaseParams { + /** The move being used */ + move: Move; + /** The priority of the move being used */ + priority: NumberHolder; +} + /** * This governs abilities that alter the priority of moves * Abilities: Prankster, Gale Wings, Triage, Mycelium Might, Stall * Note - Quick Claw has a separate and distinct implementation outside of priority + * + * @sealed */ export class ChangeMovePriorityAbAttr extends AbAttr { private moveFunc: (pokemon: Pokemon, move: Move) => boolean; private changeAmount: number; /** - * @param {(pokemon, move) => boolean} moveFunc applies priority-change to moves within a provided category - * @param {number} changeAmount the amount of priority added or subtracted + * @param moveFunc - applies priority-change to moves that meet the condition + * @param changeAmount - The amount of priority added or subtracted */ constructor(moveFunc: (pokemon: Pokemon, move: Move) => boolean, changeAmount: number) { super(false); @@ -4969,46 +3947,37 @@ export class ChangeMovePriorityAbAttr extends AbAttr { this.changeAmount = changeAmount; } - override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { - return this.moveFunc(pokemon, args[0] as Move); + override canApply({ pokemon, move }: ChangeMovePriorityAbAttrParams): boolean { + return this.moveFunc(pokemon, move); } - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - (args[1] as NumberHolder).value += this.changeAmount; + override apply({ priority }: ChangeMovePriorityAbAttrParams): void { + priority.value += this.changeAmount; } } export class IgnoreContactAbAttr extends AbAttr {} -export class PreWeatherEffectAbAttr extends AbAttr { - canApplyPreWeatherEffect( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _weather: Weather | null, - _cancelled: BooleanHolder, - _args: any[], - ): boolean { +/** + * Shared interface for attributes that respond to a weather. + */ +export interface PreWeatherEffectAbAttrParams extends AbAttrBaseParams { + /** The weather effect for the interaction. `null` is treated as no weather */ + weather: Weather | null; +} + +export abstract class PreWeatherEffectAbAttr extends AbAttr { + override canApply(_params: Closed): boolean { return true; } - applyPreWeatherEffect( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _weather: Weather | null, - _cancelled: BooleanHolder, - _args: any[], - ): void {} + override apply(_params: Closed): void {} } -export class PreWeatherDamageAbAttr extends PreWeatherEffectAbAttr {} +/** + * Base class for abilities that apply an effect before a weather effect is applied. + */ +export abstract class PreWeatherDamageAbAttr extends PreWeatherEffectAbAttr {} export class BlockWeatherDamageAttr extends PreWeatherDamageAbAttr { private weatherTypes: WeatherType[]; @@ -5019,57 +3988,36 @@ export class BlockWeatherDamageAttr extends PreWeatherDamageAbAttr { this.weatherTypes = weatherTypes; } - override canApplyPreWeatherEffect( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - weather: Weather, - _cancelled: BooleanHolder, - _args: any[], - ): boolean { - return !this.weatherTypes.length || this.weatherTypes.indexOf(weather?.weatherType) > -1; + override canApply({ weather }: PreWeatherEffectAbAttrParams): boolean { + if (!weather) { + return false; + } + const weatherType = weather.weatherType; + return !this.weatherTypes.length || this.weatherTypes.includes(weatherType); } - override applyPreWeatherEffect( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _weather: Weather, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: PreWeatherEffectAbAttrParams): void { cancelled.value = true; } } export class SuppressWeatherEffectAbAttr extends PreWeatherEffectAbAttr { - public affectsImmutable: boolean; + public readonly affectsImmutable: boolean; - constructor(affectsImmutable?: boolean) { + constructor(affectsImmutable = false) { super(true); - this.affectsImmutable = !!affectsImmutable; + this.affectsImmutable = affectsImmutable; } - override canApplyPreWeatherEffect( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - weather: Weather, - _cancelled: BooleanHolder, - _args: any[], - ): boolean { + override canApply({ weather }: PreWeatherEffectAbAttrParams): boolean { + if (!weather) { + return false; + } return this.affectsImmutable || weather.isImmutable(); } - override applyPreWeatherEffect( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _weather: Weather, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: PreWeatherEffectAbAttrParams): void { cancelled.value = true; } } @@ -5187,12 +4135,18 @@ function getOncePerBattleCondition(ability: AbilityId): AbAttrCondition { }; } +/** + * @sealed + */ export class ForewarnAbAttr extends PostSummonAbAttr { constructor() { super(true); } - override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ simulated, pokemon }: AbAttrBaseParams): void { + if (!simulated) { + return; + } let maxPowerSeen = 0; let maxMove = ""; let movePower = 0; @@ -5220,23 +4174,26 @@ export class ForewarnAbAttr extends PostSummonAbAttr { } } } - if (!simulated) { - globalScene.phaseManager.queueMessage( - i18next.t("abilityTriggers:forewarn", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - moveName: maxMove, - }), - ); - } + + globalScene.phaseManager.queueMessage( + i18next.t("abilityTriggers:forewarn", { + pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), + moveName: maxMove, + }), + ); } } +/** + * Ability attribute that reveals the abilities of all opposing Pokémon when the Pokémon with this ability is summoned. + * @sealed + */ export class FriskAbAttr extends PostSummonAbAttr { constructor() { super(true); } - override applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ simulated, pokemon }: AbAttrBaseParams): void { if (!simulated) { for (const opponent of pokemon.getOpponents()) { globalScene.phaseManager.queueMessage( @@ -5252,30 +4209,27 @@ export class FriskAbAttr extends PostSummonAbAttr { } } -export class PostWeatherChangeAbAttr extends AbAttr { - canApplyPostWeatherChange( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _weather: WeatherType, - _args: any[], - ): boolean { +export interface PostWeatherChangeAbAttrParams extends AbAttrBaseParams { + /** The kind of the weather that was just changed to */ + weather: WeatherType; +} + +/** + * Base class for ability attributes that apply their effect after a weather change. + */ +export abstract class PostWeatherChangeAbAttr extends AbAttr { + canApply(_params: Closed): boolean { return true; } - applyPostWeatherChange( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _weather: WeatherType, - _args: any[], - ): void {} + apply(_params: Closed): void {} } /** * Triggers weather-based form change when weather changes. * Used by Forecast and Flower Gift. - * @extends PostWeatherChangeAbAttr + * + * @sealed */ export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr { private ability: AbilityId; @@ -5288,13 +4242,7 @@ export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr { this.formRevertingWeathers = formRevertingWeathers; } - override canApplyPostWeatherChange( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _weather: WeatherType, - _args: any[], - ): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { const isCastformWithForecast = pokemon.species.speciesId === SpeciesId.CASTFORM && this.ability === AbilityId.FORECAST; const isCherrimWithFlowerGift = @@ -5306,23 +4254,15 @@ export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr { /** * Calls {@linkcode Arena.triggerWeatherBasedFormChangesToNormal | triggerWeatherBasedFormChangesToNormal} when the * weather changed to form-reverting weather, otherwise calls {@linkcode Arena.triggerWeatherBasedFormChanges | triggerWeatherBasedFormChanges} - * @param _pokemon - The Pokemon with this ability - * @param _passive - unused - * @param simulated - unused - * @param _weather - unused - * @param _args - unused */ - override applyPostWeatherChange( - _pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _weather: WeatherType, - _args: any[], - ): void { + override apply({ simulated }: AbAttrBaseParams): void { if (simulated) { return; } + // TODO: investigate why this is not using the weatherType parameter + // and is instead reading the weather from the global scene + const weatherType = globalScene.arena.weather?.weatherType; if (weatherType && this.formRevertingWeathers.includes(weatherType)) { @@ -5333,6 +4273,10 @@ export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr { } } +/** + * Add a battler tag to the pokemon when the weather changes. + * @sealed + */ export class PostWeatherChangeAddBattlerTagAttr extends PostWeatherChangeAbAttr { private tagType: BattlerTagType; private turnCount: number; @@ -5346,23 +4290,11 @@ export class PostWeatherChangeAddBattlerTagAttr extends PostWeatherChangeAbAttr this.weatherTypes = weatherTypes; } - override canApplyPostWeatherChange( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - weather: WeatherType, - _args: any[], - ): boolean { - return !!this.weatherTypes.find(w => weather === w) && pokemon.canAddTag(this.tagType); + override canApply({ weather, pokemon }: PostWeatherChangeAbAttrParams): boolean { + return this.weatherTypes.includes(weather) && pokemon.canAddTag(this.tagType); } - override applyPostWeatherChange( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _weather: WeatherType, - _args: any[], - ): void { + override apply({ simulated, pokemon }: PostWeatherChangeAbAttrParams): void { if (!simulated) { pokemon.addTag(this.tagType, this.turnCount); } @@ -5484,24 +4416,19 @@ 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 { - canApplyPostTerrainChange( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _terrain: TerrainType, - _args: any[], - ): boolean { + canApply(_params: Closed): boolean { return true; } - applyPostTerrainChange( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _terrain: TerrainType, - _args: any[], - ): void {} + apply(_params: Closed): void {} } export class PostTerrainChangeAddBattlerTagAttr extends PostTerrainChangeAbAttr { @@ -5517,23 +4444,11 @@ export class PostTerrainChangeAddBattlerTagAttr extends PostTerrainChangeAbAttr this.terrainTypes = terrainTypes; } - override canApplyPostTerrainChange( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - terrain: TerrainType, - _args: any[], - ): boolean { + override canApply({ pokemon, terrain }: PostTerrainChangeAbAttrParams): boolean { return !!this.terrainTypes.find(t => t === terrain) && pokemon.canAddTag(this.tagType); } - override applyPostTerrainChange( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _terrain: TerrainType, - _args: any[], - ): void { + override apply({ pokemon, simulated }: PostTerrainChangeAbAttrParams): void { if (!simulated) { pokemon.addTag(this.tagType, this.turnCount); } @@ -5548,21 +4463,23 @@ function getTerrainCondition(...terrainTypes: TerrainType[]): AbAttrCondition { } export class PostTurnAbAttr extends AbAttr { - canApplyPostTurn(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + canApply(_params: Closed): boolean { return true; } - applyPostTurn(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {} + apply(_params: Closed): void {} } /** * This attribute will heal 1/8th HP if the ability pokemon has the correct status. + * + * @sealed */ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr { private effects: StatusEffect[]; /** - * @param {StatusEffect[]} effects The status effect(s) that will qualify healing the ability pokemon + * @param effects - The status effect(s) that will qualify healing the ability pokemon */ constructor(...effects: StatusEffect[]) { super(false); @@ -5570,16 +4487,11 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr { this.effects = effects; } - override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { return !isNullOrUndefined(pokemon.status) && this.effects.includes(pokemon.status.effect) && !pokemon.isFullHp(); } - /** - * @param {Pokemon} pokemon The pokemon with the ability that will receive the healing - * @param {Boolean} passive N/A - * @param {any[]} _args N/A - */ - override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ simulated, passive, pokemon }: AbAttrBaseParams): void { if (!simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; globalScene.phaseManager.unshiftNew( @@ -5596,6 +4508,8 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr { /** * After the turn ends, resets the status of either the ability holder or their ally * @param allyTarget Whether to target ally, defaults to false (self-target) + * + * @sealed */ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr { private allyTarget: boolean; @@ -5606,7 +4520,7 @@ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr { this.allyTarget = allyTarget; } - override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { if (this.allyTarget) { this.target = pokemon.getAlly(); } else { @@ -5617,7 +4531,7 @@ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr { return !!effect && effect !== StatusEffect.FAINT; } - override applyPostTurn(_pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ simulated }: AbAttrBaseParams): void { if (!simulated && this.target?.status) { globalScene.phaseManager.queueMessage( getStatusEffectHealText(this.target.status?.effect, getPokemonNameWithAffix(this.target)), @@ -5647,7 +4561,7 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr { super(); } - override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { // Ensure we have at least 1 recoverable berry (at least 1 berry in berriesEaten is not capped) const cappedBerries = new Set( globalScene @@ -5667,7 +4581,7 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr { return this.procChance(pokemon) >= pass; } - override applyPostTurn(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ simulated, pokemon }: AbAttrBaseParams): void { if (!simulated) { this.createEatenBerry(pokemon); } @@ -5715,13 +4629,14 @@ export class PostTurnRestoreBerryAbAttr extends PostTurnAbAttr { /** * Attribute to track and re-trigger last turn's berries at the end of the `BerryPhase`. + * Must only be used by Cud Chew! Do _not_ reuse this attribute for anything else * Used by {@linkcode AbilityId.CUD_CHEW}. */ -export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr { +export class CudChewConsumeBerryAbAttr extends PostTurnAbAttr { /** * @returns `true` if the pokemon ate anything last turn */ - override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { // force ability popup for ability triggers on normal turns. // Still not used if ability doesn't proc this.showAbility = true; @@ -5729,21 +4644,10 @@ export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr { } /** - * Cause this {@linkcode Pokemon} to regurgitate and eat all berries inside its `berriesEatenLast` array. - * Triggers a berry use animation, but does *not* count for other berry or item-related abilities. - * @param pokemon - The {@linkcode Pokemon} having a bad tummy ache - * @param _passive - N/A - * @param _simulated - N/A - * @param _cancelled - N/A - * @param _args - N/A + * */ - override apply( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder | null, - _args: any[], - ): void { + override apply({ pokemon }: AbAttrBaseParams): void { + // TODO: Consider respecting the `simulated` flag globalScene.phaseManager.unshiftNew( "CommonAnimPhase", pokemon.getBattlerIndex(), @@ -5762,25 +4666,25 @@ export class RepeatBerryNextTurnAbAttr extends PostTurnAbAttr { // uncomment to make cheek pouch work with cud chew // applyAbAttrs("HealFromBerryUseAbAttr", pokemon, new BooleanHolder(false)); } +} - /** - * @returns always `true` as we always want to move berries into summon data - */ - override canApplyPostTurn(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { - this.showAbility = false; // don't show popup for turn end berry moving (should ideally be hidden) - return true; +/** + * Consume a berry at the end of the turn if the pokemon has one. + * + * Must be used in conjunction with {@linkcode CudChewConsumeBerryAbAttr}, and is + * only used by {@linkcode AbilityId.CUD_CHEW}. + */ +export class CudChewRecordBerryAbAttr extends PostTurnAbAttr { + constructor() { + super(false); } /** * Move this {@linkcode Pokemon}'s `berriesEaten` array from `PokemonTurnData` * into `PokemonSummonData` on turn end. * Both arrays are cleared on switch. - * @param pokemon - The {@linkcode Pokemon} having a nice snack - * @param _passive - N/A - * @param _simulated - N/A - * @param _args - N/A */ - override applyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { + override apply({ pokemon }: AbAttrBaseParams): void { pokemon.summonData.berriesEatenLast = pokemon.turnData.berriesEaten; } } @@ -5803,7 +4707,10 @@ export class MoodyAbAttr extends PostTurnAbAttr { * If the pokemon already has all stat stages raised to 6, it will only decrease one stat stage by 1 * If the pokemon already has all stat stages lowered to -6, it will only increase one stat stage by 2 */ - override applyPostTurn(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ pokemon, simulated }: AbAttrBaseParams): void { + if (!simulated) { + return; + } const canRaise = EFFECTIVE_STATS.filter(s => pokemon.getStatStage(s) < 6); let canLower = EFFECTIVE_STATS.filter(s => pokemon.getStatStage(s) > -6); @@ -5821,26 +4728,28 @@ export class MoodyAbAttr extends PostTurnAbAttr { } } +/** @sealed */ export class SpeedBoostAbAttr extends PostTurnAbAttr { constructor() { super(true); } - override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): boolean { + override canApply({ simulated, pokemon }: AbAttrBaseParams): boolean { + // todo: Consider moving the `simulated` check to the `apply` method return simulated || (!pokemon.turnData.switchedInThisTurn && !pokemon.turnData.failedRunAway); } - override applyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { + override apply({ pokemon }: AbAttrBaseParams): void { globalScene.phaseManager.unshiftNew("StatStageChangePhase", pokemon.getBattlerIndex(), true, [Stat.SPD], 1); } } export class PostTurnHealAbAttr extends PostTurnAbAttr { - override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { return !pokemon.isFullHp(); } - override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ simulated, pokemon, passive }: AbAttrBaseParams): void { if (!simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; globalScene.phaseManager.unshiftNew( @@ -5857,6 +4766,7 @@ export class PostTurnHealAbAttr extends PostTurnAbAttr { } } +/** @sealed */ export class PostTurnFormChangeAbAttr extends PostTurnAbAttr { private formFunc: (p: Pokemon) => number; @@ -5866,11 +4776,11 @@ export class PostTurnFormChangeAbAttr extends PostTurnAbAttr { this.formFunc = formFunc; } - override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { return this.formFunc(pokemon) !== pokemon.formIndex; } - override applyPostTurn(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + override apply({ simulated, pokemon }: AbAttrBaseParams): void { if (!simulated) { globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); } @@ -5879,9 +4789,10 @@ export class PostTurnFormChangeAbAttr extends PostTurnAbAttr { /** * Attribute used for abilities (Bad Dreams) that damages the opponents for being asleep + * @sealed */ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { - override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: AbAttrBaseParams): boolean { return pokemon .getOpponents() .some( @@ -5891,26 +4802,21 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { !opp.switchOutStatus, ); } - /** - * Deals damage to all sleeping opponents equal to 1/8 of their max hp (min 1) - * @param pokemon {@linkcode Pokemon} with this ability - * @param _passive N/A - * @param simulated `true` if applying in a simulated call. - * @param _args N/A - */ - override applyPostTurn(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): void { + /** Deals damage to all sleeping opponents equal to 1/8 of their max hp (min 1) */ + override apply({ pokemon, simulated }: AbAttrBaseParams): void { + if (simulated) { + return; + } for (const opp of pokemon.getOpponents()) { if ( (opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(AbilityId.COMATOSE)) && !opp.hasAbilityWithAttr("BlockNonDirectDamageAbAttr") && !opp.switchOutStatus ) { - if (!simulated) { - opp.damageAndUpdate(toDmgValue(opp.getMaxHp() / 8), { result: HitResult.INDIRECT }); - globalScene.phaseManager.queueMessage( - i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) }), - ); - } + opp.damageAndUpdate(toDmgValue(opp.getMaxHp() / 8), { result: HitResult.INDIRECT }); + globalScene.phaseManager.queueMessage( + i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) }), + ); } } } @@ -5918,20 +4824,17 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { /** * Grabs the last failed Pokeball used - * @extends PostTurnAbAttr + * @sealed * @see {@linkcode applyPostTurn} */ export class FetchBallAbAttr extends PostTurnAbAttr { - override canApplyPostTurn(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): boolean { + override canApply({ simulated, pokemon }: AbAttrBaseParams): boolean { return !simulated && !isNullOrUndefined(globalScene.currentBattle.lastUsedPokeball) && !!pokemon.isPlayer; } /** * Adds the last used Pokeball back into the player's inventory - * @param pokemon {@linkcode Pokemon} with this ability - * @param _passive N/A - * @param _args N/A */ - override applyPostTurn(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { + override apply({ pokemon }: AbAttrBaseParams): void { const lastUsed = globalScene.currentBattle.lastUsedPokeball; globalScene.pokeballCounts[lastUsed!]++; globalScene.currentBattle.lastUsedPokeball = null; @@ -5955,23 +4858,18 @@ export class PostBiomeChangeWeatherChangeAbAttr extends PostBiomeChangeAbAttr { this.weatherType = weatherType; } - override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply(_params: AbAttrBaseParams): boolean { return (globalScene.arena.weather?.isImmutable() ?? false) && globalScene.arena.canSetWeather(this.weatherType); } - override apply( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ simulated, pokemon }: AbAttrBaseParams): void { if (!simulated) { globalScene.arena.trySetWeather(this.weatherType, pokemon); } } } +/**@sealed */ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr { private terrainType: TerrainType; @@ -5981,47 +4879,35 @@ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr { this.terrainType = terrainType; } - override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply(_params: AbAttrBaseParams): boolean { return globalScene.arena.canSetTerrain(this.terrainType); } - override apply( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ simulated, pokemon }: AbAttrBaseParams): void { if (!simulated) { globalScene.arena.trySetTerrain(this.terrainType, false, pokemon); } } } +export interface PostMoveUsedAbAttrParams extends AbAttrBaseParams { + /** The move that was used */ + move: PokemonMove; + /** The source of the move */ + source: Pokemon; + /** The targets of the move */ + targets: BattlerIndex[]; +} + /** * Triggers just after a move is used either by the opponent or the player - * @extends AbAttr */ export class PostMoveUsedAbAttr extends AbAttr { - canApplyPostMoveUsed( - _pokemon: Pokemon, - _move: PokemonMove, - _source: Pokemon, - _targets: BattlerIndex[], - _simulated: boolean, - _args: any[], - ): boolean { + canApplyPostMoveUsed(_params: Closed): boolean { return true; } - applyPostMoveUsed( - _pokemon: Pokemon, - _move: PokemonMove, - _source: Pokemon, - _targets: BattlerIndex[], - _simulated: boolean, - _args: any[], - ): void {} + applyPostMoveUsed(_params: Closed): void {} } /** @@ -6029,14 +4915,7 @@ export class PostMoveUsedAbAttr extends AbAttr { * @extends PostMoveUsedAbAttr */ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { - override canApplyPostMoveUsed( - dancer: Pokemon, - _move: PokemonMove, - source: Pokemon, - _targets: BattlerIndex[], - _simulated: boolean, - _args: any[], - ): boolean { + override canApplyPostMoveUsed({ source, pokemon }: PostMoveUsedAbAttrParams): boolean { // List of tags that prevent the Dancer from replicating the move const forbiddenTags = [ BattlerTagType.FLYING, @@ -6046,40 +4925,28 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { ]; // The move to replicate cannot come from the Dancer return ( - source.getBattlerIndex() !== dancer.getBattlerIndex() && - !dancer.summonData.tags.some(tag => forbiddenTags.includes(tag.tagType)) + source.getBattlerIndex() !== pokemon.getBattlerIndex() && + !pokemon.summonData.tags.some(tag => forbiddenTags.includes(tag.tagType)) ); } /** * Resolves the Dancer ability by replicating the move used by the source of the dance * either on the source itself or on the target of the dance - * @param dancer {@linkcode Pokemon} with Dancer ability - * @param move {@linkcode PokemonMove} Dancing move used by the source - * @param source {@linkcode Pokemon} that used the dancing move - * @param targets {@linkcode BattlerIndex}Targets of the dancing move - * @param _args N/A */ - override applyPostMoveUsed( - dancer: Pokemon, - move: PokemonMove, - source: Pokemon, - targets: BattlerIndex[], - simulated: boolean, - _args: any[], - ): void { + override apply({ source, pokemon, move, targets, simulated }: PostMoveUsedAbAttrParams): void { if (!simulated) { - dancer.turnData.extraTurns++; + pokemon.turnData.extraTurns++; // If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance if (move.getMove().is("AttackMove") || move.getMove().is("StatusMove")) { - const target = this.getTarget(dancer, source, targets); - globalScene.phaseManager.unshiftNew("MovePhase", dancer, target, move, MoveUseMode.INDIRECT); + const target = this.getTarget(pokemon, source, targets); + globalScene.phaseManager.unshiftNew("MovePhase", pokemon, target, move, MoveUseMode.INDIRECT); } else if (move.getMove().is("SelfStatusMove")) { // If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself globalScene.phaseManager.unshiftNew( "MovePhase", - dancer, - [dancer.getBattlerIndex()], + pokemon, + [pokemon.getBattlerIndex()], move, MoveUseMode.INDIRECT, ); @@ -6090,9 +4957,9 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { /** * Get the correct targets of Dancer ability * - * @param dancer {@linkcode Pokemon} Pokemon with Dancer ability - * @param source {@linkcode Pokemon} Source of the dancing move - * @param targets {@linkcode BattlerIndex} Targets of the dancing move + * @param dancer - Pokemon with Dancer ability + * @param source - Source of the dancing move + * @param targets - Targets of the dancing move */ getTarget(dancer: Pokemon, source: Pokemon, targets: BattlerIndex[]): BattlerIndex[] { if (dancer.isPlayer()) { @@ -6116,7 +4983,6 @@ export class PostItemLostAbAttr extends AbAttr { /** * Applies a Battler Tag to the Pokemon after it loses or consumes an item - * @extends PostItemLostAbAttr */ export class PostItemLostApplyBattlerTagAbAttr extends PostItemLostAbAttr { private tagType: BattlerTagType; @@ -6139,6 +5005,10 @@ export class PostItemLostApplyBattlerTagAbAttr extends PostItemLostAbAttr { } } +export interface StatStageChangeMultiplierAbAttrParams extends AbAttrBaseParams { + /** Holder for the stages after applying the ability. */ + numStages: NumberHolder; +} export class StatStageChangeMultiplierAbAttr extends AbAttr { private multiplier: number; @@ -6148,32 +5018,27 @@ export class StatStageChangeMultiplierAbAttr extends AbAttr { this.multiplier = multiplier; } - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - (args[0] as NumberHolder).value *= this.multiplier; + override apply({ numStages }: StatStageChangeMultiplierAbAttrParams): void { + numStages.value *= this.multiplier; } } +export interface StatStageChangeCopyAbAttrParams extends AbAttrBaseParams { + /** The stats to change */ + stats: BattleStat[]; + /** The number of stages that were changed by the original*/ + numStages: number; +} + export class StatStageChangeCopyAbAttr extends AbAttr { - override apply( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { + override apply({ pokemon, stats, numStages, simulated }: StatStageChangeCopyAbAttrParams): void { if (!simulated) { globalScene.phaseManager.unshiftNew( "StatStageChangePhase", pokemon.getBattlerIndex(), true, - args[0] as BattleStat[], - args[1] as number, + stats, + numStages, true, false, false, @@ -6187,17 +5052,16 @@ export class BypassBurnDamageReductionAbAttr extends AbAttr { super(false); } - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: AbAttrBaseParams): void { cancelled.value = true; } } +export interface ReduceBurnDamageAbAttrParams extends AbAttrBaseParams { + /** Holds the damage done by the burn */ + burnDamage: NumberHolder; +} + /** * Causes Pokemon to take reduced damage from the {@linkcode StatusEffect.BURN | Burn} status * @param multiplier Multiplied with the damage taken @@ -6209,31 +5073,20 @@ export class ReduceBurnDamageAbAttr extends AbAttr { /** * Applies the damage reduction - * @param _pokemon N/A - * @param _passive N/A - * @param _cancelled N/A - * @param args `[0]` {@linkcode NumberHolder} The damage value being modified */ - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - (args[0] as NumberHolder).value = toDmgValue((args[0] as NumberHolder).value * this.multiplier); + override apply({ burnDamage }: ReduceBurnDamageAbAttrParams): void { + burnDamage.value = toDmgValue(burnDamage.value * this.multiplier); } } +export interface DoubleBerryEffectAbAttrParams extends AbAttrBaseParams { + /** The value of the berry effect that will be doubled by the ability's application */ + effectValue: NumberHolder; +} + export class DoubleBerryEffectAbAttr extends AbAttr { - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - (args[0] as NumberHolder).value *= 2; + override apply({ effectValue }: DoubleBerryEffectAbAttrParams): void { + effectValue.value *= 2; } } @@ -6244,12 +5097,8 @@ export class DoubleBerryEffectAbAttr extends AbAttr { export class PreventBerryUseAbAttr extends AbAttr { /** * Prevent use of opposing berries. - * @param _pokemon - Unused - * @param _passive - Unused - * @param _simulated - Unused - * @param cancelled - {@linkcode BooleanHolder} containing whether to block berry use */ - override apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, cancelled: BooleanHolder): void { + override apply({ cancelled }: AbAttrBaseParams): void { cancelled.value = true; } } @@ -6257,7 +5106,6 @@ export class PreventBerryUseAbAttr extends AbAttr { /** * A Pokemon with this ability heals by a percentage of their maximum hp after eating a berry * @param healPercent - Percent of Max HP to heal - * @see {@linkcode apply()} for implementation */ export class HealFromBerryUseAbAttr extends AbAttr { /** Percent of Max HP to heal */ @@ -6270,7 +5118,7 @@ export class HealFromBerryUseAbAttr extends AbAttr { this.healPercent = Math.max(Math.min(healPercent, 1), 0); } - override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, ..._args: [BooleanHolder, any[]]): void { + override apply({ simulated, passive, pokemon }: AbAttrBaseParams): void { if (simulated) { return; } @@ -6289,15 +5137,14 @@ export class HealFromBerryUseAbAttr extends AbAttr { } } +export interface RunSuccessAbAttrParams extends AbAttrBaseParams { + /** Holder for the likelihood that the pokemon will flee */ + chance: NumberHolder; +} + export class RunSuccessAbAttr extends AbAttr { - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - (args[0] as NumberHolder).value = 256; + override apply({ chance }: RunSuccessAbAttrParams): void { + chance.value = 256; } } @@ -6338,29 +5185,27 @@ export class CheckTrappedAbAttr extends AbAttr { ): void {} } +export interface CheckTrappedAbAttrParams extends AbAttrBaseParams { + /** The pokemon to attempt to trap */ + opponent: Pokemon; + /** Holds whether the other Pokemon will be trapped or not */ + trapped: BooleanHolder; +} + /** * Determines whether a Pokemon is blocked from switching/running away * because of a trapping ability or move. - * @extends CheckTrappedAbAttr * @see {@linkcode applyCheckTrapped} */ export class ArenaTrapAbAttr extends CheckTrappedAbAttr { - override canApplyCheckTrapped( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _trapped: BooleanHolder, - otherPokemon: Pokemon, - _args: any[], - ): boolean { + override canApply({ pokemon, opponent }: CheckTrappedAbAttrParams): boolean { return ( - this.arenaTrapCondition(pokemon, otherPokemon) && + this.arenaTrapCondition(pokemon, opponent) && !( - otherPokemon.getTypes(true).includes(PokemonType.GHOST) || - (otherPokemon.getTypes(true).includes(PokemonType.STELLAR) && - otherPokemon.getTypes().includes(PokemonType.GHOST)) + opponent.getTypes(true).includes(PokemonType.GHOST) || + (opponent.getTypes(true).includes(PokemonType.STELLAR) && opponent.getTypes().includes(PokemonType.GHOST)) ) && - !otherPokemon.hasAbility(AbilityId.RUN_AWAY) + !opponent.hasAbility(AbilityId.RUN_AWAY) ); } @@ -6370,24 +5215,12 @@ export class ArenaTrapAbAttr extends CheckTrappedAbAttr { * If the enemy has the ability Run Away, it is not trapped. * If the user has Magnet Pull and the enemy is not a Steel type, it is not trapped. * If the user has Arena Trap and the enemy is not grounded, it is not trapped. - * @param _pokemon The {@link Pokemon} with this {@link AbAttr} - * @param _passive N/A - * @param trapped {@link BooleanHolder} indicating whether the other Pokemon is trapped or not - * @param _otherPokemon The {@link Pokemon} that is affected by an Arena Trap ability - * @param _args N/A */ - override applyCheckTrapped( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - trapped: BooleanHolder, - _otherPokemon: Pokemon, - _args: any[], - ): void { + override apply({ trapped }: CheckTrappedAbAttrParams): void { trapped.value = true; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + getTriggerMessage({ pokemon }: CheckTrappedAbAttrParams, abilityName: string): string { return i18next.t("abilityTriggers:arenaTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, @@ -6395,50 +5228,52 @@ export class ArenaTrapAbAttr extends CheckTrappedAbAttr { } } +export interface MaxMultiHitAbAttrParams extends AbAttrBaseParams { + /** The number of hits that the move will do */ + hits: NumberHolder; +} + export class MaxMultiHitAbAttr extends AbAttr { constructor() { super(false); } - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - (args[0] as NumberHolder).value = 0; + override apply({ hits }: MaxMultiHitAbAttrParams): void { + hits.value = 0; } } -export class PostBattleAbAttr extends AbAttr { +export interface PostBattleAbAttrParams extends AbAttrBaseParams { + /** Whether the battle that just ended was a victory */ + victory: boolean; +} + +// TODO PICKUP FROM HERE 6/12/2025 +export abstract class PostBattleAbAttr extends AbAttr { constructor(showAbility = true) { super(showAbility); } - canApplyPostBattle(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + canApply(_params: Closed): boolean { return true; } - applyPostBattle(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void {} + apply(_params: Closed): void {} } export class PostBattleLootAbAttr extends PostBattleAbAttr { private randItem?: PokemonHeldItemModifier; - override canApplyPostBattle(pokemon: Pokemon, _passive: boolean, simulated: boolean, args: any[]): boolean { + override canApply({ simulated, victory, pokemon }: PostBattleAbAttrParams): boolean { const postBattleLoot = globalScene.currentBattle.postBattleLoot; - if (!simulated && postBattleLoot.length && args[0]) { + if (!simulated && postBattleLoot.length && victory) { this.randItem = randSeedItem(postBattleLoot); return globalScene.canTransferHeldItemModifier(this.randItem, pokemon, 1); } return false; } - /** - * @param _args - `[0]`: boolean for if the battle ended in a victory - */ - override applyPostBattle(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { + override apply({ pokemon }: PostBattleAbAttrParams): void { const postBattleLoot = globalScene.currentBattle.postBattleLoot; if (!this.randItem) { this.randItem = randSeedItem(postBattleLoot); @@ -6457,67 +5292,41 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr { } } -export class PostFaintAbAttr extends AbAttr { - canApplyPostFaint( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker?: Pokemon, - _move?: Move, - _hitResult?: HitResult, - ..._args: any[] - ): boolean { +/** + * Shared parameters for ability attributes that are triggered after the user faints. + */ +export interface PostFaintAbAttrParams extends AbAttrBaseParams { + /** The pokemon that caused the faint, or undefined if not caused by a pokemon */ + readonly attacker?: Pokemon; + /** The move that caused the faint, or undefined if not caused by a move */ + readonly move?: Move; + /** The result of the hit that caused the faint */ + readonly hitResult?: HitResult; +} + +export abstract class PostFaintAbAttr extends AbAttr { + canApply(_params: Closed): boolean { return true; } - applyPostFaint( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker?: Pokemon, - _move?: Move, - _hitResult?: HitResult, - ..._args: any[] - ): void {} + apply(_params: Closed): void {} } /** * Used for weather suppressing abilities to trigger weather-based form changes upon being fainted. * Used by Cloud Nine and Air Lock. - * @extends PostFaintAbAttr + * @sealed */ export class PostFaintUnsuppressedWeatherFormChangeAbAttr extends PostFaintAbAttr { - override canApplyPostFaint( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker?: Pokemon, - _move?: Move, - _hitResult?: HitResult, - ..._args: any[] - ): boolean { + override canApply(_params: PostFaintAbAttrParams): boolean { return getPokemonWithWeatherBasedForms().length > 0; } /** * Triggers {@linkcode Arena.triggerWeatherBasedFormChanges | triggerWeatherBasedFormChanges} * when the user of the ability faints - * @param {Pokemon} _pokemon the fainted Pokemon - * @param _passive n/a - * @param _attacker n/a - * @param _move n/a - * @param _hitResult n/a - * @param _args n/a */ - override applyPostFaint( - _pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _attacker: Pokemon, - _move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ simulated }: PostFaintAbAttrParams): void { if (!simulated) { globalScene.arena.triggerWeatherBasedFormChanges(); } @@ -6533,42 +5342,37 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr { this.damageRatio = damageRatio; } - override canApplyPostFaint( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - attacker?: Pokemon, - move?: Move, - _hitResult?: HitResult, - ..._args: any[] - ): boolean { + override canApply({ pokemon, attacker, move, simulated }: PostFaintAbAttrParams): boolean { + if (!move || !attacker) { + return false; + } const diedToDirectDamage = - move !== undefined && attacker !== undefined && move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }); const cancelled = new BooleanHolder(false); - globalScene.getField(true).map(p => applyAbAttrs("FieldPreventExplosiveMovesAbAttr", p, cancelled, simulated)); - return !(!diedToDirectDamage || cancelled.value || attacker!.hasAbilityWithAttr("BlockNonDirectDamageAbAttr")); - } - - override applyPostFaint( - _pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - attacker?: Pokemon, - _move?: Move, - _hitResult?: HitResult, - ..._args: any[] - ): void { - if (!simulated) { - attacker!.damageAndUpdate(toDmgValue(attacker!.getMaxHp() * (1 / this.damageRatio)), { - result: HitResult.INDIRECT, + for (const otherPokemon of globalScene.getField(true)) { + applyAbAttrs("FieldPreventExplosiveMovesAbAttr", { + pokemon: otherPokemon, + simulated, + cancelled, + move, + opponent: pokemon, }); - attacker!.turnData.damageTaken += toDmgValue(attacker!.getMaxHp() * (1 / this.damageRatio)); } + return !(!diedToDirectDamage || cancelled.value || attacker.hasAbilityWithAttr("BlockNonDirectDamageAbAttr")); } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + override apply({ simulated, attacker }: PostFaintAbAttrParams): void { + if (!attacker || simulated) { + return; + } + attacker.damageAndUpdate(toDmgValue(attacker!.getMaxHp() * (1 / this.damageRatio)), { + result: HitResult.INDIRECT, + }); + attacker.turnData.damageTaken += toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)); + } + + getTriggerMessage({ pokemon }: PostFaintAbAttrParams, abilityName: string): string { return i18next.t("abilityTriggers:postFaintContactDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, @@ -6578,17 +5382,10 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr { /** * Attribute used for abilities (Innards Out) that damage the opponent based on how much HP the last attack used to knock out the owner of the ability. + * @sealed */ export class PostFaintHPDamageAbAttr extends PostFaintAbAttr { - override applyPostFaint( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - attacker?: Pokemon, - move?: Move, - _hitResult?: HitResult, - ..._args: any[] - ): void { + override apply({ simulated, pokemon, move, attacker }: PostFaintAbAttrParams): void { //If the mon didn't die to indirect damage if (move !== undefined && attacker !== undefined && !simulated) { const damage = pokemon.turnData.attacksReceived[0].damage; @@ -6597,7 +5394,7 @@ export class PostFaintHPDamageAbAttr extends PostFaintAbAttr { } } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + getTriggerMessage({ pokemon }: PostFaintAbAttrParams, abilityName: string): string { return i18next.t("abilityTriggers:postFaintHpDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, @@ -6605,45 +5402,41 @@ export class PostFaintHPDamageAbAttr extends PostFaintAbAttr { } } -/** - * Redirects a move to the pokemon with this ability if it meets the conditions - */ -export class RedirectMoveAbAttr extends AbAttr { - /** - * @param pokemon - The Pokemon with the redirection ability - * @param args - The args passed to the `AbAttr`: - * - `[0]` - The id of the {@linkcode Move} used - * - `[1]` - The target's battler index (before redirection) - * - `[2]` - The Pokemon that used the move being redirected - */ +export interface RedirectMoveAbAttrParams extends AbAttrBaseParams { + /** The id of the move being redirected */ + moveId: MoveId; + /** The target's battler index before redirection */ + targetIndex: NumberHolder; + /** The Pokemon that used the move being redirected */ + sourcePokemon: Pokemon; +} - override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { - if (!this.canRedirect(args[0] as MoveId, args[2] as Pokemon)) { +/** + * Base class for abilities that redirect moves to the pokemon with this ability. + */ +export abstract class RedirectMoveAbAttr extends AbAttr { + override canApply({ pokemon, moveId, targetIndex, sourcePokemon }: RedirectMoveAbAttrParams): boolean { + if (!this.canRedirect(moveId, sourcePokemon)) { return false; } - const target = args[1] as NumberHolder; const newTarget = pokemon.getBattlerIndex(); - return target.value !== newTarget; + return targetIndex.value !== newTarget; } - override apply( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - const target = args[1] as NumberHolder; + override apply({ pokemon, targetIndex }: RedirectMoveAbAttrParams): void { const newTarget = pokemon.getBattlerIndex(); - target.value = newTarget; + targetIndex.value = newTarget; } - canRedirect(moveId: MoveId, _user: Pokemon): boolean { + protected canRedirect(moveId: MoveId, _user: Pokemon): boolean { const move = allMoves[moveId]; return !![MoveTarget.NEAR_OTHER, MoveTarget.OTHER].find(t => move.moveTarget === t); } } +/** + * @sealed + */ export class RedirectTypeMoveAbAttr extends RedirectMoveAbAttr { public type: PokemonType; @@ -6652,17 +5445,25 @@ export class RedirectTypeMoveAbAttr extends RedirectMoveAbAttr { this.type = type; } - canRedirect(moveId: MoveId, user: Pokemon): boolean { + protected override canRedirect(moveId: MoveId, user: Pokemon): boolean { return super.canRedirect(moveId, user) && user.getMoveType(allMoves[moveId]) === this.type; } } export class BlockRedirectAbAttr extends AbAttr {} +export interface ReduceStatusEffectDurationAbAttrParams extends AbAttrBaseParams { + /** The status effect in question */ + statusEffect: StatusEffect; + /** Holds the number of turns until the status is healed, which may be modified by ability application. */ + duration: NumberHolder; +} + /** * Used by Early Bird, makes the pokemon wake up faster * @param statusEffect - The {@linkcode StatusEffect} to check for * @see {@linkcode apply} + * @sealed */ export class ReduceStatusEffectDurationAbAttr extends AbAttr { private statusEffect: StatusEffect; @@ -6673,8 +5474,8 @@ export class ReduceStatusEffectDurationAbAttr extends AbAttr { this.statusEffect = statusEffect; } - override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { - return args[1] instanceof NumberHolder && args[0] === this.statusEffect; + override canApply({ statusEffect }: ReduceStatusEffectDurationAbAttrParams): boolean { + return statusEffect === this.statusEffect; } /** @@ -6683,21 +5484,24 @@ export class ReduceStatusEffectDurationAbAttr extends AbAttr { * - `[0]` - The {@linkcode StatusEffect} of the Pokemon * - `[1]` - The number of turns remaining until the status is healed */ - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - args[1].value -= 1; + override apply({ duration }: ReduceStatusEffectDurationAbAttrParams): void { + duration.value -= 1; } } -export class FlinchEffectAbAttr extends AbAttr { +/** + * Base class for abilities that apply an effect when the user is flinched. + */ +export abstract class FlinchEffectAbAttr extends AbAttr { constructor() { super(true); } + + canApply(_params: Closed): boolean { + return true; + } + + apply(_params: Closed): void {} } export class FlinchStatStageChangeAbAttr extends FlinchEffectAbAttr { @@ -6711,13 +5515,7 @@ export class FlinchStatStageChangeAbAttr extends FlinchEffectAbAttr { this.stages = stages; } - override apply( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ simulated, pokemon }: AbAttrBaseParams): void { if (!simulated) { globalScene.phaseManager.unshiftNew( "StatStageChangePhase", @@ -6732,42 +5530,43 @@ export class FlinchStatStageChangeAbAttr extends FlinchEffectAbAttr { export class IncreasePpAbAttr extends AbAttr {} +/** @sealed */ export class ForceSwitchOutImmunityAbAttr extends AbAttr { - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: AbAttrBaseParams): void { cancelled.value = true; } } +export interface ReduceBerryUseThresholdAbAttrParams extends AbAttrBaseParams { + /** Holds the hp ratio for the berry to proc, which may be modified by ability application */ + hpRatioReq: NumberHolder; +} + +/** @sealed */ export class ReduceBerryUseThresholdAbAttr extends AbAttr { constructor() { super(false); } - override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { + override canApply({ pokemon, hpRatioReq }: ReduceBerryUseThresholdAbAttrParams): boolean { const hpRatio = pokemon.getHpRatio(); - return args[0].value < hpRatio; + return hpRatioReq.value < hpRatio; } - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - args[0].value *= 2; + override apply({ hpRatioReq }: ReduceBerryUseThresholdAbAttrParams): void { + hpRatioReq.value *= 2; } } +export interface WeightMultiplierAbAttrParams extends AbAttrBaseParams { + /** The weight of the Pokemon, which may be modified by ability application */ + weight: NumberHolder; +} + /** * Ability attribute used for abilites that change the ability owner's weight * Used for Heavy Metal (doubling weight) and Light Metal (halving weight) + * @sealed */ export class WeightMultiplierAbAttr extends AbAttr { private multiplier: number; @@ -6778,33 +5577,34 @@ export class WeightMultiplierAbAttr extends AbAttr { this.multiplier = multiplier; } - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - (args[0] as NumberHolder).value *= this.multiplier; + override apply({ weight }: WeightMultiplierAbAttrParams): void { + weight.value *= this.multiplier; } } +export interface SyncEncounterNatureAbAttrParams extends AbAttrBaseParams { + /** The Pokemon whose nature is being synced */ + target: Pokemon; +} + +/** @sealed */ export class SyncEncounterNatureAbAttr extends AbAttr { constructor() { super(false); } - override apply( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - (args[0] as Pokemon).setNature(pokemon.getNature()); + override apply({ target, pokemon }: SyncEncounterNatureAbAttrParams): void { + target.setNature(pokemon.getNature()); } } +export interface MoveAbilityBypassAbAttrParams extends AbAttrBaseParams { + /** The move being used */ + move: Move; + /** Holds whether the move's ability should be ignored */ + cancelled: BooleanHolder; +} + export class MoveAbilityBypassAbAttr extends AbAttr { private moveIgnoreFunc: (pokemon: Pokemon, move: Move) => boolean; @@ -6814,17 +5614,11 @@ export class MoveAbilityBypassAbAttr extends AbAttr { this.moveIgnoreFunc = moveIgnoreFunc || ((_pokemon, _move) => true); } - override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { - return this.moveIgnoreFunc(pokemon, args[0] as Move); + override canApply({ pokemon, move }: MoveAbilityBypassAbAttrParams): boolean { + return this.moveIgnoreFunc(pokemon, move); } - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: MoveAbilityBypassAbAttrParams): void { cancelled.value = true; } } @@ -6834,29 +5628,30 @@ export class AlwaysHitAbAttr extends AbAttr {} /** Attribute for abilities that allow moves that make contact to ignore protection (i.e. Unseen Fist) */ export class IgnoreProtectOnContactAbAttr extends AbAttr {} +export interface InfiltratorAbAttrParams extends AbAttrBaseParams { + /** Holds a flag indicating that infiltrator's bypass is active */ + bypassed: BooleanHolder; +} + /** * Attribute implementing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Infiltrator_(Ability) | Infiltrator}. * Allows the source's moves to bypass the effects of opposing Light Screen, Reflect, Aurora Veil, Safeguard, Mist, and Substitute. + * @sealed */ export class InfiltratorAbAttr extends AbAttr { constructor() { super(false); } - override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { - return args[0] instanceof BooleanHolder; + /** @returns Whether bypassed has not yet been set */ + override canApply({ bypassed }: InfiltratorAbAttrParams): boolean { + return !bypassed.value; } /** * Sets a flag to bypass screens, Substitute, Safeguard, and Mist - * @param _pokemon n/a - * @param _passive n/a - * @param _simulated n/a - * @param _cancelled n/a - * @param args `[0]` a {@linkcode BooleanHolder | BooleanHolder} containing the flag */ - override apply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: null, args: any[]): void { - const bypassed = args[0]; + override apply({ bypassed }: InfiltratorAbAttrParams): void { bypassed.value = true; } } @@ -6865,21 +5660,34 @@ export class InfiltratorAbAttr extends AbAttr { * Attribute implementing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Magic_Bounce_(ability) | Magic Bounce}. * Allows the source to bounce back {@linkcode MoveFlags.REFLECTABLE | Reflectable} * moves as if the user had used {@linkcode MoveId.MAGIC_COAT | Magic Coat}. + * @sealed */ export class ReflectStatusMoveAbAttr extends AbAttr {} +/** @sealed */ export class NoTransformAbilityAbAttr extends AbAttr { constructor() { super(false); } } +/** @sealed */ export class NoFusionAbilityAbAttr extends AbAttr { constructor() { super(false); } } +export interface IgnoreTypeImmunityAbAttrParams extends AbAttrBaseParams { + /** The type of the move being used */ + readonly moveType: PokemonType; + /** The type being checked for */ + readonly defenderType: PokemonType; + /** Holds whether the type immunity should be bypassed */ + cancelled: BooleanHolder; +} + +/** @sealed */ export class IgnoreTypeImmunityAbAttr extends AbAttr { private defenderType: PokemonType; private allowedMoveTypes: PokemonType[]; @@ -6890,23 +5698,25 @@ export class IgnoreTypeImmunityAbAttr extends AbAttr { this.allowedMoveTypes = allowedMoveTypes; } - override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { - return this.defenderType === (args[1] as PokemonType) && this.allowedMoveTypes.includes(args[0] as PokemonType); + override canApply({ moveType, defenderType }: IgnoreTypeImmunityAbAttrParams): boolean { + return this.defenderType === defenderType && this.allowedMoveTypes.includes(moveType); } - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: IgnoreTypeImmunityAbAttrParams): void { cancelled.value = true; } } +export interface IgnoreTypeStatusEffectImmunityAbAttrParams extends AbAttrBaseParams { + /** The status effect being applied */ + readonly statusEffect: StatusEffect; + /** Holds whether the type immunity should be bypassed */ + readonly defenderType: PokemonType; +} + /** * Ignores the type immunity to Status Effects of the defender if the defender is of a certain type + * @sealed */ export class IgnoreTypeStatusEffectImmunityAbAttr extends AbAttr { private statusEffect: StatusEffect[]; @@ -6919,17 +5729,11 @@ export class IgnoreTypeStatusEffectImmunityAbAttr extends AbAttr { this.defenderType = defenderType; } - override canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, args: any[]): boolean { - return this.statusEffect.includes(args[0] as StatusEffect) && this.defenderType.includes(args[1] as PokemonType); + override canApply({ statusEffect, defenderType, cancelled }: IgnoreTypeStatusEffectImmunityAbAttrParams): boolean { + return !cancelled.value && this.statusEffect.includes(statusEffect) && this.defenderType.includes(defenderType); } - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ cancelled }: IgnoreTypeStatusEffectImmunityAbAttrParams): void { cancelled.value = true; } } @@ -6938,65 +5742,43 @@ export class IgnoreTypeStatusEffectImmunityAbAttr extends AbAttr { * Gives money to the user after the battle. * * @extends PostBattleAbAttr - * @see {@linkcode applyPostBattle} */ export class MoneyAbAttr extends PostBattleAbAttr { - override canApplyPostBattle(_pokemon: Pokemon, _passive: boolean, simulated: boolean, args: any[]): boolean { - return !simulated && args[0]; + override canApply({ simulated, victory }: PostBattleAbAttrParams): boolean { + // TODO: Consider moving the simulated check to the apply method + return !simulated && victory; } - /** - * @param _pokemon {@linkcode Pokemon} that is the user of this ability. - * @param _passive N/A - * @param _args - `[0]`: boolean for if the battle ended in a victory - */ - override applyPostBattle(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { + override apply(_params: PostBattleAbAttrParams): void { globalScene.currentBattle.moneyScattered += globalScene.getWaveMoneyAmount(0.2); } } +// TODO: Consider removing this class and just using the PostSummonStatStageChangeAbAttr with a conditionalAttr +// that checks for the presence of the tag. /** * Applies a stat change after a Pokémon is summoned, * conditioned on the presence of a specific arena tag. - * - * @extends PostSummonStatStageChangeAbAttr + * @sealed */ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageChangeAbAttr { - /** - * The type of arena tag that conditions the stat change. - * @private - */ + /** The type of arena tag that conditions the stat change. */ private tagType: ArenaTagType; /** * Creates an instance of PostSummonStatStageChangeOnArenaAbAttr. * Initializes the stat change to increase Attack by 1 stage if the specified arena tag is present. * - * @param {ArenaTagType} tagType - The type of arena tag to check for. + * @param tagType - The type of arena tag to check for. */ constructor(tagType: ArenaTagType) { super([Stat.ATK], 1, true, false); this.tagType = tagType; } - override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const side = pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - return ( - (globalScene.arena.getTagOnSide(this.tagType, side) ?? false) && - super.canApplyPostSummon(pokemon, passive, simulated, args) - ); - } - - /** - * Applies the post-summon stat change if the specified arena tag is present on pokemon's side. - * This is used in Wind Rider ability. - * - * @param {Pokemon} pokemon - The Pokémon being summoned. - * @param {boolean} passive - Whether the effect is passive. - * @param {any[]} args - Additional arguments. - */ - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void { - super.applyPostSummon(pokemon, passive, simulated, args); + override canApply(params: AbAttrBaseParams): boolean { + const side = params.pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + return (globalScene.arena.getTagOnSide(this.tagType, side) ?? false) && super.canApply(params); } } @@ -7005,7 +5787,8 @@ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageC * This is used in the Disguise and Ice Face abilities. * * Does not apply to a user's substitute - * @extends ReceivedMoveDamageMultiplierAbAttr + * @see ReceivedMoveDamageMultiplierAbAttr + * @sealed */ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { private multiplier: number; @@ -7028,40 +5811,18 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { this.triggerMessageFunc = triggerMessageFunc; } - override canApplyPreDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - attacker: Pokemon, - move: Move, - _cancelled: BooleanHolder | null, - _args: any[], - ): boolean { - return this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon); + override canApply({ pokemon, opponent, move }: PreDefendModifyDamageAbAttrParams): boolean { + // TODO: Investigate whether the substitute check can be removed, as it should be accounted for in the move effect phase + return this.condition(pokemon, opponent, move) && !move.hitsSubstitute(opponent, pokemon); } /** * Applies the pre-defense ability to the Pokémon. * Removes the appropriate `BattlerTagType` when hit by an attack and is in its defense form. - * - * @param pokemon The Pokémon with the ability. - * @param _passive n/a - * @param _attacker The attacking Pokémon. - * @param _move The move being used. - * @param _cancelled n/a - * @param args Additional arguments. */ - override applyPreDefend( - pokemon: Pokemon, - _passive: boolean, - simulated: boolean, - _attacker: Pokemon, - _move: Move, - _cancelled: BooleanHolder, - args: any[], - ): void { + override apply({ pokemon, simulated, damage }: PreDefendModifyDamageAbAttrParams): void { if (!simulated) { - (args[0] as NumberHolder).value = this.multiplier; + damage.value = this.multiplier; pokemon.removeTag(this.tagType); if (this.recoilDamageFunc) { pokemon.damageAndUpdate(this.recoilDamageFunc(pokemon), { @@ -7075,12 +5836,9 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { /** * Gets the message triggered when the Pokémon avoids damage using the form-changing ability. - * @param pokemon The Pokémon with the ability. - * @param abilityName The name of the ability. - * @param _args n/a * @returns The trigger message. */ - getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { + override getTriggerMessage({ pokemon }: PreDefendModifyDamageAbAttrParams, abilityName: string): string { return this.triggerMessageFunc(pokemon, abilityName); } } @@ -7091,23 +5849,21 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { * @see {@linkcode applyPreSummon()} */ export class PreSummonAbAttr extends AbAttr { - applyPreSummon(_pokemon: Pokemon, _passive: boolean, _args: any[]): void {} + apply(_params: Closed): void {} - canApplyPreSummon(_pokemon: Pokemon, _passive: boolean, _args: any[]): boolean { + canApply(_params: Closed): boolean { return true; } } +/** @sealed */ export class IllusionPreSummonAbAttr extends PreSummonAbAttr { /** * Apply a new illusion when summoning Zoroark if the illusion is available * * @param pokemon - The Pokémon with the Illusion ability. - * @param _passive - N/A - * @param _args - N/A - * @returns Whether the illusion was applied. */ - override applyPreSummon(pokemon: Pokemon, _passive: boolean, _args: any[]): void { + override apply({ pokemon }: AbAttrBaseParams): void { const party: Pokemon[] = (pokemon.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter( p => p.isAllowedInBattle(), ); @@ -7115,7 +5871,8 @@ export class IllusionPreSummonAbAttr extends PreSummonAbAttr { pokemon.setIllusion(lastPokemon); } - override canApplyPreSummon(pokemon: Pokemon, _passive: boolean, _args: any[]): boolean { + /** @returns Whether the illusion can be applied. */ + override canApply({ pokemon }: AbAttrBaseParams): boolean { if (pokemon.hasTrainer()) { const party: Pokemon[] = (pokemon.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter( p => p.isAllowedInBattle(), @@ -7137,53 +5894,27 @@ export class IllusionPreSummonAbAttr extends PreSummonAbAttr { } } +/** @sealed */ export class IllusionBreakAbAttr extends AbAttr { - override apply( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder | null, - _args: any[], - ): void { + // TODO: Consider adding a `canApply` method that checks if the pokemon has an active illusion + override apply({ pokemon }: AbAttrBaseParams): void { pokemon.breakIllusion(); pokemon.summonData.illusionBroken = true; } } +/** @sealed */ export class PostDefendIllusionBreakAbAttr extends PostDefendAbAttr { /** * Destroy the illusion upon taking damage - * - * @param pokemon - The Pokémon with the Illusion ability. - * @param _passive - unused - * @param _attacker - The attacking Pokémon. - * @param _move - The move being used. - * @param _hitResult - The type of hitResult the pokemon got - * @param _args - unused * @returns - Whether the illusion was destroyed. */ - override applyPostDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - _move: Move, - _hitResult: HitResult, - _args: any[], - ): void { + override apply({ pokemon }: PostMoveInteractionAbAttrParams): void { pokemon.breakIllusion(); pokemon.summonData.illusionBroken = true; } - override canApplyPostDefend( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _attacker: Pokemon, - _move: Move, - hitResult: HitResult, - _args: any[], - ): boolean { + override canApply({ pokemon, hitResult }: PostMoveInteractionAbAttrParams): boolean { const breakIllusion: HitResult[] = [ HitResult.EFFECTIVE, HitResult.SUPER_EFFECTIVE, @@ -7203,121 +5934,108 @@ export class IllusionPostBattleAbAttr extends PostBattleAbAttr { * @param _args - Unused * @returns - Whether the illusion was applied. */ - override applyPostBattle(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): void { + override apply({ pokemon }: PostBattleAbAttrParams): void { pokemon.breakIllusion(); } } +export interface BypassSpeedChanceAbAttrParams extends AbAttrBaseParams { + /** Holds whether the speed check is bypasseda after ability application */ + bypass: BooleanHolder; +} + /** * If a Pokémon with this Ability selects a damaging move, it has a 30% chance of going first in its priority bracket. If the Ability activates, this is announced at the start of the turn (after move selection). - * - * @extends AbAttr + * @sealed */ export class BypassSpeedChanceAbAttr extends AbAttr { public chance: number; /** - * @param {number} chance probability of ability being active. + * @param chance - Probability of the ability activating */ constructor(chance: number) { super(true); this.chance = chance; } - override canApply(pokemon: Pokemon, _passive: boolean, simulated: boolean, args: any[]): boolean { - const bypassSpeed = args[0] as BooleanHolder; + override canApply({ bypass, simulated, pokemon }: BypassSpeedChanceAbAttrParams): boolean { + // TODO: Consider whether we can move the simulated check to the `apply` method + // May be difficult as we likely do not want to modify the randBattleSeed const turnCommand = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; const isCommandFight = turnCommand?.command === Command.FIGHT; const move = turnCommand?.move?.move ? allMoves[turnCommand.move.move] : null; const isDamageMove = move?.category === MoveCategory.PHYSICAL || move?.category === MoveCategory.SPECIAL; return ( - !simulated && !bypassSpeed.value && pokemon.randBattleSeedInt(100) < this.chance && isCommandFight && isDamageMove + !simulated && !bypass.value && pokemon.randBattleSeedInt(100) < this.chance && isCommandFight && isDamageMove ); } /** * bypass move order in their priority bracket when pokemon choose damaging move - * @param {Pokemon} _pokemon {@linkcode Pokemon} the Pokemon applying this ability - * @param {boolean} _passive N/A - * @param {BooleanHolder} _cancelled N/A - * @param {any[]} args [0] {@linkcode BooleanHolder} set to true when the ability activated */ - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - const bypassSpeed = args[0] as BooleanHolder; - bypassSpeed.value = true; + override apply({ bypass }: BypassSpeedChanceAbAttrParams): void { + bypass.value = true; } - getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string { + override getTriggerMessage({ pokemon }: BypassSpeedChanceAbAttrParams, _abilityName: string): string { return i18next.t("abilityTriggers:quickDraw", { pokemonName: getPokemonNameWithAffix(pokemon) }); } } +export interface PreventBypassSpeedChanceAbAttrParams extends AbAttrBaseParams { + /** Holds whether the speed check is bypassed after ability application */ + bypass: BooleanHolder; + /** Holds whether the Pokemon can check held items for Quick Claw's effects */ + canCheckHeldItems: BooleanHolder; +} + /** * This attribute checks if a Pokemon's move meets a provided condition to determine if the Pokemon can use Quick Claw * It was created because Pokemon with the ability Mycelium Might cannot access Quick Claw's benefits when using status moves. + * @sealed */ export class PreventBypassSpeedChanceAbAttr extends AbAttr { private condition: (pokemon: Pokemon, move: Move) => boolean; /** - * @param {function} condition - checks if a move meets certain conditions + * @param condition - checks if a move meets certain conditions */ constructor(condition: (pokemon: Pokemon, move: Move) => boolean) { super(true); this.condition = condition; } - override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { + override canApply({ pokemon }: PreventBypassSpeedChanceAbAttrParams): boolean { + // TODO: Consider having these be passed as parameters instead of being retrieved here const turnCommand = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; const isCommandFight = turnCommand?.command === Command.FIGHT; const move = turnCommand?.move?.move ? allMoves[turnCommand.move.move] : null; return isCommandFight && this.condition(pokemon, move!); } - /** - * @argument {boolean} bypassSpeed - determines if a Pokemon is able to bypass speed at the moment - * @argument {boolean} canCheckHeldItems - determines if a Pokemon has access to Quick Claw's effects or not - */ - override apply( - _pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - args: any[], - ): void { - const bypassSpeed = args[0] as BooleanHolder; - const canCheckHeldItems = args[1] as BooleanHolder; - bypassSpeed.value = false; + override apply({ bypass, canCheckHeldItems }: PreventBypassSpeedChanceAbAttrParams): void { + bypass.value = false; canCheckHeldItems.value = false; } } +// Also consider making this a postTerrainChange attribute instead of a post-summon attribute /** * This applies a terrain-based type change to the Pokemon. * Used by Mimicry. + * @sealed */ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr { constructor() { super(true); } - override canApply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean { - return !pokemon.isTerastallized; + override canApply({ pokemon }: AbAttrBaseParams): boolean { + return !pokemon.isTerastallized && globalScene.arena.getTerrainType() !== TerrainType.NONE; } - override apply( - pokemon: Pokemon, - _passive: boolean, - _simulated: boolean, - _cancelled: BooleanHolder, - _args: any[], - ): void { + override apply({ pokemon }: AbAttrBaseParams): void { const currentTerrain = globalScene.arena.getTerrainType(); const typeChange: PokemonType[] = this.determineTypeChange(pokemon, currentTerrain); if (typeChange.length !== 0) { @@ -7359,18 +6077,7 @@ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr { return typeChange; } - override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - return globalScene.arena.getTerrainType() !== TerrainType.NONE && this.canApply(pokemon, passive, simulated, args); - } - - /** - * Checks if the Pokemon should change types if summoned into an active terrain - */ - override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, _args: any[]): void { - this.apply(pokemon, passive, simulated, new BooleanHolder(false), []); - } - - override getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]) { + override getTriggerMessage({ pokemon }: AbAttrBaseParams, _abilityName: string) { const currentTerrain = globalScene.arena.getTerrainType(); const pokemonNameWithAffix = getPokemonNameWithAffix(pokemon); if (currentTerrain === TerrainType.NONE) { @@ -7493,7 +6200,7 @@ class ForceSwitchOutHelper { if (player) { const blockedByAbility = new BooleanHolder(false); - applyAbAttrs("ForceSwitchOutImmunityAbAttr", opponent, blockedByAbility); + applyAbAttrs("ForceSwitchOutImmunityAbAttr", { pokemon: opponent, cancelled: blockedByAbility }); return !blockedByAbility.value; } @@ -7531,7 +6238,7 @@ class ForceSwitchOutHelper { */ public getFailedText(target: Pokemon): string | null { const blockedByAbility = new BooleanHolder(false); - applyAbAttrs("ForceSwitchOutImmunityAbAttr", target, blockedByAbility); + applyAbAttrs("ForceSwitchOutImmunityAbAttr", { pokemon: target, cancelled: blockedByAbility }); return blockedByAbility.value ? i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) }) : null; @@ -7556,30 +6263,21 @@ function calculateShellBellRecovery(pokemon: Pokemon): number { return 0; } +export interface PostDamageAbAttrParams extends AbAttrBaseParams { + /** The pokemon that caused the damage; omitted if the damage was not from a pokemon */ + source?: Pokemon; + /** The amount of damage that was dealt */ + readonly damage: number; +} /** * Triggers after the Pokemon takes any damage - * @extends AbAttr */ export class PostDamageAbAttr extends AbAttr { - public canApplyPostDamage( - _pokemon: Pokemon, - _damage: number, - _passive: boolean, - _simulated: boolean, - _args: any[], - _source?: Pokemon, - ): boolean { + public canApplyPostDamage(_params: PostDamageAbAttrParams): boolean { return true; } - public applyPostDamage( - _pokemon: Pokemon, - _damage: number, - _passive: boolean, - _simulated: boolean, - _args: any[], - _source?: Pokemon, - ): void {} + public applyPostDamage(_params: PostDamageAbAttrParams): void {} } /** @@ -7589,8 +6287,8 @@ export class PostDamageAbAttr extends AbAttr { * * Used by Wimp Out and Emergency Exit * - * @extends PostDamageAbAttr * @see {@linkcode applyPostDamage} + * @sealed */ export class PostDamageForceSwitchAbAttr extends PostDamageAbAttr { private helper: ForceSwitchOutHelper = new ForceSwitchOutHelper(SwitchType.SWITCH); @@ -7602,14 +6300,7 @@ export class PostDamageForceSwitchAbAttr extends PostDamageAbAttr { } // TODO: Refactor to use more early returns - public override canApplyPostDamage( - pokemon: Pokemon, - damage: number, - _passive: boolean, - _simulated: boolean, - _args: any[], - source?: Pokemon, - ): boolean { + public override canApply({ pokemon, source, damage }: PostDamageAbAttrParams): boolean { const moveHistory = pokemon.getMoveHistory(); // Will not activate when the Pokémon's HP is lowered by cutting its own HP const fordbiddenAttackingMoves = [MoveId.BELLY_DRUM, MoveId.SUBSTITUTE, MoveId.CURSE, MoveId.PAIN_SPLIT]; @@ -7667,22 +6358,9 @@ export class PostDamageForceSwitchAbAttr extends PostDamageAbAttr { * Applies the switch-out logic after the Pokémon takes damage. * Checks various conditions based on the moves used by the Pokémon, the opponents' moves, and * the Pokémon's health after damage to determine whether the switch-out should occur. - * - * @param pokemon The Pokémon that took damage. - * @param _damage N/A - * @param _passive N/A - * @param _simulated Whether the ability is being simulated. - * @param _args N/A - * @param _source N/A */ - public override applyPostDamage( - pokemon: Pokemon, - _damage: number, - _passive: boolean, - _simulated: boolean, - _args: any[], - _source?: Pokemon, - ): void { + public override apply({ pokemon }: PostDamageAbAttrParams): void { + // TODO: Consider respecting the `simulated` flag here this.helper.switchOutLogic(pokemon); } } @@ -7843,7 +6521,8 @@ const AbilityAttrs = Object.freeze({ PostTurnStatusHealAbAttr, PostTurnResetStatusAbAttr, PostTurnRestoreBerryAbAttr, - RepeatBerryNextTurnAbAttr, + CudChewConsumeBerryAbAttr, + CudChewRecordBerryAbAttr, MoodyAbAttr, SpeedBoostAbAttr, PostTurnHealAbAttr, @@ -8960,7 +7639,8 @@ export function initAbilities() { new Ability(AbilityId.OPPORTUNIST, 9) .attr(StatStageChangeCopyAbAttr), new Ability(AbilityId.CUD_CHEW, 9) - .attr(RepeatBerryNextTurnAbAttr), + .attr(CudChewConsumeBerryAbAttr) + .attr(CudChewRecordBerryAbAttr), new Ability(AbilityId.SHARPNESS, 9) .attr(MovePowerBoostAbAttr, (_user, _target, move) => move.hasFlag(MoveFlags.SLICING_MOVE), 1.5), new Ability(AbilityId.SUPREME_OVERLORD, 9) diff --git a/src/data/abilities/apply-ab-attrs.ts b/src/data/abilities/apply-ab-attrs.ts index fdbd2652698..355356ac431 100644 --- a/src/data/abilities/apply-ab-attrs.ts +++ b/src/data/abilities/apply-ab-attrs.ts @@ -1,63 +1,13 @@ -import type { AbAttrApplyFunc, AbAttrMap, AbAttrString, AbAttrSuccessFunc } from "#app/@types/ability-types"; -import type Pokemon from "#app/field/pokemon"; +import type { AbAttrBaseParams, AbAttrMap, CallableAbAttrString } from "#app/@types/ability-types"; import { globalScene } from "#app/global-scene"; -import type { BooleanHolder, NumberHolder } from "#app/utils/common"; -import type { BattlerIndex } from "#enums/battler-index"; -import type { HitResult } from "#enums/hit-result"; -import type { BattleStat, Stat } from "#enums/stat"; -import type { StatusEffect } from "#enums/status-effect"; -import type { WeatherType } from "#enums/weather-type"; -import type { BattlerTag } from "../battler-tags"; -import type Move from "../moves/move"; -import type { PokemonMove } from "../moves/pokemon-move"; -import type { TerrainType } from "../terrain"; -import type { Weather } from "../weather"; -import type { - PostBattleInitAbAttr, - PreDefendAbAttr, - PostDefendAbAttr, - PostMoveUsedAbAttr, - StatMultiplierAbAttr, - AllyStatMultiplierAbAttr, - PostSetStatusAbAttr, - PostDamageAbAttr, - FieldMultiplyStatAbAttr, - PreAttackAbAttr, - ExecutedMoveAbAttr, - PostAttackAbAttr, - PostKnockOutAbAttr, - PostVictoryAbAttr, - PostSummonAbAttr, - PreSummonAbAttr, - PreSwitchOutAbAttr, - PreLeaveFieldAbAttr, - PreStatStageChangeAbAttr, - PostStatStageChangeAbAttr, - PreSetStatusAbAttr, - PreApplyBattlerTagAbAttr, - PreWeatherEffectAbAttr, - PreWeatherDamageAbAttr, - PostTurnAbAttr, - PostWeatherChangeAbAttr, - PostWeatherLapseAbAttr, - PostTerrainChangeAbAttr, - CheckTrappedAbAttr, - PostBattleAbAttr, - PostFaintAbAttr, - PostItemLostAbAttr, -} from "./ability"; -function applySingleAbAttrs( - pokemon: Pokemon, - passive: boolean, +function applySingleAbAttrs( attrType: T, - applyFunc: AbAttrApplyFunc, - successFunc: AbAttrSuccessFunc, - args: any[], + params: Parameters[0], gainedMidTurn = false, - simulated = false, messages: string[] = [], ) { + const { simulated = false, passive = false, pokemon } = params; if (!pokemon?.canApplyAbility(passive) || (passive && pokemon.getPassiveAbility().id === pokemon.getAbility().id)) { return; } @@ -73,9 +23,10 @@ function applySingleAbAttrs( } for (const attr of ability.getAttrs(attrType)) { + declare const attr: AbAttrMap[T]; const condition = attr.getCondition(); let abShown = false; - if ((condition && !condition(pokemon)) || !successFunc(attr, passive)) { + if ((condition && !condition(pokemon)) || !attr.canApply(params)) { continue; } @@ -85,7 +36,7 @@ function applySingleAbAttrs( globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true); abShown = true; } - const message = attr.getTriggerMessage(pokemon, ability.name, args); + const message = attr.getTriggerMessage(pokemon, ability.name, params); if (message) { if (!simulated) { globalScene.phaseManager.queueMessage(message); @@ -93,7 +44,7 @@ function applySingleAbAttrs( messages.push(message); } - applyFunc(attr, passive); + attr.apply(params); if (abShown) { globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false); @@ -107,717 +58,49 @@ function applySingleAbAttrs( } } -function applyAbAttrsInternal( +function applyAbAttrsInternal( attrType: T, - pokemon: Pokemon | null, - applyFunc: AbAttrApplyFunc, - successFunc: AbAttrSuccessFunc, - args: any[], - simulated = false, + params: Parameters[0], messages: string[] = [], gainedMidTurn = false, ) { + const { pokemon } = params; for (const passive of [false, true]) { if (pokemon) { - applySingleAbAttrs(pokemon, passive, attrType, applyFunc, successFunc, args, gainedMidTurn, simulated, messages); + applySingleAbAttrs(attrType, { ...params, passive }, gainedMidTurn, messages); globalScene.phaseManager.clearPhaseQueueSplice(); } } } -export function applyAbAttrs( +/** + * @param attrType - The type of the ability attribute to apply + * @param params - The parameters to pass to the ability attribute's apply method + */ +export function applyAbAttrs( attrType: T, - pokemon: Pokemon, - cancelled: BooleanHolder | null, - simulated = false, - ...args: any[] + params: Parameters[0], ): void { - applyAbAttrsInternal( - attrType, - pokemon, - // @ts-expect-error: TODO: fix the error on `cancelled` - (attr, passive) => attr.apply(pokemon, passive, simulated, cancelled, args), - (attr, passive) => attr.canApply(pokemon, passive, simulated, args), - args, - simulated, - ); + applyAbAttrsInternal(attrType, params); } + // TODO: Improve the type signatures of the following methods / refactor the apply methods -export function applyPostBattleInitAbAttrs( - attrType: AbAttrMap[K] extends PostBattleInitAbAttr ? K : never, - pokemon: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => (attr as PostBattleInitAbAttr).applyPostBattleInit(pokemon, passive, simulated, args), - (attr, passive) => (attr as PostBattleInitAbAttr).canApplyPostBattleInit(pokemon, passive, simulated, args), - args, - simulated, - ); -} - -export function applyPreDefendAbAttrs( - attrType: AbAttrMap[K] extends PreDefendAbAttr ? K : never, - pokemon: Pokemon, - attacker: Pokemon, - move: Move | null, - cancelled: BooleanHolder | null, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => - (attr as PreDefendAbAttr).applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args), - (attr, passive) => - (attr as PreDefendAbAttr).canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args), - args, - simulated, - ); -} - -export function applyPostDefendAbAttrs( - attrType: AbAttrMap[K] extends PostDefendAbAttr ? K : never, - pokemon: Pokemon, - attacker: Pokemon, - move: Move, - hitResult: HitResult | null, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => - (attr as PostDefendAbAttr).applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args), - (attr, passive) => - (attr as PostDefendAbAttr).canApplyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args), - args, - simulated, - ); -} - -export function applyPostMoveUsedAbAttrs( - attrType: AbAttrMap[K] extends PostMoveUsedAbAttr ? K : never, - pokemon: Pokemon, - move: PokemonMove, - source: Pokemon, - targets: BattlerIndex[], - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, _passive) => (attr as PostMoveUsedAbAttr).applyPostMoveUsed(pokemon, move, source, targets, simulated, args), - (attr, _passive) => - (attr as PostMoveUsedAbAttr).canApplyPostMoveUsed(pokemon, move, source, targets, simulated, args), - args, - simulated, - ); -} - -export function applyStatMultiplierAbAttrs( - attrType: AbAttrMap[K] extends StatMultiplierAbAttr ? K : never, - pokemon: Pokemon, - stat: BattleStat, - statValue: NumberHolder, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => - (attr as StatMultiplierAbAttr).applyStatStage(pokemon, passive, simulated, stat, statValue, args), - (attr, passive) => - (attr as StatMultiplierAbAttr).canApplyStatStage(pokemon, passive, simulated, stat, statValue, args), - args, - ); -} - -/** - * Applies an ally's Stat multiplier attribute - * @param attrType - {@linkcode AllyStatMultiplierAbAttr} should always be AllyStatMultiplierAbAttr for the time being - * @param pokemon - The {@linkcode Pokemon} with the ability - * @param stat - The type of the checked {@linkcode Stat} - * @param statValue - {@linkcode NumberHolder} containing the value of the checked stat - * @param checkedPokemon - The {@linkcode Pokemon} with the checked stat - * @param ignoreAbility - Whether or not the ability should be ignored by the pokemon or its move. - * @param args - unused - */ -export function applyAllyStatMultiplierAbAttrs( - attrType: AbAttrMap[K] extends AllyStatMultiplierAbAttr ? K : never, - pokemon: Pokemon, - stat: BattleStat, - statValue: NumberHolder, - simulated = false, - checkedPokemon: Pokemon, - ignoreAbility: boolean, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => - (attr as AllyStatMultiplierAbAttr).applyAllyStat( - pokemon, - passive, - simulated, - stat, - statValue, - checkedPokemon, - ignoreAbility, - args, - ), - (attr, passive) => - (attr as AllyStatMultiplierAbAttr).canApplyAllyStat( - pokemon, - passive, - simulated, - stat, - statValue, - checkedPokemon, - ignoreAbility, - args, - ), - args, - simulated, - ); -} - -export function applyPostSetStatusAbAttrs( - attrType: AbAttrMap[K] extends PostSetStatusAbAttr ? K : never, - pokemon: Pokemon, - effect: StatusEffect, - sourcePokemon?: Pokemon | null, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => - (attr as PostSetStatusAbAttr).applyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args), - (attr, passive) => - (attr as PostSetStatusAbAttr).canApplyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args), - args, - simulated, - ); -} - -export function applyPostDamageAbAttrs( - attrType: AbAttrMap[K] extends PostDamageAbAttr ? K : never, - pokemon: Pokemon, - damage: number, - _passive: boolean, - simulated = false, - args: any[], - source?: Pokemon, -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => (attr as PostDamageAbAttr).applyPostDamage(pokemon, damage, passive, simulated, args, source), - (attr, passive) => (attr as PostDamageAbAttr).canApplyPostDamage(pokemon, damage, passive, simulated, args, source), - args, - ); -} -/** - * Applies a field Stat multiplier attribute - * @param attrType {@linkcode FieldMultiplyStatAbAttr} should always be FieldMultiplyBattleStatAbAttr for the time being - * @param pokemon {@linkcode Pokemon} the Pokemon applying this ability - * @param stat {@linkcode Stat} the type of the checked stat - * @param statValue {@linkcode NumberHolder} the value of the checked stat - * @param checkedPokemon {@linkcode Pokemon} the Pokemon with the checked stat - * @param hasApplied {@linkcode BooleanHolder} whether or not a FieldMultiplyBattleStatAbAttr has already affected this stat - * @param args unused - */ - -export function applyFieldStatMultiplierAbAttrs( - attrType: AbAttrMap[K] extends FieldMultiplyStatAbAttr ? K : never, - pokemon: Pokemon, - stat: Stat, - statValue: NumberHolder, - checkedPokemon: Pokemon, - hasApplied: BooleanHolder, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => - (attr as FieldMultiplyStatAbAttr).applyFieldStat( - pokemon, - passive, - simulated, - stat, - statValue, - checkedPokemon, - hasApplied, - args, - ), - (attr, passive) => - (attr as FieldMultiplyStatAbAttr).canApplyFieldStat( - pokemon, - passive, - simulated, - stat, - statValue, - checkedPokemon, - hasApplied, - args, - ), - args, - ); -} - -export function applyPreAttackAbAttrs( - attrType: AbAttrMap[K] extends PreAttackAbAttr ? K : never, - pokemon: Pokemon, - defender: Pokemon | null, - move: Move, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => (attr as PreAttackAbAttr).applyPreAttack(pokemon, passive, simulated, defender, move, args), - (attr, passive) => (attr as PreAttackAbAttr).canApplyPreAttack(pokemon, passive, simulated, defender, move, args), - args, - simulated, - ); -} - -export function applyExecutedMoveAbAttrs( - attrType: AbAttrMap[K] extends ExecutedMoveAbAttr ? K : never, - pokemon: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - attr => (attr as ExecutedMoveAbAttr).applyExecutedMove(pokemon, simulated), - attr => (attr as ExecutedMoveAbAttr).canApplyExecutedMove(pokemon, simulated), - args, - simulated, - ); -} - -export function applyPostAttackAbAttrs( - attrType: AbAttrMap[K] extends PostAttackAbAttr ? K : never, - pokemon: Pokemon, - defender: Pokemon, - move: Move, - hitResult: HitResult | null, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => - (attr as PostAttackAbAttr).applyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args), - (attr, passive) => - (attr as PostAttackAbAttr).canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args), - args, - simulated, - ); -} - -export function applyPostKnockOutAbAttrs( - attrType: AbAttrMap[K] extends PostKnockOutAbAttr ? K : never, - pokemon: Pokemon, - knockedOut: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => (attr as PostKnockOutAbAttr).applyPostKnockOut(pokemon, passive, simulated, knockedOut, args), - (attr, passive) => (attr as PostKnockOutAbAttr).canApplyPostKnockOut(pokemon, passive, simulated, knockedOut, args), - args, - simulated, - ); -} - -export function applyPostVictoryAbAttrs( - attrType: AbAttrMap[K] extends PostVictoryAbAttr ? K : never, - pokemon: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => (attr as PostVictoryAbAttr).applyPostVictory(pokemon, passive, simulated, args), - (attr, passive) => (attr as PostVictoryAbAttr).canApplyPostVictory(pokemon, passive, simulated, args), - args, - simulated, - ); -} - -export function applyPostSummonAbAttrs( - attrType: AbAttrMap[K] extends PostSummonAbAttr ? K : never, - pokemon: Pokemon, - passive = false, - simulated = false, - ...args: any[] -): void { - applySingleAbAttrs( - pokemon, - passive, - attrType, - (attr, passive) => (attr as PostSummonAbAttr).applyPostSummon(pokemon, passive, simulated, args), - (attr, passive) => (attr as PostSummonAbAttr).canApplyPostSummon(pokemon, passive, simulated, args), - args, - false, - simulated, - ); -} - -export function applyPreSummonAbAttrs( - attrType: AbAttrMap[K] extends PreSummonAbAttr ? K : never, - pokemon: Pokemon, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => (attr as PreSummonAbAttr).applyPreSummon(pokemon, passive, args), - (attr, passive) => (attr as PreSummonAbAttr).canApplyPreSummon(pokemon, passive, args), - args, - ); -} - -export function applyPreSwitchOutAbAttrs( - attrType: AbAttrMap[K] extends PreSwitchOutAbAttr ? K : never, - pokemon: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => (attr as PreSwitchOutAbAttr).applyPreSwitchOut(pokemon, passive, simulated, args), - (attr, passive) => (attr as PreSwitchOutAbAttr).canApplyPreSwitchOut(pokemon, passive, simulated, args), - args, - simulated, - ); -} - -export function applyPreLeaveFieldAbAttrs( - attrType: AbAttrMap[K] extends PreLeaveFieldAbAttr ? K : never, - pokemon: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => (attr as PreLeaveFieldAbAttr).applyPreLeaveField(pokemon, passive, simulated, args), - (attr, passive) => (attr as PreLeaveFieldAbAttr).canApplyPreLeaveField(pokemon, passive, simulated, args), - args, - simulated, - ); -} - -export function applyPreStatStageChangeAbAttrs( - attrType: AbAttrMap[K] extends PreStatStageChangeAbAttr ? K : never, - pokemon: Pokemon | null, - stat: BattleStat, - cancelled: BooleanHolder, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => - (attr as PreStatStageChangeAbAttr).applyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args), - (attr, passive) => - (attr as PreStatStageChangeAbAttr).canApplyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args), - args, - simulated, - ); -} - -export function applyPostStatStageChangeAbAttrs( - attrType: AbAttrMap[K] extends PostStatStageChangeAbAttr ? K : never, - pokemon: Pokemon, - stats: BattleStat[], - stages: number, - selfTarget: boolean, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, _passive) => - (attr as PostStatStageChangeAbAttr).applyPostStatStageChange(pokemon, simulated, stats, stages, selfTarget, args), - (attr, _passive) => - (attr as PostStatStageChangeAbAttr).canApplyPostStatStageChange( - pokemon, - simulated, - stats, - stages, - selfTarget, - args, - ), - args, - simulated, - ); -} - -export function applyPreSetStatusAbAttrs( - attrType: AbAttrMap[K] extends PreSetStatusAbAttr ? K : never, - pokemon: Pokemon, - effect: StatusEffect | undefined, - cancelled: BooleanHolder, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => - (attr as PreSetStatusAbAttr).applyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args), - (attr, passive) => - (attr as PreSetStatusAbAttr).canApplyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args), - args, - simulated, - ); -} - -export function applyPreApplyBattlerTagAbAttrs( - attrType: AbAttrMap[K] extends PreApplyBattlerTagAbAttr ? K : never, - pokemon: Pokemon, - tag: BattlerTag, - cancelled: BooleanHolder, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => - (attr as PreApplyBattlerTagAbAttr).applyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args), - (attr, passive) => - (attr as PreApplyBattlerTagAbAttr).canApplyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args), - args, - simulated, - ); -} - -export function applyPreWeatherEffectAbAttrs( - attrType: AbAttrMap[K] extends PreWeatherEffectAbAttr ? K : never, - pokemon: Pokemon, - weather: Weather | null, - cancelled: BooleanHolder, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => - (attr as PreWeatherDamageAbAttr).applyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args), - (attr, passive) => - (attr as PreWeatherDamageAbAttr).canApplyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args), - args, - simulated, - ); -} - -export function applyPostTurnAbAttrs( - attrType: AbAttrMap[K] extends PostTurnAbAttr ? K : never, - pokemon: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => (attr as PostTurnAbAttr).applyPostTurn(pokemon, passive, simulated, args), - (attr, passive) => (attr as PostTurnAbAttr).canApplyPostTurn(pokemon, passive, simulated, args), - args, - simulated, - ); -} - -export function applyPostWeatherChangeAbAttrs( - attrType: AbAttrMap[K] extends PostWeatherChangeAbAttr ? K : never, - pokemon: Pokemon, - weather: WeatherType, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => - (attr as PostWeatherChangeAbAttr).applyPostWeatherChange(pokemon, passive, simulated, weather, args), - (attr, passive) => - (attr as PostWeatherChangeAbAttr).canApplyPostWeatherChange(pokemon, passive, simulated, weather, args), - args, - simulated, - ); -} - -export function applyPostWeatherLapseAbAttrs( - attrType: AbAttrMap[K] extends PostWeatherLapseAbAttr ? K : never, - pokemon: Pokemon, - weather: Weather | null, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => - (attr as PostWeatherLapseAbAttr).applyPostWeatherLapse(pokemon, passive, simulated, weather, args), - (attr, passive) => - (attr as PostWeatherLapseAbAttr).canApplyPostWeatherLapse(pokemon, passive, simulated, weather, args), - args, - simulated, - ); -} - -export function applyPostTerrainChangeAbAttrs( - attrType: AbAttrMap[K] extends PostTerrainChangeAbAttr ? K : never, - pokemon: Pokemon, - terrain: TerrainType, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => - (attr as PostTerrainChangeAbAttr).applyPostTerrainChange(pokemon, passive, simulated, terrain, args), - (attr, passive) => - (attr as PostTerrainChangeAbAttr).canApplyPostTerrainChange(pokemon, passive, simulated, terrain, args), - args, - simulated, - ); -} - -export function applyCheckTrappedAbAttrs( - attrType: AbAttrMap[K] extends CheckTrappedAbAttr ? K : never, - pokemon: Pokemon, - trapped: BooleanHolder, - otherPokemon: Pokemon, - messages: string[], - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => - (attr as CheckTrappedAbAttr).applyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args), - (attr, passive) => - (attr as CheckTrappedAbAttr).canApplyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args), - args, - simulated, - messages, - ); -} - -export function applyPostBattleAbAttrs( - attrType: AbAttrMap[K] extends PostBattleAbAttr ? K : never, - pokemon: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => (attr as PostBattleAbAttr).applyPostBattle(pokemon, passive, simulated, args), - (attr, passive) => (attr as PostBattleAbAttr).canApplyPostBattle(pokemon, passive, simulated, args), - args, - simulated, - ); -} - -export function applyPostFaintAbAttrs( - attrType: AbAttrMap[K] extends PostFaintAbAttr ? K : never, - pokemon: Pokemon, - attacker?: Pokemon, - move?: Move, - hitResult?: HitResult, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, passive) => - (attr as PostFaintAbAttr).applyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args), - (attr, passive) => - (attr as PostFaintAbAttr).canApplyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args), - args, - simulated, - ); -} - -export function applyPostItemLostAbAttrs( - attrType: AbAttrMap[K] extends PostItemLostAbAttr ? K : never, - pokemon: Pokemon, - simulated = false, - ...args: any[] -): void { - applyAbAttrsInternal( - attrType, - pokemon, - (attr, _passive) => (attr as PostItemLostAbAttr).applyPostItemLost(pokemon, simulated, args), - (attr, _passive) => (attr as PostItemLostAbAttr).canApplyPostItemLost(pokemon, simulated, args), - args, - ); -} - /** * Applies abilities when they become active mid-turn (ability switch) * * Ignores passives as they don't change and shouldn't be reapplied when main abilities change */ -export function applyOnGainAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void { - applySingleAbAttrs( - pokemon, - passive, - "PostSummonAbAttr", - (attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args), - (attr, passive) => attr.canApplyPostSummon(pokemon, passive, simulated, args), - args, - true, - simulated, - ); +export function applyOnGainAbAttrs(params: AbAttrBaseParams): void { + applySingleAbAttrs("PostSummonAbAttr", params, true); } + /** * Applies ability attributes which activate when the ability is lost or suppressed (i.e. primal weather) */ -export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void { - applySingleAbAttrs( - pokemon, - passive, - "PreLeaveFieldAbAttr", - (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [...args, true]), - (attr, passive) => attr.canApplyPreLeaveField(pokemon, passive, simulated, [...args, true]), - args, - true, - simulated, - ); +export function applyOnLoseAbAttrs(params): void { + applySingleAbAttrs("PreLeaveFieldAbAttr"); applySingleAbAttrs( pokemon, diff --git a/src/data/berry.ts b/src/data/berry.ts index 7d1e62362a8..69853547624 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -38,7 +38,7 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate { const threshold = new NumberHolder(0.25); // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth const stat: BattleStat = berryType - BerryType.ENIGMA; - applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold); + applyAbAttrs("ReduceBerryUseThresholdAbAttr", { pokemon, stat}); return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6; }; case BerryType.LANSAT: @@ -72,7 +72,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { case BerryType.ENIGMA: { const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4)); - applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, hpHealed); + applyAbAttrs("DoubleBerryEffectAbAttr", { pokemon: consumer, effectValue: hpHealed, cancelled: null}); globalScene.phaseManager.unshiftNew( "PokemonHealPhase", consumer.getBattlerIndex(), diff --git a/src/field/arena.ts b/src/field/arena.ts index 8d7e5037852..2bb00bd00f7 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -20,11 +20,7 @@ import { ArenaTrapTag, getArenaTag } from "#app/data/arena-tag"; import { ArenaTagSide } from "#enums/arena-tag-side"; import type { BattlerIndex } from "#enums/battler-index"; import { Terrain, TerrainType } from "#app/data/terrain"; -import { - applyAbAttrs, - applyPostTerrainChangeAbAttrs, - applyPostWeatherChangeAbAttrs, -} from "#app/data/abilities/apply-ab-attrs"; +import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import type Pokemon from "#app/field/pokemon"; import Overrides from "#app/overrides"; import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena"; @@ -372,7 +368,7 @@ export class Arena { pokemon.findAndRemoveTags( t => "weatherTypes" in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather), ); - applyPostWeatherChangeAbAttrs("PostWeatherChangeAbAttr", pokemon, weather); + applyAbAttrs("PostWeatherChangeAbAttr", {pokemon, weather}); }); return true; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index e9cc4f70d70..e2a498ebb46 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -4046,7 +4046,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const stubTag = new BattlerTag(tagType, 0, 0); const cancelled = new BooleanHolder(false); - applyPreApplyBattlerTagAbAttrs("BattlerTagImmunityAbAttr", this, stubTag, cancelled, true); + applyAbAttrs("BattlerTagImmunityAbAttr", this, stubTag, cancelled, true, this); const userField = this.getAlliedField(); userField.forEach(pokemon => diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index c126f3306b9..3348fa3902f 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -20,7 +20,7 @@ export class BerryPhase extends FieldPhase { this.executeForAll(pokemon => { this.eatBerries(pokemon); - applyAbAttrs("RepeatBerryNextTurnAbAttr", pokemon, null); + applyAbAttrs("CudChewConsumeBerryAbAttr", { pokemon }); }); this.end();