Add abilityAttr.is methods

This commit is contained in:
Sirz Benjie 2025-06-09 19:08:17 -05:00
parent f499ea0568
commit 7beb5446e7
No known key found for this signature in database
GPG Key ID: 38AC42D68CF5E138
50 changed files with 1593 additions and 1335 deletions

View File

@ -1,11 +1,27 @@
import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr"; import type { AbAttr } from "#app/data/abilities/ability";
import type Move from "#app/data/moves/move"; import type Move from "#app/data/moves/move";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import type { BattleStat } from "#enums/stat"; import type { BattleStat } from "#enums/stat";
import type { AbAttrConstructorMap } from "#app/data/abilities/ability";
export type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => void; // Intentionally re-export all types from the ability attributes module
export type AbAttrSuccessFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => boolean; export type * from "#app/data/abilities/ability";
export type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean, ...args: any[]) => void;
export type AbAttrSuccessFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean, ...args: any[]) => boolean;
export type AbAttrCondition = (pokemon: Pokemon) => boolean; export type AbAttrCondition = (pokemon: Pokemon) => boolean;
export type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean; export type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
export type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean; export type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
export type PokemonStatStageChangeCondition = (target: Pokemon, statsChanged: BattleStat[], stages: number) => boolean; export type PokemonStatStageChangeCondition = (target: Pokemon, statsChanged: BattleStat[], stages: number) => boolean;
/**
* Union type of all ability attribute class names as strings
*/
export type AbAttrString = keyof AbAttrConstructorMap;
/**
* Map of ability attribute class names to an instance of the class.
*/
export type AbAttrMap = {
[K in keyof AbAttrConstructorMap]: InstanceType<AbAttrConstructorMap[K]>;
};

View File

