[P2] Fix for Speed Boost is Activated on the Turn a Pokemon is Switched In, and When a Pokemon Fails to Escape #4353 (#4676)
* fixing speed boost for pokemon being switched in and for if failed escape * adding unit tests * adding failed run away test case * adding failed run away test case modification * refactoring solution to be more consistent with coding style * more fixes for consistency * more fixes for consistency * adding new AbAttr in abiliity.ts for posterity * removing uneccesary variables * fixing a merge conflict
This commit is contained in:
parent
fd1aa41d09
commit
afe6d2900d
|
@ -3614,22 +3614,19 @@ export class MoodyAbAttr extends PostTurnAbAttr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PostTurnStatStageChangeAbAttr extends PostTurnAbAttr {
|
export class SpeedBoostAbAttr extends PostTurnAbAttr {
|
||||||
private stats: BattleStat[];
|
|
||||||
private stages: number;
|
|
||||||
|
|
||||||
constructor(stats: BattleStat[], stages: number) {
|
constructor() {
|
||||||
super(true);
|
super(true);
|
||||||
|
|
||||||
this.stats = Array.isArray(stats)
|
|
||||||
? stats
|
|
||||||
: [ stats ];
|
|
||||||
this.stages = stages;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
||||||
if (!simulated) {
|
if (!simulated) {
|
||||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.stages));
|
if (!pokemon.turnData.switchedInThisTurn && !pokemon.turnData.failedRunAway) {
|
||||||
|
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPD ], 1));
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -5011,7 +5008,7 @@ export function initAbilities() {
|
||||||
.attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN)
|
.attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN)
|
||||||
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.RAIN),
|
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.RAIN),
|
||||||
new Ability(Abilities.SPEED_BOOST, 3)
|
new Ability(Abilities.SPEED_BOOST, 3)
|
||||||
.attr(PostTurnStatStageChangeAbAttr, [ Stat.SPD ], 1),
|
.attr(SpeedBoostAbAttr),
|
||||||
new Ability(Abilities.BATTLE_ARMOR, 3)
|
new Ability(Abilities.BATTLE_ARMOR, 3)
|
||||||
.attr(BlockCritAbAttr)
|
.attr(BlockCritAbAttr)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
* or {@linkcode SwitchSummonPhase} will carry out.
|
* or {@linkcode SwitchSummonPhase} will carry out.
|
||||||
*/
|
*/
|
||||||
export enum SwitchType {
|
export enum SwitchType {
|
||||||
|
/** Switchout specifically for when combat starts and the player is prompted if they will switch Pokemon */
|
||||||
|
INITIAL_SWITCH,
|
||||||
/** Basic switchout where the Pokemon to switch in is selected */
|
/** Basic switchout where the Pokemon to switch in is selected */
|
||||||
SWITCH,
|
SWITCH,
|
||||||
/** Transfers stat stages and other effects from the returning Pokemon to the switched in Pokemon */
|
/** Transfers stat stages and other effects from the returning Pokemon to the switched in Pokemon */
|
||||||
|
|
|
@ -5143,6 +5143,8 @@ export class PokemonTurnData {
|
||||||
public statStagesDecreased: boolean = false;
|
public statStagesDecreased: boolean = false;
|
||||||
public moveEffectiveness: TypeDamageMultiplier | null = null;
|
public moveEffectiveness: TypeDamageMultiplier | null = null;
|
||||||
public combiningPledge?: Moves;
|
public combiningPledge?: Moves;
|
||||||
|
public switchedInThisTurn: boolean = false;
|
||||||
|
public failedRunAway: boolean = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AiType {
|
export enum AiType {
|
||||||
|
|
|
@ -10,6 +10,10 @@ import { NewBattlePhase } from "./new-battle-phase";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
|
|
||||||
export class AttemptRunPhase extends PokemonPhase {
|
export class AttemptRunPhase extends PokemonPhase {
|
||||||
|
|
||||||
|
/** For testing purposes: this is to force the pokemon to fail and escape */
|
||||||
|
public forceFailEscape = false;
|
||||||
|
|
||||||
constructor(scene: BattleScene, fieldIndex: number) {
|
constructor(scene: BattleScene, fieldIndex: number) {
|
||||||
super(scene, fieldIndex);
|
super(scene, fieldIndex);
|
||||||
}
|
}
|
||||||
|
@ -28,7 +32,7 @@ export class AttemptRunPhase extends PokemonPhase {
|
||||||
|
|
||||||
applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, false, escapeChance);
|
applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, false, escapeChance);
|
||||||
|
|
||||||
if (playerPokemon.randSeedInt(100) < escapeChance.value) {
|
if (playerPokemon.randSeedInt(100) < escapeChance.value && !this.forceFailEscape) {
|
||||||
this.scene.playSound("se/flee");
|
this.scene.playSound("se/flee");
|
||||||
this.scene.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
|
this.scene.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
|
||||||
|
|
||||||
|
@ -51,6 +55,7 @@ export class AttemptRunPhase extends PokemonPhase {
|
||||||
this.scene.pushPhase(new BattleEndPhase(this.scene));
|
this.scene.pushPhase(new BattleEndPhase(this.scene));
|
||||||
this.scene.pushPhase(new NewBattlePhase(this.scene));
|
this.scene.pushPhase(new NewBattlePhase(this.scene));
|
||||||
} else {
|
} else {
|
||||||
|
playerPokemon.turnData.failedRunAway = true;
|
||||||
this.scene.queueMessage(i18next.t("battle:runAwayCannotEscape"), null, true, 500);
|
this.scene.queueMessage(i18next.t("battle:runAwayCannotEscape"), null, true, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ export class CheckSwitchPhase extends BattlePhase {
|
||||||
this.scene.ui.setMode(Mode.CONFIRM, () => {
|
this.scene.ui.setMode(Mode.CONFIRM, () => {
|
||||||
this.scene.ui.setMode(Mode.MESSAGE);
|
this.scene.ui.setMode(Mode.MESSAGE);
|
||||||
this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex);
|
this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex);
|
||||||
this.scene.unshiftPhase(new SwitchPhase(this.scene, SwitchType.SWITCH, this.fieldIndex, false, true));
|
this.scene.unshiftPhase(new SwitchPhase(this.scene, SwitchType.INITIAL_SWITCH, this.fieldIndex, false, true));
|
||||||
this.end();
|
this.end();
|
||||||
}, () => {
|
}, () => {
|
||||||
this.scene.ui.setMode(Mode.MESSAGE);
|
this.scene.ui.setMode(Mode.MESSAGE);
|
||||||
|
|
|
@ -64,10 +64,8 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||||
}
|
}
|
||||||
|
|
||||||
const pokemon = this.getPokemon();
|
const pokemon = this.getPokemon();
|
||||||
|
|
||||||
(this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id));
|
(this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id));
|
||||||
|
if (this.switchType === SwitchType.SWITCH || this.switchType === SwitchType.INITIAL_SWITCH) {
|
||||||
if (this.switchType === SwitchType.SWITCH) {
|
|
||||||
const substitute = pokemon.getTag(SubstituteTag);
|
const substitute = pokemon.getTag(SubstituteTag);
|
||||||
if (substitute) {
|
if (substitute) {
|
||||||
this.scene.tweens.add({
|
this.scene.tweens.add({
|
||||||
|
@ -186,6 +184,11 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.switchType !== SwitchType.INITIAL_SWITCH) {
|
||||||
|
pokemon.resetTurnData();
|
||||||
|
pokemon.turnData.switchedInThisTurn = true;
|
||||||
|
}
|
||||||
|
|
||||||
this.lastPokemon?.resetSummonData();
|
this.lastPokemon?.resetSummonData();
|
||||||
|
|
||||||
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
|
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
import { Stat } from "#enums/stat";
|
||||||
|
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";
|
||||||
|
import { CommandPhase } from "#app/phases/command-phase";
|
||||||
|
import { Command } from "#app/ui/command-ui-handler";
|
||||||
|
import { AttemptRunPhase } from "#app/phases/attempt-run-phase";
|
||||||
|
|
||||||
|
describe("Abilities - Speed Boost", () => {
|
||||||
|
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.DRAGAPULT)
|
||||||
|
.ability(Abilities.SPEED_BOOST)
|
||||||
|
.enemyMoveset(Moves.SPLASH)
|
||||||
|
.moveset([ Moves.SPLASH, Moves.U_TURN ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should increase speed by 1 stage at end of turn",
|
||||||
|
async () => {
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not trigger this turn if pokemon was switched into combat via attack, but the turn after",
|
||||||
|
async () => {
|
||||||
|
await game.classicMode.startBattle([
|
||||||
|
Species.SHUCKLE,
|
||||||
|
Species.NINJASK
|
||||||
|
]);
|
||||||
|
|
||||||
|
game.move.select(Moves.U_TURN);
|
||||||
|
game.doSelectPartyPokemon(1);
|
||||||
|
await game.toNextTurn();
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("checking back to back swtiches",
|
||||||
|
async () => {
|
||||||
|
await game.classicMode.startBattle([
|
||||||
|
Species.SHUCKLE,
|
||||||
|
Species.NINJASK
|
||||||
|
]);
|
||||||
|
|
||||||
|
game.move.select(Moves.U_TURN);
|
||||||
|
game.doSelectPartyPokemon(1);
|
||||||
|
await game.toNextTurn();
|
||||||
|
let playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
|
||||||
|
|
||||||
|
game.move.select(Moves.U_TURN);
|
||||||
|
game.doSelectPartyPokemon(1);
|
||||||
|
await game.toNextTurn();
|
||||||
|
playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not trigger this turn if pokemon was switched into combat via normal switch, but the turn after",
|
||||||
|
async () => {
|
||||||
|
await game.classicMode.startBattle([
|
||||||
|
Species.SHUCKLE,
|
||||||
|
Species.NINJASK
|
||||||
|
]);
|
||||||
|
|
||||||
|
game.doSwitchPokemon(1);
|
||||||
|
await game.toNextTurn();
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not trigger if pokemon fails to escape",
|
||||||
|
async () => {
|
||||||
|
await game.classicMode.startBattle([ Species.SHUCKLE ]);
|
||||||
|
|
||||||
|
const commandPhase = game.scene.getCurrentPhase() as CommandPhase;
|
||||||
|
commandPhase.handleCommand(Command.RUN, 0);
|
||||||
|
const runPhase = game.scene.getCurrentPhase() as AttemptRunPhase;
|
||||||
|
runPhase.forceFailEscape = true;
|
||||||
|
await game.phaseInterceptor.to(AttemptRunPhase);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue