From e8bc2fb736d4bf04a683156fec8002ff54df522b Mon Sep 17 00:00:00 2001 From: dobin Date: Thu, 5 Jun 2025 18:40:02 +0900 Subject: [PATCH 01/15] [Bug] Fix rattled speed stat increase delay --- src/data/abilities/ability.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 8f5f267f7ef..5ccce2b3c11 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -2130,7 +2130,12 @@ export class PostIntimidateStatStageChangeAbAttr extends AbAttr { override apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: BooleanHolder, args: any[]): void { if (!simulated) { - globalScene.pushPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages)); + const newStatStageChangePhase = new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages) + if(globalScene.findPhase(m => m instanceof MovePhase)){ + globalScene.prependToPhase(newStatStageChangePhase, MovePhase) + }else { + globalScene.pushPhase(newStatStageChangePhase); + } } cancelled.value = this.overwrites; } From a201d2e6a12a0dd1cda3288f24305dc7204ff165 Mon Sep 17 00:00:00 2001 From: dobin Date: Thu, 5 Jun 2025 19:16:44 +0900 Subject: [PATCH 02/15] [Bug] Fix rattled speed stat delay on SwitchSummonPhase --- src/data/abilities/ability.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 5ccce2b3c11..836c4213928 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -2133,6 +2133,8 @@ export class PostIntimidateStatStageChangeAbAttr extends AbAttr { const newStatStageChangePhase = new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages) if(globalScene.findPhase(m => m instanceof MovePhase)){ globalScene.prependToPhase(newStatStageChangePhase, MovePhase) + } else if(globalScene.findPhase(m => m instanceof SwitchSummonPhase)){ + globalScene.prependToPhase(newStatStageChangePhase, SwitchSummonPhase) }else { globalScene.pushPhase(newStatStageChangePhase); } From 400733442e284081d6650dcc08440f3bdaddaa4f Mon Sep 17 00:00:00 2001 From: dobin Date: Fri, 6 Jun 2025 00:53:57 +0900 Subject: [PATCH 03/15] [TEST] Rattled should be activated after Intimidate --- test/abilities/rattled.test.ts | 50 ++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 test/abilities/rattled.test.ts diff --git a/test/abilities/rattled.test.ts b/test/abilities/rattled.test.ts new file mode 100644 index 00000000000..b5785b909c6 --- /dev/null +++ b/test/abilities/rattled.test.ts @@ -0,0 +1,50 @@ +import { MoveId } from "#enums/move-id"; +import { AbilityId } from "#enums/ability-id"; +import { SpeciesId } from "#enums/species-id"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { BattleType } from "#enums/battle-type"; +import { Stat } from "#enums/stat"; + +describe("Abilities - Rattled", () => { + 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 + .moveset([MoveId.FALSE_SWIPE, MoveId.TRICK_ROOM]) + .ability(AbilityId.RATTLED) + .battleType(BattleType.TRAINER) + .disableCrits() + .enemySpecies(SpeciesId.MAGIKARP) + .enemyAbility(AbilityId.INTIMIDATE) + .enemyMoveset(MoveId.PIN_MISSILE); + }); + + it("should reduce attack and then increase speed", async () => { + await game.classicMode.runToSummon([SpeciesId.GIMMIGHOUL]); + + const playerPokemon = game.scene.getPlayerPokemon(); + await game.phaseInterceptor.to("StatStageChangePhase"); + + expect(playerPokemon!.getStatStage(Stat.ATK)).toBe(-1); + expect(playerPokemon!.getStatStage(Stat.SPD)).toBe(0); + + await game.phaseInterceptor.to("StatStageChangePhase"); + + expect(playerPokemon!.getStatStage(Stat.ATK)).toBe(-1); + expect(playerPokemon!.getStatStage(Stat.SPD)).toBe(1); + }); +}); From fa22a7f3ebfd6acfd740dc52a8cb8a0b488011e1 Mon Sep 17 00:00:00 2001 From: Dobin Shin <34051876+Gamez0@users.noreply.github.com> Date: Fri, 6 Jun 2025 03:01:55 +0900 Subject: [PATCH 04/15] Update test title Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> --- test/abilities/rattled.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/abilities/rattled.test.ts b/test/abilities/rattled.test.ts index b5785b909c6..77a7156b380 100644 --- a/test/abilities/rattled.test.ts +++ b/test/abilities/rattled.test.ts @@ -33,7 +33,7 @@ describe("Abilities - Rattled", () => { .enemyMoveset(MoveId.PIN_MISSILE); }); - it("should reduce attack and then increase speed", async () => { + it("should trigger and boost speed immediately after Intimidate attack drop on initial send out", async () => { await game.classicMode.runToSummon([SpeciesId.GIMMIGHOUL]); const playerPokemon = game.scene.getPlayerPokemon(); From 41b05f0ce367a6d493a9398245279aabbd3dc049 Mon Sep 17 00:00:00 2001 From: Dobin Shin <34051876+Gamez0@users.noreply.github.com> Date: Fri, 6 Jun 2025 03:03:09 +0900 Subject: [PATCH 05/15] Add comment justifying usage of runToSummon Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> --- test/abilities/rattled.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/abilities/rattled.test.ts b/test/abilities/rattled.test.ts index 77a7156b380..48f6a0182af 100644 --- a/test/abilities/rattled.test.ts +++ b/test/abilities/rattled.test.ts @@ -34,6 +34,7 @@ describe("Abilities - Rattled", () => { }); it("should trigger and boost speed immediately after Intimidate attack drop on initial send out", async () => { + // `runToSummon` used instead of `startBattle` to avoid skipping past initial "post send out" effects await game.classicMode.runToSummon([SpeciesId.GIMMIGHOUL]); const playerPokemon = game.scene.getPlayerPokemon(); From 04d7606122df4c7b23baa580a77a0163d63ea228 Mon Sep 17 00:00:00 2001 From: Dobin Shin <34051876+Gamez0@users.noreply.github.com> Date: Fri, 6 Jun 2025 03:04:30 +0900 Subject: [PATCH 06/15] Update test utils Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> --- test/abilities/rattled.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/abilities/rattled.test.ts b/test/abilities/rattled.test.ts index 48f6a0182af..301a0e27b13 100644 --- a/test/abilities/rattled.test.ts +++ b/test/abilities/rattled.test.ts @@ -37,15 +37,15 @@ describe("Abilities - Rattled", () => { // `runToSummon` used instead of `startBattle` to avoid skipping past initial "post send out" effects await game.classicMode.runToSummon([SpeciesId.GIMMIGHOUL]); - const playerPokemon = game.scene.getPlayerPokemon(); + const playerPokemon = game.field.getPlayerPokemon(); await game.phaseInterceptor.to("StatStageChangePhase"); - expect(playerPokemon!.getStatStage(Stat.ATK)).toBe(-1); - expect(playerPokemon!.getStatStage(Stat.SPD)).toBe(0); + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1); + expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0); await game.phaseInterceptor.to("StatStageChangePhase"); - expect(playerPokemon!.getStatStage(Stat.ATK)).toBe(-1); - expect(playerPokemon!.getStatStage(Stat.SPD)).toBe(1); + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1); + expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1); }); }); From 3e1adaa015173eee3a451666f240fa631b04b599 Mon Sep 17 00:00:00 2001 From: Dobin Shin <34051876+Gamez0@users.noreply.github.com> Date: Fri, 6 Jun 2025 03:06:00 +0900 Subject: [PATCH 07/15] Add battleStyle override Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> --- test/abilities/rattled.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/abilities/rattled.test.ts b/test/abilities/rattled.test.ts index 301a0e27b13..ddef2028bee 100644 --- a/test/abilities/rattled.test.ts +++ b/test/abilities/rattled.test.ts @@ -28,6 +28,7 @@ describe("Abilities - Rattled", () => { .ability(AbilityId.RATTLED) .battleType(BattleType.TRAINER) .disableCrits() + .battleStyle("single") .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.INTIMIDATE) .enemyMoveset(MoveId.PIN_MISSILE); From 377f5967782fd1764ef51b82c86f8f2743ea26d4 Mon Sep 17 00:00:00 2001 From: Dobin Shin <34051876+Gamez0@users.noreply.github.com> Date: Fri, 6 Jun 2025 03:06:51 +0900 Subject: [PATCH 08/15] Apply lint Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> --- src/data/abilities/ability.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 836c4213928..4590592fd20 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -2131,11 +2131,11 @@ export class PostIntimidateStatStageChangeAbAttr extends AbAttr { override apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: BooleanHolder, args: any[]): void { if (!simulated) { const newStatStageChangePhase = new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages) - if(globalScene.findPhase(m => m instanceof MovePhase)){ + if (globalScene.findPhase(m => m instanceof MovePhase)) { globalScene.prependToPhase(newStatStageChangePhase, MovePhase) - } else if(globalScene.findPhase(m => m instanceof SwitchSummonPhase)){ + } else if (globalScene.findPhase(m => m instanceof SwitchSummonPhase)) { globalScene.prependToPhase(newStatStageChangePhase, SwitchSummonPhase) - }else { + } else { globalScene.pushPhase(newStatStageChangePhase); } } From 92e45fba81c9bdb16464190f909dc3dba12c77ce Mon Sep 17 00:00:00 2001 From: dobin Date: Fri, 6 Jun 2025 14:58:26 +0900 Subject: [PATCH 09/15] Early return when simulated is true --- src/data/abilities/ability.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 4590592fd20..4dc817e1f9a 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -2129,16 +2129,20 @@ export class PostIntimidateStatStageChangeAbAttr extends AbAttr { } override apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: BooleanHolder, args: any[]): void { - if (!simulated) { - const newStatStageChangePhase = new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages) - if (globalScene.findPhase(m => m instanceof MovePhase)) { - globalScene.prependToPhase(newStatStageChangePhase, MovePhase) - } else if (globalScene.findPhase(m => m instanceof SwitchSummonPhase)) { - globalScene.prependToPhase(newStatStageChangePhase, SwitchSummonPhase) - } else { - globalScene.pushPhase(newStatStageChangePhase); - } + if (simulated) { + cancelled.value = this.overwrites; + return } + + const newStatStageChangePhase = new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages) + if (globalScene.findPhase(m => m instanceof MovePhase)) { + globalScene.prependToPhase(newStatStageChangePhase, MovePhase) + } else if (globalScene.findPhase(m => m instanceof SwitchSummonPhase)) { + globalScene.prependToPhase(newStatStageChangePhase, SwitchSummonPhase) + } else { + globalScene.pushPhase(newStatStageChangePhase); + } + cancelled.value = this.overwrites; } } From 6bf78cd732a5f020be005c3c6d8958cb6db22879 Mon Sep 17 00:00:00 2001 From: dobin Date: Fri, 6 Jun 2025 23:55:46 +0900 Subject: [PATCH 10/15] [TEST]: Ensure Rattled triggers from Intimidate before player switches out --- test/abilities/rattled.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/abilities/rattled.test.ts b/test/abilities/rattled.test.ts index ddef2028bee..c27dca87dbc 100644 --- a/test/abilities/rattled.test.ts +++ b/test/abilities/rattled.test.ts @@ -49,4 +49,19 @@ describe("Abilities - Rattled", () => { expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1); expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1); }); + + it("should activate Rattled from Intimidate before the Pokémon is switched out.", async () => { + game.override.enemyLevel(100); // Ensures the opponent switches first by overriding their Pokémon's level to 100. + await game.classicMode.startBattle([SpeciesId.GIMMIGHOUL, SpeciesId.BULBASAUR]); + + const playerPokemon = game.field.getPlayerPokemon(); + + game.forceEnemyToSwitch(); + game.doSwitchPokemon(1); + + await game.phaseInterceptor.to("StatStageChangePhase"); + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-2); + await game.phaseInterceptor.to("StatStageChangePhase"); + expect(playerPokemon.getStatStage(Stat.SPD)).toBe(2); + }); }); From 3a85c7830ea64b3399c86dd02a4714ba50bf7567 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Fri, 6 Jun 2025 12:58:02 -0400 Subject: [PATCH 11/15] Made Guard Dog use proper attribute; added test for on get hit effects --- src/data/abilities/ability.ts | 32 +++++------------- test/abilities/rattled.test.ts | 61 ++++++++++++++++++++++++++++------ 2 files changed, 59 insertions(+), 34 deletions(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 4dc817e1f9a..e8662d0e822 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -1246,7 +1246,7 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr { /** * Determine if the move type change attribute can be applied - * + * * Can be applied if: * - The ability's condition is met, e.g. pixilate only boosts normal moves, * - The move is not forbidden from having its type changed by an ability, e.g. {@linkcode MoveId.MULTI_ATTACK} @@ -1262,7 +1262,7 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr { */ override canApplyPreAttack(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _defender: Pokemon | null, move: Move, _args: [NumberHolder?, NumberHolder?, ...any]): boolean { return (!this.condition || this.condition(pokemon, _defender, move)) && - !noAbilityTypeOverrideMoves.has(move.id) && + !noAbilityTypeOverrideMoves.has(move.id) && (!pokemon.isTerastallized || (move.id !== MoveId.TERA_BLAST && (move.id !== MoveId.TERA_STARSTORM || pokemon.getTeraType() !== PokemonType.STELLAR || !pokemon.hasSpecies(SpeciesId.TERAPAGOS)))); @@ -2119,31 +2119,17 @@ export class IntimidateImmunityAbAttr extends AbAttr { export class PostIntimidateStatStageChangeAbAttr extends AbAttr { private stats: BattleStat[]; private stages: number; - private overwrites: boolean; - constructor(stats: BattleStat[], stages: number, overwrites?: boolean) { + constructor(stats: BattleStat[], stages: number) { super(true); this.stats = stats; this.stages = stages; - this.overwrites = !!overwrites; } - override apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: BooleanHolder, args: any[]): void { - if (simulated) { - cancelled.value = this.overwrites; - return + override apply(pokemon: Pokemon, _passive: boolean, simulated: boolean, _cancelled: BooleanHolder, _args: any[]): void { + if (!simulated) { + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages)); } - - const newStatStageChangePhase = new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages) - if (globalScene.findPhase(m => m instanceof MovePhase)) { - globalScene.prependToPhase(newStatStageChangePhase, MovePhase) - } else if (globalScene.findPhase(m => m instanceof SwitchSummonPhase)) { - globalScene.prependToPhase(newStatStageChangePhase, SwitchSummonPhase) - } else { - globalScene.pushPhase(newStatStageChangePhase); - } - - cancelled.value = this.overwrites; } } @@ -2349,8 +2335,6 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { const cancelled = new BooleanHolder(false); if (this.intimidate) { applyAbAttrs(IntimidateImmunityAbAttr, opponent, cancelled, simulated); - applyAbAttrs(PostIntimidateStatStageChangeAbAttr, opponent, cancelled, simulated); - if (opponent.getTag(BattlerTagType.SUBSTITUTE)) { cancelled.value = true; } @@ -2358,6 +2342,7 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { if (!cancelled.value) { globalScene.unshiftPhase(new StatStageChangePhase(opponent.getBattlerIndex(), false, this.stats, this.stages)); } + applyAbAttrs(PostIntimidateStatStageChangeAbAttr, opponent, cancelled, simulated); } } } @@ -7401,7 +7386,8 @@ export function initAbilities() { .attr(PostSummonStatStageChangeOnArenaAbAttr, ArenaTagType.TAILWIND) .ignorable(), new Ability(AbilityId.GUARD_DOG, 9) - .attr(PostIntimidateStatStageChangeAbAttr, [ Stat.ATK ], 1, true) + .attr(PostIntimidateStatStageChangeAbAttr, [ Stat.ATK ], 1) + .attr(IntimidateImmunityAbAttr) .attr(ForceSwitchOutImmunityAbAttr) .ignorable(), new Ability(AbilityId.ROCKY_PAYLOAD, 9) diff --git a/test/abilities/rattled.test.ts b/test/abilities/rattled.test.ts index c27dca87dbc..ff183131bb0 100644 --- a/test/abilities/rattled.test.ts +++ b/test/abilities/rattled.test.ts @@ -5,7 +5,10 @@ import GameManager from "#test/testUtils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { BattleType } from "#enums/battle-type"; -import { Stat } from "#enums/stat"; +import { getStatKey, getStatStageChangeDescriptionKey, Stat } from "#enums/stat"; +import { BattlerIndex } from "#app/battle"; +import i18next from "i18next"; +import { getPokemonNameWithAffix } from "#app/messages"; describe("Abilities - Rattled", () => { let phaserGame: Phaser.Game; @@ -24,43 +27,79 @@ describe("Abilities - Rattled", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .moveset([MoveId.FALSE_SWIPE, MoveId.TRICK_ROOM]) .ability(AbilityId.RATTLED) .battleType(BattleType.TRAINER) .disableCrits() .battleStyle("single") .enemySpecies(SpeciesId.MAGIKARP) .enemyAbility(AbilityId.INTIMIDATE) - .enemyMoveset(MoveId.PIN_MISSILE); + .enemyPassiveAbility(AbilityId.NO_GUARD); }); - it("should trigger and boost speed immediately after Intimidate attack drop on initial send out", async () => { + it.each<{ type: string; move: MoveId }>([ + { type: "Bug", move: MoveId.TWINEEDLE }, + { type: "Ghost", move: MoveId.ASTONISH }, + { type: "Dark", move: MoveId.BEAT_UP }, + ])("should raise the user's Speed by 1 stage for each hit of a $type-type move", async ({ move }) => { + game.override.enemyAbility(AbilityId.BALL_FETCH); + await game.classicMode.startBattle([SpeciesId.GIMMIGHOUL]); + + game.move.use(MoveId.SPLASH); + await game.move.forceEnemyMove(move); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + game.phaseInterceptor.clearLogs(); + + await game.phaseInterceptor.to("MoveEffectPhase"); + const enemyHits = game.field.getEnemyPokemon().turnData.hitCount; + await game.phaseInterceptor.to("MoveEndPhase"); + + // Rattled should've raised speed once per hit, displaying a separate message each time + const gimmighoul = game.field.getPlayerPokemon(); + expect(gimmighoul.getStatStage(Stat.SPD)).toBe(enemyHits); + expect(game.phaseInterceptor.log.filter(p => p === "ShowAbilityPhase")).toHaveLength(enemyHits); + expect(game.phaseInterceptor.log.filter(p => p === "StatStageChangePhase")).toHaveLength(enemyHits); + const statChangeText = i18next.t(getStatStageChangeDescriptionKey(1, true), { + pokemonNameWithAffix: getPokemonNameWithAffix(gimmighoul), + stats: i18next.t(getStatKey(Stat.SPD)), + count: 1, + }); + expect(game.textInterceptor.logs.filter(t => t === statChangeText)).toHaveLength(enemyHits); + }); + + it("should activate after Intimidate attack drop on initial send out", async () => { // `runToSummon` used instead of `startBattle` to avoid skipping past initial "post send out" effects await game.classicMode.runToSummon([SpeciesId.GIMMIGHOUL]); - const playerPokemon = game.field.getPlayerPokemon(); + // Intimidate await game.phaseInterceptor.to("StatStageChangePhase"); + const playerPokemon = game.field.getPlayerPokemon(); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1); expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0); + game.phaseInterceptor.clearLogs(); + // Rattled await game.phaseInterceptor.to("StatStageChangePhase"); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1); expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1); + // Nothing but show/hide ability phases should be visible + for (const log of game.phaseInterceptor.log) { + expect(log).toBeOneOf(["ShowAbilityPhase", "HideAbilityPhase", "StatStageChangePhase", "MessagePhase"]); + } }); - it("should activate Rattled from Intimidate before the Pokémon is switched out.", async () => { - game.override.enemyLevel(100); // Ensures the opponent switches first by overriding their Pokémon's level to 100. + it("should activate after Intimidate from enemy switch", async () => { await game.classicMode.startBattle([SpeciesId.GIMMIGHOUL, SpeciesId.BULBASAUR]); - const playerPokemon = game.field.getPlayerPokemon(); - + game.move.use(MoveId.SPLASH); game.forceEnemyToSwitch(); - game.doSwitchPokemon(1); - await game.phaseInterceptor.to("StatStageChangePhase"); + + const playerPokemon = game.field.getPlayerPokemon(); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-2); + expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1); + await game.phaseInterceptor.to("StatStageChangePhase"); expect(playerPokemon.getStatStage(Stat.SPD)).toBe(2); }); From f49e4d647e6b373c8bd8e8f1a91b8936925a6986 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Fri, 6 Jun 2025 13:52:02 -0400 Subject: [PATCH 12/15] Added test for status moves --- test/abilities/rattled.test.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/test/abilities/rattled.test.ts b/test/abilities/rattled.test.ts index ff183131bb0..f3732688100 100644 --- a/test/abilities/rattled.test.ts +++ b/test/abilities/rattled.test.ts @@ -31,7 +31,7 @@ describe("Abilities - Rattled", () => { .battleType(BattleType.TRAINER) .disableCrits() .battleStyle("single") - .enemySpecies(SpeciesId.MAGIKARP) + .enemySpecies(SpeciesId.DUSKULL) .enemyAbility(AbilityId.INTIMIDATE) .enemyPassiveAbility(AbilityId.NO_GUARD); }); @@ -66,6 +66,25 @@ describe("Abilities - Rattled", () => { expect(game.textInterceptor.logs.filter(t => t === statChangeText)).toHaveLength(enemyHits); }); + it.each<{ type: string; move: MoveId }>([ + { type: "Bug", move: MoveId.POWDER }, + { type: "Ghost", move: MoveId.CONFUSE_RAY }, + { type: "Dark", move: MoveId.TAUNT }, + ])("should not trigger from $type-type status moves", async ({ move }) => { + game.override.enemyAbility(AbilityId.BALL_FETCH); + await game.classicMode.startBattle([SpeciesId.GIMMIGHOUL]); + + game.move.use(MoveId.SPLASH); + await game.move.forceEnemyMove(move); + await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); + await game.phaseInterceptor.to("MoveEndPhase"); + + const gimmighoul = game.field.getPlayerPokemon(); + expect(gimmighoul.getStatStage(Stat.SPD)).toBe(0); + expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase"); + expect(game.phaseInterceptor.log).not.toContain("StatStageChangePhase"); + }); + it("should activate after Intimidate attack drop on initial send out", async () => { // `runToSummon` used instead of `startBattle` to avoid skipping past initial "post send out" effects await game.classicMode.runToSummon([SpeciesId.GIMMIGHOUL]); From 74ff6938e3200fbda4bdd5b8e6856bd6e97e7b94 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Fri, 6 Jun 2025 14:47:44 -0700 Subject: [PATCH 13/15] Fix style issues --- src/data/abilities/ability.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 4dc817e1f9a..13f3b04f8fd 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -2131,18 +2131,17 @@ export class PostIntimidateStatStageChangeAbAttr extends AbAttr { override apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: BooleanHolder, args: any[]): void { if (simulated) { cancelled.value = this.overwrites; - return + return; } - const newStatStageChangePhase = new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages) + const newStatStageChangePhase = new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages); if (globalScene.findPhase(m => m instanceof MovePhase)) { - globalScene.prependToPhase(newStatStageChangePhase, MovePhase) + globalScene.prependToPhase(newStatStageChangePhase, MovePhase); } else if (globalScene.findPhase(m => m instanceof SwitchSummonPhase)) { - globalScene.prependToPhase(newStatStageChangePhase, SwitchSummonPhase) + globalScene.prependToPhase(newStatStageChangePhase, SwitchSummonPhase); } else { globalScene.pushPhase(newStatStageChangePhase); } - cancelled.value = this.overwrites; } } From f68899c187af136436e234e9827b3a2655736f11 Mon Sep 17 00:00:00 2001 From: Dobin Shin <34051876+Gamez0@users.noreply.github.com> Date: Sat, 7 Jun 2025 20:56:40 +0900 Subject: [PATCH 14/15] Delete cancelled.value = this.overwrites --- src/data/abilities/ability.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 82ba9495b3b..05ccf048a36 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -2130,7 +2130,6 @@ export class PostIntimidateStatStageChangeAbAttr extends AbAttr { if (!simulated) { globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages)); } - cancelled.value = this.overwrites; } } From e669b828f2f2547dd52044284addb6bf930132ee Mon Sep 17 00:00:00 2001 From: dobin Date: Fri, 13 Jun 2025 17:07:23 +0900 Subject: [PATCH 15/15] Update BattlerIndex import path --- test/abilities/rattled.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/abilities/rattled.test.ts b/test/abilities/rattled.test.ts index f3732688100..301e102511f 100644 --- a/test/abilities/rattled.test.ts +++ b/test/abilities/rattled.test.ts @@ -6,7 +6,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { BattleType } from "#enums/battle-type"; import { getStatKey, getStatStageChangeDescriptionKey, Stat } from "#enums/stat"; -import { BattlerIndex } from "#app/battle"; +import { BattlerIndex } from "#enums/battler-index"; import i18next from "i18next"; import { getPokemonNameWithAffix } from "#app/messages";