diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index da8b6b1c00b..263a576c4f0 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -272,6 +272,7 @@ export class MoveEffectPhase extends PokemonPhase { if (move.hitsSubstitute(user, target)) { return resolve(); } + // If the invoked move is an enemy attack, apply the enemy's status effect-inflicting tokens if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) { user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target); diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index a94d8ca1424..fafe5cf19c3 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -1,6 +1,6 @@ import { BattlerIndex } from "#app/battle"; import BattleScene from "#app/battle-scene"; -import { applyAbAttrs, applyPostStatStageChangeAbAttrs, applyPreStatStageChangeAbAttrs, PostStatStageChangeAbAttr, PostStatStageChangeStatStageChangeAbAttr, ProtectStatAbAttr, StatStageChangeCopyAbAttr, StatStageChangeMultiplierAbAttr } from "#app/data/ability"; +import { applyAbAttrs, applyPostStatStageChangeAbAttrs, applyPreStatStageChangeAbAttrs, PostStatStageChangeAbAttr, ProtectStatAbAttr, StatStageChangeCopyAbAttr, StatStageChangeMultiplierAbAttr } from "#app/data/ability"; import { ArenaTagSide, MistTag } from "#app/data/arena-tag"; import Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; @@ -36,6 +36,16 @@ export class StatStageChangePhase extends PokemonPhase { } start() { + + // Check if multiple stats are being changed at the same time, then run SSCPhase for each of them + if (this.stats.length > 1) { + for (let i = 0; i < this.stats.length; i++) { + const stat = [this.stats[i]]; + this.scene.unshiftPhase(new StatStageChangePhase(this.scene, this.battlerIndex, this.selfTarget, stat, this.stages, this.showMessage, this.ignoreAbilities, this.canBeCopied, this.onChange)); + } + return this.end(); + } + const pokemon = this.getPokemon(); if (!pokemon.isActive(true)) { @@ -106,15 +116,7 @@ export class StatStageChangePhase extends PokemonPhase { } } - // If a pokemon has defiant or competitive which activates for each stat lowered, loop over each stat lowered - const defiantOrCompetitive = pokemon.getAbilityAttrs(PostStatStageChangeStatStageChangeAbAttr); - if (defiantOrCompetitive?.length > 0) { - for (let _ = 0; _ < filteredStats.length; _++) { - applyPostStatStageChangeAbAttrs(PostStatStageChangeAbAttr, pokemon, filteredStats, this.stages, this.selfTarget); - } - } else { - 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 const existingPhase = this.scene.findPhase(p => p instanceof StatStageChangePhase && p.battlerIndex === this.battlerIndex); diff --git a/src/test/abilities/competitive.test.ts b/src/test/abilities/competitive.test.ts index 6598ba97a6a..f8c55e1572c 100644 --- a/src/test/abilities/competitive.test.ts +++ b/src/test/abilities/competitive.test.ts @@ -25,10 +25,10 @@ describe("Abilities - Competitive", () => { game = new GameManager(phaserGame); game.override.battleType("single") - .enemySpecies(Species.RATTATA) + .enemySpecies(Species.BEEDRILL) .enemyMoveset(Moves.TICKLE) - .startingLevel(100) - .moveset(Moves.SPLASH) + .startingLevel(1) + .moveset([Moves.SPLASH, Moves.CLOSE_COMBAT]) .ability(Abilities.COMPETITIVE); }); @@ -43,4 +43,29 @@ describe("Abilities - Competitive", () => { expect(playerPokemon.getStatStage(Stat.DEF)).toBe(-1); expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(4); }); + + it("lowering your own stats should not trigger competitive", async () => { + game.override.enemyMoveset(Moves.SPLASH); + await game.classicMode.startBattle([ Species.FLYGON ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.CLOSE_COMBAT); + await game.phaseInterceptor.to(TurnInitPhase); + + expect(playerPokemon.getStatStage(Stat.SPDEF)).toBe(-1); + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(-1); + expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(0); + }); + it("white herb should remove only the negative effects", async () => { + game.override.startingHeldItems([{ name: "WHITE_HERB"}]); + await game.classicMode.startBattle([ Species.FLYGON ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to(TurnInitPhase); + + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0); + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(0); + expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(4); + }); }); diff --git a/src/test/abilities/defiant.test.ts b/src/test/abilities/defiant.test.ts new file mode 100644 index 00000000000..81857eb358e --- /dev/null +++ b/src/test/abilities/defiant.test.ts @@ -0,0 +1,69 @@ +import { Stat } from "#enums/stat"; +import { TurnInitPhase } from "#app/phases/turn-init-phase"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Defiant", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + + game.override.battleType("single") + .enemySpecies(Species.BEEDRILL) + .enemyMoveset(Moves.TICKLE) + .startingLevel(1) + .moveset([Moves.SPLASH, Moves.CLOSE_COMBAT]) + .ability(Abilities.DEFIANT); + }); + + it("lower atk and def by 1 via tickle, then increase atk by 4 via defiant", async () => { + await game.classicMode.startBattle([ Species.FLYGON ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to(TurnInitPhase); + + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(3); + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(-1); + }); + + it("lowering your own stats should not trigger defiant", async () => { + game.override.enemyMoveset(Moves.SPLASH); + await game.classicMode.startBattle([ Species.FLYGON ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.CLOSE_COMBAT); + await game.phaseInterceptor.to(TurnInitPhase); + + expect(playerPokemon.getStatStage(Stat.SPDEF)).toBe(-1); + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(-1); + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0); + }); + it("white herb should remove only the negative effects", async () => { + game.override.startingHeldItems([{ name: "WHITE_HERB"}]); + await game.classicMode.startBattle([ Species.FLYGON ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to(TurnInitPhase); + + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(0); + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(3); + }); +});