Fixed Dancer last hit, flinch move interaction

This commit is contained in:
Bertie690 2025-06-06 10:55:13 -04:00
parent 243a93c7fc
commit 7e5a6d26c3
3 changed files with 35 additions and 8 deletions

View File

@ -1246,7 +1246,7 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr {
/** /**
* Determine if the move type change attribute can be applied * Determine if the move type change attribute can be applied
* *
* Can be applied if: * Can be applied if:
* - The ability's condition is met, e.g. pixilate only boosts normal moves, * - 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} * - 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 { 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)) && return (!this.condition || this.condition(pokemon, _defender, move)) &&
!noAbilityTypeOverrideMoves.has(move.id) && !noAbilityTypeOverrideMoves.has(move.id) &&
(!pokemon.isTerastallized || (!pokemon.isTerastallized ||
(move.id !== MoveId.TERA_BLAST && (move.id !== MoveId.TERA_BLAST &&
(move.id !== MoveId.TERA_STARSTORM || pokemon.getTeraType() !== PokemonType.STELLAR || !pokemon.hasSpecies(SpeciesId.TERAPAGOS)))); (move.id !== MoveId.TERA_STARSTORM || pokemon.getTeraType() !== PokemonType.STELLAR || !pokemon.hasSpecies(SpeciesId.TERAPAGOS))));
@ -4449,6 +4449,7 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr {
simulated: boolean, simulated: boolean,
args: any[]): void { args: any[]): void {
if (!simulated) { if (!simulated) {
dancer.turnData.extraTurns++;
// If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance // If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance
if (move.getMove() instanceof AttackMove || move.getMove() instanceof StatusMove) { if (move.getMove() instanceof AttackMove || move.getMove() instanceof StatusMove) {
const target = this.getTarget(dancer, source, targets); const target = this.getTarget(dancer, source, targets);

View File

@ -649,7 +649,7 @@ class NoRetreatTag extends TrappedTag {
*/ */
export class FlinchedTag extends BattlerTag { export class FlinchedTag extends BattlerTag {
constructor(sourceMove: MoveId) { constructor(sourceMove: MoveId) {
super(BattlerTagType.FLINCHED, [BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END], 0, sourceMove); super(BattlerTagType.FLINCHED, [BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END], 1, sourceMove);
} }
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
@ -659,10 +659,10 @@ export class FlinchedTag extends BattlerTag {
} }
/** /**
* Cancels the Pokemon's next Move on the turn this tag is applied * Cancels all subsequent moves used by this tag's Pokemon this turn.
* @param pokemon The {@linkcode Pokemon} with this tag * @param pokemon - The {@linkcode Pokemon} with this tag.
* @param lapseType The {@linkcode BattlerTagLapseType lapse type} used for this function call * @param lapseType - The {@linkcode BattlerTagLapseType | lapse type} used for this function call.
* @returns `false` (This tag is always removed after applying its effects) * @returns Whether the tag should remain active.
*/ */
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.PRE_MOVE) { if (lapseType === BattlerTagLapseType.PRE_MOVE) {
@ -672,6 +672,7 @@ export class FlinchedTag extends BattlerTag {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
}), }),
); );
return true;
} }
return super.lapse(pokemon, lapseType); return super.lapse(pokemon, lapseType);

View File

@ -1,13 +1,15 @@
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { allMoves } from "#app/data/data-lists";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { MoveResult } from "#app/field/pokemon"; import { MoveResult } from "#app/field/pokemon";
import type { MovePhase } from "#app/phases/move-phase"; import type { MovePhase } from "#app/phases/move-phase";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { Stat } from "#enums/stat";
import GameManager from "#test/testUtils/gameManager"; import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Moves - Instruct", () => { describe("Moves - Instruct", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
@ -536,4 +538,27 @@ describe("Moves - Instruct", () => {
expect(ivysaur.turnData.attacksReceived.length).toBe(15); expect(ivysaur.turnData.attacksReceived.length).toBe(15);
}); });
it("should respect prior flinches and trigger Steadfast", async () => {
game.override.battleStyle("double");
vi.spyOn(allMoves[MoveId.AIR_SLASH], "chance", "get").mockReturnValue(100);
await game.classicMode.startBattle([SpeciesId.AUDINO, SpeciesId.ABRA]);
// Fake enemy 1 having attacked prior
const [, player2, enemy1, enemy2] = game.scene.getField();
enemy1.pushMoveHistory({ move: MoveId.ABSORB, targets: [BattlerIndex.PLAYER] });
game.field.mockAbility(enemy1, AbilityId.STEADFAST);
game.move.use(MoveId.AIR_SLASH, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
game.move.use(MoveId.INSTRUCT, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY);
await game.move.forceEnemyMove(MoveId.ABSORB);
await game.move.forceEnemyMove(MoveId.INSTRUCT, BattlerIndex.ENEMY);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2]);
await game.toEndOfTurn();
expect(enemy1.getLastXMoves(-1).map(m => m.move)).toEqual([MoveId.NONE, MoveId.NONE, MoveId.NONE, MoveId.ABSORB]);
expect(enemy1.getStatStage(Stat.SPD)).toBe(3);
expect(player2.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
expect(enemy2.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
});
}); });