Update callsites in pokemon.ts

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

View File

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

View File

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

View File

@ -22,3 +22,11 @@ export type Exact<T> = {
*
*/
export type Closed<X> = X;
/**
* Remove `readonly` from all properties of the provided type
* @typeParam T - The type to make mutable
*/
export type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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