From 698aa62fdd087d2e6a1324ce7da663e26dee647e Mon Sep 17 00:00:00 2001 From: innerthunder Date: Sun, 17 Nov 2024 15:48:47 -0800 Subject: [PATCH] Fixed Flame Burst/substitute interaction --- src/data/move.ts | 17 +++++++++---- src/test/moves/flame_burst.test.ts | 41 ++++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index e17c095575c..678c958c34c 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1837,6 +1837,15 @@ export class PartyStatusCureAttr extends MoveEffectAttr { * @extends MoveEffectAttr */ export class FlameBurstAttr extends MoveEffectAttr { + constructor() { + /** + * This is self-targeted to bypass immunity to target-facing secondary + * effects when the target has an active Substitute doll. + * TODO: Find a more intuitive way to implement Substitute bypassing. + */ + super(true); + } + /** * @param user - n/a * @param target - The target Pokémon. @@ -5176,21 +5185,19 @@ export class AddBattlerTagAttr extends MoveEffectAttr { public tagType: BattlerTagType; public turnCountMin: integer; public turnCountMax: integer; - protected cancelOnFail: boolean; private failOnOverlap: boolean; - constructor(tagType: BattlerTagType, selfTarget: boolean = false, failOnOverlap: boolean = false, turnCountMin: integer = 0, turnCountMax?: integer, lastHitOnly: boolean = false, cancelOnFail: boolean = false) { + constructor(tagType: BattlerTagType, selfTarget: boolean = false, failOnOverlap: boolean = false, turnCountMin: integer = 0, turnCountMax?: integer, lastHitOnly: boolean = false) { super(selfTarget, { lastHitOnly: lastHitOnly }); this.tagType = tagType; this.turnCountMin = turnCountMin; this.turnCountMax = turnCountMax !== undefined ? turnCountMax : turnCountMin; this.failOnOverlap = !!failOnOverlap; - this.cancelOnFail = cancelOnFail; } canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (!super.canApply(user, target, move, args) || (this.cancelOnFail === true && user.getLastXMoves(1)[0]?.result === MoveResult.FAIL)) { + if (!super.canApply(user, target, move, args)) { return false; } else { return true; @@ -5452,7 +5459,7 @@ export class ConfuseAttr extends AddBattlerTagAttr { export class RechargeAttr extends AddBattlerTagAttr { constructor() { - super(BattlerTagType.RECHARGING, true, false, 1, 1, true, true); + super(BattlerTagType.RECHARGING, true, false, 1, 1, true); } } diff --git a/src/test/moves/flame_burst.test.ts b/src/test/moves/flame_burst.test.ts index feedee3b7bc..2e9490304f0 100644 --- a/src/test/moves/flame_burst.test.ts +++ b/src/test/moves/flame_burst.test.ts @@ -1,7 +1,7 @@ +import { BattlerIndex } from "#app/battle"; import { allAbilities } from "#app/data/ability"; import { Abilities } from "#app/enums/abilities"; import Pokemon from "#app/field/pokemon"; -import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; @@ -46,12 +46,12 @@ describe("Moves - Flame Burst", () => { }); it("inflicts damage to the target's ally equal to 1/16 of its max HP", async () => { - await game.startBattle([ Species.PIKACHU, Species.PIKACHU ]); + await game.classicMode.startBattle([ Species.PIKACHU, Species.PIKACHU ]); const [ leftEnemy, rightEnemy ] = game.scene.getEnemyField(); game.move.select(Moves.FLAME_BURST, 0, leftEnemy.getBattlerIndex()); game.move.select(Moves.SPLASH, 1); - await game.phaseInterceptor.to(TurnEndPhase); + await game.phaseInterceptor.to("TurnEndPhase"); expect(leftEnemy.hp).toBeLessThan(leftEnemy.getMaxHp()); expect(rightEnemy.hp).toBe(rightEnemy.getMaxHp() - getEffectDamage(rightEnemy)); @@ -60,46 +60,65 @@ describe("Moves - Flame Burst", () => { it("does not inflict damage to the target's ally if the target was not affected by Flame Burst", async () => { game.override.enemyAbility(Abilities.FLASH_FIRE); - await game.startBattle([ Species.PIKACHU, Species.PIKACHU ]); + await game.classicMode.startBattle([ Species.PIKACHU, Species.PIKACHU ]); const [ leftEnemy, rightEnemy ] = game.scene.getEnemyField(); game.move.select(Moves.FLAME_BURST, 0, leftEnemy.getBattlerIndex()); game.move.select(Moves.SPLASH, 1); - await game.phaseInterceptor.to(TurnEndPhase); + await game.phaseInterceptor.to("TurnEndPhase"); expect(leftEnemy.hp).toBe(leftEnemy.getMaxHp()); expect(rightEnemy.hp).toBe(rightEnemy.getMaxHp()); }); it("does not interact with the target ally's abilities", async () => { - await game.startBattle([ Species.PIKACHU, Species.PIKACHU ]); + await game.classicMode.startBattle([ Species.PIKACHU, Species.PIKACHU ]); const [ leftEnemy, rightEnemy ] = game.scene.getEnemyField(); vi.spyOn(rightEnemy, "getAbility").mockReturnValue(allAbilities[Abilities.FLASH_FIRE]); game.move.select(Moves.FLAME_BURST, 0, leftEnemy.getBattlerIndex()); game.move.select(Moves.SPLASH, 1); - await game.phaseInterceptor.to(TurnEndPhase); + await game.phaseInterceptor.to("TurnEndPhase"); expect(leftEnemy.hp).toBeLessThan(leftEnemy.getMaxHp()); expect(rightEnemy.hp).toBe(rightEnemy.getMaxHp() - getEffectDamage(rightEnemy)); }); it("effect damage is prevented by Magic Guard", async () => { - await game.startBattle([ Species.PIKACHU, Species.PIKACHU ]); + await game.classicMode.startBattle([ Species.PIKACHU, Species.PIKACHU ]); const [ leftEnemy, rightEnemy ] = game.scene.getEnemyField(); vi.spyOn(rightEnemy, "getAbility").mockReturnValue(allAbilities[Abilities.MAGIC_GUARD]); game.move.select(Moves.FLAME_BURST, 0, leftEnemy.getBattlerIndex()); game.move.select(Moves.SPLASH, 1); - await game.phaseInterceptor.to(TurnEndPhase); + await game.phaseInterceptor.to("TurnEndPhase"); expect(leftEnemy.hp).toBeLessThan(leftEnemy.getMaxHp()); expect(rightEnemy.hp).toBe(rightEnemy.getMaxHp()); }); - it("is not affected by protection moves and Endure", async () => { + it("effect damage should apply even when targeting a Substitute", async () => { + game.override.enemyMoveset([ Moves.SUBSTITUTE, Moves.SPLASH ]); + + await game.classicMode.startBattle([ Species.PIKACHU, Species.PIKACHU ]); + const [ leftEnemy, rightEnemy ] = game.scene.getEnemyField(); + + game.move.select(Moves.FLAME_BURST, 0, leftEnemy.getBattlerIndex()); + game.move.select(Moves.SPLASH, 1); + + await game.forceEnemyMove(Moves.SUBSTITUTE); + await game.forceEnemyMove(Moves.SPLASH); + + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2 ]); + + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(rightEnemy.hp).toBe(rightEnemy.getMaxHp() - getEffectDamage(rightEnemy)); + }); + + it.skip("is not affected by protection moves and Endure", async () => { // TODO: update this test when it's possible to select move for each enemy - }, { skip: true }); + }); });