From 55974903aecf23b3bcdff82ae5ad08b4bbb27b21 Mon Sep 17 00:00:00 2001 From: innerthunder Date: Mon, 19 Aug 2024 23:49:04 -0700 Subject: [PATCH 1/8] Powder basic implementation --- src/data/battler-tags.ts | 37 +++++++++++++++++++++++++++++++++++ src/data/move.ts | 4 ++-- src/enums/battler-tag-type.ts | 3 ++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index ede8d029327..a08631877cb 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -542,6 +542,41 @@ export class SeedTag extends BattlerTag { } } +/** + * BattlerTag representing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Powder_(move) | Powder}. + * When the afflicted Pokemon uses a Fire-type move, the move is cancelled, and the + * Pokemon takes damage equal to 1/4 of it's maximum HP (rounded down). + */ +export class PowderTag extends BattlerTag { + constructor() { + super(BattlerTagType.POWDER, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END ], 1); + } + + onAdd(pokemon: Pokemon): void { + super.onAdd(pokemon); + + pokemon.scene.queueMessage(i18next.t("battlerTags:powderOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + } + + lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { + if (lapseType === BattlerTagLapseType.PRE_MOVE) { + const movePhase = pokemon.scene.getCurrentPhase(); + if (movePhase instanceof MovePhase) { + const move = movePhase.move.getMove(); + if (move.type === Type.FIRE) { + movePhase.cancel(); + pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), HitResult.OTHER); + + pokemon.scene.queueMessage(i18next.t("battlerTags:powderLapse")); + } + } + return true; + } else { + return super.lapse(pokemon, lapseType); + } + } +} + export class NightmareTag extends BattlerTag { constructor() { super(BattlerTagType.NIGHTMARE, BattlerTagLapseType.AFTER_MOVE, 1, Moves.NIGHTMARE); @@ -1846,6 +1881,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source return new InfatuatedTag(sourceMove, sourceId); case BattlerTagType.SEEDED: return new SeedTag(sourceId); + case BattlerTagType.POWDER: + return new PowderTag(); case BattlerTagType.NIGHTMARE: return new NightmareTag(); case BattlerTagType.FRENZY: diff --git a/src/data/move.ts b/src/data/move.ts index acb61042e70..6ac893fe849 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -7864,8 +7864,8 @@ export function initMoves() { .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK, BattleStat.SPD ], -1, false, (user, target, move) => target.status?.effect === StatusEffect.POISON || target.status?.effect === StatusEffect.TOXIC) .target(MoveTarget.ALL_NEAR_ENEMIES), new StatusMove(Moves.POWDER, Type.BUG, 100, 20, -1, 1, 6) - .powderMove() - .unimplemented(), + .attr(AddBattlerTagAttr, BattlerTagType.POWDER, false, true) + .powderMove(), new SelfStatusMove(Moves.GEOMANCY, Type.FAIRY, -1, 10, -1, 0, 6) .attr(ChargeAttr, ChargeAnim.GEOMANCY_CHARGING, i18next.t("moveTriggers:isChargingPower", {pokemonName: "{USER}"})) .attr(StatChangeAttr, [ BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 2, true) diff --git a/src/enums/battler-tag-type.ts b/src/enums/battler-tag-type.ts index b133b442801..e5a43579d3d 100644 --- a/src/enums/battler-tag-type.ts +++ b/src/enums/battler-tag-type.ts @@ -69,5 +69,6 @@ export enum BattlerTagType { GULP_MISSILE_ARROKUDA = "GULP_MISSILE_ARROKUDA", GULP_MISSILE_PIKACHU = "GULP_MISSILE_PIKACHU", BEAK_BLAST_CHARGING = "BEAK_BLAST_CHARGING", - SHELL_TRAP = "SHELL_TRAP" + SHELL_TRAP = "SHELL_TRAP", + POWDER = "POWDER" } From f149e31ce5904b9e58922748780e9b86a5b5e9ff Mon Sep 17 00:00:00 2001 From: innerthunder Date: Tue, 20 Aug 2024 01:32:21 -0700 Subject: [PATCH 2/8] Add Powder integration tests --- src/data/battler-tags.ts | 14 +++- src/test/moves/powder.test.ts | 145 ++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 src/test/moves/powder.test.ts diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index a08631877cb..7a185d67f38 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -558,6 +558,13 @@ export class PowderTag extends BattlerTag { pokemon.scene.queueMessage(i18next.t("battlerTags:powderOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } + /** + * Applies Powder's effects before the tag owner uses a Fire-type move. + * Also causes the tag to expire at the end of turn. + * @param pokemon {@linkcode Pokemon} the owner of this tag + * @param lapseType {@linkcode BattlerTagLapseType} the type of lapse functionality to carry out + * @returns `true` if the tag should not expire after this lapse; `false` otherwise. + */ lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.PRE_MOVE) { const movePhase = pokemon.scene.getCurrentPhase(); @@ -565,7 +572,12 @@ export class PowderTag extends BattlerTag { const move = movePhase.move.getMove(); if (move.type === Type.FIRE) { movePhase.cancel(); - pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), HitResult.OTHER); + + const cancelDamage = new Utils.BooleanHolder(false); + applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelDamage); + if (!cancelDamage.value) { + pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), HitResult.OTHER); + } pokemon.scene.queueMessage(i18next.t("battlerTags:powderLapse")); } diff --git a/src/test/moves/powder.test.ts b/src/test/moves/powder.test.ts new file mode 100644 index 00000000000..f2c245cd141 --- /dev/null +++ b/src/test/moves/powder.test.ts @@ -0,0 +1,145 @@ +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import Phaser from "phaser"; +import GameManager from "#test/utils/gameManager"; +import { Abilities } from "#app/enums/abilities"; +import { Moves } from "#app/enums/moves"; +import { Species } from "#app/enums/species"; +import { getMovePosition } from "#app/test/utils/gameManagerUtils"; +import { BerryPhase } from "#app/phases/berry-phase"; +import { MoveResult } from "#app/field/pokemon"; +import { Type } from "#app/data/type"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase"; +import { StatusEffect } from "#app/enums/status-effect"; + +const TIMEOUT = 20 * 1000; + +describe("Moves - Powder", () => { + 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"); + + game.override.enemySpecies(Species.SNORLAX); + game.override.enemyLevel(100); + game.override.enemyMoveset(Array(4).fill(Moves.EMBER)); + game.override.enemyAbility(Abilities.INSOMNIA); + + game.override.startingLevel(100); + game.override.moveset([Moves.POWDER, Moves.SPLASH, Moves.FIERY_DANCE]); + }); + + it( + "should cancel the target's Fire-type move and damage the target", + async () => { + await game.startBattle([Species.CHARIZARD]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.POWDER)); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4)); + }, TIMEOUT + ); + + it.todo("should not cancel Fire-type moves after the turn it's used"); + + it.todo("should have no effect against Grass-type Pokemon"); + + it.todo("should have no effect against Pokemon with Overcoat"); + + it( + "should not damage the target if the target has Magic Guard", + async () => { + game.override.enemyAbility(Abilities.MAGIC_GUARD); + + await game.startBattle([Species.CHARIZARD]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.POWDER)); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); + }, TIMEOUT + ); + + it( + "should not prevent the target from thawing out with its Fire-type move", + async () => { + game.override.enemyStatusEffect(StatusEffect.FREEZE); + + await game.startBattle([Species.CHARIZARD]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.POWDER)); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.status?.effect).not.toBe(StatusEffect.FREEZE); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4)); + } + ); + + it( + "should not allow a target with Protean to change to Fire type", + async () => { + game.override.enemyAbility(Abilities.PROTEAN); + + await game.startBattle([Species.CHARIZARD]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.POWDER)); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); + expect(enemyPokemon.summonData?.types).not.toBe(Type.FIRE); + }, TIMEOUT + ); + + it.skip( + "should cancel Fire-type moves generated by the target's Dancer ability", + async () => { + game.override + .enemySpecies(Species.BLASTOISE) + .enemyAbility(Abilities.DANCER); + + await game.startBattle([Species.CHARIZARD]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.doAttack(getMovePosition(game.scene, 0, Moves.FIERY_DANCE)); + + await game.phaseInterceptor.to(MoveEffectPhase); + const enemyStartingHp = enemyPokemon.hp; + + await game.phaseInterceptor.to(BerryPhase, false); + // player should not take damage + expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp()); + // enemy should have taken damage from player's Fiery Dance + 2 Powder procs + expect(enemyPokemon.hp).toBe(enemyStartingHp - 2*Math.floor(enemyPokemon.getMaxHp() / 4)); + }, TIMEOUT + ); + + it.todo("should cancel Hidden Power if it becomes a Fire-type move"); + + it.todo("should cancel Shell Trap and damage the target, even if the move would fail"); +}); From 5967775b076cfa5b8abe2a6b251b6235fe9c5a27 Mon Sep 17 00:00:00 2001 From: innerthunder Date: Tue, 20 Aug 2024 10:29:24 -0700 Subject: [PATCH 3/8] Fix thaw test --- src/test/moves/powder.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/moves/powder.test.ts b/src/test/moves/powder.test.ts index f2c245cd141..99cf9d6cc7d 100644 --- a/src/test/moves/powder.test.ts +++ b/src/test/moves/powder.test.ts @@ -79,9 +79,11 @@ describe("Moves - Powder", () => { ); it( - "should not prevent the target from thawing out with its Fire-type move", + "should not prevent the target from thawing out with Flame Wheel", async () => { - game.override.enemyStatusEffect(StatusEffect.FREEZE); + game.override + .enemyMoveset(Array(4).fill(Moves.FLAME_WHEEL)) + .enemyStatusEffect(StatusEffect.FREEZE); await game.startBattle([Species.CHARIZARD]); From a3986b1fac45e912044c16b3e7b8ef834a450484 Mon Sep 17 00:00:00 2001 From: innerthunder Date: Thu, 29 Aug 2024 22:14:48 -0700 Subject: [PATCH 4/8] Use new test utils and type check function --- src/data/battler-tags.ts | 2 +- src/test/moves/powder.test.ts | 37 +++++++++++++++++------------------ 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 67bc6be8c3a..c4263bd3ad4 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -570,7 +570,7 @@ export class PowderTag extends BattlerTag { const movePhase = pokemon.scene.getCurrentPhase(); if (movePhase instanceof MovePhase) { const move = movePhase.move.getMove(); - if (move.type === Type.FIRE) { + if (pokemon.getMoveType(move) === Type.FIRE) { movePhase.cancel(); const cancelDamage = new Utils.BooleanHolder(false); diff --git a/src/test/moves/powder.test.ts b/src/test/moves/powder.test.ts index 99cf9d6cc7d..3f8a05940ae 100644 --- a/src/test/moves/powder.test.ts +++ b/src/test/moves/powder.test.ts @@ -4,7 +4,6 @@ import GameManager from "#test/utils/gameManager"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; -import { getMovePosition } from "#app/test/utils/gameManagerUtils"; import { BerryPhase } from "#app/phases/berry-phase"; import { MoveResult } from "#app/field/pokemon"; import { Type } from "#app/data/type"; @@ -31,23 +30,23 @@ describe("Moves - Powder", () => { game = new GameManager(phaserGame); game.override.battleType("single"); - game.override.enemySpecies(Species.SNORLAX); - game.override.enemyLevel(100); - game.override.enemyMoveset(Array(4).fill(Moves.EMBER)); - game.override.enemyAbility(Abilities.INSOMNIA); - - game.override.startingLevel(100); - game.override.moveset([Moves.POWDER, Moves.SPLASH, Moves.FIERY_DANCE]); + game.override + .enemySpecies(Species.SNORLAX) + .enemyLevel(100) + .enemyMoveset(Array(4).fill(Moves.EMBER)) + .enemyAbility(Abilities.INSOMNIA) + .startingLevel(100) + .moveset([Moves.POWDER, Moves.SPLASH, Moves.FIERY_DANCE]); }); it( "should cancel the target's Fire-type move and damage the target", async () => { - await game.startBattle([Species.CHARIZARD]); + await game.classicMode.startBattle([Species.CHARIZARD]); const enemyPokemon = game.scene.getEnemyPokemon()!; - game.doAttack(getMovePosition(game.scene, 0, Moves.POWDER)); + game.move.select(Moves.POWDER); await game.phaseInterceptor.to(BerryPhase, false); expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); @@ -66,11 +65,11 @@ describe("Moves - Powder", () => { async () => { game.override.enemyAbility(Abilities.MAGIC_GUARD); - await game.startBattle([Species.CHARIZARD]); + await game.classicMode.startBattle([Species.CHARIZARD]); const enemyPokemon = game.scene.getEnemyPokemon()!; - game.doAttack(getMovePosition(game.scene, 0, Moves.POWDER)); + game.move.select(Moves.POWDER); await game.phaseInterceptor.to(BerryPhase, false); expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); @@ -85,11 +84,11 @@ describe("Moves - Powder", () => { .enemyMoveset(Array(4).fill(Moves.FLAME_WHEEL)) .enemyStatusEffect(StatusEffect.FREEZE); - await game.startBattle([Species.CHARIZARD]); + await game.classicMode.startBattle([Species.CHARIZARD]); const enemyPokemon = game.scene.getEnemyPokemon()!; - game.doAttack(getMovePosition(game.scene, 0, Moves.POWDER)); + game.move.select(Moves.POWDER); await game.phaseInterceptor.to(BerryPhase, false); expect(enemyPokemon.status?.effect).not.toBe(StatusEffect.FREEZE); @@ -103,11 +102,11 @@ describe("Moves - Powder", () => { async () => { game.override.enemyAbility(Abilities.PROTEAN); - await game.startBattle([Species.CHARIZARD]); + await game.classicMode.startBattle([Species.CHARIZARD]); const enemyPokemon = game.scene.getEnemyPokemon()!; - game.doAttack(getMovePosition(game.scene, 0, Moves.POWDER)); + game.move.select(Moves.POWDER); await game.phaseInterceptor.to(BerryPhase, false); expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); @@ -123,12 +122,12 @@ describe("Moves - Powder", () => { .enemySpecies(Species.BLASTOISE) .enemyAbility(Abilities.DANCER); - await game.startBattle([Species.CHARIZARD]); + await game.classicMode.startBattle([Species.CHARIZARD]); const playerPokemon = game.scene.getPlayerPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!; - game.doAttack(getMovePosition(game.scene, 0, Moves.FIERY_DANCE)); + game.move.select(Moves.FIERY_DANCE); await game.phaseInterceptor.to(MoveEffectPhase); const enemyStartingHp = enemyPokemon.hp; @@ -141,7 +140,7 @@ describe("Moves - Powder", () => { }, TIMEOUT ); - it.todo("should cancel Hidden Power if it becomes a Fire-type move"); + it.todo("should cancel Revelation Dance if it becomes a Fire-type move"); it.todo("should cancel Shell Trap and damage the target, even if the move would fail"); }); From 7870c718b2718227e1841068a7420fc03a75bbb6 Mon Sep 17 00:00:00 2001 From: innerthunder Date: Thu, 29 Aug 2024 23:18:20 -0700 Subject: [PATCH 5/8] More edge case tests --- src/test/moves/powder.test.ts | 79 ++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 5 deletions(-) diff --git a/src/test/moves/powder.test.ts b/src/test/moves/powder.test.ts index 3f8a05940ae..e38d508a002 100644 --- a/src/test/moves/powder.test.ts +++ b/src/test/moves/powder.test.ts @@ -51,14 +51,50 @@ describe("Moves - Powder", () => { await game.phaseInterceptor.to(BerryPhase, false); expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4)); + + await game.toNextTurn(); + + game.move.select(Moves.SPLASH); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4)); }, TIMEOUT ); - it.todo("should not cancel Fire-type moves after the turn it's used"); + it( + "should have no effect against Grass-type Pokemon", + async () => { + game.override.enemySpecies(Species.AMOONGUSS); - it.todo("should have no effect against Grass-type Pokemon"); + await game.classicMode.startBattle([Species.CHARIZARD]); - it.todo("should have no effect against Pokemon with Overcoat"); + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.POWDER); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); + }, TIMEOUT + ); + + it( + "should have no effect against Pokemon with Overcoat", + async () => { + game.override.enemyAbility(Abilities.OVERCOAT); + + await game.classicMode.startBattle([Species.CHARIZARD]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.POWDER); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); + }, TIMEOUT + ); it( "should not damage the target if the target has Magic Guard", @@ -115,6 +151,7 @@ describe("Moves - Powder", () => { }, TIMEOUT ); + // TODO: Implement this interaction and enable this test it.skip( "should cancel Fire-type moves generated by the target's Dancer ability", async () => { @@ -140,7 +177,39 @@ describe("Moves - Powder", () => { }, TIMEOUT ); - it.todo("should cancel Revelation Dance if it becomes a Fire-type move"); + it( + "should cancel Revelation Dance if it becomes a Fire-type move", + async () => { + game.override + .enemySpecies(Species.CHARIZARD) + .enemyMoveset(Array(4).fill(Moves.REVELATION_DANCE)); - it.todo("should cancel Shell Trap and damage the target, even if the move would fail"); + await game.classicMode.startBattle([Species.CHARIZARD]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.POWDER); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4)); + }, TIMEOUT + ); + + it( + "should cancel Shell Trap and damage the target, even if the move would fail", + async () => { + game.override.enemyMoveset(Array(4).fill(Moves.SHELL_TRAP)); + + await game.classicMode.startBattle([Species.CHARIZARD]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.POWDER); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4)); + }, TIMEOUT + ); }); From f7465bb74d910ecc9735364984981dabf91787a1 Mon Sep 17 00:00:00 2001 From: innerthunder Date: Thu, 29 Aug 2024 23:26:45 -0700 Subject: [PATCH 6/8] Make Powder (P) --- src/data/move.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/data/move.ts b/src/data/move.ts index 7e1c7ea3f0f..004630c2eea 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -8042,7 +8042,8 @@ export function initMoves() { .target(MoveTarget.ALL_NEAR_ENEMIES), new StatusMove(Moves.POWDER, Type.BUG, 100, 20, -1, 1, 6) .attr(AddBattlerTagAttr, BattlerTagType.POWDER, false, true) - .powderMove(), + .powderMove() + .partial(), // does not affect virtual moves (e.g. from Metronome or Dancer) when it should new SelfStatusMove(Moves.GEOMANCY, Type.FAIRY, -1, 10, -1, 0, 6) .attr(ChargeAttr, ChargeAnim.GEOMANCY_CHARGING, i18next.t("moveTriggers:isChargingPower", {pokemonName: "{USER}"})) .attr(StatChangeAttr, [ BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 2, true) From 3b3069ee732823ee1ce04e3992b97455270b6b7c Mon Sep 17 00:00:00 2001 From: innerthunder Date: Fri, 30 Aug 2024 00:45:05 -0700 Subject: [PATCH 7/8] Add locale keys --- src/data/move.ts | 2 +- src/locales/en/battler-tags.json | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/data/move.ts b/src/data/move.ts index 004630c2eea..82d44f76d87 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -8043,7 +8043,7 @@ export function initMoves() { new StatusMove(Moves.POWDER, Type.BUG, 100, 20, -1, 1, 6) .attr(AddBattlerTagAttr, BattlerTagType.POWDER, false, true) .powderMove() - .partial(), // does not affect virtual moves (e.g. from Metronome or Dancer) when it should + .partial(), // does not cancel Fire-type moves generated by Dancer new SelfStatusMove(Moves.GEOMANCY, Type.FAIRY, -1, 10, -1, 0, 6) .attr(ChargeAttr, ChargeAnim.GEOMANCY_CHARGING, i18next.t("moveTriggers:isChargingPower", {pokemonName: "{USER}"})) .attr(StatChangeAttr, [ BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 2, true) diff --git a/src/locales/en/battler-tags.json b/src/locales/en/battler-tags.json index 94ea3b14958..cedb0a68d86 100644 --- a/src/locales/en/battler-tags.json +++ b/src/locales/en/battler-tags.json @@ -26,6 +26,8 @@ "seededOnAdd": "{{pokemonNameWithAffix}} was seeded!", "seededLapse": "{{pokemonNameWithAffix}}'s health is\nsapped by Leech Seed!", "seededLapseShed": "{{pokemonNameWithAffix}}'s Leech Seed\nsucked up the liquid ooze!", + "powderOnAdd": "{{pokemonNameWithAffix}} is covered in powder!", + "powderLapse": "When the flame touched the powder\non the Pokémon, it exploded!", "nightmareOnAdd": "{{pokemonNameWithAffix}} began\nhaving a Nightmare!", "nightmareOnOverlap": "{{pokemonNameWithAffix}} is\nalready locked in a Nightmare!", "nightmareLapse": "{{pokemonNameWithAffix}} is locked\nin a Nightmare!", From ed4ac9ac6b772b935a7dd02fa4273f0a63057276 Mon Sep 17 00:00:00 2001 From: innerthunder Date: Thu, 5 Sep 2024 13:57:29 -0700 Subject: [PATCH 8/8] Add placeholder common anim --- public/battle-anims/common-powder.json | 111 +++++++++++++++++++++++++ src/data/battle-anims.ts | 1 + src/data/battler-tags.ts | 2 + 3 files changed, 114 insertions(+) create mode 100644 public/battle-anims/common-powder.json diff --git a/public/battle-anims/common-powder.json b/public/battle-anims/common-powder.json new file mode 100644 index 00000000000..a56b004c914 --- /dev/null +++ b/public/battle-anims/common-powder.json @@ -0,0 +1,111 @@ +{ + "graphic": "", + "frames": [ + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 3, + "zoomX": 100, + "zoomY": 50, + "visible": true, + "target": 0, + "graphicFrame": 0, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 50, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 50, + "visible": true, + "target": 0, + "graphicFrame": 0, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 50, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 50, + "visible": true, + "target": 0, + "graphicFrame": 0, + "priority": 1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 50, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": 1, + "focus": 1 + } + ] + ], + "position": 2, + "hue": 0 +} diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index da4e7f6a33b..ce3bb44e919 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -86,6 +86,7 @@ export enum CommonAnim { RAGING_BULL_FIRE, RAGING_BULL_WATER, SALT_CURE, + POWDER, SUNNY = 2100, RAIN, SANDSTORM, diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 6fd44cc9782..2c590bb6547 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -712,6 +712,8 @@ export class PowderTag extends BattlerTag { if (pokemon.getMoveType(move) === Type.FIRE) { movePhase.cancel(); + new CommonBattleAnim(CommonAnim.POWDER, pokemon).play(pokemon.scene); + const cancelDamage = new Utils.BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelDamage); if (!cancelDamage.value) {