@ -66,15 +66,7 @@ import {
PokemonHeldItemModifierType, PokemonHeldItemModifierType,
} from "#app/modifier/modifier-type"; } from "#app/modifier/modifier-type";
import AbilityBar from "#app/ui/ability-bar"; import AbilityBar from "#app/ui/ability-bar";
import { import { applyAbAttrs, applyPostBattleInitAbAttrs, applyPostItemLostAbAttrs } from "./data/abilities/apply-ab-attrs";
applyAbAttrs,
applyPostBattleInitAbAttrs,
applyPostItemLostAbAttrs,
BlockItemTheftAbAttr,
DoubleBattleChanceAbAttr,
PostBattleInitAbAttr,
PostItemLostAbAttr,
} from "#app/data/abilities/ability";
import { allAbilities } from "./data/data-lists"; import { allAbilities } from "./data/data-lists";
import type { FixedBattleConfig } from "#app/battle"; import type { FixedBattleConfig } from "#app/battle";
import Battle from "#app/battle"; import Battle from "#app/battle";
@ -1264,7 +1256,7 @@ export default class BattleScene extends SceneBase {
const doubleChance = new NumberHolder(newWaveIndex % 10 === 0 ? 32 : 8); const doubleChance = new NumberHolder(newWaveIndex % 10 === 0 ? 32 : 8);
this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance); this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance);
for (const p of playerField) { for (const p of playerField) {
applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance); applyAbAttrs("DoubleBattleChanceAbAttr", p, null, false, doubleChance);
} }
return Math.max(doubleChance.value, 1); return Math.max(doubleChance.value, 1);
} }
@ -1469,7 +1461,7 @@ export default class BattleScene extends SceneBase {
for (const pokemon of this.getPlayerParty()) { for (const pokemon of this.getPlayerParty()) {
pokemon.resetBattleAndWaveData(); pokemon.resetBattleAndWaveData();
pokemon.resetTera(); pokemon.resetTera();
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon); applyPostBattleInitAbAttrs("PostBattleInitAbAttr", pokemon);
if ( if (
pokemon.hasSpecies(SpeciesId.TERAPAGOS) || pokemon.hasSpecies(SpeciesId.TERAPAGOS) ||
(this.gameMode.isClassic && this.currentBattle.waveIndex > 180 && this.currentBattle.waveIndex <= 190) (this.gameMode.isClassic && this.currentBattle.waveIndex > 180 && this.currentBattle.waveIndex <= 190)
@ -2745,7 +2737,7 @@ export default class BattleScene extends SceneBase {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
if (source && source.isPlayer() !== target.isPlayer()) { if (source && source.isPlayer() !== target.isPlayer()) {
applyAbAttrs(BlockItemTheftAbAttr, source, cancelled); applyAbAttrs("BlockItemTheftAbAttr", source, cancelled);
} }
if (cancelled.value) { if (cancelled.value) {
@ -2785,13 +2777,13 @@ export default class BattleScene extends SceneBase {
if (target.isPlayer()) { if (target.isPlayer()) {
this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant); this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant);
if (source && itemLost) { if (source && itemLost) {
applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); applyPostItemLostAbAttrs("PostItemLostAbAttr", source, false);
} }
return true; return true;
} }
this.addEnemyModifier(newItemModifier, ignoreUpdate, instant); this.addEnemyModifier(newItemModifier, ignoreUpdate, instant);
if (source && itemLost) { if (source && itemLost) {
applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); applyPostItemLostAbAttrs("PostItemLostAbAttr", source, false);
} }
return true; return true;
} }
@ -2814,7 +2806,7 @@ export default class BattleScene extends SceneBase {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
if (source && source.isPlayer() !== target.isPlayer()) { if (source && source.isPlayer() !== target.isPlayer()) {
applyAbAttrs(BlockItemTheftAbAttr, source, cancelled); applyAbAttrs("BlockItemTheftAbAttr", source, cancelled);
} }
if (cancelled.value) { if (cancelled.value) {

View File

@ -1,58 +0,0 @@
import type { AbAttrCondition } from "#app/@types/ability-types";
import type Pokemon from "#app/field/pokemon";
import type { BooleanHolder } from "#app/utils/common";
export abstract class AbAttr {
public showAbility: boolean;
private extraCondition: AbAttrCondition;
/**
* @param showAbility - Whether to show this ability as a flyout during battle; default `true`.
* Should be kept in parity with mainline where possible.
*/
constructor(showAbility = true) {
this.showAbility = showAbility;
}
/**
* 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(
_pokemon: Pokemon,
_passive: boolean,
_simulated: boolean,
_cancelled: BooleanHolder | null,
_args: any[],
): void {}
getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null {
return null;
}
getCondition(): AbAttrCondition | null {
return this.extraCondition || null;
}
addCondition(condition: AbAttrCondition): 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;
}
}

View File

@ -1,137 +0,0 @@
import { AbilityId } from "#enums/ability-id";
import type { AbAttrCondition } from "#app/@types/ability-types";
import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
import i18next from "i18next";
import type { Localizable } from "#app/@types/locales";
import type { Constructor } from "#app/utils/common";
export class Ability implements Localizable {
public id: AbilityId;
private nameAppend: string;
public name: string;
public description: string;
public generation: number;
public isBypassFaint: boolean;
public isIgnorable: boolean;
public isSuppressable = true;
public isCopiable = true;
public isReplaceable = true;
public attrs: AbAttr[];
public conditions: AbAttrCondition[];
constructor(id: AbilityId, generation: number) {
this.id = id;
this.nameAppend = "";
this.generation = generation;
this.attrs = [];
this.conditions = [];
this.isSuppressable = true;
this.isCopiable = true;
this.isReplaceable = true;
this.localize();
}
public get isSwappable(): boolean {
return this.isCopiable && this.isReplaceable;
}
localize(): void {
const i18nKey = AbilityId[this.id]
.split("_")
.filter(f => f)
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.join("") as string;
this.name = this.id ? `${i18next.t(`ability:${i18nKey}.name`) as string}${this.nameAppend}` : "";
this.description = this.id ? (i18next.t(`ability:${i18nKey}.description`) as string) : "";
}
/**
* Get all ability attributes that match `attrType`
* @param attrType any attribute that extends {@linkcode AbAttr}
* @returns Array of attributes that match `attrType`, Empty Array if none match.
*/
getAttrs<T extends AbAttr>(attrType: Constructor<T>): T[] {
return this.attrs.filter((a): a is T => a instanceof attrType);
}
/**
* Check if an ability has an attribute that matches `attrType`
* @param attrType any attribute that extends {@linkcode AbAttr}
* @returns true if the ability has attribute `attrType`
*/
hasAttr<T extends AbAttr>(attrType: Constructor<T>): boolean {
return this.attrs.some(attr => attr instanceof attrType);
}
attr<T extends Constructor<AbAttr>>(AttrType: T, ...args: ConstructorParameters<T>): Ability {
const attr = new AttrType(...args);
this.attrs.push(attr);
return this;
}
conditionalAttr<T extends Constructor<AbAttr>>(
condition: AbAttrCondition,
AttrType: T,
...args: ConstructorParameters<T>
): Ability {
const attr = new AttrType(...args);
attr.addCondition(condition);
this.attrs.push(attr);
return this;
}
bypassFaint(): Ability {
this.isBypassFaint = true;
return this;
}
ignorable(): Ability {
this.isIgnorable = true;
return this;
}
unsuppressable(): Ability {
this.isSuppressable = false;
return this;
}
uncopiable(): Ability {
this.isCopiable = false;
return this;
}
unreplaceable(): Ability {
this.isReplaceable = false;
return this;
}
condition(condition: AbAttrCondition): Ability {
this.conditions.push(condition);
return this;
}
partial(): this {
this.nameAppend += " (P)";
return this;
}
unimplemented(): this {
this.nameAppend += " (N)";
return this;
}
/**
* Internal flag used for developers to document edge cases. When using this, please be sure to document the edge case.
* @returns the ability
*/
edgeCase(): this {
return this;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,829 @@
import type { AbAttrApplyFunc, AbAttrMap, AbAttrString, AbAttrSuccessFunc } from "#app/@types/ability-types";
import type Pokemon from "#app/field/pokemon";
import { globalScene } from "#app/global-scene";
import type { BooleanHolder, NumberHolder } from "#app/utils/common";
import type { BattlerIndex } from "#enums/battler-index";
import type { HitResult } from "#enums/hit-result";
import type { BattleStat, Stat } from "#enums/stat";
import type { StatusEffect } from "#enums/status-effect";
import type { WeatherType } from "#enums/weather-type";
import type { BattlerTag } from "../battler-tags";
import type Move from "../moves/move";
import type { PokemonMove } from "../moves/pokemon-move";
import type { TerrainType } from "../terrain";
import type { Weather } from "../weather";
import type {
PostBattleInitAbAttr,
PreDefendAbAttr,
PostDefendAbAttr,
PostMoveUsedAbAttr,
StatMultiplierAbAttr,
AllyStatMultiplierAbAttr,
PostSetStatusAbAttr,
PostDamageAbAttr,
FieldMultiplyStatAbAttr,
PreAttackAbAttr,
ExecutedMoveAbAttr,
PostAttackAbAttr,
PostKnockOutAbAttr,
PostVictoryAbAttr,
PostSummonAbAttr,
PreSummonAbAttr,
PreSwitchOutAbAttr,
PreLeaveFieldAbAttr,
PreStatStageChangeAbAttr,
PostStatStageChangeAbAttr,
PreSetStatusAbAttr,
PreApplyBattlerTagAbAttr,
PreWeatherEffectAbAttr,
PreWeatherDamageAbAttr,
PostTurnAbAttr,
PostWeatherChangeAbAttr,
PostWeatherLapseAbAttr,
PostTerrainChangeAbAttr,
CheckTrappedAbAttr,
PostBattleAbAttr,
PostFaintAbAttr,
PostItemLostAbAttr,
} from "./ability";
function applySingleAbAttrs<T extends AbAttrString>(
pokemon: Pokemon,
passive: boolean,
attrType: T,
applyFunc: AbAttrApplyFunc<AbAttrMap[T]>,
successFunc: AbAttrSuccessFunc<AbAttrMap[T]>,
args: any[],
gainedMidTurn = false,
simulated = false,
messages: string[] = [],
) {
if (!pokemon?.canApplyAbility(passive) || (passive && pokemon.getPassiveAbility().id === pokemon.getAbility().id)) {
return;
}
const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility();
if (
gainedMidTurn &&
ability.getAttrs(attrType).some(attr => {
attr.is("PostSummonAbAttr") && !attr.shouldActivateOnGain();
})
) {
return;
}
for (const attr of ability.getAttrs(attrType)) {
const condition = attr.getCondition();
let abShown = false;
if ((condition && !condition(pokemon)) || !successFunc(attr, passive)) {
continue;
}
globalScene.phaseManager.setPhaseQueueSplice();
if (attr.showAbility && !simulated) {
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, true);
abShown = true;
}
const message = attr.getTriggerMessage(pokemon, ability.name, args);
if (message) {
if (!simulated) {
globalScene.phaseManager.queueMessage(message);
}
messages.push(message);
}
applyFunc(attr, passive);
if (abShown) {
globalScene.phaseManager.queueAbilityDisplay(pokemon, passive, false);
}
if (!simulated) {
pokemon.waveData.abilitiesApplied.add(ability.id);
}
globalScene.phaseManager.clearPhaseQueueSplice();
}
}
function applyAbAttrsInternal<T extends AbAttrString>(
attrType: T,
pokemon: Pokemon | null,
applyFunc: AbAttrApplyFunc<AbAttrMap[T]>,
successFunc: AbAttrSuccessFunc<AbAttrMap[T]>,
args: any[],
simulated = false,
messages: string[] = [],
gainedMidTurn = false,
) {
for (const passive of [false, true]) {
if (pokemon) {
applySingleAbAttrs(pokemon, passive, attrType, applyFunc, successFunc, args, gainedMidTurn, simulated, messages);
globalScene.phaseManager.clearPhaseQueueSplice();
}
}
}
export function applyAbAttrs<T extends AbAttrString>(
attrType: T,
pokemon: Pokemon,
cancelled: BooleanHolder | null,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal<T>(
attrType,
pokemon,
// @ts-expect-error: TODO: fix the error on `cancelled`
(attr, passive) => attr.apply(pokemon, passive, simulated, cancelled, args),
(attr, passive) => attr.canApply(pokemon, passive, simulated, args),
args,
simulated,
);
}
// TODO: Improve the type signatures of the following methods / refactor the apply methods
export function applyPostBattleInitAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostBattleInitAbAttr ? K : never,
pokemon: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PostBattleInitAbAttr).applyPostBattleInit(pokemon, passive, simulated, args),
(attr, passive) => (attr as PostBattleInitAbAttr).canApplyPostBattleInit(pokemon, passive, simulated, args),
args,
simulated,
);
}
export function applyPreDefendAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PreDefendAbAttr ? K : never,
pokemon: Pokemon,
attacker: Pokemon,
move: Move | null,
cancelled: BooleanHolder | null,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PreDefendAbAttr).applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args),
(attr, passive) =>
(attr as PreDefendAbAttr).canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args),
args,
simulated,
);
}
export function applyPostDefendAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostDefendAbAttr ? K : never,
pokemon: Pokemon,
attacker: Pokemon,
move: Move,
hitResult: HitResult | null,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PostDefendAbAttr).applyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args),
(attr, passive) =>
(attr as PostDefendAbAttr).canApplyPostDefend(pokemon, passive, simulated, attacker, move, hitResult, args),
args,
simulated,
);
}
export function applyPostMoveUsedAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostMoveUsedAbAttr ? K : never,
pokemon: Pokemon,
move: PokemonMove,
source: Pokemon,
targets: BattlerIndex[],
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, _passive) => (attr as PostMoveUsedAbAttr).applyPostMoveUsed(pokemon, move, source, targets, simulated, args),
(attr, _passive) =>
(attr as PostMoveUsedAbAttr).canApplyPostMoveUsed(pokemon, move, source, targets, simulated, args),
args,
simulated,
);
}
export function applyStatMultiplierAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends StatMultiplierAbAttr ? K : never,
pokemon: Pokemon,
stat: BattleStat,
statValue: NumberHolder,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as StatMultiplierAbAttr).applyStatStage(pokemon, passive, simulated, stat, statValue, args),
(attr, passive) =>
(attr as StatMultiplierAbAttr).canApplyStatStage(pokemon, passive, simulated, stat, statValue, args),
args,
);
}
/**
* Applies an ally's Stat multiplier attribute
* @param attrType - {@linkcode AllyStatMultiplierAbAttr} should always be AllyStatMultiplierAbAttr for the time being
* @param pokemon - The {@linkcode Pokemon} with the ability
* @param stat - The type of the checked {@linkcode Stat}
* @param statValue - {@linkcode NumberHolder} containing the value of the checked stat
* @param checkedPokemon - The {@linkcode Pokemon} with the checked stat
* @param ignoreAbility - Whether or not the ability should be ignored by the pokemon or its move.
* @param args - unused
*/
export function applyAllyStatMultiplierAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends AllyStatMultiplierAbAttr ? K : never,
pokemon: Pokemon,
stat: BattleStat,
statValue: NumberHolder,
simulated = false,
checkedPokemon: Pokemon,
ignoreAbility: boolean,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as AllyStatMultiplierAbAttr).applyAllyStat(
pokemon,
passive,
simulated,
stat,
statValue,
checkedPokemon,
ignoreAbility,
args,
),
(attr, passive) =>
(attr as AllyStatMultiplierAbAttr).canApplyAllyStat(
pokemon,
passive,
simulated,
stat,
statValue,
checkedPokemon,
ignoreAbility,
args,
),
args,
simulated,
);
}
export function applyPostSetStatusAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostSetStatusAbAttr ? K : never,
pokemon: Pokemon,
effect: StatusEffect,
sourcePokemon?: Pokemon | null,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PostSetStatusAbAttr).applyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args),
(attr, passive) =>
(attr as PostSetStatusAbAttr).canApplyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args),
args,
simulated,
);
}
export function applyPostDamageAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostDamageAbAttr ? K : never,
pokemon: Pokemon,
damage: number,
_passive: boolean,
simulated = false,
args: any[],
source?: Pokemon,
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PostDamageAbAttr).applyPostDamage(pokemon, damage, passive, simulated, args, source),
(attr, passive) => (attr as PostDamageAbAttr).canApplyPostDamage(pokemon, damage, passive, simulated, args, source),
args,
);
}
/**
* Applies a field Stat multiplier attribute
* @param attrType {@linkcode FieldMultiplyStatAbAttr} should always be FieldMultiplyBattleStatAbAttr for the time being
* @param pokemon {@linkcode Pokemon} the Pokemon applying this ability
* @param stat {@linkcode Stat} the type of the checked stat
* @param statValue {@linkcode NumberHolder} the value of the checked stat
* @param checkedPokemon {@linkcode Pokemon} the Pokemon with the checked stat
* @param hasApplied {@linkcode BooleanHolder} whether or not a FieldMultiplyBattleStatAbAttr has already affected this stat
* @param args unused
*/
export function applyFieldStatMultiplierAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends FieldMultiplyStatAbAttr ? K : never,
pokemon: Pokemon,
stat: Stat,
statValue: NumberHolder,
checkedPokemon: Pokemon,
hasApplied: BooleanHolder,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as FieldMultiplyStatAbAttr).applyFieldStat(
pokemon,
passive,
simulated,
stat,
statValue,
checkedPokemon,
hasApplied,
args,
),
(attr, passive) =>
(attr as FieldMultiplyStatAbAttr).canApplyFieldStat(
pokemon,
passive,
simulated,
stat,
statValue,
checkedPokemon,
hasApplied,
args,
),
args,
);
}
export function applyPreAttackAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PreAttackAbAttr ? K : never,
pokemon: Pokemon,
defender: Pokemon | null,
move: Move,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PreAttackAbAttr).applyPreAttack(pokemon, passive, simulated, defender, move, args),
(attr, passive) => (attr as PreAttackAbAttr).canApplyPreAttack(pokemon, passive, simulated, defender, move, args),
args,
simulated,
);
}
export function applyExecutedMoveAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends ExecutedMoveAbAttr ? K : never,
pokemon: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
attr => (attr as ExecutedMoveAbAttr).applyExecutedMove(pokemon, simulated),
attr => (attr as ExecutedMoveAbAttr).canApplyExecutedMove(pokemon, simulated),
args,
simulated,
);
}
export function applyPostAttackAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostAttackAbAttr ? K : never,
pokemon: Pokemon,
defender: Pokemon,
move: Move,
hitResult: HitResult | null,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PostAttackAbAttr).applyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args),
(attr, passive) =>
(attr as PostAttackAbAttr).canApplyPostAttack(pokemon, passive, simulated, defender, move, hitResult, args),
args,
simulated,
);
}
export function applyPostKnockOutAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostKnockOutAbAttr ? K : never,
pokemon: Pokemon,
knockedOut: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PostKnockOutAbAttr).applyPostKnockOut(pokemon, passive, simulated, knockedOut, args),
(attr, passive) => (attr as PostKnockOutAbAttr).canApplyPostKnockOut(pokemon, passive, simulated, knockedOut, args),
args,
simulated,
);
}
export function applyPostVictoryAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostVictoryAbAttr ? K : never,
pokemon: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PostVictoryAbAttr).applyPostVictory(pokemon, passive, simulated, args),
(attr, passive) => (attr as PostVictoryAbAttr).canApplyPostVictory(pokemon, passive, simulated, args),
args,
simulated,
);
}
export function applyPostSummonAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostSummonAbAttr ? K : never,
pokemon: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PostSummonAbAttr).applyPostSummon(pokemon, passive, simulated, args),
(attr, passive) => (attr as PostSummonAbAttr).canApplyPostSummon(pokemon, passive, simulated, args),
args,
simulated,
);
}
export function applyPreSummonAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PreSummonAbAttr ? K : never,
pokemon: Pokemon,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PreSummonAbAttr).applyPreSummon(pokemon, passive, args),
(attr, passive) => (attr as PreSummonAbAttr).canApplyPreSummon(pokemon, passive, args),
args,
);
}
export function applyPreSwitchOutAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PreSwitchOutAbAttr ? K : never,
pokemon: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PreSwitchOutAbAttr).applyPreSwitchOut(pokemon, passive, simulated, args),
(attr, passive) => (attr as PreSwitchOutAbAttr).canApplyPreSwitchOut(pokemon, passive, simulated, args),
args,
simulated,
);
}
export function applyPreLeaveFieldAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PreLeaveFieldAbAttr ? K : never,
pokemon: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PreLeaveFieldAbAttr).applyPreLeaveField(pokemon, passive, simulated, args),
(attr, passive) => (attr as PreLeaveFieldAbAttr).canApplyPreLeaveField(pokemon, passive, simulated, args),
args,
simulated,
);
}
export function applyPreStatStageChangeAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PreStatStageChangeAbAttr ? K : never,
pokemon: Pokemon | null,
stat: BattleStat,
cancelled: BooleanHolder,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PreStatStageChangeAbAttr).applyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args),
(attr, passive) =>
(attr as PreStatStageChangeAbAttr).canApplyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args),
args,
simulated,
);
}
export function applyPostStatStageChangeAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostStatStageChangeAbAttr ? K : never,
pokemon: Pokemon,
stats: BattleStat[],
stages: number,
selfTarget: boolean,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, _passive) =>
(attr as PostStatStageChangeAbAttr).applyPostStatStageChange(pokemon, simulated, stats, stages, selfTarget, args),
(attr, _passive) =>
(attr as PostStatStageChangeAbAttr).canApplyPostStatStageChange(
pokemon,
simulated,
stats,
stages,
selfTarget,
args,
),
args,
simulated,
);
}
export function applyPreSetStatusAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PreSetStatusAbAttr ? K : never,
pokemon: Pokemon,
effect: StatusEffect | undefined,
cancelled: BooleanHolder,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PreSetStatusAbAttr).applyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args),
(attr, passive) =>
(attr as PreSetStatusAbAttr).canApplyPreSetStatus(pokemon, passive, simulated, effect, cancelled, args),
args,
simulated,
);
}
export function applyPreApplyBattlerTagAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PreApplyBattlerTagAbAttr ? K : never,
pokemon: Pokemon,
tag: BattlerTag,
cancelled: BooleanHolder,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PreApplyBattlerTagAbAttr).applyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args),
(attr, passive) =>
(attr as PreApplyBattlerTagAbAttr).canApplyPreApplyBattlerTag(pokemon, passive, simulated, tag, cancelled, args),
args,
simulated,
);
}
export function applyPreWeatherEffectAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PreWeatherEffectAbAttr ? K : never,
pokemon: Pokemon,
weather: Weather | null,
cancelled: BooleanHolder,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PreWeatherDamageAbAttr).applyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args),
(attr, passive) =>
(attr as PreWeatherDamageAbAttr).canApplyPreWeatherEffect(pokemon, passive, simulated, weather, cancelled, args),
args,
simulated,
);
}
export function applyPostTurnAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostTurnAbAttr ? K : never,
pokemon: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PostTurnAbAttr).applyPostTurn(pokemon, passive, simulated, args),
(attr, passive) => (attr as PostTurnAbAttr).canApplyPostTurn(pokemon, passive, simulated, args),
args,
simulated,
);
}
export function applyPostWeatherChangeAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostWeatherChangeAbAttr ? K : never,
pokemon: Pokemon,
weather: WeatherType,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PostWeatherChangeAbAttr).applyPostWeatherChange(pokemon, passive, simulated, weather, args),
(attr, passive) =>
(attr as PostWeatherChangeAbAttr).canApplyPostWeatherChange(pokemon, passive, simulated, weather, args),
args,
simulated,
);
}
export function applyPostWeatherLapseAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostWeatherLapseAbAttr ? K : never,
pokemon: Pokemon,
weather: Weather | null,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PostWeatherLapseAbAttr).applyPostWeatherLapse(pokemon, passive, simulated, weather, args),
(attr, passive) =>
(attr as PostWeatherLapseAbAttr).canApplyPostWeatherLapse(pokemon, passive, simulated, weather, args),
args,
simulated,
);
}
export function applyPostTerrainChangeAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostTerrainChangeAbAttr ? K : never,
pokemon: Pokemon,
terrain: TerrainType,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PostTerrainChangeAbAttr).applyPostTerrainChange(pokemon, passive, simulated, terrain, args),
(attr, passive) =>
(attr as PostTerrainChangeAbAttr).canApplyPostTerrainChange(pokemon, passive, simulated, terrain, args),
args,
simulated,
);
}
export function applyCheckTrappedAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends CheckTrappedAbAttr ? K : never,
pokemon: Pokemon,
trapped: BooleanHolder,
otherPokemon: Pokemon,
messages: string[],
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as CheckTrappedAbAttr).applyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args),
(attr, passive) =>
(attr as CheckTrappedAbAttr).canApplyCheckTrapped(pokemon, passive, simulated, trapped, otherPokemon, args),
args,
simulated,
messages,
);
}
export function applyPostBattleAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostBattleAbAttr ? K : never,
pokemon: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) => (attr as PostBattleAbAttr).applyPostBattle(pokemon, passive, simulated, args),
(attr, passive) => (attr as PostBattleAbAttr).canApplyPostBattle(pokemon, passive, simulated, args),
args,
simulated,
);
}
export function applyPostFaintAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostFaintAbAttr ? K : never,
pokemon: Pokemon,
attacker?: Pokemon,
move?: Move,
hitResult?: HitResult,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, passive) =>
(attr as PostFaintAbAttr).applyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args),
(attr, passive) =>
(attr as PostFaintAbAttr).canApplyPostFaint(pokemon, passive, simulated, attacker, move, hitResult, args),
args,
simulated,
);
}
export function applyPostItemLostAbAttrs<K extends AbAttrString>(
attrType: AbAttrMap[K] extends PostItemLostAbAttr ? K : never,
pokemon: Pokemon,
simulated = false,
...args: any[]
): void {
applyAbAttrsInternal(
attrType,
pokemon,
(attr, _passive) => (attr as PostItemLostAbAttr).applyPostItemLost(pokemon, simulated, args),
(attr, _passive) => (attr as PostItemLostAbAttr).canApplyPostItemLost(pokemon, simulated, args),
args,
);
}
/**
* Applies abilities when they become active mid-turn (ability switch)
*
* Ignores passives as they don't change and shouldn't be reapplied when main abilities change
*/
export function applyOnGainAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void {
applySingleAbAttrs(
pokemon,
passive,
"PostSummonAbAttr",
(attr, passive) => attr.applyPostSummon(pokemon, passive, simulated, args),
(attr, passive) => attr.canApplyPostSummon(pokemon, passive, simulated, args),
args,
true,
simulated,
);
}
/**
* 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,
);
applySingleAbAttrs(
pokemon,
passive,
"IllusionBreakAbAttr",
(attr, passive) => attr.apply(pokemon, passive, simulated, null, args),
(attr, passive) => attr.canApply(pokemon, passive, simulated, args),
args,
true,
simulated,
);
}

View File

@ -10,15 +10,7 @@ import type Pokemon from "#app/field/pokemon";
import { HitResult } from "#enums/hit-result"; import { HitResult } from "#enums/hit-result";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import type { BattlerIndex } from "#enums/battler-index"; import type { BattlerIndex } from "#enums/battler-index";
import { import { applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "./abilities/apply-ab-attrs";
BlockNonDirectDamageAbAttr,
InfiltratorAbAttr,
PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr,
ProtectStatAbAttr,
applyAbAttrs,
applyOnGainAbAttrs,
applyOnLoseAbAttrs,
} from "#app/data/abilities/ability";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { CommonBattleAnim } from "#app/data/battle-anims"; import { CommonBattleAnim } from "#app/data/battle-anims";
import { CommonAnim } from "#enums/move-anims-common"; import { CommonAnim } from "#enums/move-anims-common";
@ -144,7 +136,7 @@ export class MistTag extends ArenaTag {
if (attacker) { if (attacker) {
const bypassed = new BooleanHolder(false); const bypassed = new BooleanHolder(false);
// TODO: Allow this to be simulated // TODO: Allow this to be simulated
applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed); applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
if (bypassed.value) { if (bypassed.value) {
return false; return false;
} }
@ -209,7 +201,7 @@ export class WeakenMoveScreenTag extends ArenaTag {
): boolean { ): boolean {
if (this.weakenedCategories.includes(moveCategory)) { if (this.weakenedCategories.includes(moveCategory)) {
const bypassed = new BooleanHolder(false); const bypassed = new BooleanHolder(false);
applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed); applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
if (bypassed.value) { if (bypassed.value) {
return false; return false;
} }
@ -765,7 +757,7 @@ class SpikesTag extends ArenaTrapTag {
} }
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
if (simulated || cancelled.value) { if (simulated || cancelled.value) {
return !cancelled.value; return !cancelled.value;
} }
@ -953,7 +945,7 @@ class StealthRockTag extends ArenaTrapTag {
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean { override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
if (cancelled.value) { if (cancelled.value) {
return false; return false;
} }
@ -1010,7 +1002,7 @@ class StickyWebTag extends ArenaTrapTag {
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean { override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
if (pokemon.isGrounded()) { if (pokemon.isGrounded()) {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled); applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled);
if (simulated) { if (simulated) {
return !cancelled.value; return !cancelled.value;
@ -1444,8 +1436,8 @@ export class SuppressAbilitiesTag extends ArenaTag {
// Could have a custom message that plays when a specific pokemon's NG ends? This entire thing exists due to passives after all // Could have a custom message that plays when a specific pokemon's NG ends? This entire thing exists due to passives after all
const setter = globalScene const setter = globalScene
.getField() .getField()
.filter(p => p?.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false))[0]; .filter(p => p?.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false))[0];
applyOnGainAbAttrs(setter, setter.getAbility().hasAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr)); applyOnGainAbAttrs(setter, setter.getAbility().hasAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr"));
} }
} }
@ -1457,7 +1449,7 @@ export class SuppressAbilitiesTag extends ArenaTag {
for (const pokemon of globalScene.getField(true)) { for (const pokemon of globalScene.getField(true)) {
// There is only one pokemon with this attr on the field on removal, so its abilities are already active // There is only one pokemon with this attr on the field on removal, so its abilities are already active
if (pokemon && !pokemon.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false)) { if (pokemon && !pokemon.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false)) {
[true, false].forEach(passive => applyOnGainAbAttrs(pokemon, passive)); [true, false].forEach(passive => applyOnGainAbAttrs(pokemon, passive));
} }
} }

View File

@ -1,13 +1,6 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { import { applyAbAttrs } from "./abilities/apply-ab-attrs";
applyAbAttrs,
BlockNonDirectDamageAbAttr,
FlinchEffectAbAttr,
ProtectStatAbAttr,
ConditionalUserFieldProtectStatAbAttr,
ReverseDrainAbAttr,
} from "#app/data/abilities/ability";
import { allAbilities } from "./data-lists"; import { allAbilities } from "./data-lists";
import { CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims"; import { CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims";
import { ChargeAnim, CommonAnim } from "#enums/move-anims-common"; import { ChargeAnim, CommonAnim } from "#enums/move-anims-common";
@ -648,7 +641,7 @@ export class FlinchedTag extends BattlerTag {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), }),
); );
applyAbAttrs(FlinchEffectAbAttr, pokemon, null); applyAbAttrs("FlinchEffectAbAttr", pokemon, null);
return true; return true;
} }
@ -942,7 +935,7 @@ export class SeedTag extends BattlerTag {
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex); const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
if (source) { if (source) {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
@ -953,7 +946,7 @@ export class SeedTag extends BattlerTag {
); );
const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT }); const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false); const reverseDrain = pokemon.hasAbilityWithAttr("ReverseDrainAbAttr", false);
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
"PokemonHealPhase", "PokemonHealPhase",
source.getBattlerIndex(), source.getBattlerIndex(),
@ -1026,7 +1019,7 @@ export class PowderTag extends BattlerTag {
globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER); globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER);
const cancelDamage = new BooleanHolder(false); const cancelDamage = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelDamage); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelDamage);
if (!cancelDamage.value) { if (!cancelDamage.value) {
pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT }); pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
} }
@ -1079,7 +1072,7 @@ export class NightmareTag extends BattlerTag {
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE); // TODO: Update animation type phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE); // TODO: Update animation type
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT }); pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
@ -1438,7 +1431,7 @@ export abstract class DamagingTrapTag extends TrappedTag {
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, this.commonAnim); phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, this.commonAnim);
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT }); pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
@ -1681,7 +1674,7 @@ export class ContactDamageProtectedTag extends ContactProtectedTag {
*/ */
override onContact(attacker: Pokemon, user: Pokemon): void { override onContact(attacker: Pokemon, user: Pokemon): void {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), { attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), {
result: HitResult.INDIRECT, result: HitResult.INDIRECT,
@ -2277,7 +2270,7 @@ export class SaltCuredTag extends BattlerTag {
); );
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
const pokemonSteelOrWater = pokemon.isOfType(PokemonType.STEEL) || pokemon.isOfType(PokemonType.WATER); const pokemonSteelOrWater = pokemon.isOfType(PokemonType.STEEL) || pokemon.isOfType(PokemonType.WATER);
@ -2331,7 +2324,7 @@ export class CursedTag extends BattlerTag {
); );
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT }); pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
@ -2666,7 +2659,7 @@ export class GulpMissileTag extends BattlerTag {
} }
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", attacker, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), { result: HitResult.INDIRECT }); attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), { result: HitResult.INDIRECT });
@ -3056,8 +3049,8 @@ export class MysteryEncounterPostSummonTag extends BattlerTag {
if (lapseType === BattlerTagLapseType.CUSTOM) { if (lapseType === BattlerTagLapseType.CUSTOM) {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled); applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled);
applyAbAttrs(ConditionalUserFieldProtectStatAbAttr, pokemon, cancelled, false, pokemon); applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", pokemon, cancelled, false, pokemon);
if (!cancelled.value) { if (!cancelled.value) {
if (pokemon.mysteryEncounterBattleEffects) { if (pokemon.mysteryEncounterBattleEffects) {
pokemon.mysteryEncounterBattleEffects(pokemon); pokemon.mysteryEncounterBattleEffects(pokemon);

View File

@ -3,7 +3,7 @@ import type Pokemon from "../field/pokemon";
import { HitResult } from "#enums/hit-result"; import { HitResult } from "#enums/hit-result";
import { getStatusEffectHealText } from "./status-effect"; import { getStatusEffectHealText } from "./status-effect";
import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils/common"; import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils/common";
import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./abilities/ability"; import { applyAbAttrs } from "./abilities/apply-ab-attrs";
import i18next from "i18next"; import i18next from "i18next";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
@ -38,25 +38,25 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
const threshold = new NumberHolder(0.25); const threshold = new NumberHolder(0.25);
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
const stat: BattleStat = berryType - BerryType.ENIGMA; const stat: BattleStat = berryType - BerryType.ENIGMA;
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6; return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6;
}; };
case BerryType.LANSAT: case BerryType.LANSAT:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const threshold = new NumberHolder(0.25); const threshold = new NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST); return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST);
}; };
case BerryType.STARF: case BerryType.STARF:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const threshold = new NumberHolder(0.25); const threshold = new NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
return pokemon.getHpRatio() < 0.25; return pokemon.getHpRatio() < 0.25;
}; };
case BerryType.LEPPA: case BerryType.LEPPA:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const threshold = new NumberHolder(0.25); const threshold = new NumberHolder(0.25);
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); applyAbAttrs("ReduceBerryUseThresholdAbAttr", pokemon, null, false, threshold);
return !!pokemon.getMoveset().find(m => !m.getPpRatio()); return !!pokemon.getMoveset().find(m => !m.getPpRatio());
}; };
} }
@ -72,7 +72,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
case BerryType.ENIGMA: case BerryType.ENIGMA:
{ {
const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4)); const hpHealed = new NumberHolder(toDmgValue(consumer.getMaxHp() / 4));
applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, hpHealed); applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, hpHealed);
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
"PokemonHealPhase", "PokemonHealPhase",
consumer.getBattlerIndex(), consumer.getBattlerIndex(),
@ -105,7 +105,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
// Offset BerryType such that LIECHI --> Stat.ATK = 1, GANLON --> Stat.DEF = 2, etc etc. // Offset BerryType such that LIECHI --> Stat.ATK = 1, GANLON --> Stat.DEF = 2, etc etc.
const stat: BattleStat = berryType - BerryType.ENIGMA; const stat: BattleStat = berryType - BerryType.ENIGMA;
const statStages = new NumberHolder(1); const statStages = new NumberHolder(1);
applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, statStages); applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, statStages);
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
"StatStageChangePhase", "StatStageChangePhase",
consumer.getBattlerIndex(), consumer.getBattlerIndex(),
@ -126,7 +126,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
{ {
const randStat = randSeedInt(Stat.SPD, Stat.ATK); const randStat = randSeedInt(Stat.SPD, Stat.ATK);
const stages = new NumberHolder(2); const stages = new NumberHolder(2);
applyAbAttrs(DoubleBerryEffectAbAttr, consumer, null, false, stages); applyAbAttrs("DoubleBerryEffectAbAttr", consumer, null, false, stages);
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
"StatStageChangePhase", "StatStageChangePhase",
consumer.getBattlerIndex(), consumer.getBattlerIndex(),

View File

@ -1,4 +1,4 @@
import type { Ability } from "./abilities/ability-class"; import type { Ability } from "./abilities/ability";
import type Move from "./moves/move"; import type Move from "./moves/move";
export const allAbilities: Ability[] = []; export const allAbilities: Ability[] = [];

View File

@ -33,38 +33,12 @@ import type { ArenaTrapTag } from "../arena-tag";
import { WeakenMoveTypeTag } from "../arena-tag"; import { WeakenMoveTypeTag } from "../arena-tag";
import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagSide } from "#enums/arena-tag-side";
import { import {
AllyMoveCategoryPowerBoostAbAttr,
applyAbAttrs, applyAbAttrs,
applyPostAttackAbAttrs, applyPostAttackAbAttrs,
applyPostItemLostAbAttrs, applyPostItemLostAbAttrs,
applyPreAttackAbAttrs, applyPreAttackAbAttrs,
applyPreDefendAbAttrs, applyPreDefendAbAttrs
BlockItemTheftAbAttr, } from "../abilities/apply-ab-attrs";
BlockNonDirectDamageAbAttr,
BlockOneHitKOAbAttr,
BlockRecoilDamageAttr,
ChangeMovePriorityAbAttr,
ConfusionOnStatusEffectAbAttr,
FieldMoveTypePowerBoostAbAttr,
FieldPreventExplosiveMovesAbAttr,
ForceSwitchOutImmunityAbAttr,
HealFromBerryUseAbAttr,
IgnoreContactAbAttr,
IgnoreMoveEffectsAbAttr,
IgnoreProtectOnContactAbAttr,
InfiltratorAbAttr,
MaxMultiHitAbAttr,
MoveAbilityBypassAbAttr,
MoveEffectChanceMultiplierAbAttr,
MoveTypeChangeAbAttr,
PostDamageForceSwitchAbAttr,
PostItemLostAbAttr,
ReflectStatusMoveAbAttr,
ReverseDrainAbAttr,
UserFieldMoveTypePowerBoostAbAttr,
VariableMovePowerAbAttr,
WonderSkinAbAttr,
} from "../abilities/ability";
import { allAbilities, allMoves } from "../data-lists"; import { allAbilities, allMoves } from "../data-lists";
import { import {
AttackTypeBoosterModifier, AttackTypeBoosterModifier,
@ -377,7 +351,7 @@ export default abstract class Move implements Localizable {
const bypassed = new BooleanHolder(false); const bypassed = new BooleanHolder(false);
// TODO: Allow this to be simulated // TODO: Allow this to be simulated
applyAbAttrs(InfiltratorAbAttr, user, null, false, bypassed); applyAbAttrs("InfiltratorAbAttr", user, null, false, bypassed);
return !bypassed.value return !bypassed.value
&& !this.hasFlag(MoveFlags.SOUND_BASED) && !this.hasFlag(MoveFlags.SOUND_BASED)
@ -668,14 +642,14 @@ export default abstract class Move implements Localizable {
// special cases below, eg: if the move flag is MAKES_CONTACT, and the user pokemon has an ability that ignores contact (like "Long Reach"), then overrides and move does not make contact // special cases below, eg: if the move flag is MAKES_CONTACT, and the user pokemon has an ability that ignores contact (like "Long Reach"), then overrides and move does not make contact
switch (flag) { switch (flag) {
case MoveFlags.MAKES_CONTACT: case MoveFlags.MAKES_CONTACT:
if (user.hasAbilityWithAttr(IgnoreContactAbAttr) || this.hitsSubstitute(user, target)) { if (user.hasAbilityWithAttr("IgnoreContactAbAttr") || this.hitsSubstitute(user, target)) {
return false; return false;
} }
break; break;
case MoveFlags.IGNORE_ABILITIES: case MoveFlags.IGNORE_ABILITIES:
if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) { if (user.hasAbilityWithAttr("MoveAbilityBypassAbAttr")) {
const abilityEffectsIgnored = new BooleanHolder(false); const abilityEffectsIgnored = new BooleanHolder(false);
applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, false, this); applyAbAttrs("MoveAbilityBypassAbAttr", user, abilityEffectsIgnored, false, this);
if (abilityEffectsIgnored.value) { if (abilityEffectsIgnored.value) {
return true; return true;
} }
@ -684,7 +658,7 @@ export default abstract class Move implements Localizable {
} }
return this.hasFlag(MoveFlags.IGNORE_ABILITIES) && !isFollowUp; return this.hasFlag(MoveFlags.IGNORE_ABILITIES) && !isFollowUp;
case MoveFlags.IGNORE_PROTECT: case MoveFlags.IGNORE_PROTECT:
if (user.hasAbilityWithAttr(IgnoreProtectOnContactAbAttr) if (user.hasAbilityWithAttr("IgnoreProtectOnContactAbAttr")
&& this.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user })) { && this.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user })) {
return true; return true;
} }
@ -695,7 +669,7 @@ export default abstract class Move implements Localizable {
target?.getTag(SemiInvulnerableTag) || target?.getTag(SemiInvulnerableTag) ||
!(target?.getTag(BattlerTagType.MAGIC_COAT) || !(target?.getTag(BattlerTagType.MAGIC_COAT) ||
(!this.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user, target }) && (!this.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user, target }) &&
target?.hasAbilityWithAttr(ReflectStatusMoveAbAttr))) target?.hasAbilityWithAttr("ReflectStatusMoveAbAttr")))
) { ) {
return false; return false;
} }
@ -792,7 +766,7 @@ export default abstract class Move implements Localizable {
const moveAccuracy = new NumberHolder(this.accuracy); const moveAccuracy = new NumberHolder(this.accuracy);
applyMoveAttrs("VariableAccuracyAttr", user, target, this, moveAccuracy); applyMoveAttrs("VariableAccuracyAttr", user, target, this, moveAccuracy);
applyPreDefendAbAttrs(WonderSkinAbAttr, target, user, this, { value: false }, simulated, moveAccuracy); applyPreDefendAbAttrs("WonderSkinAbAttr", target, user, this, { value: false }, simulated, moveAccuracy);
if (moveAccuracy.value === -1) { if (moveAccuracy.value === -1) {
return moveAccuracy.value; return moveAccuracy.value;
@ -835,25 +809,25 @@ export default abstract class Move implements Localizable {
const typeChangeMovePowerMultiplier = new NumberHolder(1); const typeChangeMovePowerMultiplier = new NumberHolder(1);
const typeChangeHolder = new NumberHolder(this.type); const typeChangeHolder = new NumberHolder(this.type);
applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, typeChangeHolder, typeChangeMovePowerMultiplier); applyPreAttackAbAttrs("MoveTypeChangeAbAttr", source, target, this, true, typeChangeHolder, typeChangeMovePowerMultiplier);
const sourceTeraType = source.getTeraType(); const sourceTeraType = source.getTeraType();
if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
power.value = 60; power.value = 60;
} }
applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, target, this, simulated, power); applyPreAttackAbAttrs("VariableMovePowerAbAttr", source, target, this, simulated, power);
const ally = source.getAlly(); const ally = source.getAlly();
if (!isNullOrUndefined(ally)) { if (!isNullOrUndefined(ally)) {
applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, ally, target, this, simulated, power); applyPreAttackAbAttrs("AllyMoveCategoryPowerBoostAbAttr", ally, target, this, simulated, power);
} }
const fieldAuras = new Set( const fieldAuras = new Set(
globalScene.getField(true) globalScene.getField(true)
.map((p) => p.getAbilityAttrs(FieldMoveTypePowerBoostAbAttr).filter(attr => { .map((p) => p.getAbilityAttrs("FieldMoveTypePowerBoostAbAttr").filter(attr => {
const condition = attr.getCondition(); const condition = attr.getCondition();
return (!condition || condition(p)); return (!condition || condition(p));
}) as FieldMoveTypePowerBoostAbAttr[]) }))
.flat(), .flat(),
); );
for (const aura of fieldAuras) { for (const aura of fieldAuras) {
@ -861,7 +835,7 @@ export default abstract class Move implements Localizable {
} }
const alliedField: Pokemon[] = source.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); const alliedField: Pokemon[] = source.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, target, this, simulated, power)); alliedField.forEach(p => applyPreAttackAbAttrs("UserFieldMoveTypePowerBoostAbAttr", p, target, this, simulated, power));
power.value *= typeChangeMovePowerMultiplier.value; power.value *= typeChangeMovePowerMultiplier.value;
@ -888,7 +862,7 @@ export default abstract class Move implements Localizable {
const priority = new NumberHolder(this.priority); const priority = new NumberHolder(this.priority);
applyMoveAttrs("IncrementMovePriorityAttr", user, null, this, priority); applyMoveAttrs("IncrementMovePriorityAttr", user, null, this, priority);
applyAbAttrs(ChangeMovePriorityAbAttr, user, null, simulated, this, priority); applyAbAttrs("ChangeMovePriorityAbAttr", user, null, simulated, this, priority);
return priority.value; return priority.value;
} }
@ -1340,7 +1314,7 @@ export class MoveEffectAttr extends MoveAttr {
getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): number { getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): number {
const moveChance = new NumberHolder(this.effectChanceOverride ?? move.chance); const moveChance = new NumberHolder(this.effectChanceOverride ?? move.chance);
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, !showAbility, moveChance, move); applyAbAttrs("MoveEffectChanceMultiplierAbAttr", user, null, !showAbility, moveChance, move);
if ((!move.hasAttr("FlinchAttr") || moveChance.value <= move.chance) && !move.hasAttr("SecretPowerAttr")) { if ((!move.hasAttr("FlinchAttr") || moveChance.value <= move.chance) && !move.hasAttr("SecretPowerAttr")) {
const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
@ -1348,7 +1322,7 @@ export class MoveEffectAttr extends MoveAttr {
} }
if (!selfEffect) { if (!selfEffect) {
applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, target, user, null, null, !showAbility, moveChance); applyPreDefendAbAttrs("IgnoreMoveEffectsAbAttr", target, user, null, null, !showAbility, moveChance);
} }
return moveChance.value; return moveChance.value;
} }
@ -1726,8 +1700,8 @@ export class RecoilAttr extends MoveEffectAttr {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
if (!this.unblockable) { if (!this.unblockable) {
applyAbAttrs(BlockRecoilDamageAttr, user, cancelled); applyAbAttrs("BlockRecoilDamageAttr", user, cancelled);
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
} }
if (cancelled.value) { if (cancelled.value) {
@ -1860,7 +1834,7 @@ export class HalfSacrificialAttr extends MoveEffectAttr {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
// Check to see if the Pokemon has an ability that blocks non-direct damage // Check to see if the Pokemon has an ability that blocks non-direct damage
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT, ignoreSegments: true }); user.damageAndUpdate(toDmgValue(user.getMaxHp() / 2), { result: HitResult.INDIRECT, ignoreSegments: true });
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", { pokemonName: getPokemonNameWithAffix(user) })); // Queue recoil message globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", { pokemonName: getPokemonNameWithAffix(user) })); // Queue recoil message
@ -2059,7 +2033,7 @@ export class FlameBurstAttr extends MoveEffectAttr {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
if (!isNullOrUndefined(targetAlly)) { if (!isNullOrUndefined(targetAlly)) {
applyAbAttrs(BlockNonDirectDamageAbAttr, targetAlly, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", targetAlly, cancelled);
} }
if (cancelled.value || !targetAlly || targetAlly.switchOutStatus) { if (cancelled.value || !targetAlly || targetAlly.switchOutStatus) {
@ -2289,7 +2263,7 @@ export class HitHealAttr extends MoveEffectAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
let healAmount = 0; let healAmount = 0;
let message = ""; let message = "";
const reverseDrain = target.hasAbilityWithAttr(ReverseDrainAbAttr, false); const reverseDrain = target.hasAbilityWithAttr("ReverseDrainAbAttr", false);
if (this.healStat !== null) { if (this.healStat !== null) {
// Strength Sap formula // Strength Sap formula
healAmount = target.getEffectiveStat(this.healStat); healAmount = target.getEffectiveStat(this.healStat);
@ -2300,7 +2274,7 @@ export class HitHealAttr extends MoveEffectAttr {
message = i18next.t("battle:regainHealth", { pokemonName: getPokemonNameWithAffix(user) }); message = i18next.t("battle:regainHealth", { pokemonName: getPokemonNameWithAffix(user) });
} }
if (reverseDrain) { if (reverseDrain) {
if (user.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { if (user.hasAbilityWithAttr("BlockNonDirectDamageAbAttr")) {
healAmount = 0; healAmount = 0;
message = ""; message = "";
} else { } else {
@ -2430,7 +2404,7 @@ export class MultiHitAttr extends MoveAttr {
{ {
const rand = user.randBattleSeedInt(20); const rand = user.randBattleSeedInt(20);
const hitValue = new NumberHolder(rand); const hitValue = new NumberHolder(rand);
applyAbAttrs(MaxMultiHitAbAttr, user, null, false, hitValue); applyAbAttrs("MaxMultiHitAbAttr", user, null, false, hitValue);
if (hitValue.value >= 13) { if (hitValue.value >= 13) {
return 2; return 2;
} else if (hitValue.value >= 6) { } else if (hitValue.value >= 6) {
@ -2538,7 +2512,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
} }
if (((!pokemon.status || this.overrideStatus) || (pokemon.status.effect === this.effect && moveChance < 0)) if (((!pokemon.status || this.overrideStatus) || (pokemon.status.effect === this.effect && moveChance < 0))
&& pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining, null, this.overrideStatus, quiet)) { && pokemon.trySetStatus(this.effect, true, user, this.turnsRemaining, null, this.overrideStatus, quiet)) {
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null, false, this.effect); applyPostAttackAbAttrs("ConfusionOnStatusEffectAbAttr", user, target, move, null, false, this.effect);
return true; return true;
} }
} }
@ -2694,7 +2668,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
// Check for abilities that block item theft // Check for abilities that block item theft
// TODO: This should not trigger if the target would faint beforehand // TODO: This should not trigger if the target would faint beforehand
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); applyAbAttrs("BlockItemTheftAbAttr", target, cancelled);
if (cancelled.value) { if (cancelled.value) {
return false; return false;
@ -2811,8 +2785,8 @@ export class EatBerryAttr extends MoveEffectAttr {
protected eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) { protected eatBerry(consumer: Pokemon, berryOwner: Pokemon = consumer, updateHarvest = consumer === berryOwner) {
// consumer eats berry, owner triggers unburden and similar effects // consumer eats berry, owner triggers unburden and similar effects
getBerryEffectFunc(this.chosenBerry.berryType)(consumer); getBerryEffectFunc(this.chosenBerry.berryType)(consumer);
applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner, false); applyPostItemLostAbAttrs("PostItemLostAbAttr", berryOwner, false);
applyAbAttrs(HealFromBerryUseAbAttr, consumer, new BooleanHolder(false)); applyAbAttrs("HealFromBerryUseAbAttr", consumer, new BooleanHolder(false));
consumer.recordEatenBerry(this.chosenBerry.berryType, updateHarvest); consumer.recordEatenBerry(this.chosenBerry.berryType, updateHarvest);
} }
} }
@ -2837,7 +2811,7 @@ export class StealEatBerryAttr extends EatBerryAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
// check for abilities that block item theft // check for abilities that block item theft
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); applyAbAttrs("BlockItemTheftAbAttr", target, cancelled);
if (cancelled.value === true) { if (cancelled.value === true) {
return false; return false;
} }
@ -2851,7 +2825,7 @@ export class StealEatBerryAttr extends EatBerryAttr {
// pick a random berry and eat it // pick a random berry and eat it
this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)]; this.chosenBerry = heldBerries[user.randBattleSeedInt(heldBerries.length)];
applyPostItemLostAbAttrs(PostItemLostAbAttr, target, false); applyPostItemLostAbAttrs("PostItemLostAbAttr", target, false);
const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name }); const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name });
globalScene.phaseManager.queueMessage(message); globalScene.phaseManager.queueMessage(message);
this.reduceBerryModifier(target); this.reduceBerryModifier(target);
@ -2892,7 +2866,7 @@ export class HealStatusEffectAttr extends MoveEffectAttr {
// Special edge case for shield dust blocking Sparkling Aria curing burn // Special edge case for shield dust blocking Sparkling Aria curing burn
const moveTargets = getMoveTargets(user, move.id); const moveTargets = getMoveTargets(user, move.id);
if (target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && move.id === MoveId.SPARKLING_ARIA && moveTargets.targets.length === 1) { if (target.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") && move.id === MoveId.SPARKLING_ARIA && moveTargets.targets.length === 1) {
return false; return false;
} }
@ -3042,7 +3016,7 @@ export class OneHitKOAttr extends MoveAttr {
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc {
return (user, target, move) => { return (user, target, move) => {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockOneHitKOAbAttr, target, cancelled); applyAbAttrs("BlockOneHitKOAbAttr", target, cancelled);
return !cancelled.value && user.level >= target.level; return !cancelled.value && user.level >= target.level;
}; };
} }
@ -5442,7 +5416,7 @@ export class NoEffectAttr extends MoveAttr {
const crashDamageFunc = (user: Pokemon, move: Move) => { const crashDamageFunc = (user: Pokemon, move: Move) => {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
if (cancelled.value) { if (cancelled.value) {
return false; return false;
} }
@ -6302,7 +6276,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
* Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch * Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch
* If it did, the user of U-turn or Volt Switch will not be switched out. * If it did, the user of U-turn or Volt Switch will not be switched out.
*/ */
if (target.getAbility().hasAttr(PostDamageForceSwitchAbAttr) if (target.getAbility().hasAttr("PostDamageForceSwitchAbAttr")
&& [ MoveId.U_TURN, MoveId.VOLT_SWITCH, MoveId.FLIP_TURN ].includes(move.id) && [ MoveId.U_TURN, MoveId.VOLT_SWITCH, MoveId.FLIP_TURN ].includes(move.id)
) { ) {
if (this.hpDroppedBelowHalf(target)) { if (this.hpDroppedBelowHalf(target)) {
@ -6391,7 +6365,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
* Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch * Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch
* If it did, the user of U-turn or Volt Switch will not be switched out. * If it did, the user of U-turn or Volt Switch will not be switched out.
*/ */
if (target.getAbility().hasAttr(PostDamageForceSwitchAbAttr) if (target.getAbility().hasAttr("PostDamageForceSwitchAbAttr")
&& [ MoveId.U_TURN, MoveId.VOLT_SWITCH, MoveId.FLIP_TURN ].includes(move.id) && [ MoveId.U_TURN, MoveId.VOLT_SWITCH, MoveId.FLIP_TURN ].includes(move.id)
) { ) {
if (this.hpDroppedBelowHalf(target)) { if (this.hpDroppedBelowHalf(target)) {
@ -6434,7 +6408,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
getFailedText(_user: Pokemon, target: Pokemon, _move: Move): string | undefined { getFailedText(_user: Pokemon, target: Pokemon, _move: Move): string | undefined {
const blockedByAbility = new BooleanHolder(false); const blockedByAbility = new BooleanHolder(false);
applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility); applyAbAttrs("ForceSwitchOutImmunityAbAttr", target, blockedByAbility);
if (blockedByAbility.value) { if (blockedByAbility.value) {
return i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) }); return i18next.t("moveTriggers:cannotBeSwitchedOut", { pokemonName: getPokemonNameWithAffix(target) });
} }
@ -6474,7 +6448,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
} }
const blockedByAbility = new BooleanHolder(false); const blockedByAbility = new BooleanHolder(false);
applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility); applyAbAttrs("ForceSwitchOutImmunityAbAttr", target, blockedByAbility);
if (blockedByAbility.value) { if (blockedByAbility.value) {
return false; return false;
} }
@ -7981,7 +7955,7 @@ const failIfSingleBattle: MoveConditionFunc = (user, target, move) => globalScen
const failIfDampCondition: MoveConditionFunc = (user, target, move) => { const failIfDampCondition: MoveConditionFunc = (user, target, move) => {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
globalScene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled)); globalScene.getField(true).map(p=>applyAbAttrs("FieldPreventExplosiveMovesAbAttr", p, cancelled));
// Queue a message if an ability prevented usage of the move // Queue a message if an ability prevented usage of the move
if (cancelled.value) { if (cancelled.value) {
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name })); globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name }));

View File

@ -38,7 +38,6 @@ import i18next from "i18next";
import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { Ability } from "#app/data/abilities/ability-class";
import { BerryModifier } from "#app/modifier/modifier"; import { BerryModifier } from "#app/modifier/modifier";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
@ -49,6 +48,7 @@ import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { EncounterAnim } from "#enums/encounter-anims"; import { EncounterAnim } from "#enums/encounter-anims";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
import { allAbilities } from "#app/data/data-lists";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/clowningAround"; const namespace = "mysteryEncounters/clowningAround";
@ -139,7 +139,7 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder
// Generate random ability for Blacephalon from pool // Generate random ability for Blacephalon from pool
const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)]; const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)];
encounter.setDialogueToken("ability", new Ability(ability, 3).name); encounter.setDialogueToken("ability", allAbilities[ability].name);
encounter.misc = { ability }; encounter.misc = { ability };
// Decide the random types for Blacephalon. They should not be the same. // Decide the random types for Blacephalon. They should not be the same.

