diff --git a/src/data/ability.ts b/src/data/ability.ts index 66624f03436..234b502c23f 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -3720,16 +3720,16 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { /** * Deals damage to all sleeping opponents equal to 1/8 of their max hp (min 1) - * @param {Pokemon} pokemon Pokemon that has this ability - * @param {boolean} passive N/A - * @param {boolean} simulated true if applying in a simulated call. - * @param {any[]} args N/A - * @returns {boolean} true if any opponents are sleeping + * @param pokemon Pokemon that has this ability + * @param passive N/A + * @param simulated `true` if applying in a simulated call. + * @param args N/A + * @returns `true` if any opponents are sleeping */ applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { let hadEffect: boolean = false; for (const opp of pokemon.getOpponents()) { - if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { + if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus) { if (!simulated) { opp.damageAndUpdate(Utils.toDmgValue(opp.getMaxHp() / 8), HitResult.OTHER); pokemon.scene.queueMessage(i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) })); diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 2f57650c65d..8bb74d29a4e 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -1144,7 +1144,7 @@ class FireGrassPledgeTag extends ArenaTag { ? arena.scene.getPlayerField() : arena.scene.getEnemyField(); - field.filter(pokemon => !pokemon.isOfType(Type.FIRE)).forEach(pokemon => { + field.filter(pokemon => !pokemon.isOfType(Type.FIRE) && !pokemon.switchOutStatus).forEach(pokemon => { // "{pokemonNameWithAffix} was hurt by the sea of fire!" pokemon.scene.queueMessage(i18next.t("arenaTag:fireGrassPledgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); // TODO: Replace this with a proper animation diff --git a/src/data/move.ts b/src/data/move.ts index 091301990f3..166058178f5 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1867,7 +1867,7 @@ export class FlameBurstAttr extends MoveEffectAttr { applyAbAttrs(BlockNonDirectDamageAbAttr, targetAlly, cancelled); } - if (cancelled.value || !targetAlly) { + if (cancelled.value || !targetAlly || targetAlly.switchOutStatus) { return false; } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 397bf2cbf36..2638a6cfc62 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3007,6 +3007,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { damageAndUpdate(damage: integer, result?: DamageResult, critical: boolean = false, ignoreSegments: boolean = false, preventEndure: boolean = false, ignoreFaintPhase: boolean = false, source?: Pokemon): integer { const damagePhase = new DamageAnimPhase(this.scene, this.getBattlerIndex(), damage, result as DamageResult, critical); this.scene.unshiftPhase(damagePhase); + if (this.switchOutStatus && source) { + damage = 0; + } damage = this.damage(damage, ignoreSegments, preventEndure, ignoreFaintPhase); // Damage amount may have changed, but needed to be queued before calling damage function damagePhase.updateAmount(damage); diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index f7a9b22c396..b85be5b46aa 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -228,9 +228,11 @@ export class MoveEffectPhase extends PokemonPhase { * If the move missed a target, stop all future hits against that target * and move on to the next target (if there is one). */ - if (isCommanding || (!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()])) { + if (target.switchOutStatus || isCommanding || (!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()])) { this.stopMultiHit(target); - this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); + if (!target.switchOutStatus) { + this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); + } if (moveHistoryEntry.result === MoveResult.PENDING) { moveHistoryEntry.result = MoveResult.MISS; } diff --git a/src/phases/post-turn-status-effect-phase.ts b/src/phases/post-turn-status-effect-phase.ts index 08e4d7cb952..378a932cdc5 100644 --- a/src/phases/post-turn-status-effect-phase.ts +++ b/src/phases/post-turn-status-effect-phase.ts @@ -16,7 +16,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { start() { const pokemon = this.getPokemon(); - if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn()) { + if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn() && !pokemon.switchOutStatus) { pokemon.status.incrementTurn(); const cancelled = new Utils.BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index 60a2e6600db..e5f1850758d 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -23,22 +23,24 @@ export class TurnEndPhase extends FieldPhase { this.scene.eventTarget.dispatchEvent(new TurnEndEvent(this.scene.currentBattle.turn)); const handlePokemon = (pokemon: Pokemon) => { - pokemon.lapseTags(BattlerTagLapseType.TURN_END); + if (!pokemon.switchOutStatus) { + pokemon.lapseTags(BattlerTagLapseType.TURN_END); - this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); + this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); - if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { - this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(), - Math.max(pokemon.getMaxHp() >> 4, 1), i18next.t("battle:turnEndHpRestore", { pokemonName: getPokemonNameWithAffix(pokemon) }), true)); + if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { + this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(), + Math.max(pokemon.getMaxHp() >> 4, 1), i18next.t("battle:turnEndHpRestore", { pokemonName: getPokemonNameWithAffix(pokemon) }), true)); + } + + if (!pokemon.isPlayer()) { + this.scene.applyModifiers(EnemyTurnHealModifier, false, pokemon); + this.scene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon); + } + + applyPostTurnAbAttrs(PostTurnAbAttr, pokemon); } - if (!pokemon.isPlayer()) { - this.scene.applyModifiers(EnemyTurnHealModifier, false, pokemon); - this.scene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon); - } - - applyPostTurnAbAttrs(PostTurnAbAttr, pokemon); - this.scene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon); this.scene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon); diff --git a/src/phases/weather-effect-phase.ts b/src/phases/weather-effect-phase.ts index b48ee342780..442bafa0ca7 100644 --- a/src/phases/weather-effect-phase.ts +++ b/src/phases/weather-effect-phase.ts @@ -51,7 +51,7 @@ export class WeatherEffectPhase extends CommonAnimPhase { }; this.executeForAll((pokemon: Pokemon) => { - const immune = !pokemon || !!pokemon.getTypes(true, true).filter(t => this.weather?.isTypeDamageImmune(t)).length; + const immune = !pokemon || !!pokemon.getTypes(true, true).filter(t => this.weather?.isTypeDamageImmune(t)).length || pokemon.switchOutStatus; if (!immune) { inflictDamage(pokemon); } @@ -59,8 +59,12 @@ export class WeatherEffectPhase extends CommonAnimPhase { } } - this.scene.ui.showText(getWeatherLapseMessage(this.weather.weatherType)!, null, () => { // TODO: is this bang correct? - this.executeForAll((pokemon: Pokemon) => applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather)); + this.scene.ui.showText(getWeatherLapseMessage(this.weather.weatherType) ?? "", null, () => { + this.executeForAll((pokemon: Pokemon) => { + if (!pokemon.switchOutStatus) { + applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather); + } + }); super.start(); }); diff --git a/src/test/abilities/wimp_out.test.ts b/src/test/abilities/wimp_out.test.ts index 4283386b248..bff68b54c75 100644 --- a/src/test/abilities/wimp_out.test.ts +++ b/src/test/abilities/wimp_out.test.ts @@ -632,4 +632,34 @@ describe("Abilities - Wimp Out", () => { const hasFled = enemyPokemon.switchOutStatus; expect(isVisible && !hasFled).toBe(true); }); + it("wimp out will not skip battles when triggered in a double battle", async () => { + const wave = 2; + game.override + .enemyMoveset(Moves.SPLASH) + .enemySpecies(Species.WIMPOD) + .enemyAbility(Abilities.WIMP_OUT) + .moveset([ Moves.MATCHA_GOTCHA, Moves.FALSE_SWIPE ]) + .startingLevel(50) + .enemyLevel(1) + .battleType("double") + .startingWave(wave); + await game.classicMode.startBattle([ + Species.RAICHU, + Species.PIKACHU + ]); + const [ wimpod0, wimpod1 ] = game.scene.getEnemyField(); + + game.move.select(Moves.FALSE_SWIPE, 0, BattlerIndex.ENEMY); + game.move.select(Moves.MATCHA_GOTCHA, 1); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(wimpod0.hp).toBeGreaterThan(0); + expect(wimpod0.switchOutStatus).toBe(true); + expect(wimpod0.isFainted()).toBe(false); + expect(wimpod1.isFainted()).toBe(true); + + await game.toNextWave(); + expect(game.scene.currentBattle.waveIndex).toBe(wave + 1); + }); });