Update abattr callsites in battler-tags

Also removed stat drop ability application from cancelling ME stat boost effects
This commit is contained in:
Sirz Benjie 2025-06-14 22:04:47 -05:00
parent 1deb74e926
commit f432f8cbf6
No known key found for this signature in database
GPG Key ID: 38AC42D68CF5E138
10 changed files with 87 additions and 70 deletions

View File

@ -1402,6 +1402,12 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
export class PostStatStageChangeAbAttr extends AbAttr {
private declare readonly _: never;
override canApply(_params: Closed<PostStatStageChangeAbAttrParams>) {
return true;
}
override apply(_params: Closed<PostStatStageChangeAbAttrParams>) {}
}
export interface PostStatStageChangeAbAttrParams extends AbAttrBaseParams {
@ -3415,9 +3421,12 @@ export interface PreStatStageChangeAbAttrParams extends AbAttrBaseParams {
stat: BattleStat;
/** The amount of stages to change by (negative if the stat is being decreased) */
stages: number;
/** The source of the stat stage drop */
source: Pokemon;
// Note: `cancelled` already exists in `AbAttrBaseParams`, though we redefine it here to change its tsdoc
/** The source of the stat stage drop. May be omitted if the source of the stat drop is the user itself.
*
* @remarks
* Currently, only used by {@linkcode ReflectStatStageChangeAbAttr} in order to reflect the stat stage change
*/
source?: Pokemon;
/** Holder that will be set to true if the stat stage change should be cancelled due to the ability */
cancelled: BooleanHolder;
}
@ -3440,10 +3449,17 @@ export class ReflectStatStageChangeAbAttr extends PreStatStageChangeAbAttr {
/** {@linkcode BattleStat} to reflect */
private reflectedStat?: BattleStat;
override canApply({ source, cancelled }: PreStatStageChangeAbAttrParams): boolean {
return !!source && !cancelled.value;
}
/**
* Apply the {@linkcode ReflectStatStageChangeAbAttr} to an interaction
*/
override apply({ source, cancelled, stat, simulated, stages }: PreStatStageChangeAbAttrParams): void {
if (!source) {
return;
}
this.reflectedStat = stat;
if (!simulated) {
globalScene.phaseManager.unshiftNew(

View File

@ -946,7 +946,7 @@ class StealthRockTag extends ArenaTrapTag {
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
if (cancelled.value) {
return false;
}
@ -1003,7 +1003,12 @@ class StickyWebTag extends ArenaTrapTag {
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
if (pokemon.isGrounded()) {
const cancelled = new BooleanHolder(false);
applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled);
applyAbAttrs("ProtectStatAbAttr", {
pokemon,
cancelled,
stat: Stat.SPD,
stages: -1,
});
if (simulated) {
return !cancelled.value;
@ -1416,7 +1421,9 @@ export class SuppressAbilitiesTag extends ArenaTag {
for (const fieldPokemon of globalScene.getField(true)) {
if (fieldPokemon && fieldPokemon.id !== pokemon.id) {
[true, false].forEach(passive => applyOnLoseAbAttrs(fieldPokemon, passive));
// TODO: investigate whether we can just remove the foreach and call `applyAbAttrs` directly, providing
// the appropriate attributes (preLEaveField and IllusionBreak)
[true, false].forEach(passive => applyOnLoseAbAttrs({ pokemon: fieldPokemon, passive }));
}
}
}

View File

@ -621,7 +621,7 @@ export class FlinchedTag extends BattlerTag {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}),
);
applyAbAttrs("FlinchEffectAbAttr", pokemon, null);
applyAbAttrs("FlinchEffectAbAttr", { pokemon });
return true;
}
@ -916,7 +916,7 @@ export class SeedTag extends BattlerTag {
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
if (source) {
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
if (!cancelled.value) {
globalScene.phaseManager.unshiftNew(
@ -1006,7 +1006,7 @@ export class PowderTag extends BattlerTag {
globalScene.phaseManager.unshiftNew("CommonAnimPhase", idx, idx, CommonAnim.POWDER);
const cancelDamage = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelDamage);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled: cancelDamage });
if (!cancelDamage.value) {
pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
}
@ -1056,7 +1056,7 @@ export class NightmareTag extends BattlerTag {
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE); // TODO: Update animation type
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
if (!cancelled.value) {
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
@ -1409,7 +1409,7 @@ export abstract class DamagingTrapTag extends TrappedTag {
phaseManager.unshiftNew("CommonAnimPhase", pokemon.getBattlerIndex(), undefined, this.commonAnim);
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
if (!cancelled.value) {
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8), { result: HitResult.INDIRECT });
@ -1642,7 +1642,7 @@ export class ContactDamageProtectedTag extends ContactProtectedTag {
*/
override onContact(attacker: Pokemon, user: Pokemon): void {
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", user, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon: user, cancelled });
if (!cancelled.value) {
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), {
result: HitResult.INDIRECT,
@ -2243,7 +2243,7 @@ export class SaltCuredTag extends BattlerTag {
);
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
if (!cancelled.value) {
const pokemonSteelOrWater = pokemon.isOfType(PokemonType.STEEL) || pokemon.isOfType(PokemonType.WATER);
@ -2297,7 +2297,7 @@ export class CursedTag extends BattlerTag {
);
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", pokemon, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon, cancelled });
if (!cancelled.value) {
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4), { result: HitResult.INDIRECT });
@ -2632,7 +2632,7 @@ export class GulpMissileTag extends BattlerTag {
}
const cancelled = new BooleanHolder(false);
applyAbAttrs("BlockNonDirectDamageAbAttr", attacker, cancelled);
applyAbAttrs("BlockNonDirectDamageAbAttr", { pokemon: attacker, cancelled });
if (!cancelled.value) {
attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), { result: HitResult.INDIRECT });
@ -3021,14 +3021,7 @@ export class MysteryEncounterPostSummonTag extends BattlerTag {
const ret = super.lapse(pokemon, lapseType);
if (lapseType === BattlerTagLapseType.CUSTOM) {
const cancelled = new BooleanHolder(false);
applyAbAttrs("ProtectStatAbAttr", pokemon, cancelled);
applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", pokemon, cancelled, false, pokemon);
if (!cancelled.value) {
if (pokemon.mysteryEncounterBattleEffects) {
pokemon.mysteryEncounterBattleEffects(pokemon);
}
}
pokemon.mysteryEncounterBattleEffects?.(pokemon);
}
return ret;

View File

@ -14,7 +14,7 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase {
if (pokemon) {
pokemon.resetBattleAndWaveData();
if (pokemon.isOnField()) {
applyAbAttrs("PostBiomeChangeAbAttr", pokemon, null);
applyAbAttrs("PostBiomeChangeAbAttr", { pokemon });
}
}
}

View File

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

View File

@ -1,4 +1,4 @@
import { applyPostSummonAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { PostSummonPhase } from "#app/phases/post-summon-phase";
import type { BattlerIndex } from "#enums/battler-index";
@ -16,7 +16,8 @@ export class PostSummonActivateAbilityPhase extends PostSummonPhase {
}
start() {
applyPostSummonAbAttrs("PostSummonAbAttr", this.getPokemon(), this.passive, false);
// TODO: Check with Dean on whether or not passive must be provided to `this.passive`
applyAbAttrs("PostSummonAbAttr", { pokemon: this.getPokemon(), passive: this.passive });
this.end();
}

View File

@ -28,7 +28,7 @@ export class PostSummonPhase extends PokemonPhase {
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
for (const p of field) {
applyAbAttrs("CommanderAbAttr", p, null, false);
applyAbAttrs("CommanderAbAttr", { pokemon: p });
}
this.end();

View File

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

View File

@ -181,9 +181,10 @@ export class QuietFormChangePhase extends BattlePhase {
}
}
if (this.formChange.trigger instanceof SpeciesFormChangeTeraTrigger) {
applyAbAttrs("PostTeraFormChangeStatChangeAbAttr", this.pokemon, null);
applyAbAttrs("ClearWeatherAbAttr", this.pokemon, null);
applyAbAttrs("ClearTerrainAbAttr", this.pokemon, null);
const params = { pokemon: this.pokemon };
applyAbAttrs("PostTeraFormChangeStatChangeAbAttr", params);
applyAbAttrs("ClearWeatherAbAttr", params);
applyAbAttrs("ClearTerrainAbAttr", params);
}
super.end();

View File

@ -1,10 +1,6 @@
import { globalScene } from "#app/global-scene";
import type { BattlerIndex } from "#enums/battler-index";
import {
applyAbAttrs,
applyPostStatStageChangeAbAttrs,
applyPreStatStageChangeAbAttrs,
} from "#app/data/abilities/apply-ab-attrs";
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { MistTag } from "#app/data/arena-tag";
import { ArenaTagSide } from "#enums/arena-tag-side";
import type { ArenaTag } from "#app/data/arena-tag";
@ -18,6 +14,10 @@ import { PokemonPhase } from "./pokemon-phase";
import { Stat, type BattleStat, getStatKey, getStatStageChangeDescriptionKey } from "#enums/stat";
import { OctolockTag } from "#app/data/battler-tags";
import { ArenaTagType } from "#app/enums/arena-tag-type";
import type {
ConditionalUserFieldProtectStatAbAttrParams,
PreStatStageChangeAbAttrParams,
} from "#app/@types/ability-types";
export type StatStageChangeCallback = (
target: Pokemon | null,
@ -126,7 +126,7 @@ export class StatStageChangePhase extends PokemonPhase {
const stages = new NumberHolder(this.stages);
if (!this.ignoreAbilities) {
applyAbAttrs("StatStageChangeMultiplierAbAttr", pokemon, null, false, stages);
applyAbAttrs("StatStageChangeMultiplierAbAttr", { pokemon, numStages: stages });
}
let simulate = false;
@ -146,42 +146,38 @@ export class StatStageChangePhase extends PokemonPhase {
}
if (!cancelled.value && !this.selfTarget && stages.value < 0) {
applyPreStatStageChangeAbAttrs("ProtectStatAbAttr", pokemon, stat, cancelled, simulate);
applyPreStatStageChangeAbAttrs(
"ConditionalUserFieldProtectStatAbAttr",
const abAttrParams: PreStatStageChangeAbAttrParams & ConditionalUserFieldProtectStatAbAttrParams = {
pokemon,
stat,
cancelled,
simulate,
pokemon,
);
simulated: simulate,
target: pokemon,
stages: this.stages,
};
applyAbAttrs("ProtectStatAbAttr", abAttrParams);
applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", abAttrParams);
// TODO: Consider skipping this call if `cancelled` is false.
const ally = pokemon.getAlly();
if (!isNullOrUndefined(ally)) {
applyPreStatStageChangeAbAttrs(
"ConditionalUserFieldProtectStatAbAttr",
ally,
stat,
cancelled,
simulate,
pokemon,
);
applyAbAttrs("ConditionalUserFieldProtectStatAbAttr", { ...abAttrParams, pokemon: ally });
}
/** Potential stat reflection due to Mirror Armor, does not apply to Octolock end of turn effect */
if (
opponentPokemon !== undefined &&
// TODO: investigate whether this is stoping mirror armor from applying to non-octolock
// reasons for stat drops if the user has the Octolock tag
!pokemon.findTag(t => t instanceof OctolockTag) &&
!this.comingFromMirrorArmorUser
) {
applyPreStatStageChangeAbAttrs(
"ReflectStatStageChangeAbAttr",
applyAbAttrs("ReflectStatStageChangeAbAttr", {
pokemon,
stat,
cancelled,
simulate,
opponentPokemon,
this.stages,
);
simulated: simulate,
source: opponentPokemon,
stages: this.stages,
});
}
}
@ -222,17 +218,16 @@ export class StatStageChangePhase extends PokemonPhase {
if (stages.value > 0 && this.canBeCopied) {
for (const opponent of pokemon.getOpponents()) {
applyAbAttrs("StatStageChangeCopyAbAttr", opponent, null, false, this.stats, stages.value);
applyAbAttrs("StatStageChangeCopyAbAttr", { pokemon: opponent, stats: this.stats, numStages: stages.value });
}
}
applyPostStatStageChangeAbAttrs(
"PostStatStageChangeAbAttr",
applyAbAttrs("PostStatStageChangeAbAttr", {
pokemon,
filteredStats,
this.stages,
this.selfTarget,
);
stats: filteredStats,
stages: this.stages,
selfTarget: this.selfTarget,
});
// Look for any other stat change phases; if this is the last one, do White Herb check
const existingPhase = globalScene.phaseManager.findPhase(