View File

@ -45,8 +45,8 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { Ability } from "#app/data/abilities/ability-class";
import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { allAbilities } from "#app/data/data-lists";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/fieryFallout"; const namespace = "mysteryEncounters/fieryFallout";
@ -246,7 +246,7 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w
if (chosenPokemon.trySetStatus(StatusEffect.BURN)) { if (chosenPokemon.trySetStatus(StatusEffect.BURN)) {
// Burn applied // Burn applied
encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender()); encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender());
encounter.setDialogueToken("abilityName", new Ability(AbilityId.HEATPROOF, 3).name); encounter.setDialogueToken("abilityName", allAbilities[AbilityId.HEATPROOF].name);
queueEncounterMessage(`${namespace}:option.2.target_burned`); queueEncounterMessage(`${namespace}:option.2.target_burned`);
// Also permanently change the burned Pokemon's ability to Heatproof // Also permanently change the burned Pokemon's ability to Heatproof

View File

@ -24,7 +24,7 @@ import { PokemonType } from "#enums/pokemon-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/abilities/ability"; import { applyPostBattleInitAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import i18next from "i18next"; import i18next from "i18next";
@ -221,7 +221,7 @@ function endTrainerBattleAndShowDialogue(): Promise<void> {
// Each trainer battle is supposed to be a new fight, so reset all per-battle activation effects // Each trainer battle is supposed to be a new fight, so reset all per-battle activation effects
pokemon.resetBattleAndWaveData(); pokemon.resetBattleAndWaveData();
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon); applyPostBattleInitAbAttrs("PostBattleInitAbAttr", pokemon);
} }
globalScene.phaseManager.unshiftNew("ShowTrainerPhase"); globalScene.phaseManager.unshiftNew("ShowTrainerPhase");

View File

@ -1,4 +1,4 @@
import type { Ability } from "#app/data/abilities/ability-class"; import type { Ability } from "#app/data/abilities/ability";
import { allAbilities } from "#app/data/data-lists"; import { allAbilities } from "#app/data/data-lists";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { import {

View File

@ -5,7 +5,6 @@ import type Pokemon from "../field/pokemon";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import type Move from "./moves/move"; import type Move from "./moves/move";
import { randSeedInt } from "#app/utils/common"; import { randSeedInt } from "#app/utils/common";
import { SuppressWeatherEffectAbAttr } from "./abilities/ability";
import { TerrainType, getTerrainName } from "./terrain"; import { TerrainType, getTerrainName } from "./terrain";
import i18next from "i18next"; import i18next from "i18next";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
@ -108,10 +107,10 @@ export class Weather {
for (const pokemon of field) { for (const pokemon of field) {
let suppressWeatherEffectAbAttr: SuppressWeatherEffectAbAttr | null = pokemon let suppressWeatherEffectAbAttr: SuppressWeatherEffectAbAttr | null = pokemon
.getAbility() .getAbility()
.getAttrs(SuppressWeatherEffectAbAttr)[0]; .getAttrs("SuppressWeatherEffectAbAttr")[0];
if (!suppressWeatherEffectAbAttr) { if (!suppressWeatherEffectAbAttr) {
suppressWeatherEffectAbAttr = pokemon.hasPassive() suppressWeatherEffectAbAttr = pokemon.hasPassive()
? pokemon.getPassiveAbility().getAttrs(SuppressWeatherEffectAbAttr)[0] ? pokemon.getPassiveAbility().getAttrs("SuppressWeatherEffectAbAttr")[0]
: null; : null;
} }
if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable)) { if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable)) {

View File

@ -24,10 +24,7 @@ import {
applyAbAttrs, applyAbAttrs,
applyPostTerrainChangeAbAttrs, applyPostTerrainChangeAbAttrs,
applyPostWeatherChangeAbAttrs, applyPostWeatherChangeAbAttrs,
PostTerrainChangeAbAttr, } from "#app/data/abilities/apply-ab-attrs";
PostWeatherChangeAbAttr,
TerrainEventTypeChangeAbAttr,
} from "#app/data/abilities/ability";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena"; import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
@ -374,7 +371,7 @@ export class Arena {
pokemon.findAndRemoveTags( pokemon.findAndRemoveTags(
t => "weatherTypes" in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather), t => "weatherTypes" in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather),
); );
applyPostWeatherChangeAbAttrs(PostWeatherChangeAbAttr, pokemon, weather); applyPostWeatherChangeAbAttrs("PostWeatherChangeAbAttr", pokemon, weather);
}); });
return true; return true;
@ -463,8 +460,8 @@ export class Arena {
pokemon.findAndRemoveTags( pokemon.findAndRemoveTags(
t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain), t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain),
); );
applyPostTerrainChangeAbAttrs(PostTerrainChangeAbAttr, pokemon, terrain); applyPostTerrainChangeAbAttrs("PostTerrainChangeAbAttr", pokemon, terrain);
applyAbAttrs(TerrainEventTypeChangeAbAttr, pokemon, null, false); applyAbAttrs("TerrainEventTypeChangeAbAttr", pokemon, null, false);
}); });
return true; return true;

View File

