[Move][Beta] Powder edge cases (#4960)
* [Move][Beta] Powder edge cases * Fix Heavy Rain check to account for weather suppression Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * "{Pokemon} used {Fire-type move}!" now displays before Powder activation Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> * Make `showMoveText()` and `showFailedText()` public for now --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com>
This commit is contained in:
parent
c6e80de1be
commit
dd72c5e189
|
@ -885,8 +885,10 @@ export class PowderTag extends BattlerTag {
|
||||||
const movePhase = pokemon.scene.getCurrentPhase();
|
const movePhase = pokemon.scene.getCurrentPhase();
|
||||||
if (movePhase instanceof MovePhase) {
|
if (movePhase instanceof MovePhase) {
|
||||||
const move = movePhase.move.getMove();
|
const move = movePhase.move.getMove();
|
||||||
if (pokemon.getMoveType(move) === Type.FIRE) {
|
const weather = pokemon.scene.arena.weather;
|
||||||
movePhase.cancel();
|
if (pokemon.getMoveType(move) === Type.FIRE && !(weather && weather.weatherType === WeatherType.HEAVY_RAIN && !weather.isEffectSuppressed(pokemon.scene))) {
|
||||||
|
movePhase.fail();
|
||||||
|
movePhase.showMoveText();
|
||||||
|
|
||||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.POWDER));
|
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.POWDER));
|
||||||
|
|
||||||
|
|
|
@ -9734,8 +9734,7 @@ export function initMoves() {
|
||||||
new StatusMove(Moves.POWDER, Type.BUG, 100, 20, -1, 1, 6)
|
new StatusMove(Moves.POWDER, Type.BUG, 100, 20, -1, 1, 6)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.POWDER, false, true)
|
.attr(AddBattlerTagAttr, BattlerTagType.POWDER, false, true)
|
||||||
.ignoresSubstitute()
|
.ignoresSubstitute()
|
||||||
.powderMove()
|
.powderMove(),
|
||||||
.edgeCase(), // does not cancel Fire-type moves generated by Dancer
|
|
||||||
new ChargingSelfStatusMove(Moves.GEOMANCY, Type.FAIRY, -1, 10, -1, 0, 6)
|
new ChargingSelfStatusMove(Moves.GEOMANCY, Type.FAIRY, -1, 10, -1, 0, 6)
|
||||||
.chargeText(i18next.t("moveTriggers:isChargingPower", { pokemonName: "{USER}" }))
|
.chargeText(i18next.t("moveTriggers:isChargingPower", { pokemonName: "{USER}" }))
|
||||||
.attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true)
|
.attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true)
|
||||||
|
|
|
@ -529,7 +529,7 @@ export class MovePhase extends BattlePhase {
|
||||||
* Displays the move's usage text to the player, unless it's a charge turn (ie: {@link Moves.SOLAR_BEAM Solar Beam}),
|
* Displays the move's usage text to the player, unless it's a charge turn (ie: {@link Moves.SOLAR_BEAM Solar Beam}),
|
||||||
* the pokemon is on a recharge turn (ie: {@link Moves.HYPER_BEAM Hyper Beam}), or a 2-turn move was interrupted (ie: {@link Moves.FLY Fly}).
|
* the pokemon is on a recharge turn (ie: {@link Moves.HYPER_BEAM Hyper Beam}), or a 2-turn move was interrupted (ie: {@link Moves.FLY Fly}).
|
||||||
*/
|
*/
|
||||||
protected showMoveText(): void {
|
public showMoveText(): void {
|
||||||
if (this.move.moveId === Moves.NONE) {
|
if (this.move.moveId === Moves.NONE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -545,7 +545,7 @@ export class MovePhase extends BattlePhase {
|
||||||
applyMoveAttrs(PreMoveMessageAttr, this.pokemon, this.pokemon.getOpponents()[0], this.move.getMove());
|
applyMoveAttrs(PreMoveMessageAttr, this.pokemon, this.pokemon.getOpponents()[0], this.move.getMove());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected showFailedText(failedText?: string): void {
|
public showFailedText(failedText?: string): void {
|
||||||
this.scene.queueMessage(failedText ?? i18next.t("battle:attackFailed"));
|
this.scene.queueMessage(failedText ?? i18next.t("battle:attackFailed"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,11 @@ import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import { BerryPhase } from "#app/phases/berry-phase";
|
import { BerryPhase } from "#app/phases/berry-phase";
|
||||||
import { MoveResult } from "#app/field/pokemon";
|
import { MoveResult, PokemonMove } from "#app/field/pokemon";
|
||||||
import { Type } from "#enums/type";
|
import { Type } from "#enums/type";
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
|
||||||
describe("Moves - Powder", () => {
|
describe("Moves - Powder", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
|
@ -34,21 +35,25 @@ describe("Moves - Powder", () => {
|
||||||
.enemyMoveset(Moves.EMBER)
|
.enemyMoveset(Moves.EMBER)
|
||||||
.enemyAbility(Abilities.INSOMNIA)
|
.enemyAbility(Abilities.INSOMNIA)
|
||||||
.startingLevel(100)
|
.startingLevel(100)
|
||||||
.moveset([ Moves.POWDER, Moves.SPLASH, Moves.FIERY_DANCE ]);
|
.moveset([ Moves.POWDER, Moves.SPLASH, Moves.FIERY_DANCE, Moves.ROAR ]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(
|
it(
|
||||||
"should cancel the target's Fire-type move and damage the target",
|
"should cancel the target's Fire-type move, damage the target, and still consume the target's PP",
|
||||||
async () => {
|
async () => {
|
||||||
|
// Cannot use enemy moveset override for this test, since it interferes with checking PP
|
||||||
|
game.override.enemyMoveset([]);
|
||||||
await game.classicMode.startBattle([ Species.CHARIZARD ]);
|
await game.classicMode.startBattle([ Species.CHARIZARD ]);
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
enemyPokemon.moveset = [ new PokemonMove(Moves.EMBER) ];
|
||||||
|
|
||||||
game.move.select(Moves.POWDER);
|
game.move.select(Moves.POWDER);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(BerryPhase, false);
|
await game.phaseInterceptor.to(BerryPhase, false);
|
||||||
expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||||
expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4));
|
expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4));
|
||||||
|
expect(enemyPokemon.moveset[0]!.ppUsed).toBe(1);
|
||||||
|
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
@ -57,6 +62,7 @@ describe("Moves - Powder", () => {
|
||||||
await game.phaseInterceptor.to(BerryPhase, false);
|
await game.phaseInterceptor.to(BerryPhase, false);
|
||||||
expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
||||||
expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4));
|
expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4));
|
||||||
|
expect(enemyPokemon.moveset[0]!.ppUsed).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(
|
it(
|
||||||
|
@ -107,6 +113,22 @@ describe("Moves - Powder", () => {
|
||||||
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it(
|
||||||
|
"should not damage the target if Primordial Sea is active",
|
||||||
|
async () => {
|
||||||
|
game.override.enemyAbility(Abilities.PRIMORDIAL_SEA);
|
||||||
|
|
||||||
|
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(enemyPokemon.getMaxHp());
|
||||||
|
});
|
||||||
|
|
||||||
it(
|
it(
|
||||||
"should not prevent the target from thawing out with Flame Wheel",
|
"should not prevent the target from thawing out with Flame Wheel",
|
||||||
async () => {
|
async () => {
|
||||||
|
@ -144,29 +166,60 @@ describe("Moves - Powder", () => {
|
||||||
expect(enemyPokemon.summonData?.types).not.toBe(Type.FIRE);
|
expect(enemyPokemon.summonData?.types).not.toBe(Type.FIRE);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Implement this interaction to pass this test
|
it(
|
||||||
it.skip(
|
|
||||||
"should cancel Fire-type moves generated by the target's Dancer ability",
|
"should cancel Fire-type moves generated by the target's Dancer ability",
|
||||||
async () => {
|
async () => {
|
||||||
game.override
|
game.override
|
||||||
|
.battleType("double")
|
||||||
.enemySpecies(Species.BLASTOISE)
|
.enemySpecies(Species.BLASTOISE)
|
||||||
.enemyAbility(Abilities.DANCER);
|
.enemyAbility(Abilities.DANCER);
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.CHARIZARD, Species.CHARIZARD ]);
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
// Turn 1: Roar away 1 opponent
|
||||||
|
game.move.select(Moves.ROAR, 0, BattlerIndex.ENEMY_2);
|
||||||
|
game.move.select(Moves.SPLASH, 1);
|
||||||
|
await game.toNextTurn();
|
||||||
|
await game.toNextTurn(); // Requires game.toNextTurn() twice due to double battle
|
||||||
|
|
||||||
|
// Turn 2: Enemy should activate Powder twice: From using Ember, and from copying Fiery Dance via Dancer
|
||||||
|
playerPokemon.hp = playerPokemon.getMaxHp();
|
||||||
|
game.move.select(Moves.FIERY_DANCE, 0, BattlerIndex.ENEMY);
|
||||||
|
game.move.select(Moves.POWDER, 1, BattlerIndex.ENEMY);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to(MoveEffectPhase);
|
||||||
|
const enemyStartingHp = enemyPokemon.hp;
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to(BerryPhase, false);
|
||||||
|
|
||||||
|
|
||||||
|
// player should not take damage
|
||||||
|
expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||||
|
expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp());
|
||||||
|
// enemy should have taken damage from player's Fiery Dance + 2 Powder procs
|
||||||
|
expect(enemyPokemon.hp).toBe(enemyStartingHp - playerPokemon.turnData.totalDamageDealt - 2 * Math.floor(enemyPokemon.getMaxHp() / 4));
|
||||||
|
});
|
||||||
|
|
||||||
|
it(
|
||||||
|
"should cancel Fiery Dance, then prevent it from triggering Dancer",
|
||||||
|
async () => {
|
||||||
|
game.override.ability(Abilities.DANCER)
|
||||||
|
.enemyMoveset(Moves.FIERY_DANCE);
|
||||||
|
|
||||||
await game.classicMode.startBattle([ Species.CHARIZARD ]);
|
await game.classicMode.startBattle([ Species.CHARIZARD ]);
|
||||||
|
|
||||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
game.move.select(Moves.FIERY_DANCE);
|
game.move.select(Moves.POWDER);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
|
||||||
const enemyStartingHp = enemyPokemon.hp;
|
|
||||||
|
|
||||||
await game.phaseInterceptor.to(BerryPhase, false);
|
await game.phaseInterceptor.to(BerryPhase, false);
|
||||||
// player should not take damage
|
expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||||
expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp());
|
expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4));
|
||||||
// enemy should have taken damage from player's Fiery Dance + 2 Powder procs
|
expect(playerPokemon.getLastXMoves()[0].move).toBe(Moves.POWDER);
|
||||||
expect(enemyPokemon.hp).toBe(enemyStartingHp - 2 * Math.floor(enemyPokemon.getMaxHp() / 4));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it(
|
it(
|
||||||
|
@ -202,4 +255,64 @@ describe("Moves - Powder", () => {
|
||||||
expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||||
expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4));
|
expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it(
|
||||||
|
"should cancel Grass Pledge if used after ally's Fire Pledge",
|
||||||
|
async () => {
|
||||||
|
game.override.enemyMoveset([ Moves.FIRE_PLEDGE, Moves.GRASS_PLEDGE ])
|
||||||
|
.battleType("double");
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.CHARIZARD, Species.CHARIZARD ]);
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(Moves.POWDER, 0, BattlerIndex.ENEMY);
|
||||||
|
game.move.select(Moves.SPLASH, 1);
|
||||||
|
await game.forceEnemyMove(Moves.GRASS_PLEDGE, BattlerIndex.PLAYER);
|
||||||
|
await game.forceEnemyMove(Moves.FIRE_PLEDGE, BattlerIndex.PLAYER);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2, BattlerIndex.ENEMY ]);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to(BerryPhase, false);
|
||||||
|
expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||||
|
expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4));
|
||||||
|
});
|
||||||
|
|
||||||
|
it(
|
||||||
|
"should cancel Fire Pledge if used before ally's Water Pledge",
|
||||||
|
async () => {
|
||||||
|
game.override.enemyMoveset([ Moves.FIRE_PLEDGE, Moves.WATER_PLEDGE ])
|
||||||
|
.battleType("double");
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.CHARIZARD, Species.CHARIZARD ]);
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(Moves.POWDER, 0, BattlerIndex.ENEMY);
|
||||||
|
game.move.select(Moves.SPLASH, 1);
|
||||||
|
await game.forceEnemyMove(Moves.FIRE_PLEDGE, BattlerIndex.PLAYER);
|
||||||
|
await game.forceEnemyMove(Moves.WATER_PLEDGE, BattlerIndex.PLAYER);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to(BerryPhase, false);
|
||||||
|
expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||||
|
expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4));
|
||||||
|
});
|
||||||
|
|
||||||
|
it(
|
||||||
|
"should NOT cancel Fire Pledge if used after ally's Water Pledge",
|
||||||
|
async () => {
|
||||||
|
game.override.enemyMoveset([ Moves.FIRE_PLEDGE, Moves.WATER_PLEDGE ])
|
||||||
|
.battleType("double");
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.CHARIZARD, Species.CHARIZARD ]);
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(Moves.POWDER, 0, BattlerIndex.ENEMY);
|
||||||
|
game.move.select(Moves.SPLASH, 1);
|
||||||
|
await game.forceEnemyMove(Moves.FIRE_PLEDGE, BattlerIndex.PLAYER);
|
||||||
|
await game.forceEnemyMove(Moves.WATER_PLEDGE, BattlerIndex.PLAYER);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2, BattlerIndex.ENEMY ]);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to(BerryPhase, false);
|
||||||
|
expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
||||||
|
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue