Substitute now blocks all target-facing secondary effects

This commit is contained in:
innerthunder 2024-11-17 14:02:41 -08:00
parent f421b5d5a6
commit 1e8cbc9d3c
3 changed files with 49 additions and 96 deletions

View File

@ -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<boolean> {
return new Promise<boolean>(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);

View File

@ -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<boolean> {
return new Promise<boolean>(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)) {

View File

@ -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<void | null> {
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);
})
);
}