@ -111,61 +111,23 @@ import { WeatherType } from "#enums/weather-type";
import { NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; import { NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagSide } from "#enums/arena-tag-side";
import type { SuppressAbilitiesTag } from "#app/data/arena-tag"; import type { SuppressAbilitiesTag } from "#app/data/arena-tag";
import type { Ability } from "#app/data/abilities/ability-class"; import type { Ability } from "#app/data/abilities/ability";
import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
import { import {
StatMultiplierAbAttr,
BlockCritAbAttr,
BonusCritAbAttr,
BypassBurnDamageReductionAbAttr,
FieldPriorityMoveImmunityAbAttr,
IgnoreOpponentStatStagesAbAttr,
MoveImmunityAbAttr,
PreDefendFullHpEndureAbAttr,
ReceivedMoveDamageMultiplierAbAttr,
StabBoostAbAttr,
StatusEffectImmunityAbAttr,
TypeImmunityAbAttr,
WeightMultiplierAbAttr,
applyAbAttrs, applyAbAttrs,
applyStatMultiplierAbAttrs, applyStatMultiplierAbAttrs,
applyPreApplyBattlerTagAbAttrs, applyPreApplyBattlerTagAbAttrs,
applyPreAttackAbAttrs, applyPreAttackAbAttrs,
applyPreDefendAbAttrs, applyPreDefendAbAttrs,
applyPreSetStatusAbAttrs, applyPreSetStatusAbAttrs,
NoFusionAbilityAbAttr,
MultCritAbAttr,
IgnoreTypeImmunityAbAttr,
DamageBoostAbAttr,
IgnoreTypeStatusEffectImmunityAbAttr,
ConditionalCritAbAttr,
applyFieldStatMultiplierAbAttrs, applyFieldStatMultiplierAbAttrs,
FieldMultiplyStatAbAttr,
AddSecondStrikeAbAttr,
UserFieldStatusEffectImmunityAbAttr,
UserFieldBattlerTagImmunityAbAttr,
BattlerTagImmunityAbAttr,
MoveTypeChangeAbAttr,
FullHpResistTypeAbAttr,
applyCheckTrappedAbAttrs, applyCheckTrappedAbAttrs,
CheckTrappedAbAttr,
InfiltratorAbAttr,
AlliedFieldDamageReductionAbAttr,
PostDamageAbAttr,
applyPostDamageAbAttrs, applyPostDamageAbAttrs,
CommanderAbAttr,
applyPostItemLostAbAttrs, applyPostItemLostAbAttrs,
PostItemLostAbAttr,
applyOnGainAbAttrs, applyOnGainAbAttrs,
PreLeaveFieldAbAttr,
applyPreLeaveFieldAbAttrs, applyPreLeaveFieldAbAttrs,
applyOnLoseAbAttrs, applyOnLoseAbAttrs,
PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr,
applyAllyStatMultiplierAbAttrs, applyAllyStatMultiplierAbAttrs,
AllyStatMultiplierAbAttr, } from "#app/data/abilities/apply-ab-attrs";
MoveAbilityBypassAbAttr,
PreSummonAbAttr,
} from "#app/data/abilities/ability";
import { allAbilities } from "#app/data/data-lists"; import { allAbilities } from "#app/data/data-lists";
import type PokemonData from "#app/system/pokemon-data"; import type PokemonData from "#app/system/pokemon-data";
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
@ -229,6 +191,7 @@ import { HitResult } from "#enums/hit-result";
import { AiType } from "#enums/ai-type"; import { AiType } from "#enums/ai-type";
import type { MoveResult } from "#enums/move-result"; import type { MoveResult } from "#enums/move-result";
import { PokemonMove } from "#app/data/moves/pokemon-move"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import type { AbAttrMap, AbAttrString } from "#app/@types/ability-types";
/** Base typeclass for damage parameter methods, used for DRY */ /** Base typeclass for damage parameter methods, used for DRY */
type damageParams = { type damageParams = {
@ -1403,7 +1366,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs("HighCritAttr", source, this, move, critStage); applyMoveAttrs("HighCritAttr", source, this, move, critStage);
globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage); globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage);
globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage); globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
applyAbAttrs(BonusCritAbAttr, source, null, false, critStage); applyAbAttrs("BonusCritAbAttr", source, null, false, critStage);
const critBoostTag = source.getTag(CritBoostTag); const critBoostTag = source.getTag(CritBoostTag);
if (critBoostTag) { if (critBoostTag) {
if (critBoostTag instanceof DragonCheerTag) { if (critBoostTag instanceof DragonCheerTag) {
@ -1464,19 +1427,27 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// The Ruin abilities here are never ignored, but they reveal themselves on summon anyway // The Ruin abilities here are never ignored, but they reveal themselves on summon anyway
const fieldApplied = new BooleanHolder(false); const fieldApplied = new BooleanHolder(false);
for (const pokemon of globalScene.getField(true)) { for (const pokemon of globalScene.getField(true)) {
applyFieldStatMultiplierAbAttrs(FieldMultiplyStatAbAttr, pokemon, stat, statValue, this, fieldApplied, simulated); applyFieldStatMultiplierAbAttrs(
"FieldMultiplyStatAbAttr",
pokemon,
stat,
statValue,
this,
fieldApplied,
simulated,
);
if (fieldApplied.value) { if (fieldApplied.value) {
break; break;
} }
} }
if (!ignoreAbility) { if (!ignoreAbility) {
applyStatMultiplierAbAttrs(StatMultiplierAbAttr, this, stat, statValue, simulated); applyStatMultiplierAbAttrs("StatMultiplierAbAttr", this, stat, statValue, simulated);
} }
const ally = this.getAlly(); const ally = this.getAlly();
if (!isNullOrUndefined(ally)) { if (!isNullOrUndefined(ally)) {
applyAllyStatMultiplierAbAttrs( applyAllyStatMultiplierAbAttrs(
AllyStatMultiplierAbAttr, "AllyStatMultiplierAbAttr",
ally, ally,
stat, stat,
statValue, statValue,
@ -2059,15 +2030,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param ignoreOverride - Whether to ignore ability changing effects; Default `false` * @param ignoreOverride - Whether to ignore ability changing effects; Default `false`
* @returns An array of all the ability attributes on this ability. * @returns An array of all the ability attributes on this ability.
*/ */
public getAbilityAttrs<T extends AbAttr = AbAttr>( public getAbilityAttrs<T extends AbAttrString>(attrType: T, canApply = true, ignoreOverride = false): AbAttrMap[T][] {
attrType: { new (...args: any[]): T }, const abilityAttrs: AbAttrMap[T][] = [];
canApply = true,
ignoreOverride = false,
): T[] {
const abilityAttrs: T[] = [];
if (!canApply || this.canApplyAbility()) { if (!canApply || this.canApplyAbility()) {
abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs<T>(attrType)); abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs(attrType));
} }
if (!canApply || this.canApplyAbility(true)) { if (!canApply || this.canApplyAbility(true)) {
@ -2152,7 +2119,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return false; return false;
} }
const ability = !passive ? this.getAbility() : this.getPassiveAbility(); const ability = !passive ? this.getAbility() : this.getPassiveAbility();
if (this.isFusion() && ability.hasAttr(NoFusionAbilityAbAttr)) { if (this.isFusion() && ability.hasAttr("NoFusionAbilityAbAttr")) {
return false; return false;
} }
const arena = globalScene?.arena; const arena = globalScene?.arena;
@ -2163,10 +2130,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return false; return false;
} }
const suppressAbilitiesTag = arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag; const suppressAbilitiesTag = arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag;
const suppressOffField = ability.hasAttr(PreSummonAbAttr); const suppressOffField = ability.hasAttr("PreSummonAbAttr");
if ((this.isOnField() || suppressOffField) && suppressAbilitiesTag && !suppressAbilitiesTag.isBeingRemoved()) { if ((this.isOnField() || suppressOffField) && suppressAbilitiesTag && !suppressAbilitiesTag.isBeingRemoved()) {
const thisAbilitySuppressing = ability.hasAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr); const thisAbilitySuppressing = ability.hasAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr");
const hasSuppressingAbility = this.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false); const hasSuppressingAbility = this.hasAbilityWithAttr("PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr", false);
// Neutralizing gas is up - suppress abilities unless they are unsuppressable or this pokemon is responsible for the gas // Neutralizing gas is up - suppress abilities unless they are unsuppressable or this pokemon is responsible for the gas
// (Balance decided that the other ability of a neutralizing gas pokemon should not be neutralized) // (Balance decided that the other ability of a neutralizing gas pokemon should not be neutralized)
// If the ability itself is neutralizing gas, don't suppress it (handled through arena tag) // If the ability itself is neutralizing gas, don't suppress it (handled through arena tag)
@ -2207,7 +2174,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param ignoreOverride Whether to ignore ability changing effects; default `false` * @param ignoreOverride Whether to ignore ability changing effects; default `false`
* @returns `true` if an ability with the given {@linkcode AbAttr} is present and active * @returns `true` if an ability with the given {@linkcode AbAttr} is present and active
*/ */
public hasAbilityWithAttr(attrType: Constructor<AbAttr>, canApply = true, ignoreOverride = false): boolean { public hasAbilityWithAttr(attrType: AbAttrString, canApply = true, ignoreOverride = false): boolean {
if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).hasAttr(attrType)) { if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).hasAttr(attrType)) {
return true; return true;
} }
@ -2229,7 +2196,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const weight = new NumberHolder(this.species.weight - weightRemoved); const weight = new NumberHolder(this.species.weight - weightRemoved);
// This will trigger the ability overlay so only call this function when necessary // This will trigger the ability overlay so only call this function when necessary
applyAbAttrs(WeightMultiplierAbAttr, this, null, false, weight); applyAbAttrs("WeightMultiplierAbAttr", this, null, false, weight);
return Math.max(minWeight, weight.value); return Math.max(minWeight, weight.value);
} }
@ -2300,7 +2267,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const opposingField = opposingFieldUnfiltered.filter(enemyPkm => enemyPkm.switchOutStatus === false); const opposingField = opposingFieldUnfiltered.filter(enemyPkm => enemyPkm.switchOutStatus === false);
for (const opponent of opposingField) { for (const opponent of opposingField) {
applyCheckTrappedAbAttrs(CheckTrappedAbAttr, opponent, trappedByAbility, this, trappedAbMessages, simulated); applyCheckTrappedAbAttrs("CheckTrappedAbAttr", opponent, trappedByAbility, this, trappedAbMessages, simulated);
} }
const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
@ -2322,7 +2289,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const moveTypeHolder = new NumberHolder(move.type); const moveTypeHolder = new NumberHolder(move.type);
applyMoveAttrs("VariableMoveTypeAttr", this, null, move, moveTypeHolder); applyMoveAttrs("VariableMoveTypeAttr", this, null, move, moveTypeHolder);
applyPreAttackAbAttrs(MoveTypeChangeAbAttr, this, null, move, simulated, moveTypeHolder); applyPreAttackAbAttrs("MoveTypeChangeAbAttr", this, null, move, simulated, moveTypeHolder);
// If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type, // If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type,
// then bypass the check for ion deluge and electrify // then bypass the check for ion deluge and electrify
@ -2387,16 +2354,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const cancelledHolder = cancelled ?? new BooleanHolder(false); const cancelledHolder = cancelled ?? new BooleanHolder(false);
if (!ignoreAbility) { if (!ignoreAbility) {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, move, cancelledHolder, simulated, typeMultiplier); applyPreDefendAbAttrs("TypeImmunityAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier);
if (!cancelledHolder.value) { if (!cancelledHolder.value) {
applyPreDefendAbAttrs(MoveImmunityAbAttr, this, source, move, cancelledHolder, simulated, typeMultiplier); applyPreDefendAbAttrs("MoveImmunityAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier);
} }
if (!cancelledHolder.value) { if (!cancelledHolder.value) {
const defendingSidePlayField = this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); const defendingSidePlayField = this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
defendingSidePlayField.forEach(p => defendingSidePlayField.forEach(p =>
applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelledHolder), applyPreDefendAbAttrs("FieldPriorityMoveImmunityAbAttr", p, source, move, cancelledHolder),
); );
} }
} }
@ -2411,7 +2378,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// Apply Tera Shell's effect to attacks after all immunities are accounted for // Apply Tera Shell's effect to attacks after all immunities are accounted for
if (!ignoreAbility && move.category !== MoveCategory.STATUS) { if (!ignoreAbility && move.category !== MoveCategory.STATUS) {
applyPreDefendAbAttrs(FullHpResistTypeAbAttr, this, source, move, cancelledHolder, simulated, typeMultiplier); applyPreDefendAbAttrs("FullHpResistTypeAbAttr", this, source, move, cancelledHolder, simulated, typeMultiplier);
} }
if (move.category === MoveCategory.STATUS && move.hitsSubstitute(source, this)) { if (move.category === MoveCategory.STATUS && move.hitsSubstitute(source, this)) {
@ -2463,8 +2430,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
if (source) { if (source) {
const ignoreImmunity = new BooleanHolder(false); const ignoreImmunity = new BooleanHolder(false);
if (source.isActive(true) && source.hasAbilityWithAttr(IgnoreTypeImmunityAbAttr)) { if (source.isActive(true) && source.hasAbilityWithAttr("IgnoreTypeImmunityAbAttr")) {
applyAbAttrs(IgnoreTypeImmunityAbAttr, source, ignoreImmunity, simulated, moveType, defType); applyAbAttrs("IgnoreTypeImmunityAbAttr", source, ignoreImmunity, simulated, moveType, defType);
} }
if (ignoreImmunity.value) { if (ignoreImmunity.value) {
if (multiplier.value === 0) { if (multiplier.value === 0) {
@ -3415,7 +3382,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
if (!ignoreOppAbility) { if (!ignoreOppAbility) {
applyAbAttrs(IgnoreOpponentStatStagesAbAttr, opponent, null, simulated, stat, ignoreStatStage); applyAbAttrs("IgnoreOpponentStatStagesAbAttr", opponent, null, simulated, stat, ignoreStatStage);
} }
if (move) { if (move) {
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, opponent, move, ignoreStatStage); applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, opponent, move, ignoreStatStage);
@ -3454,8 +3421,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const ignoreAccStatStage = new BooleanHolder(false); const ignoreAccStatStage = new BooleanHolder(false);
const ignoreEvaStatStage = new BooleanHolder(false); const ignoreEvaStatStage = new BooleanHolder(false);
applyAbAttrs(IgnoreOpponentStatStagesAbAttr, target, null, false, Stat.ACC, ignoreAccStatStage); applyAbAttrs("IgnoreOpponentStatStagesAbAttr", target, null, false, Stat.ACC, ignoreAccStatStage);
applyAbAttrs(IgnoreOpponentStatStagesAbAttr, this, null, false, Stat.EVA, ignoreEvaStatStage); applyAbAttrs("IgnoreOpponentStatStagesAbAttr", this, null, false, Stat.EVA, ignoreEvaStatStage);
applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage); applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage);
globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage); globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage);
@ -3475,16 +3442,33 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
: 3 / (3 + Math.min(targetEvaStage.value - userAccStage.value, 6)); : 3 / (3 + Math.min(targetEvaStage.value - userAccStage.value, 6));
} }
applyStatMultiplierAbAttrs(StatMultiplierAbAttr, this, Stat.ACC, accuracyMultiplier, false, sourceMove); applyStatMultiplierAbAttrs("StatMultiplierAbAttr", this, Stat.ACC, accuracyMultiplier, false, sourceMove);
const evasionMultiplier = new NumberHolder(1); const evasionMultiplier = new NumberHolder(1);
applyStatMultiplierAbAttrs(StatMultiplierAbAttr, target, Stat.EVA, evasionMultiplier); applyStatMultiplierAbAttrs("StatMultiplierAbAttr", target, Stat.EVA, evasionMultiplier);
const ally = this.getAlly(); const ally = this.getAlly();
if (!isNullOrUndefined(ally)) { if (!isNullOrUndefined(ally)) {
const ignore = this.hasAbilityWithAttr(MoveAbilityBypassAbAttr) || sourceMove.hasFlag(MoveFlags.IGNORE_ABILITIES); const ignore =
applyAllyStatMultiplierAbAttrs(AllyStatMultiplierAbAttr, ally, Stat.ACC, accuracyMultiplier, false, this, ignore); this.hasAbilityWithAttr("MoveAbilityBypassAbAttr") || sourceMove.hasFlag(MoveFlags.IGNORE_ABILITIES);
applyAllyStatMultiplierAbAttrs(AllyStatMultiplierAbAttr, ally, Stat.EVA, evasionMultiplier, false, this, ignore); applyAllyStatMultiplierAbAttrs(
"AllyStatMultiplierAbAttr",
ally,
Stat.ACC,
accuracyMultiplier,
false,
this,
ignore,
);
applyAllyStatMultiplierAbAttrs(
"AllyStatMultiplierAbAttr",
ally,
Stat.EVA,
evasionMultiplier,
false,
this,
ignore,
);
} }
return accuracyMultiplier.value / evasionMultiplier.value; return accuracyMultiplier.value / evasionMultiplier.value;
@ -3599,7 +3583,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs("CombinedPledgeStabBoostAttr", source, this, move, stabMultiplier); applyMoveAttrs("CombinedPledgeStabBoostAttr", source, this, move, stabMultiplier);
if (!ignoreSourceAbility) { if (!ignoreSourceAbility) {
applyAbAttrs(StabBoostAbAttr, source, null, simulated, stabMultiplier); applyAbAttrs("StabBoostAbAttr", source, null, simulated, stabMultiplier);
} }
if (source.isTerastallized && sourceTeraType === moveType && moveType !== PokemonType.STELLAR) { if (source.isTerastallized && sourceTeraType === moveType && moveType !== PokemonType.STELLAR) {
@ -3748,7 +3732,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
); );
if (!ignoreSourceAbility) { if (!ignoreSourceAbility) {
applyPreAttackAbAttrs( applyPreAttackAbAttrs(
AddSecondStrikeAbAttr, "AddSecondStrikeAbAttr",
source, source,
this, this,
move, move,
@ -3766,7 +3750,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** The damage multiplier when the given move critically hits */ /** The damage multiplier when the given move critically hits */
const criticalMultiplier = new NumberHolder(isCritical ? 1.5 : 1); const criticalMultiplier = new NumberHolder(isCritical ? 1.5 : 1);
applyAbAttrs(MultCritAbAttr, source, null, simulated, criticalMultiplier); applyAbAttrs("MultCritAbAttr", source, null, simulated, criticalMultiplier);
/** /**
* A multiplier for random damage spread in the range [0.85, 1] * A multiplier for random damage spread in the range [0.85, 1]
@ -3787,7 +3771,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
) { ) {
const burnDamageReductionCancelled = new BooleanHolder(false); const burnDamageReductionCancelled = new BooleanHolder(false);
if (!ignoreSourceAbility) { if (!ignoreSourceAbility) {
applyAbAttrs(BypassBurnDamageReductionAbAttr, source, burnDamageReductionCancelled, simulated); applyAbAttrs("BypassBurnDamageReductionAbAttr", source, burnDamageReductionCancelled, simulated);
} }
if (!burnDamageReductionCancelled.value) { if (!burnDamageReductionCancelled.value) {
burnMultiplier = 0.5; burnMultiplier = 0.5;
@ -3851,7 +3835,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** Doubles damage if the attacker has Tinted Lens and is using a resisted move */ /** Doubles damage if the attacker has Tinted Lens and is using a resisted move */
if (!ignoreSourceAbility) { if (!ignoreSourceAbility) {
applyPreAttackAbAttrs(DamageBoostAbAttr, source, this, move, simulated, damage); applyPreAttackAbAttrs("DamageBoostAbAttr", source, this, move, simulated, damage);
} }
/** Apply the enemy's Damage and Resistance tokens */ /** Apply the enemy's Damage and Resistance tokens */
@ -3864,12 +3848,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** Apply this Pokemon's post-calc defensive modifiers (e.g. Fur Coat) */ /** Apply this Pokemon's post-calc defensive modifiers (e.g. Fur Coat) */
if (!ignoreAbility) { if (!ignoreAbility) {
applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, move, cancelled, simulated, damage); applyPreDefendAbAttrs("ReceivedMoveDamageMultiplierAbAttr", this, source, move, cancelled, simulated, damage);
const ally = this.getAlly(); const ally = this.getAlly();
/** Additionally apply friend guard damage reduction if ally has it. */ /** Additionally apply friend guard damage reduction if ally has it. */
if (globalScene.currentBattle.double && !isNullOrUndefined(ally) && ally.isActive(true)) { if (globalScene.currentBattle.double && !isNullOrUndefined(ally) && ally.isActive(true)) {
applyPreDefendAbAttrs(AlliedFieldDamageReductionAbAttr, ally, source, move, cancelled, simulated, damage); applyPreDefendAbAttrs("AlliedFieldDamageReductionAbAttr", ally, source, move, cancelled, simulated, damage);
} }
} }
@ -3877,7 +3861,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs("ModifiedDamageAttr", source, this, move, damage); applyMoveAttrs("ModifiedDamageAttr", source, this, move, damage);
if (this.isFullHp() && !ignoreAbility) { if (this.isFullHp() && !ignoreAbility) {
applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, move, cancelled, false, damage); applyPreDefendAbAttrs("PreDefendFullHpEndureAbAttr", this, source, move, cancelled, false, damage);
} }
// debug message for when damage is applied (i.e. not simulated) // debug message for when damage is applied (i.e. not simulated)
@ -3919,13 +3903,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
isCritical.value = true; isCritical.value = true;
} }
applyMoveAttrs("CritOnlyAttr", source, this, move, isCritical); applyMoveAttrs("CritOnlyAttr", source, this, move, isCritical);
applyAbAttrs(ConditionalCritAbAttr, source, null, simulated, isCritical, this, move); applyAbAttrs("ConditionalCritAbAttr", source, null, simulated, isCritical, this, move);
if (!isCritical.value) { if (!isCritical.value) {
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))]; const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))];
isCritical.value = critChance === 1 || !globalScene.randBattleSeedInt(critChance); isCritical.value = critChance === 1 || !globalScene.randBattleSeedInt(critChance);
} }
applyAbAttrs(BlockCritAbAttr, this, null, simulated, isCritical); applyAbAttrs("BlockCritAbAttr", this, null, simulated, isCritical);
return isCritical.value; return isCritical.value;
} }
@ -4032,7 +4016,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* Multi-hits are handled in move-effect-phase.ts for PostDamageAbAttr * Multi-hits are handled in move-effect-phase.ts for PostDamageAbAttr
*/ */
if (!source || source.turnData.hitCount <= 1) { if (!source || source.turnData.hitCount <= 1) {
applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, [], source); applyPostDamageAbAttrs("PostDamageAbAttr", this, damage, this.hasPassive(), false, [], source);
} }
return damage; return damage;
} }
@ -4080,11 +4064,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const stubTag = new BattlerTag(tagType, 0, 0); const stubTag = new BattlerTag(tagType, 0, 0);
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyPreApplyBattlerTagAbAttrs(BattlerTagImmunityAbAttr, this, stubTag, cancelled, true); applyPreApplyBattlerTagAbAttrs("BattlerTagImmunityAbAttr", this, stubTag, cancelled, true);
const userField = this.getAlliedField(); const userField = this.getAlliedField();
userField.forEach(pokemon => userField.forEach(pokemon =>
applyPreApplyBattlerTagAbAttrs(UserFieldBattlerTagImmunityAbAttr, pokemon, stubTag, cancelled, true, this), applyPreApplyBattlerTagAbAttrs("UserFieldBattlerTagImmunityAbAttr", pokemon, stubTag, cancelled, true, this),
); );
return !cancelled.value; return !cancelled.value;
@ -4100,13 +4084,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const newTag = getBattlerTag(tagType, turnCount, sourceMove!, sourceId!); // TODO: are the bangs correct? const newTag = getBattlerTag(tagType, turnCount, sourceMove!, sourceId!); // TODO: are the bangs correct?
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyPreApplyBattlerTagAbAttrs(BattlerTagImmunityAbAttr, this, newTag, cancelled); applyPreApplyBattlerTagAbAttrs("BattlerTagImmunityAbAttr", this, newTag, cancelled);
if (cancelled.value) { if (cancelled.value) {
return false; return false;
} }
for (const pokemon of this.getAlliedField()) { for (const pokemon of this.getAlliedField()) {
applyPreApplyBattlerTagAbAttrs(UserFieldBattlerTagImmunityAbAttr, pokemon, newTag, cancelled, false, this); applyPreApplyBattlerTagAbAttrs("UserFieldBattlerTagImmunityAbAttr", pokemon, newTag, cancelled, false, this);
if (cancelled.value) { if (cancelled.value) {
return false; return false;
} }
@ -4626,7 +4610,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity // Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity
const cancelImmunity = new BooleanHolder(false); const cancelImmunity = new BooleanHolder(false);
if (sourcePokemon) { if (sourcePokemon) {
applyAbAttrs(IgnoreTypeStatusEffectImmunityAbAttr, sourcePokemon, cancelImmunity, false, effect, defType); applyAbAttrs("IgnoreTypeStatusEffectImmunityAbAttr", sourcePokemon, cancelImmunity, false, effect, defType);
if (cancelImmunity.value) { if (cancelImmunity.value) {
return false; return false;
} }
@ -4675,14 +4659,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyPreSetStatusAbAttrs(StatusEffectImmunityAbAttr, this, effect, cancelled, quiet); applyPreSetStatusAbAttrs("StatusEffectImmunityAbAttr", this, effect, cancelled, quiet);
if (cancelled.value) { if (cancelled.value) {
return false; return false;
} }
for (const pokemon of this.getAlliedField()) { for (const pokemon of this.getAlliedField()) {
applyPreSetStatusAbAttrs( applyPreSetStatusAbAttrs(
UserFieldStatusEffectImmunityAbAttr, "UserFieldStatusEffectImmunityAbAttr",
pokemon, pokemon,
effect, effect,
cancelled, cancelled,
@ -4839,7 +4823,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (globalScene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) { if (globalScene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) {
const bypassed = new BooleanHolder(false); const bypassed = new BooleanHolder(false);
if (attacker) { if (attacker) {
applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed); applyAbAttrs("InfiltratorAbAttr", attacker, null, false, bypassed);
} }
return !bypassed.value; return !bypassed.value;
} }
@ -4863,7 +4847,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// If this Pokemon has Commander and Dondozo as an active ally, hide this Pokemon's sprite. // If this Pokemon has Commander and Dondozo as an active ally, hide this Pokemon's sprite.
if ( if (
this.hasAbilityWithAttr(CommanderAbAttr) && this.hasAbilityWithAttr("CommanderAbAttr") &&
globalScene.currentBattle.double && globalScene.currentBattle.double &&
this.getAlly()?.species.speciesId === SpeciesId.DONDOZO this.getAlly()?.species.speciesId === SpeciesId.DONDOZO
) { ) {
@ -5388,7 +5372,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.hideInfo(); this.hideInfo();
} }
// Trigger abilities that activate upon leaving the field // Trigger abilities that activate upon leaving the field
applyPreLeaveFieldAbAttrs(PreLeaveFieldAbAttr, this); applyPreLeaveFieldAbAttrs("PreLeaveFieldAbAttr", this);
this.setSwitchOutStatus(true); this.setSwitchOutStatus(true);
globalScene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true); globalScene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true);
globalScene.field.remove(this, destroy); globalScene.field.remove(this, destroy);
@ -5448,7 +5432,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
globalScene.removeModifier(heldItem, this.isEnemy()); globalScene.removeModifier(heldItem, this.isEnemy());
} }
if (forBattle) { if (forBattle) {
applyPostItemLostAbAttrs(PostItemLostAbAttr, this, false); applyPostItemLostAbAttrs("PostItemLostAbAttr", this, false);
} }
return true; return true;

View File

@ -44,12 +44,7 @@ import {
} from "./modifier-type"; } from "./modifier-type";
import { Color, ShadowColor } from "#enums/color"; import { Color, ShadowColor } from "#enums/color";
import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters"; import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters";
import { import { applyAbAttrs, applyPostItemLostAbAttrs } from "#app/data/abilities/apply-ab-attrs";
applyAbAttrs,
applyPostItemLostAbAttrs,
CommanderAbAttr,
PostItemLostAbAttr,
} from "#app/data/abilities/ability";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
export type ModifierPredicate = (modifier: Modifier) => boolean; export type ModifierPredicate = (modifier: Modifier) => boolean;
@ -1877,7 +1872,7 @@ export class BerryModifier extends PokemonHeldItemModifier {
// munch the berry and trigger unburden-like effects // munch the berry and trigger unburden-like effects
getBerryEffectFunc(this.berryType)(pokemon); getBerryEffectFunc(this.berryType)(pokemon);
applyPostItemLostAbAttrs(PostItemLostAbAttr, pokemon, false); applyPostItemLostAbAttrs("PostItemLostAbAttr", pokemon, false);
// Update berry eaten trackers for Belch, Harvest, Cud Chew, etc. // Update berry eaten trackers for Belch, Harvest, Cud Chew, etc.
// Don't recover it if we proc berry pouch (no item duplication) // Don't recover it if we proc berry pouch (no item duplication)
@ -1965,7 +1960,7 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier {
// Reapply Commander on the Pokemon's side of the field, if applicable // Reapply Commander on the Pokemon's side of the field, if applicable
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
for (const p of field) { for (const p of field) {
applyAbAttrs(CommanderAbAttr, p, null, false); applyAbAttrs("CommanderAbAttr", p, null, false);
} }
return true; return true;
} }

View File

@ -1,9 +1,4 @@
import { import { applyAbAttrs, applyPreLeaveFieldAbAttrs } from "#app/data/abilities/apply-ab-attrs";
applyAbAttrs,
applyPreLeaveFieldAbAttrs,
PreLeaveFieldAbAttr,
RunSuccessAbAttr,
} from "#app/data/abilities/ability";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon"; import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
@ -30,10 +25,10 @@ export class AttemptRunPhase extends PokemonPhase {
this.attemptRunAway(playerField, enemyField, escapeChance); this.attemptRunAway(playerField, enemyField, escapeChance);
applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, false, escapeChance); applyAbAttrs("RunSuccessAbAttr", playerPokemon, null, false, escapeChance);
if (playerPokemon.randBattleSeedInt(100) < escapeChance.value && !this.forceFailEscape) { if (playerPokemon.randBattleSeedInt(100) < escapeChance.value && !this.forceFailEscape) {
enemyField.forEach(enemyPokemon => applyPreLeaveFieldAbAttrs(PreLeaveFieldAbAttr, enemyPokemon)); enemyField.forEach(enemyPokemon => applyPreLeaveFieldAbAttrs("PreLeaveFieldAbAttr", enemyPokemon));
globalScene.playSound("se/flee"); globalScene.playSound("se/flee");
globalScene.phaseManager.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500); globalScene.phaseManager.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);

View File

@ -1,5 +1,5 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { applyPostBattleAbAttrs, PostBattleAbAttr } from "#app/data/abilities/ability"; import { applyPostBattleAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier"; import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier";
import { BattlePhase } from "./battle-phase"; import { BattlePhase } from "./battle-phase";
@ -65,7 +65,7 @@ export class BattleEndPhase extends BattlePhase {
} }
for (const pokemon of globalScene.getPokemonAllowedInBattle()) { for (const pokemon of globalScene.getPokemonAllowedInBattle()) {
applyPostBattleAbAttrs(PostBattleAbAttr, pokemon, false, this.isVictory); applyPostBattleAbAttrs("PostBattleAbAttr", pokemon, false, this.isVictory);
} }
if (globalScene.currentBattle.moneyScattered) { if (globalScene.currentBattle.moneyScattered) {

View File

@ -1,9 +1,4 @@
import { import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
applyAbAttrs,
PreventBerryUseAbAttr,
HealFromBerryUseAbAttr,
RepeatBerryNextTurnAbAttr,
} from "#app/data/abilities/ability";
import { CommonAnim } from "#enums/move-anims-common"; import { CommonAnim } from "#enums/move-anims-common";
import { BerryUsedEvent } from "#app/events/battle-scene"; import { BerryUsedEvent } from "#app/events/battle-scene";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
@ -25,7 +20,7 @@ export class BerryPhase extends FieldPhase {
this.executeForAll(pokemon => { this.executeForAll(pokemon => {
this.eatBerries(pokemon); this.eatBerries(pokemon);
applyAbAttrs(RepeatBerryNextTurnAbAttr, pokemon, null); applyAbAttrs("RepeatBerryNextTurnAbAttr", pokemon, null);
}); });
this.end(); this.end();
@ -47,7 +42,7 @@ export class BerryPhase extends FieldPhase {
// TODO: If both opponents on field have unnerve, which one displays its message? // TODO: If both opponents on field have unnerve, which one displays its message?
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
pokemon.getOpponents().forEach(opp => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled)); pokemon.getOpponents().forEach(opp => applyAbAttrs("PreventBerryUseAbAttr", opp, cancelled));
if (cancelled.value) { if (cancelled.value) {
globalScene.phaseManager.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("abilityTriggers:preventBerryUse", { i18next.t("abilityTriggers:preventBerryUse", {
@ -75,6 +70,6 @@ export class BerryPhase extends FieldPhase {
globalScene.updateModifiers(pokemon.isPlayer()); globalScene.updateModifiers(pokemon.isPlayer());
// AbilityId.CHEEK_POUCH only works once per round of nom noms // AbilityId.CHEEK_POUCH only works once per round of nom noms
applyAbAttrs(HealFromBerryUseAbAttr, pokemon, new BooleanHolder(false)); applyAbAttrs("HealFromBerryUseAbAttr", pokemon, new BooleanHolder(false));
} }
} }

View File

@ -2,12 +2,7 @@ import { BattlerIndex } from "#enums/battler-index";
import { BattleType } from "#enums/battle-type"; import { BattleType } from "#enums/battle-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
import { import { applyAbAttrs, applyPreSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
applyAbAttrs,
SyncEncounterNatureAbAttr,
applyPreSummonAbAttrs,
PreSummonAbAttr,
} from "#app/data/abilities/ability";
import { initEncounterAnims, loadEncounterAnimAssets } from "#app/data/battle-anims"; import { initEncounterAnims, loadEncounterAnimAssets } from "#app/data/battle-anims";
import { getCharVariantFromDialogue } from "#app/data/dialogue"; import { getCharVariantFromDialogue } from "#app/data/dialogue";
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -132,7 +127,7 @@ export class EncounterPhase extends BattlePhase {
.slice(0, !battle.double ? 1 : 2) .slice(0, !battle.double ? 1 : 2)
.reverse() .reverse()
.forEach(playerPokemon => { .forEach(playerPokemon => {
applyAbAttrs(SyncEncounterNatureAbAttr, playerPokemon, null, false, battle.enemyParty[e]); applyAbAttrs("SyncEncounterNatureAbAttr", playerPokemon, null, false, battle.enemyParty[e]);
}); });
} }
} }
@ -253,7 +248,7 @@ export class EncounterPhase extends BattlePhase {
if (e < (battle.double ? 2 : 1)) { if (e < (battle.double ? 2 : 1)) {
if (battle.battleType === BattleType.WILD) { if (battle.battleType === BattleType.WILD) {
for (const pokemon of globalScene.getField()) { for (const pokemon of globalScene.getField()) {
applyPreSummonAbAttrs(PreSummonAbAttr, pokemon, []); applyPreSummonAbAttrs("PreSummonAbAttr", pokemon, []);
} }
globalScene.field.add(enemyPokemon); globalScene.field.add(enemyPokemon);
battle.seenEnemyPartyMemberIds.add(enemyPokemon.id); battle.seenEnemyPartyMemberIds.add(enemyPokemon.id);

View File

@ -5,10 +5,7 @@ import {
applyPostFaintAbAttrs, applyPostFaintAbAttrs,
applyPostKnockOutAbAttrs, applyPostKnockOutAbAttrs,
applyPostVictoryAbAttrs, applyPostVictoryAbAttrs,
PostFaintAbAttr, } from "#app/data/abilities/apply-ab-attrs";
PostKnockOutAbAttr,
PostVictoryAbAttr,
} from "#app/data/abilities/ability";
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
import { battleSpecDialogue } from "#app/data/dialogue"; import { battleSpecDialogue } from "#app/data/dialogue";
import { allMoves } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists";
@ -123,7 +120,7 @@ export class FaintPhase extends PokemonPhase {
if (pokemon.turnData.attacksReceived?.length) { if (pokemon.turnData.attacksReceived?.length) {
const lastAttack = pokemon.turnData.attacksReceived[0]; const lastAttack = pokemon.turnData.attacksReceived[0];
applyPostFaintAbAttrs( applyPostFaintAbAttrs(
PostFaintAbAttr, "PostFaintAbAttr",
pokemon, pokemon,
globalScene.getPokemonById(lastAttack.sourceId)!, globalScene.getPokemonById(lastAttack.sourceId)!,
new PokemonMove(lastAttack.move).getMove(), new PokemonMove(lastAttack.move).getMove(),
@ -131,18 +128,18 @@ export class FaintPhase extends PokemonPhase {
); // TODO: is this bang correct? ); // TODO: is this bang correct?
} else { } else {
//If killed by indirect damage, apply post-faint abilities without providing a last move //If killed by indirect damage, apply post-faint abilities without providing a last move
applyPostFaintAbAttrs(PostFaintAbAttr, pokemon); applyPostFaintAbAttrs("PostFaintAbAttr", pokemon);
} }
const alivePlayField = globalScene.getField(true); const alivePlayField = globalScene.getField(true);
for (const p of alivePlayField) { for (const p of alivePlayField) {
applyPostKnockOutAbAttrs(PostKnockOutAbAttr, p, pokemon); applyPostKnockOutAbAttrs("PostKnockOutAbAttr", p, pokemon);
} }
if (pokemon.turnData.attacksReceived?.length) { if (pokemon.turnData.attacksReceived?.length) {
const defeatSource = this.source; const defeatSource = this.source;
if (defeatSource?.isOnField()) { if (defeatSource?.isOnField()) {
applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource); applyPostVictoryAbAttrs("PostVictoryAbAttr", defeatSource);
const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move]; const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move];
const pvattrs = pvmove.getAttrs("PostVictoryStatStageChangeAttr"); const pvattrs = pvmove.getAttrs("PostVictoryStatStageChangeAttr");
if (pvattrs.length) { if (pvattrs.length) {

View File

@ -1,21 +1,12 @@
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { import {
AddSecondStrikeAbAttr,
AlwaysHitAbAttr,
applyExecutedMoveAbAttrs, applyExecutedMoveAbAttrs,
applyPostAttackAbAttrs, applyPostAttackAbAttrs,
applyPostDamageAbAttrs, applyPostDamageAbAttrs,
applyPostDefendAbAttrs, applyPostDefendAbAttrs,
applyPreAttackAbAttrs, applyPreAttackAbAttrs,
ExecutedMoveAbAttr, } from "#app/data/abilities/apply-ab-attrs";
IgnoreMoveEffectsAbAttr,
MaxMultiHitAbAttr,
PostAttackAbAttr,
PostDamageAbAttr,
PostDefendAbAttr,
ReflectStatusMoveAbAttr,
} from "#app/data/abilities/ability";
import { ConditionalProtectTag } from "#app/data/arena-tag"; import { ConditionalProtectTag } from "#app/data/arena-tag";
import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagSide } from "#enums/arena-tag-side";
import { MoveAnim } from "#app/data/battle-anims"; import { MoveAnim } from "#app/data/battle-anims";
@ -179,7 +170,7 @@ export class MoveEffectPhase extends PokemonPhase {
globalScene.phaseManager.create( globalScene.phaseManager.create(
"ShowAbilityPhase", "ShowAbilityPhase",
target.getBattlerIndex(), target.getBattlerIndex(),
target.getPassiveAbility().hasAttr(ReflectStatusMoveAbAttr), target.getPassiveAbility().hasAttr("ReflectStatusMoveAbAttr"),
), ),
); );
this.queuedPhases.push(globalScene.phaseManager.create("HideAbilityPhase")); this.queuedPhases.push(globalScene.phaseManager.create("HideAbilityPhase"));
@ -317,7 +308,7 @@ export class MoveEffectPhase extends PokemonPhase {
// Assume single target for multi hit // Assume single target for multi hit
applyMoveAttrs("MultiHitAttr", user, this.getFirstTarget() ?? null, move, hitCount); applyMoveAttrs("MultiHitAttr", user, this.getFirstTarget() ?? null, move, hitCount);
// If Parental Bond is applicable, add another hit // If Parental Bond is applicable, add another hit
applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, hitCount, null); applyPreAttackAbAttrs("AddSecondStrikeAbAttr", user, null, move, false, hitCount, null);
// If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses // If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses
globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount); globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount);
// Set the user's relevant turnData fields to reflect the final hit count // Set the user's relevant turnData fields to reflect the final hit count
@ -370,7 +361,7 @@ export class MoveEffectPhase extends PokemonPhase {
// Add to the move history entry // Add to the move history entry
if (this.firstHit) { if (this.firstHit) {
user.pushMoveHistory(this.moveHistoryEntry); user.pushMoveHistory(this.moveHistoryEntry);
applyExecutedMoveAbAttrs(ExecutedMoveAbAttr, user); applyExecutedMoveAbAttrs("ExecutedMoveAbAttr", user);
} }
try { try {
@ -434,7 +425,7 @@ export class MoveEffectPhase extends PokemonPhase {
* @returns a `Promise` intended to be passed into a `then()` call. * @returns a `Promise` intended to be passed into a `then()` call.
*/ */
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void { protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void {
applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, hitResult); applyPostDefendAbAttrs("PostDefendAbAttr", target, user, this.move, hitResult);
target.lapseTags(BattlerTagLapseType.AFTER_HIT); target.lapseTags(BattlerTagLapseType.AFTER_HIT);
} }
@ -450,7 +441,11 @@ export class MoveEffectPhase extends PokemonPhase {
return; return;
} }
if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && !this.move.hitsSubstitute(user, target)) { if (
dealsDamage &&
!target.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") &&
!this.move.hitsSubstitute(user, target)
) {
const flinched = new BooleanHolder(false); const flinched = new BooleanHolder(false);
globalScene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); globalScene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
if (flinched.value) { if (flinched.value) {
@ -580,7 +575,7 @@ export class MoveEffectPhase extends PokemonPhase {
// Strikes after the first in a multi-strike move are guaranteed to hit, // Strikes after the first in a multi-strike move are guaranteed to hit,
// unless the move is flagged to check all hits and the user does not have Skill Link. // unless the move is flagged to check all hits and the user does not have Skill Link.
if (user.turnData.hitsLeft < user.turnData.hitCount) { if (user.turnData.hitsLeft < user.turnData.hitCount) {
if (!move.hasFlag(MoveFlags.CHECK_ALL_HITS) || user.hasAbilityWithAttr(MaxMultiHitAbAttr)) { if (!move.hasFlag(MoveFlags.CHECK_ALL_HITS) || user.hasAbilityWithAttr("MaxMultiHitAbAttr")) {
return [HitCheckResult.HIT, effectiveness]; return [HitCheckResult.HIT, effectiveness];
} }
} }
@ -626,7 +621,7 @@ export class MoveEffectPhase extends PokemonPhase {
if (!user) { if (!user) {
return false; return false;
} }
if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) { if (user.hasAbilityWithAttr("AlwaysHitAbAttr") || target.hasAbilityWithAttr("AlwaysHitAbAttr")) {
return true; return true;
} }
if (this.move.hasAttr("ToxicAccuracyAttr") && user.isOfType(PokemonType.POISON)) { if (this.move.hasAttr("ToxicAccuracyAttr") && user.isOfType(PokemonType.POISON)) {
@ -789,7 +784,7 @@ export class MoveEffectPhase extends PokemonPhase {
// Multi-hit check for Wimp Out/Emergency Exit // Multi-hit check for Wimp Out/Emergency Exit
if (user.turnData.hitCount > 1) { if (user.turnData.hitCount > 1) {
applyPostDamageAbAttrs(PostDamageAbAttr, target, 0, target.hasPassive(), false, [], user); applyPostDamageAbAttrs("PostDamageAbAttr", target, 0, target.hasPassive(), false, [], user);
} }
} }
} }
@ -983,7 +978,7 @@ export class MoveEffectPhase extends PokemonPhase {
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, false); this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, false);
this.applyHeldItemFlinchCheck(user, target, dealsDamage); this.applyHeldItemFlinchCheck(user, target, dealsDamage);
this.applyOnGetHitAbEffects(user, target, hitResult); this.applyOnGetHitAbEffects(user, target, hitResult);
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move, hitResult); applyPostAttackAbAttrs("PostAttackAbAttr", user, target, this.move, hitResult);
// We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens // We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens
if (!user.isPlayer() && this.move.is("AttackMove")) { if (!user.isPlayer() && this.move.is("AttackMove")) {

View File

@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene";
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
import { PokemonPhase } from "./pokemon-phase"; import { PokemonPhase } from "./pokemon-phase";
import type { BattlerIndex } from "#enums/battler-index"; import type { BattlerIndex } from "#enums/battler-index";
import { applyPostSummonAbAttrs, PostSummonRemoveEffectAbAttr } from "#app/data/abilities/ability"; import { applyPostSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
export class MoveEndPhase extends PokemonPhase { export class MoveEndPhase extends PokemonPhase {
@ -30,7 +30,7 @@ export class MoveEndPhase extends PokemonPhase {
// Remove effects which were set on a Pokemon which removes them on summon (i.e. via Mold Breaker) // Remove effects which were set on a Pokemon which removes them on summon (i.e. via Mold Breaker)
for (const target of this.targets) { for (const target of this.targets) {
if (target) { if (target) {
applyPostSummonAbAttrs(PostSummonRemoveEffectAbAttr, target); applyPostSummonAbAttrs("PostSummonRemoveEffectAbAttr", target);
} }
} }

View File

@ -1,16 +1,6 @@
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs } from "#app/data/abilities/apply-ab-attrs";
applyAbAttrs,
applyPostMoveUsedAbAttrs,
applyPreAttackAbAttrs,
BlockRedirectAbAttr,
IncreasePpAbAttr,
PokemonTypeChangeAbAttr,
PostMoveUsedAbAttr,
RedirectMoveAbAttr,
ReduceStatusEffectDurationAbAttr,
} from "#app/data/abilities/ability";
import type { DelayedAttackTag } from "#app/data/arena-tag"; import type { DelayedAttackTag } from "#app/data/arena-tag";
import { CommonAnim } from "#enums/move-anims-common"; import { CommonAnim } from "#enums/move-anims-common";
import { CenterOfAttentionTag } from "#app/data/battler-tags"; import { CenterOfAttentionTag } from "#app/data/battler-tags";
@ -228,7 +218,7 @@ export class MovePhase extends BattlePhase {
applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove()); applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove());
const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0); const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0);
applyAbAttrs( applyAbAttrs(
ReduceStatusEffectDurationAbAttr, "ReduceStatusEffectDurationAbAttr",
this.pokemon, this.pokemon,
null, null,
false, false,
@ -396,7 +386,7 @@ export class MovePhase extends BattlePhase {
*/ */
if (success) { if (success) {
const move = this.move.getMove(); const move = this.move.getMove();
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, move); applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, move);
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
"MoveEffectPhase", "MoveEffectPhase",
this.pokemon.getBattlerIndex(), this.pokemon.getBattlerIndex(),
@ -407,7 +397,7 @@ export class MovePhase extends BattlePhase {
); );
} else { } else {
if ([MoveId.ROAR, MoveId.WHIRLWIND, MoveId.TRICK_OR_TREAT, MoveId.FORESTS_CURSE].includes(this.move.moveId)) { if ([MoveId.ROAR, MoveId.WHIRLWIND, MoveId.TRICK_OR_TREAT, MoveId.FORESTS_CURSE].includes(this.move.moveId)) {
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove()); applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, this.move.getMove());
} }
this.pokemon.pushMoveHistory({ this.pokemon.pushMoveHistory({
@ -437,7 +427,7 @@ export class MovePhase extends BattlePhase {
// Note that the `!this.followUp` check here prevents an infinite Dancer loop. // Note that the `!this.followUp` check here prevents an infinite Dancer loop.
if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !this.followUp) { if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !this.followUp) {
globalScene.getField(true).forEach(pokemon => { globalScene.getField(true).forEach(pokemon => {
applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets); applyPostMoveUsedAbAttrs("PostMoveUsedAbAttr", pokemon, this.move, this.pokemon, this.targets);
}); });
} }
} }
@ -449,7 +439,7 @@ export class MovePhase extends BattlePhase {
if (move.applyConditions(this.pokemon, targets[0], move)) { if (move.applyConditions(this.pokemon, targets[0], move)) {
// Protean and Libero apply on the charging turn of charge moves // Protean and Libero apply on the charging turn of charge moves
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove()); applyPreAttackAbAttrs("PokemonTypeChangeAbAttr", this.pokemon, null, this.move.getMove());
this.showMoveText(); this.showMoveText();
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
@ -498,7 +488,7 @@ export class MovePhase extends BattlePhase {
public getPpIncreaseFromPressure(targets: Pokemon[]): number { public getPpIncreaseFromPressure(targets: Pokemon[]): number {
const foesWithPressure = this.pokemon const foesWithPressure = this.pokemon
.getOpponents() .getOpponents()
.filter(o => targets.includes(o) && o.isActive(true) && o.hasAbilityWithAttr(IncreasePpAbAttr)); .filter(o => targets.includes(o) && o.isActive(true) && o.hasAbilityWithAttr("IncreasePpAbAttr"));
return foesWithPressure.length; return foesWithPressure.length;
} }
@ -516,7 +506,9 @@ export class MovePhase extends BattlePhase {
globalScene globalScene
.getField(true) .getField(true)
.filter(p => p !== this.pokemon) .filter(p => p !== this.pokemon)
.forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, redirectTarget, this.pokemon)); .forEach(p =>
applyAbAttrs("RedirectMoveAbAttr", p, null, false, this.move.moveId, redirectTarget, this.pokemon),
);
/** `true` if an Ability is responsible for redirecting the move to another target; `false` otherwise */ /** `true` if an Ability is responsible for redirecting the move to another target; `false` otherwise */
let redirectedByAbility = currentTarget !== redirectTarget.value; let redirectedByAbility = currentTarget !== redirectTarget.value;
@ -545,17 +537,17 @@ export class MovePhase extends BattlePhase {
} }
}); });
if (this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr)) { if (this.pokemon.hasAbilityWithAttr("BlockRedirectAbAttr")) {
redirectTarget.value = currentTarget; redirectTarget.value = currentTarget;
// TODO: Ability displays should be handled by the ability // TODO: Ability displays should be handled by the ability
globalScene.phaseManager.queueAbilityDisplay( globalScene.phaseManager.queueAbilityDisplay(
this.pokemon, this.pokemon,
this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr), this.pokemon.getPassiveAbility().hasAttr("BlockRedirectAbAttr"),
true, true,
); );
globalScene.phaseManager.queueAbilityDisplay( globalScene.phaseManager.queueAbilityDisplay(
this.pokemon, this.pokemon,
this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr), this.pokemon.getPassiveAbility().hasAttr("BlockRedirectAbAttr"),
false, false,
); );
} }

View File

@ -1,5 +1,5 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { applyAbAttrs, PostBiomeChangeAbAttr } from "#app/data/abilities/ability"; import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { getRandomWeatherType } from "#app/data/weather"; import { getRandomWeatherType } from "#app/data/weather";
import { NextEncounterPhase } from "./next-encounter-phase"; import { NextEncounterPhase } from "./next-encounter-phase";
@ -14,7 +14,7 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase {
if (pokemon) { if (pokemon) {
pokemon.resetBattleAndWaveData(); pokemon.resetBattleAndWaveData();
if (pokemon.isOnField()) { if (pokemon.isOnField()) {
applyAbAttrs(PostBiomeChangeAbAttr, pokemon, null); applyAbAttrs("PostBiomeChangeAbAttr", pokemon, null);
} }
} }
} }

View File

@ -8,7 +8,7 @@ import type Pokemon from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { PokemonPhase } from "./pokemon-phase"; import { PokemonPhase } from "./pokemon-phase";
import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import { applyPostSetStatusAbAttrs, PostSetStatusAbAttr } from "#app/data/abilities/ability"; import { applyPostSetStatusAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { isNullOrUndefined } from "#app/utils/common"; import { isNullOrUndefined } from "#app/utils/common";
export class ObtainStatusEffectPhase extends PokemonPhase { export class ObtainStatusEffectPhase extends PokemonPhase {
@ -53,7 +53,7 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeStatusEffectTrigger, true); globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeStatusEffectTrigger, true);
// If mold breaker etc was used to set this status, it shouldn't apply to abilities activated afterwards // If mold breaker etc was used to set this status, it shouldn't apply to abilities activated afterwards
globalScene.arena.setIgnoreAbilities(false); globalScene.arena.setIgnoreAbilities(false);
applyPostSetStatusAbAttrs(PostSetStatusAbAttr, pokemon, this.statusEffect, this.sourcePokemon); applyPostSetStatusAbAttrs("PostSetStatusAbAttr", pokemon, this.statusEffect, this.sourcePokemon);
} }
this.end(); this.end();
}); });

View File

@ -1,5 +1,5 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { applyAbAttrs, applyPostSummonAbAttrs, CommanderAbAttr, PostSummonAbAttr } from "#app/data/abilities/ability"; import { applyAbAttrs, applyPostSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { ArenaTrapTag } from "#app/data/arena-tag"; import { ArenaTrapTag } from "#app/data/arena-tag";
import { StatusEffect } from "#app/enums/status-effect"; import { StatusEffect } from "#app/enums/status-effect";
import { PokemonPhase } from "./pokemon-phase"; import { PokemonPhase } from "./pokemon-phase";
@ -26,10 +26,10 @@ export class PostSummonPhase extends PokemonPhase {
pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON); pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON);
} }
applyPostSummonAbAttrs(PostSummonAbAttr, pokemon); applyPostSummonAbAttrs("PostSummonAbAttr", pokemon);
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
for (const p of field) { for (const p of field) {
applyAbAttrs(CommanderAbAttr, p, null, false); applyAbAttrs("CommanderAbAttr", p, null, false);
} }
this.end(); this.end();

View File

@ -1,13 +1,6 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { BattlerIndex } from "#enums/battler-index"; import type { BattlerIndex } from "#enums/battler-index";
import { import { applyAbAttrs, applyPostDamageAbAttrs } from "#app/data/abilities/apply-ab-attrs";
applyAbAttrs,
applyPostDamageAbAttrs,
BlockNonDirectDamageAbAttr,
BlockStatusDamageAbAttr,
PostDamageAbAttr,
ReduceBurnDamageAbAttr,
} from "#app/data/abilities/ability";
import { CommonBattleAnim } from "#app/data/battle-anims"; import { CommonBattleAnim } from "#app/data/battle-anims";
import { CommonAnim } from "#enums/move-anims-common"; import { CommonAnim } from "#enums/move-anims-common";
import { getStatusEffectActivationText } from "#app/data/status-effect"; import { getStatusEffectActivationText } from "#app/data/status-effect";
@ -29,8 +22,8 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn() && !pokemon.switchOutStatus) { if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn() && !pokemon.switchOutStatus) {
pokemon.status.incrementTurn(); pokemon.status.incrementTurn();
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
applyAbAttrs(BlockStatusDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockStatusDamageAbAttr", pokemon, cancelled);
if (!cancelled.value) { if (!cancelled.value) {
globalScene.phaseManager.queueMessage( globalScene.phaseManager.queueMessage(
@ -46,14 +39,14 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
break; break;
case StatusEffect.BURN: case StatusEffect.BURN:
damage.value = Math.max(pokemon.getMaxHp() >> 4, 1); damage.value = Math.max(pokemon.getMaxHp() >> 4, 1);
applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, false, damage); applyAbAttrs("ReduceBurnDamageAbAttr", pokemon, null, false, damage);
break; break;
} }
if (damage.value) { if (damage.value) {
// Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ... // Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ...
globalScene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true)); globalScene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true));
pokemon.updateInfo(); pokemon.updateInfo();
applyPostDamageAbAttrs(PostDamageAbAttr, pokemon, damage.value, pokemon.hasPassive(), false, []); applyPostDamageAbAttrs("PostDamageAbAttr", pokemon, damage.value, pokemon.hasPassive(), false, []);
} }
new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(false, () => this.end()); new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(false, () => this.end());
} else { } else {

View File

@ -12,12 +12,7 @@ import type Pokemon from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { BattlePhase } from "./battle-phase"; import { BattlePhase } from "./battle-phase";
import type { MovePhase } from "./move-phase"; import type { MovePhase } from "./move-phase";
import { import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
applyAbAttrs,
ClearTerrainAbAttr,
ClearWeatherAbAttr,
PostTeraFormChangeStatChangeAbAttr,
} from "#app/data/abilities/ability";
export class QuietFormChangePhase extends BattlePhase { export class QuietFormChangePhase extends BattlePhase {
public readonly phaseName = "QuietFormChangePhase"; public readonly phaseName = "QuietFormChangePhase";
@ -185,9 +180,9 @@ export class QuietFormChangePhase extends BattlePhase {
} }
} }
if (this.formChange.trigger instanceof SpeciesFormChangeTeraTrigger) { if (this.formChange.trigger instanceof SpeciesFormChangeTeraTrigger) {
applyAbAttrs(PostTeraFormChangeStatChangeAbAttr, this.pokemon, null); applyAbAttrs("PostTeraFormChangeStatChangeAbAttr", this.pokemon, null);
applyAbAttrs(ClearWeatherAbAttr, this.pokemon, null); applyAbAttrs("ClearWeatherAbAttr", this.pokemon, null);
applyAbAttrs(ClearTerrainAbAttr, this.pokemon, null); applyAbAttrs("ClearTerrainAbAttr", this.pokemon, null);
} }
super.end(); super.end();

