From 1e8cbc9d3cc4110ba7663774e7a100e8d85fab17 Mon Sep 17 00:00:00 2001 From: innerthunder Date: Sun, 17 Nov 2024 14:02:41 -0800 Subject: [PATCH] Substitute now blocks all target-facing secondary effects --- src/data/ability.ts | 41 +++++++++++-------------- src/data/move.ts | 53 ++------------------------------- src/phases/move-effect-phase.ts | 51 +++++++++++++++++-------------- 3 files changed, 49 insertions(+), 96 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index b207904c2c1..768426d1cd8 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -683,7 +683,7 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr { * @returns true if healing should be reversed on a healing move, false otherwise. */ override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (move.hasAttr(HitHealAttr) && !move.hitsSubstitute(attacker, pokemon)) { + if (move.hasAttr(HitHealAttr)) { if (!simulated) { pokemon.scene.queueMessage(i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) })); } @@ -711,7 +711,7 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr { } override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { + if (this.condition(pokemon, attacker, move)) { if (simulated) { return true; } @@ -753,7 +753,7 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr { const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1]; const damageReceived = lastAttackReceived?.damage || 0; - if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat) && !move.hitsSubstitute(attacker, pokemon)) { + if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat)) { if (!simulated) { pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.stages)); } @@ -776,7 +776,7 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr { } override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { + if (this.condition(pokemon, attacker, move)) { const tag = pokemon.scene.arena.getTag(this.tagType) as ArenaTrapTag; if (!pokemon.scene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) { if (!simulated) { @@ -800,7 +800,7 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { } override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { + if (this.condition(pokemon, attacker, move)) { if (!pokemon.getTag(this.tagType) && !simulated) { pokemon.addTag(this.tagType, undefined, undefined, pokemon.id); pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name })); @@ -813,7 +813,7 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean { - if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) { + if (hitResult < HitResult.NO_EFFECT) { if (simulated) { return true; } @@ -847,7 +847,7 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr { } override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean { - if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) { + if (hitResult < HitResult.NO_EFFECT) { if (simulated) { return pokemon.scene.arena.terrain?.terrainType !== (this.terrainType || undefined); } else { @@ -872,7 +872,7 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status - && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !move.hitsSubstitute(attacker, pokemon)) { + && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) { const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; if (simulated) { return attacker.canSetStatus(effect, true, false, pokemon); @@ -912,7 +912,7 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr { } override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance && !move.hitsSubstitute(attacker, pokemon)) { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance) { if (simulated) { return attacker.canAddTag(this.tagType); } else { @@ -936,10 +936,6 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr { } override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (move.hitsSubstitute(attacker, pokemon)) { - return false; - } - if (!simulated) { pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); } @@ -962,8 +958,7 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { } override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (!simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) - && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !move.hitsSubstitute(attacker, pokemon)) { + if (!simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); attacker.turnData.damageTaken += Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)); return true; @@ -996,7 +991,7 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr { } override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !move.hitsSubstitute(attacker, pokemon)) { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { if (pokemon.getTag(BattlerTagType.PERISH_SONG) || attacker.getTag(BattlerTagType.PERISH_SONG)) { return false; } else { @@ -1027,7 +1022,7 @@ export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr { } override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (this.condition && !this.condition(pokemon, attacker, move) || move.hitsSubstitute(attacker, pokemon)) { + if (this.condition && !this.condition(pokemon, attacker, move)) { return false; } if (!pokemon.scene.arena.weather?.isImmutable()) { @@ -1048,7 +1043,7 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, args: any[]): boolean { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) - && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr) && !move.hitsSubstitute(attacker, pokemon)) { + && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr)) { if (!simulated) { const tempAbilityId = attacker.getAbility().id; attacker.summonData.ability = pokemon.getAbility().id; @@ -1075,7 +1070,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) - && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) && !move.hitsSubstitute(attacker, pokemon)) { + && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr)) { if (!simulated) { attacker.summonData.ability = this.ability; } @@ -1106,7 +1101,7 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { } override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { - if (attacker.getTag(BattlerTagType.DISABLED) === null && !move.hitsSubstitute(attacker, pokemon)) { + if (attacker.getTag(BattlerTagType.DISABLED) === null) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) { if (simulated) { return true; @@ -1694,7 +1689,7 @@ export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr { } applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (pokemon !== attacker && move.hitsSubstitute(attacker, pokemon)) { + if (pokemon !== attacker) { return false; } @@ -1750,7 +1745,7 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): Promise { return new Promise(resolve => { - if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move)) && !move.hitsSubstitute(attacker, pokemon)) { + if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) { const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable); if (heldItems.length) { const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; @@ -4668,7 +4663,7 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { * @returns `true` if the immunity was applied. */ override applyPreDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { + if (this.condition(pokemon, attacker, move)) { if (!simulated) { (args[0] as Utils.NumberHolder).value = this.multiplier; pokemon.removeTag(this.tagType); diff --git a/src/data/move.ts b/src/data/move.ts index 033445a35f9..085a29deb25 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -2259,10 +2259,6 @@ export class StatusEffectAttr extends MoveEffectAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (!this.selfTarget && move.hitsSubstitute(user, target)) { - return false; - } - const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); const statusCheck = moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance; if (statusCheck) { @@ -2369,9 +2365,6 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { return new Promise(resolve => { - if (move.hitsSubstitute(user, target)) { - return resolve(false); - } const rand = Phaser.Math.RND.realInRange(0, 1); if (rand >= this.chance) { return resolve(false); @@ -2441,10 +2434,6 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { return false; } - if (move.hitsSubstitute(user, target)) { - return false; - } - const cancelled = new Utils.BooleanHolder(false); applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // Check for abilities that block item theft @@ -2565,9 +2554,6 @@ export class StealEatBerryAttr extends EatBerryAttr { * @returns {boolean} true if the function succeeds */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (move.hitsSubstitute(user, target)) { - return false; - } const cancelled = new Utils.BooleanHolder(false); applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // check for abilities that block item theft if (cancelled.value === true) { @@ -2618,10 +2604,6 @@ export class HealStatusEffectAttr extends MoveEffectAttr { return false; } - if (!this.selfTarget && move.hitsSubstitute(user, target)) { - return false; - } - // Special edge case for shield dust blocking Sparkling Aria curing burn const moveTargets = getMoveTargets(user, move.id); if (target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && move.id === Moves.SPARKLING_ARIA && moveTargets.targets.length === 1) { @@ -3013,10 +2995,6 @@ export class StatStageChangeAttr extends MoveEffectAttr { return false; } - if (!this.selfTarget && move.hitsSubstitute(user, target)) { - return false; - } - const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) { const stages = this.getLevels(user); @@ -3388,10 +3366,8 @@ export class ResetStatsAttr extends MoveEffectAttr { activePokemon.forEach(p => promises.push(this.resetStats(p))); target.scene.queueMessage(i18next.t("moveTriggers:statEliminated")); } else { // Affects only the single target when Clear Smog is used - if (!move.hitsSubstitute(user, target)) { - promises.push(this.resetStats(target)); - target.scene.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) })); - } + promises.push(this.resetStats(target)); + target.scene.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) })); } await Promise.all(promises); @@ -5305,19 +5281,6 @@ export class LeechSeedAttr extends AddBattlerTagAttr { constructor() { super(BattlerTagType.SEEDED); } - - /** - * Adds a Seeding effect to the target if the target does not have an active Substitute. - * @param user the {@linkcode Pokemon} using the move - * @param target the {@linkcode Pokemon} targeted by the move - * @param move the {@linkcode Move} invoking this effect - * @param args n/a - * @returns `true` if the effect successfully applies; `false` otherwise - */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - return !move.hitsSubstitute(user, target) - && super.apply(user, target, move, args); - } } /** @@ -5470,9 +5433,6 @@ export class FlinchAttr extends AddBattlerTagAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (!move.hitsSubstitute(user, target)) { - return super.apply(user, target, move, args); - } return false; } } @@ -5490,9 +5450,6 @@ export class ConfuseAttr extends AddBattlerTagAttr { return false; } - if (!move.hitsSubstitute(user, target)) { - return super.apply(user, target, move, args); - } return false; } } @@ -5882,7 +5839,7 @@ export class AddPledgeEffectAttr extends AddArenaTagAttr { * @see {@linkcode apply} */ export class RevivalBlessingAttr extends MoveEffectAttr { - constructor(user?: boolean) { + constructor() { super(true); } @@ -6063,10 +6020,6 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { const player = switchOutTarget instanceof PlayerPokemon; if (!this.selfSwitch) { - if (move.hitsSubstitute(user, target)) { - return false; - } - // Dondozo with an allied Tatsugiri in its mouth cannot be forced out const commandedTag = switchOutTarget.getTag(BattlerTagType.COMMANDED); if (commandedTag?.getSourcePokemon(switchOutTarget.scene)?.isActive(true)) { diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 3832720b3e9..e3138eed7c8 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -257,23 +257,10 @@ export class MoveEffectPhase extends PokemonPhase { return this.triggerMoveEffects(MoveEffectTrigger.PRE_APPLY, user, target).then(() => { const hitResult = this.applyMove(target, effectiveness); - /** Does {@linkcode hitResult} indicate that damage was dealt to the target? */ - const dealsDamage = [ - HitResult.EFFECTIVE, - HitResult.SUPER_EFFECTIVE, - HitResult.NOT_VERY_EFFECTIVE, - HitResult.ONE_HIT_KO - ].includes(hitResult); - - return this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget) - .then(() => this.applyHeldItemFlinchCheck(user, target, dealsDamage)) - .then(() => this.applyOnGetHitAbEffects(user, target, hitResult)) - .then(() => applyPostAttackAbAttrs(PostAttackAbAttr, user, target, move, hitResult)) + return this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, true) + .then(() => executeIf(!move.hitsSubstitute(user, target), + () => this.applyOnTargetEffects(user, target, hitResult, firstTarget))) .then(() => { - if (move instanceof AttackMove) { - this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); - } - if (this.lastHit) { this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); } @@ -442,6 +429,28 @@ export class MoveEffectPhase extends PokemonPhase { return result; } + protected applyOnTargetEffects(user: Pokemon, target: Pokemon, hitResult: HitResult, firstTarget: boolean): Promise { + const move = this.move.getMove(); + + /** Does {@linkcode hitResult} indicate that damage was dealt to the target? */ + const dealsDamage = [ + HitResult.EFFECTIVE, + HitResult.SUPER_EFFECTIVE, + HitResult.NOT_VERY_EFFECTIVE, + HitResult.ONE_HIT_KO + ].includes(hitResult); + + return this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, false) + .then(() => this.applyHeldItemFlinchCheck(user, target, dealsDamage)) + .then(() => this.applyOnGetHitAbEffects(user, target, hitResult)) + .then(() => applyPostAttackAbAttrs(PostAttackAbAttr, user, target, move, hitResult)) + .then(() => { + if (move instanceof AttackMove) { + this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); + } + }); + } + /** * Applies reactive effects that occur when a Pokémon is hit. @@ -455,15 +464,11 @@ export class MoveEffectPhase extends PokemonPhase { return executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult) .then(() => { - - if (!this.move.getMove().hitsSubstitute(user, target)) { - if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) { - user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target); - } - - target.lapseTags(BattlerTagLapseType.AFTER_HIT); + if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) { + user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target); } + target.lapseTags(BattlerTagLapseType.AFTER_HIT); }) ); }