View File

@ -4,13 +4,7 @@ import {
applyAbAttrs, applyAbAttrs,
applyPostStatStageChangeAbAttrs, applyPostStatStageChangeAbAttrs,
applyPreStatStageChangeAbAttrs, applyPreStatStageChangeAbAttrs,
ConditionalUserFieldProtectStatAbAttr, } from "#app/data/abilities/apply-ab-attrs";
PostStatStageChangeAbAttr,
ProtectStatAbAttr,
ReflectStatStageChangeAbAttr,
StatStageChangeCopyAbAttr,
StatStageChangeMultiplierAbAttr,
} from "#app/data/abilities/ability";
import { MistTag } from "#app/data/arena-tag"; import { MistTag } from "#app/data/arena-tag";
import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagSide } from "#enums/arena-tag-side";
import type { ArenaTag } from "#app/data/arena-tag"; import type { ArenaTag } from "#app/data/arena-tag";
@ -132,7 +126,7 @@ export class StatStageChangePhase extends PokemonPhase {
const stages = new NumberHolder(this.stages); const stages = new NumberHolder(this.stages);
if (!this.ignoreAbilities) { if (!this.ignoreAbilities) {
applyAbAttrs(StatStageChangeMultiplierAbAttr, pokemon, null, false, stages); applyAbAttrs("StatStageChangeMultiplierAbAttr", pokemon, null, false, stages);
} }
let simulate = false; let simulate = false;
@ -152,9 +146,9 @@ export class StatStageChangePhase extends PokemonPhase {
} }
if (!cancelled.value && !this.selfTarget && stages.value < 0) { if (!cancelled.value && !this.selfTarget && stages.value < 0) {
applyPreStatStageChangeAbAttrs(ProtectStatAbAttr, pokemon, stat, cancelled, simulate); applyPreStatStageChangeAbAttrs("ProtectStatAbAttr", pokemon, stat, cancelled, simulate);
applyPreStatStageChangeAbAttrs( applyPreStatStageChangeAbAttrs(
ConditionalUserFieldProtectStatAbAttr, "ConditionalUserFieldProtectStatAbAttr",
pokemon, pokemon,
stat, stat,
cancelled, cancelled,
@ -164,7 +158,7 @@ export class StatStageChangePhase extends PokemonPhase {
const ally = pokemon.getAlly(); const ally = pokemon.getAlly();
if (!isNullOrUndefined(ally)) { if (!isNullOrUndefined(ally)) {
applyPreStatStageChangeAbAttrs( applyPreStatStageChangeAbAttrs(
ConditionalUserFieldProtectStatAbAttr, "ConditionalUserFieldProtectStatAbAttr",
ally, ally,
stat, stat,
cancelled, cancelled,
@ -180,7 +174,7 @@ export class StatStageChangePhase extends PokemonPhase {
!this.comingFromMirrorArmorUser !this.comingFromMirrorArmorUser
) { ) {
applyPreStatStageChangeAbAttrs( applyPreStatStageChangeAbAttrs(
ReflectStatStageChangeAbAttr, "ReflectStatStageChangeAbAttr",
pokemon, pokemon,
stat, stat,
cancelled, cancelled,
@ -228,11 +222,17 @@ export class StatStageChangePhase extends PokemonPhase {
if (stages.value > 0 && this.canBeCopied) { if (stages.value > 0 && this.canBeCopied) {
for (const opponent of pokemon.getOpponents()) { for (const opponent of pokemon.getOpponents()) {
applyAbAttrs(StatStageChangeCopyAbAttr, opponent, null, false, this.stats, stages.value); applyAbAttrs("StatStageChangeCopyAbAttr", opponent, null, false, this.stats, stages.value);
} }
} }
applyPostStatStageChangeAbAttrs(PostStatStageChangeAbAttr, pokemon, filteredStats, this.stages, this.selfTarget); applyPostStatStageChangeAbAttrs(
"PostStatStageChangeAbAttr",
pokemon,
filteredStats,
this.stages,
this.selfTarget,
);
// Look for any other stat change phases; if this is the last one, do White Herb check // Look for any other stat change phases; if this is the last one, do White Herb check
const existingPhase = globalScene.phaseManager.findPhase( const existingPhase = globalScene.phaseManager.findPhase(

View File

@ -10,7 +10,7 @@ import { getPokemonNameWithAffix } from "#app/messages";
import i18next from "i18next"; import i18next from "i18next";
import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase"; import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { applyPreSummonAbAttrs, PreSummonAbAttr } from "#app/data/abilities/ability"; import { applyPreSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
export class SummonPhase extends PartyMemberPokemonPhase { export class SummonPhase extends PartyMemberPokemonPhase {
@ -27,7 +27,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
start() { start() {
super.start(); super.start();
applyPreSummonAbAttrs(PreSummonAbAttr, this.getPokemon()); applyPreSummonAbAttrs("PreSummonAbAttr", this.getPokemon());
this.preSummon(); this.preSummon();
} }

View File

@ -1,11 +1,5 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { import { applyPreSummonAbAttrs, applyPreSwitchOutAbAttrs } from "#app/data/abilities/apply-ab-attrs";
applyPreSummonAbAttrs,
applyPreSwitchOutAbAttrs,
PostDamageForceSwitchAbAttr,
PreSummonAbAttr,
PreSwitchOutAbAttr,
} from "#app/data/abilities/ability";
import { allMoves } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists";
import { getPokeballTintColor } from "#app/data/pokeball"; import { getPokeballTintColor } from "#app/data/pokeball";
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
@ -130,8 +124,8 @@ export class SwitchSummonPhase extends SummonPhase {
switchedInPokemon.resetSummonData(); switchedInPokemon.resetSummonData();
switchedInPokemon.loadAssets(true); switchedInPokemon.loadAssets(true);
applyPreSummonAbAttrs(PreSummonAbAttr, switchedInPokemon); applyPreSummonAbAttrs("PreSummonAbAttr", switchedInPokemon);
applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon); applyPreSwitchOutAbAttrs("PreSwitchOutAbAttr", this.lastPokemon);
if (!switchedInPokemon) { if (!switchedInPokemon) {
this.end(); this.end();
return; return;
@ -215,7 +209,7 @@ export class SwitchSummonPhase extends SummonPhase {
const lastPokemonIsForceSwitchedAndNotFainted = const lastPokemonIsForceSwitchedAndNotFainted =
lastUsedMove?.hasAttr("ForceSwitchOutAttr") && !this.lastPokemon.isFainted(); lastUsedMove?.hasAttr("ForceSwitchOutAttr") && !this.lastPokemon.isFainted();
const lastPokemonHasForceSwitchAbAttr = const lastPokemonHasForceSwitchAbAttr =
this.lastPokemon.hasAbilityWithAttr(PostDamageForceSwitchAbAttr) && !this.lastPokemon.isFainted(); this.lastPokemon.hasAbilityWithAttr("PostDamageForceSwitchAbAttr") && !this.lastPokemon.isFainted();
// Compensate for turn spent summoning/forced switch if switched out pokemon is not fainted. // Compensate for turn spent summoning/forced switch if switched out pokemon is not fainted.
// Needed as we increment turn counters in `TurnEndPhase`. // Needed as we increment turn counters in `TurnEndPhase`.

View File

@ -1,4 +1,4 @@
import { applyPostTurnAbAttrs, PostTurnAbAttr } from "#app/data/abilities/ability"; import { applyPostTurnAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
import { TerrainType } from "#app/data/terrain"; import { TerrainType } from "#app/data/terrain";
import { WeatherType } from "#app/enums/weather-type"; import { WeatherType } from "#app/enums/weather-type";
@ -49,7 +49,7 @@ export class TurnEndPhase extends FieldPhase {
globalScene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon); globalScene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon);
} }
applyPostTurnAbAttrs(PostTurnAbAttr, pokemon); applyPostTurnAbAttrs("PostTurnAbAttr", pokemon);
} }
globalScene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon); globalScene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon);

View File

@ -1,4 +1,4 @@
import { applyAbAttrs, BypassSpeedChanceAbAttr, PreventBypassSpeedChanceAbAttr } from "#app/data/abilities/ability"; import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { allMoves } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { Stat } from "#app/enums/stat"; import { Stat } from "#app/enums/stat";
@ -66,8 +66,8 @@ export class TurnStartPhase extends FieldPhase {
globalScene.getField(true).map(p => { globalScene.getField(true).map(p => {
const bypassSpeed = new BooleanHolder(false); const bypassSpeed = new BooleanHolder(false);
const canCheckHeldItems = new BooleanHolder(true); const canCheckHeldItems = new BooleanHolder(true);
applyAbAttrs(BypassSpeedChanceAbAttr, p, null, false, bypassSpeed); applyAbAttrs("BypassSpeedChanceAbAttr", p, null, false, bypassSpeed);
applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, false, bypassSpeed, canCheckHeldItems); applyAbAttrs("PreventBypassSpeedChanceAbAttr", p, null, false, bypassSpeed, canCheckHeldItems);
if (canCheckHeldItems.value) { if (canCheckHeldItems.value) {
globalScene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed); globalScene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed);
} }

View File

@ -1,13 +1,9 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { import {
applyPreWeatherEffectAbAttrs, applyPreWeatherEffectAbAttrs,
SuppressWeatherEffectAbAttr,
PreWeatherDamageAbAttr,
applyAbAttrs, applyAbAttrs,
BlockNonDirectDamageAbAttr,
applyPostWeatherLapseAbAttrs, applyPostWeatherLapseAbAttrs,
PostWeatherLapseAbAttr, } from "#app/data/abilities/apply-ab-attrs";
} from "#app/data/abilities/ability";
import { CommonAnim } from "#enums/move-anims-common"; import { CommonAnim } from "#enums/move-anims-common";
import type { Weather } from "#app/data/weather"; import type { Weather } from "#app/data/weather";
import { getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather"; import { getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather";
@ -45,15 +41,15 @@ export class WeatherEffectPhase extends CommonAnimPhase {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
this.executeForAll((pokemon: Pokemon) => this.executeForAll((pokemon: Pokemon) =>
applyPreWeatherEffectAbAttrs(SuppressWeatherEffectAbAttr, pokemon, this.weather, cancelled), applyPreWeatherEffectAbAttrs("SuppressWeatherEffectAbAttr", pokemon, this.weather, cancelled),
); );
if (!cancelled.value) { if (!cancelled.value) {
const inflictDamage = (pokemon: Pokemon) => { const inflictDamage = (pokemon: Pokemon) => {
const cancelled = new BooleanHolder(false); const cancelled = new BooleanHolder(false);
applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather, cancelled); applyPreWeatherEffectAbAttrs("PreWeatherDamageAbAttr", pokemon, this.weather, cancelled);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
if ( if (
cancelled.value || cancelled.value ||
@ -84,7 +80,7 @@ export class WeatherEffectPhase extends CommonAnimPhase {
globalScene.ui.showText(getWeatherLapseMessage(this.weather.weatherType) ?? "", null, () => { globalScene.ui.showText(getWeatherLapseMessage(this.weather.weatherType) ?? "", null, () => {
this.executeForAll((pokemon: Pokemon) => { this.executeForAll((pokemon: Pokemon) => {
if (!pokemon.switchOutStatus) { if (!pokemon.switchOutStatus) {
applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather); applyPostWeatherLapseAbAttrs("PostWeatherLapseAbAttr", pokemon, this.weather);
} }
}); });

View File

@ -8,7 +8,7 @@ import i18next from "i18next";
import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
import { starterColors } from "#app/global-vars/starter-colors"; import { starterColors } from "#app/global-vars/starter-colors";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { Ability } from "#app/data/abilities/ability-class"; import type { Ability } from "#app/data/abilities/ability";
import { allAbilities } from "#app/data/data-lists"; import { allAbilities } from "#app/data/data-lists";
import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { speciesEggMoves } from "#app/data/balance/egg-moves";
import { GrowthRate, getGrowthRateColor } from "#app/data/exp"; import { GrowthRate, getGrowthRateColor } from "#app/data/exp";

View File

@ -33,7 +33,7 @@ import { loggedInUser } from "#app/account";
import type { Variant } from "#app/sprites/variant"; import type { Variant } from "#app/sprites/variant";
import { getVariantTint } from "#app/sprites/variant"; import { getVariantTint } from "#app/sprites/variant";
import { Button } from "#enums/buttons"; import { Button } from "#enums/buttons";
import type { Ability } from "#app/data/abilities/ability-class"; import type { Ability } from "#app/data/abilities/ability";
import i18next from "i18next"; import i18next from "i18next";
import { modifierSortFunc } from "#app/modifier/modifier"; import { modifierSortFunc } from "#app/modifier/modifier";
import { PlayerGender } from "#enums/player-gender"; import { PlayerGender } from "#enums/player-gender";

View File

@ -6,7 +6,6 @@ import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi, type MockInstance } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi, type MockInstance } from "vitest";
import { isNullOrUndefined } from "#app/utils/common"; import { isNullOrUndefined } from "#app/utils/common";
import { PostTurnResetStatusAbAttr } from "#app/data/abilities/ability";
import { allAbilities } from "#app/data/data-lists"; import { allAbilities } from "#app/data/data-lists";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
@ -38,7 +37,7 @@ describe("Abilities - Healer", () => {
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);
healerAttr = allAbilities[AbilityId.HEALER].getAttrs(PostTurnResetStatusAbAttr)[0]; healerAttr = allAbilities[AbilityId.HEALER].getAttrs("PostTurnResetStatusAbAttr")[0];
healerAttrSpy = vi healerAttrSpy = vi
.spyOn(healerAttr, "getCondition") .spyOn(healerAttr, "getCondition")
.mockReturnValue((pokemon: Pokemon) => !isNullOrUndefined(pokemon.getAlly())); .mockReturnValue((pokemon: Pokemon) => !isNullOrUndefined(pokemon.getAlly()));

View File

@ -1,7 +1,6 @@
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
import type { CommandPhase } from "#app/phases/command-phase"; import type { CommandPhase } from "#app/phases/command-phase";
import { Command } from "#enums/command"; import { Command } from "#enums/command";
import { PostSummonWeatherChangeAbAttr } from "#app/data/abilities/ability";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
@ -178,7 +177,7 @@ describe("Abilities - Neutralizing Gas", () => {
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
const enemy = game.scene.getEnemyPokemon()!; const enemy = game.scene.getEnemyPokemon()!;
const weatherChangeAttr = enemy.getAbilityAttrs(PostSummonWeatherChangeAbAttr, false)[0]; const weatherChangeAttr = enemy.getAbilityAttrs("PostSummonWeatherChangeAbAttr", false)[0];
vi.spyOn(weatherChangeAttr, "applyPostSummon"); vi.spyOn(weatherChangeAttr, "applyPostSummon");
expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeDefined(); expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeDefined();

View File

@ -9,7 +9,6 @@ import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants";
import { allAbilities } from "#app/data/data-lists"; import { allAbilities } from "#app/data/data-lists";
import { MoveTypeChangeAbAttr } from "#app/data/abilities/ability";
import { toDmgValue } from "#app/utils/common"; import { toDmgValue } from "#app/utils/common";
/** /**
@ -160,7 +159,7 @@ describe.each([
// get the power boost from the ability so we can compare it to the item // get the power boost from the ability so we can compare it to the item
// @ts-expect-error power multiplier is private // @ts-expect-error power multiplier is private
const boost = allAbilities[ab]?.getAttrs(MoveTypeChangeAbAttr)[0]?.powerMultiplier; const boost = allAbilities[ab]?.getAttrs("MoveTypeChangeAbAttr")[0]?.powerMultiplier;
expect(boost, "power boost should be defined").toBeDefined(); expect(boost, "power boost should be defined").toBeDefined();
const powerSpy = vi.spyOn(testMoveInstance, "calculateBattlePower"); const powerSpy = vi.spyOn(testMoveInstance, "calculateBattlePower");
@ -177,7 +176,7 @@ describe.each([
// get the power boost from the ability so we can compare it to the item // get the power boost from the ability so we can compare it to the item
// @ts-expect-error power multiplier is private // @ts-expect-error power multiplier is private
const boost = allAbilities[ab]?.getAttrs(MoveTypeChangeAbAttr)[0]?.powerMultiplier; const boost = allAbilities[ab]?.getAttrs("MoveTypeChangeAbAttr")[0]?.powerMultiplier;
expect(boost, "power boost should be defined").toBeDefined(); expect(boost, "power boost should be defined").toBeDefined();
const tackle = allMoves[MoveId.TACKLE]; const tackle = allMoves[MoveId.TACKLE];

View File

@ -1,4 +1,3 @@
import { BypassSpeedChanceAbAttr } from "#app/data/abilities/ability";
import { allAbilities } from "#app/data/data-lists"; import { allAbilities } from "#app/data/data-lists";
import { FaintPhase } from "#app/phases/faint-phase"; import { FaintPhase } from "#app/phases/faint-phase";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
@ -35,9 +34,11 @@ describe("Abilities - Quick Draw", () => {
game.override.enemyAbility(AbilityId.BALL_FETCH); game.override.enemyAbility(AbilityId.BALL_FETCH);
game.override.enemyMoveset([MoveId.TACKLE]); game.override.enemyMoveset([MoveId.TACKLE]);
vi.spyOn(allAbilities[AbilityId.QUICK_DRAW].getAttrs(BypassSpeedChanceAbAttr)[0], "chance", "get").mockReturnValue( vi.spyOn(
100, allAbilities[AbilityId.QUICK_DRAW].getAttrs("BypassSpeedChanceAbAttr")[0],
); "chance",
"get",
).mockReturnValue(100);
}); });
test("makes pokemon going first in its priority bracket", async () => { test("makes pokemon going first in its priority bracket", async () => {

View File

@ -1,4 +1,3 @@
import { StatMultiplierAbAttr } from "#app/data/abilities/ability";
import { allAbilities } from "#app/data/data-lists"; import { allAbilities } from "#app/data/data-lists";
import { CommandPhase } from "#app/phases/command-phase"; import { CommandPhase } from "#app/phases/command-phase";
import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase";
@ -46,7 +45,7 @@ describe("Abilities - Sand Veil", () => {
vi.spyOn(leadPokemon[0], "getAbility").mockReturnValue(allAbilities[AbilityId.SAND_VEIL]); vi.spyOn(leadPokemon[0], "getAbility").mockReturnValue(allAbilities[AbilityId.SAND_VEIL]);
const sandVeilAttr = allAbilities[AbilityId.SAND_VEIL].getAttrs(StatMultiplierAbAttr)[0]; const sandVeilAttr = allAbilities[AbilityId.SAND_VEIL].getAttrs("StatMultiplierAbAttr")[0];
vi.spyOn(sandVeilAttr, "applyStatStage").mockImplementation( vi.spyOn(sandVeilAttr, "applyStatStage").mockImplementation(
(_pokemon, _passive, _simulated, stat, statValue, _args) => { (_pokemon, _passive, _simulated, stat, statValue, _args) => {
if (stat === Stat.EVA && game.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) { if (stat === Stat.EVA && game.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) {

View File

@ -1,10 +1,5 @@
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
import { import { applyAbAttrs, applyPreDefendAbAttrs } from "#app/data/abilities/apply-ab-attrs";
applyAbAttrs,
applyPreDefendAbAttrs,
IgnoreMoveEffectsAbAttr,
MoveEffectChanceMultiplierAbAttr,
} from "#app/data/abilities/ability";
import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { NumberHolder } from "#app/utils/common"; import { NumberHolder } from "#app/utils/common";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
@ -57,7 +52,7 @@ describe("Abilities - Shield Dust", () => {
const chance = new NumberHolder(move.chance); const chance = new NumberHolder(move.chance);
await applyAbAttrs( await applyAbAttrs(
MoveEffectChanceMultiplierAbAttr, "MoveEffectChanceMultiplierAbAttr",
phase.getUserPokemon()!, phase.getUserPokemon()!,
null, null,
false, false,
@ -67,7 +62,7 @@ describe("Abilities - Shield Dust", () => {
false, false,
); );
await applyPreDefendAbAttrs( await applyPreDefendAbAttrs(
IgnoreMoveEffectsAbAttr, "IgnoreMoveEffectsAbAttr",
phase.getFirstTarget()!, phase.getFirstTarget()!,
phase.getUserPokemon()!, phase.getUserPokemon()!,
null, null,

View File

@ -1,5 +1,4 @@
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
import { PostItemLostAbAttr } from "#app/data/abilities/ability";
import { StealHeldItemChanceAttr } from "#app/data/moves/move"; import { StealHeldItemChanceAttr } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
@ -277,7 +276,7 @@ describe("Abilities - Unburden", () => {
const [treecko, purrloin] = game.scene.getPlayerParty(); const [treecko, purrloin] = game.scene.getPlayerParty();
const initialTreeckoSpeed = treecko.getStat(Stat.SPD); const initialTreeckoSpeed = treecko.getStat(Stat.SPD);
const initialPurrloinSpeed = purrloin.getStat(Stat.SPD); const initialPurrloinSpeed = purrloin.getStat(Stat.SPD);
const unburdenAttr = treecko.getAbilityAttrs(PostItemLostAbAttr)[0]; const unburdenAttr = treecko.getAbilityAttrs("PostItemLostAbAttr")[0];
vi.spyOn(unburdenAttr, "applyPostItemLost"); vi.spyOn(unburdenAttr, "applyPostItemLost");
// Player uses Baton Pass, which also passes the Baton item // Player uses Baton Pass, which also passes the Baton item

View File

@ -1,5 +1,4 @@
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
import { PostDefendContactApplyStatusEffectAbAttr } from "#app/data/abilities/ability";
import { allAbilities } from "#app/data/data-lists"; import { allAbilities } from "#app/data/data-lists";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { StatusEffect } from "#app/enums/status-effect"; import { StatusEffect } from "#app/enums/status-effect";
@ -139,7 +138,7 @@ describe("Moves - Safeguard", () => {
it("protects from ability-inflicted status", async () => { it("protects from ability-inflicted status", async () => {
game.override.ability(AbilityId.STATIC); game.override.ability(AbilityId.STATIC);
vi.spyOn( vi.spyOn(
allAbilities[AbilityId.STATIC].getAttrs(PostDefendContactApplyStatusEffectAbAttr)[0], allAbilities[AbilityId.STATIC].getAttrs("PostDefendContactApplyStatusEffectAbAttr")[0],
"chance", "chance",
"get", "get",
).mockReturnValue(100); ).mockReturnValue(100);

View File

@ -11,7 +11,6 @@ import { StatusEffect } from "#enums/status-effect";
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
import { ArenaTagSide } from "#enums/arena-tag-side"; import { ArenaTagSide } from "#enums/arena-tag-side";
import { MoveEffectChanceMultiplierAbAttr } from "#app/data/abilities/ability";
import { allAbilities } from "#app/data/data-lists"; import { allAbilities } from "#app/data/data-lists";
describe("Moves - Secret Power", () => { describe("Moves - Secret Power", () => {
@ -68,7 +67,7 @@ describe("Moves - Secret Power", () => {
.battleStyle("double"); .battleStyle("double");
await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]); await game.classicMode.startBattle([SpeciesId.BLASTOISE, SpeciesId.CHARIZARD]);
const sereneGraceAttr = allAbilities[AbilityId.SERENE_GRACE].getAttrs(MoveEffectChanceMultiplierAbAttr)[0]; const sereneGraceAttr = allAbilities[AbilityId.SERENE_GRACE].getAttrs("MoveEffectChanceMultiplierAbAttr")[0];
vi.spyOn(sereneGraceAttr, "canApply"); vi.spyOn(sereneGraceAttr, "canApply");
game.move.select(MoveId.WATER_PLEDGE, 0, BattlerIndex.ENEMY); game.move.select(MoveId.WATER_PLEDGE, 0, BattlerIndex.ENEMY);

View File

@ -4,7 +4,7 @@ import type { globalScene } from "#app/global-scene";
// -- end tsdoc imports -- // -- end tsdoc imports --
import type { BattlerIndex } from "#enums/battler-index"; import type { BattlerIndex } from "#enums/battler-index";
import type { Ability } from "#app/data/abilities/ability-class"; import type { Ability } from "#app/data/abilities/ability";
import { allAbilities } from "#app/data/data-lists"; import { allAbilities } from "#app/data/data-lists";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";