Revamp telekinesis tests; fix up stuff

This commit is contained in:
Bertie690 2025-06-10 16:51:11 -04:00
parent e59bf5254f
commit 06b6143931
7 changed files with 169 additions and 137 deletions

View File

@ -1096,13 +1096,11 @@ export class GravityTag extends ArenaTag {
onAdd(_arena: Arena): void {
globalScene.phaseManager.queueMessage(i18next.t("arenaTag:gravityOnAdd"));
globalScene.getField(true).forEach(pokemon => {
if (pokemon !== null) {
pokemon.removeTag(BattlerTagType.FLOATING);
pokemon.removeTag(BattlerTagType.TELEKINESIS);
if (pokemon.getTag(BattlerTagType.FLYING)) {
pokemon.addTag(BattlerTagType.INTERRUPTED);
}
}
});
}

View File

@ -660,6 +660,10 @@ export class FlinchedTag extends BattlerTag {
}
}
/**
* Tag to cancel the target's action when knocked out of a flying move by Smack Down or Gravity.
*/
// TODO: This is not a very good way to cancel a semi invulnerable turn
export class InterruptedTag extends BattlerTag {
constructor(sourceMove: MoveId) {
super(BattlerTagType.INTERRUPTED, BattlerTagLapseType.PRE_MOVE, 0, sourceMove);

View File

@ -5299,13 +5299,11 @@ export class VariableMoveTypeMultiplierAttr extends MoveAttr {
}
export class NeutralDamageAgainstFlyingTypeMultiplierAttr extends VariableMoveTypeMultiplierAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!target.getTag(BattlerTagType.IGNORE_FLYING)) {
const multiplier = args[0] as NumberHolder;
apply(user: Pokemon, target: Pokemon, move: Move, args: [NumberHolder]): boolean {
if (!target.isGrounded(true) && target.isOfType(PokemonType.FLYING)) {
const multiplier = args[0];
// When a flying type is hit, the first hit is always 1x multiplier.
if (target.isOfType(PokemonType.FLYING)) {
multiplier.value = 1;
}
return true;
}
@ -5634,8 +5632,8 @@ export class LeechSeedAttr extends AddBattlerTagAttr {
}
/**
* Adds the appropriate battler tag for Smack Down and Thousand arrows
* @extends AddBattlerTagAttr
* Attribute to add the {@linkcode BattlerTagType.IGNORE_FLYING | IGNORE_FLYING} battler tag to the target
* and remove any prior sources of ungroundedness.
*/
export class FallDownAttr extends AddBattlerTagAttr {
constructor() {
@ -5644,20 +5642,30 @@ export class FallDownAttr extends AddBattlerTagAttr {
/**
* Adds Grounded Tag to the target and checks if fallDown message should be displayed
* @param user the {@linkcode Pokemon} using the move
* @param target the {@linkcode Pokemon} targeted by the move
* @param move the {@linkcode Move} invoking this effect
* @param user - The {@linkcode Pokemon} using the move
* @param target - The {@linkcode Pokemon} targeted by the move
* @param move - The {@linkcode Move} invoking this effect
* @param args n/a
* @returns `true` if the effect successfully applies; `false` otherwise
* @returns Whether the target was successfully brought down to earth.
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
// Smack down and similar only add their tag if the pokemon is already ungrounded without considering semi-invulnerability
if (!target.isGrounded(true)) {
// Smack down and similar only add their tag if the pokemon is already ungrounded
// TODO: Does this work if the target is semi-invulnerable?
if (target.isGrounded(true)) {
return false;
}
// Remove the target's flying/floating state and telekinesis, interrupting fly/bounce's activation.
// TODO: This is not a good way to remove flying...
target.removeTag(BattlerTagType.FLOATING);
target.removeTag(BattlerTagType.TELEKINESIS);
if (target.getTag(BattlerTagType.FLYING)) {
target.addTag(BattlerTagType.INTERRUPTED);
}
globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:fallDown", { targetPokemonName: getPokemonNameWithAffix(target) }));
return super.apply(user, target, move, args);
}
return false;
}
}
/**
@ -7975,6 +7983,7 @@ const phaseForcedSlower = (phase: MovePhase, target: Pokemon, trickRoom: boolean
return phase.isForcedLast() && slower;
};
// TODO: This needs to become unselectable, not merely fail
const failOnGravityCondition: MoveConditionFunc = (user, target, move) => !globalScene.arena.getTag(ArenaTagType.GRAVITY);
const failOnBossCondition: MoveConditionFunc = (user, target, move) => !target.isBossImmune();
@ -9280,6 +9289,7 @@ export function initMoves() {
.attr(RandomMovesetMoveAttr, invalidAssistMoves, true),
new SelfStatusMove(MoveId.INGRAIN, PokemonType.GRASS, -1, 20, -1, 0, 3)
.attr(AddBattlerTagAttr, BattlerTagType.INGRAIN, true, true)
// NB: We add the tag directly to avoid removing Telekinesis' accuracy boost
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, true, true)
.attr(RemoveBattlerTagAttr, [ BattlerTagType.FLOATING ], true),
new AttackMove(MoveId.SUPERPOWER, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 3)
@ -9907,8 +9917,6 @@ export function initMoves() {
.unimplemented(),
new AttackMove(MoveId.SMACK_DOWN, PokemonType.ROCK, MoveCategory.PHYSICAL, 50, 100, 15, -1, 0, 5)
.attr(FallDownAttr)
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
.attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.FLOATING, BattlerTagType.TELEKINESIS ])
.attr(HitsTagAttr, BattlerTagType.FLYING)
.makesContact(false)
// TODO: Confirm if Smack Down & Thousand Arrows will ground semi-invulnerable Pokemon with No Guard, etc.
@ -10364,8 +10372,6 @@ export function initMoves() {
.attr(FallDownAttr)
.attr(HitsTagAttr, BattlerTagType.FLYING)
.attr(HitsTagAttr, BattlerTagType.FLOATING)
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
.attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.FLOATING, BattlerTagType.TELEKINESIS ])
.makesContact(false)
.target(MoveTarget.ALL_NEAR_ENEMIES)
// TODO: Confirm if Smack Down & Thousand Arrows will ground semi-invulnerable Pokemon with No Guard, etc.

View File

@ -2464,7 +2464,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// Handle flying v ground type immunity without removing flying type so effective types are still effective
// Related to https://github.com/pagefaultgames/pokerogue/issues/524
//
if (moveType === PokemonType.GROUND && this.isGrounded(true)) {
if (moveType === PokemonType.GROUND && this.isGrounded()) {
const flyingIndex = types.indexOf(PokemonType.FLYING);
if (flyingIndex > -1) {
types.splice(flyingIndex, 1);

View File

@ -42,8 +42,8 @@ describe("Terrain -", () => {
.disableCrits()
.enemySpecies(SpeciesId.SNOM)
.enemyAbility(AbilityId.STURDY)
.ability(AbilityId.NO_GUARD)
.passiveAbility(ability)
.ability(ability)
.passiveAbility(AbilityId.NO_GUARD)
.enemyPassiveAbility(AbilityId.LEVITATE);
});
@ -55,27 +55,31 @@ describe("Terrain -", () => {
const powerSpy = vi.spyOn(allMoves[move], "calculateBattlePower");
game.move.use(move);
await game.move.forceEnemyMove(move);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.toEndOfTurn();
// Player grounded attack got boost while enemy ungrounded attack didn't
expect(powerSpy).toHaveLastReturnedWith(allMoves[move].power);
expect(powerSpy).toHaveNthReturnedWith(2, allMoves[move].power * 1.3);
// Player grounded attack got boosted while enemy ungrounded attack didn't
expect(powerSpy).toHaveLastReturnedWith(allMoves[move].power * 1.3);
expect(powerSpy).toHaveNthReturnedWith(1, allMoves[move].power);
});
it(`should change Terrain Pulse into a ${typeStr}-type move and double its base power`, async () => {
await game.classicMode.startBattle([SpeciesId.BLISSEY]);
const blissey = game.field.getPlayerPokemon();
const powerSpy = vi.spyOn(allMoves[MoveId.TERRAIN_PULSE], "calculateBattlePower");
const typeSpy = vi.spyOn(blissey, "getMoveType");
const playerTypeSpy = vi.spyOn(game.field.getPlayerPokemon(), "getMoveType");
const enemyTypeSpy = vi.spyOn(game.field.getEnemyPokemon(), "getMoveType");
game.move.use(move);
await game.move.forceEnemyMove(MoveId.SPLASH);
game.move.use(MoveId.TERRAIN_PULSE);
await game.move.forceEnemyMove(MoveId.TERRAIN_PULSE);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.toEndOfTurn();
// player grounded terrain pulse was boosted & type converted; enemy ungrounded one wasn't
expect(powerSpy).toHaveLastReturnedWith(allMoves[move].power * 2.6); // 2 * 1.3
expect(typeSpy).toHaveLastReturnedWith(type);
expect(playerTypeSpy).toHaveLastReturnedWith(type);
expect(powerSpy).toHaveNthReturnedWith(1, allMoves[move].power);
expect(enemyTypeSpy).toHaveNthReturnedWith(1, allMoves[MoveId.TERRAIN_PULSE].type);
});
});
@ -169,7 +173,7 @@ describe("Terrain -", () => {
);
});
// TODO: Enable tests after terrain-msg branch is merged
// TODO: Enable suites after terrain-fail-msg branch is merged
describe.skip("Electric Terrain", () => {
afterEach(() => {
game.phaseInterceptor.restoreOg();
@ -234,8 +238,7 @@ describe("Terrain -", () => {
});
});
// TODO: Enable tests after terrain-msg branch is merged
describe("Misty Terrain", () => {
describe.skip("Misty Terrain", () => {
afterEach(() => {
game.phaseInterceptor.restoreOg();
});

View File

@ -1,5 +1,4 @@
import { BattlerTagType } from "#enums/battler-tag-type";
import { allMoves } from "#app/data/data-lists";
import { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
@ -8,6 +7,9 @@ import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
import { BattlerIndex } from "#enums/battler-index";
import type { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { HitCheckResult } from "#enums/hit-check-result";
import { StatusEffect } from "#enums/status-effect";
describe("Moves - Telekinesis", () => {
let phaserGame: Phaser.Game;
@ -26,114 +28,129 @@ describe("Moves - Telekinesis", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.moveset([MoveId.TELEKINESIS, MoveId.TACKLE, MoveId.MUD_SHOT, MoveId.SMACK_DOWN])
.battleStyle("single")
.enemySpecies(SpeciesId.SNORLAX)
.enemyLevel(60)
.enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset([MoveId.SPLASH]);
.enemyMoveset(MoveId.SPLASH);
});
it("Telekinesis makes the affected vulnerable to most attacking moves regardless of accuracy", async () => {
it("should cause opposing non-OHKO moves to always hit the target", async () => {
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
const enemyOpponent = game.scene.getEnemyPokemon()!;
game.move.select(MoveId.TELEKINESIS);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined();
expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined();
const player = game.field.getPlayerPokemon();
const enemy = game.field.getEnemyPokemon();
game.move.use(MoveId.TELEKINESIS);
await game.toNextTurn();
vi.spyOn(allMoves[MoveId.TACKLE], "accuracy", "get").mockReturnValue(0);
game.move.select(MoveId.TACKLE);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyOpponent.isFullHp()).toBe(false);
expect(enemy.getTag(BattlerTagType.TELEKINESIS)).toBeDefined();
expect(enemy.getTag(BattlerTagType.FLOATING)).toBeDefined();
game.move.use(MoveId.TACKLE);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.move.forceMiss();
await game.toEndOfTurn();
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
expect(player.getLastXMoves()[0].result).toBe(MoveResult.MISS);
});
it("Telekinesis makes the affected airborne and immune to most Ground-moves", async () => {
it("should render the target immune to Ground-moves and terrain", async () => {
game.override.ability(AbilityId.ELECTRIC_SURGE);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
const enemyOpponent = game.scene.getEnemyPokemon()!;
const enemy = game.field.getEnemyPokemon();
game.move.select(MoveId.TELEKINESIS);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined();
expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined();
game.move.use(MoveId.TELEKINESIS);
await game.toNextTurn();
game.move.use(MoveId.EARTHQUAKE);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.phaseInterceptor.to("MoveEffectPhase");
const hitSpy = vi.spyOn(game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase, "hitCheck");
await game.toNextTurn();
vi.spyOn(allMoves[MoveId.MUD_SHOT], "accuracy", "get").mockReturnValue(100);
game.move.select(MoveId.MUD_SHOT);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyOpponent.isFullHp()).toBe(true);
expect(enemy.hp).toBe(enemy.getMaxHp());
expect(hitSpy).toHaveLastReturnedWith([HitCheckResult.NO_EFFECT, 0]);
game.move.use(MoveId.SPORE);
await game.toNextTurn();
expect(enemy.status?.effect).toBe(StatusEffect.SLEEP);
});
it("Telekinesis can still affect Pokemon that have been transformed into invalid Pokemon", async () => {
game.override.enemyMoveset(MoveId.TRANSFORM);
it("should still affect enemies transformed into invalid Pokemon", async () => {
game.override.enemyAbility(AbilityId.IMPOSTER);
await game.classicMode.startBattle([SpeciesId.DIGLETT]);
const enemyOpponent = game.scene.getEnemyPokemon()!;
const enemyOpponent = game.field.getEnemyPokemon();
game.move.use(MoveId.TELEKINESIS);
await game.move.forceEnemyMove(MoveId.SPLASH);
await game.toNextTurn();
game.move.select(MoveId.TELEKINESIS);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined();
expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined();
expect(enemyOpponent.summonData.speciesForm?.speciesId).toBe(SpeciesId.DIGLETT);
});
it("Moves like Smack Down and 1000 Arrows remove all effects of Telekinesis from the target Pokemon", async () => {
it.each<{ name: string; move: MoveId }>([
{ name: "Smack Down", move: MoveId.SMACK_DOWN },
{ name: "Thousand Arrows", move: MoveId.THOUSAND_ARROWS },
])("should be removed when hit by $name", async ({ move }) => {
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
const enemyOpponent = game.scene.getEnemyPokemon()!;
const enemy = game.field.getEnemyPokemon();
game.move.select(MoveId.TELEKINESIS);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined();
expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined();
await game.toNextTurn();
game.move.select(MoveId.SMACK_DOWN);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeUndefined();
expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeUndefined();
game.move.use(move);
await game.toNextTurn();
expect(enemy.getTag(BattlerTagType.TELEKINESIS)).toBeUndefined();
expect(enemy.getTag(BattlerTagType.FLOATING)).toBeUndefined();
});
it("Ingrain will remove the floating effect of Telekinesis, but not the 100% hit", async () => {
game.override.enemyMoveset([MoveId.SPLASH, MoveId.INGRAIN]);
it("should become grounded when Ingrain is used, but not remove the guaranteed hit effect", async () => {
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
const playerPokemon = game.scene.getPlayerPokemon()!;
const enemyOpponent = game.scene.getEnemyPokemon()!;
game.move.select(MoveId.TELEKINESIS);
await game.move.selectEnemyMove(MoveId.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined();
expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined();
const playerPokemon = game.field.getPlayerPokemon();
const enemy = game.field.getEnemyPokemon();
game.move.use(MoveId.TELEKINESIS);
await game.toNextTurn();
vi.spyOn(allMoves[MoveId.MUD_SHOT], "accuracy", "get").mockReturnValue(0);
game.move.select(MoveId.MUD_SHOT);
await game.move.selectEnemyMove(MoveId.INGRAIN);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined();
expect(enemyOpponent.getTag(BattlerTagType.INGRAIN)).toBeDefined();
expect(enemyOpponent.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined();
expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeUndefined();
game.move.use(MoveId.MUD_SHOT);
await game.move.forceEnemyMove(MoveId.INGRAIN);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("MoveEndPhase");
await game.move.forceMiss();
await game.toNextTurn();
expect(enemy.getTag(BattlerTagType.TELEKINESIS)).toBeDefined();
expect(enemy.getTag(BattlerTagType.INGRAIN)).toBeDefined();
expect(enemy.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined();
expect(enemy.getTag(BattlerTagType.FLOATING)).toBeUndefined();
expect(enemy.isGrounded()).toBe(false);
expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
});
it("should not be baton passed onto a mega gengar", async () => {
it("should not be baton passable onto a mega gengar", async () => {
game.override
.moveset([MoveId.BATON_PASS])
.enemyMoveset([MoveId.TELEKINESIS])
.starterForms({ [SpeciesId.GENGAR]: 1 });
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.GENGAR]);
game.move.select(MoveId.BATON_PASS);
game.move.use(MoveId.BATON_PASS);
game.doSelectPartyPokemon(1);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.getPlayerPokemon()!.getTag(BattlerTagType.TELEKINESIS)).toBeUndefined();
await game.toEndOfTurn();
expect(game.field.getPlayerPokemon().getTag(BattlerTagType.TELEKINESIS)).toBeUndefined();
});
});

View File

@ -26,11 +26,11 @@ describe("Moves - Thousand Arrows", () => {
game = new GameManager(phaserGame);
game.override
.battleStyle("single")
.enemySpecies(SpeciesId.SHUCKLE)
.enemySpecies(SpeciesId.EELEKTROSS)
.startingLevel(100)
.enemyLevel(100)
.enemyLevel(50)
.ability(AbilityId.COMPOUND_EYES)
.enemyAbility(AbilityId.BALL_FETCH)
.enemyAbility(AbilityId.STURDY)
.moveset(MoveId.THOUSAND_ARROWS)
.enemyMoveset(MoveId.SPLASH);
});
@ -39,32 +39,35 @@ describe("Moves - Thousand Arrows", () => {
game.override.enemySpecies(SpeciesId.ARCHEOPS);
await game.classicMode.startBattle([SpeciesId.ILLUMISE]);
const shuckle = game.scene.getEnemyPokemon()!;
expect(shuckle.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined();
const archeops = game.scene.getEnemyPokemon()!;
expect(archeops.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined();
game.move.select(MoveId.THOUSAND_ARROWS);
await game.phaseInterceptor.to("MoveEffectPhase", false);
const hitSpy = vi.spyOn(game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase, "hitCheck");
await game.toEndOfTurn();
expect(hitSpy).toHaveReturnedWith([expect.anything(), 1]);
expect(shuckle.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined();
expect(shuckle.hp).toBeLessThan(shuckle.getMaxHp());
expect(archeops.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined();
expect(archeops.isGrounded()).toBe(true);
expect(archeops.hp).toBeLessThan(archeops.getMaxHp());
});
it("should hit and ground targets with Levitate", async () => {
game.override.enemyAbility(AbilityId.LEVITATE);
it("should hit and ground targets with Levitate without affecting effectiveness", async () => {
game.override.enemyPassiveAbility(AbilityId.LEVITATE);
await game.classicMode.startBattle([SpeciesId.ILLUMISE]);
const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined();
const eelektross = game.scene.getEnemyPokemon()!;
expect(eelektross.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined();
game.move.select(MoveId.THOUSAND_ARROWS);
await game.phaseInterceptor.to("MoveEffectPhase", false);
const hitSpy = vi.spyOn(game.scene.phaseManager.getCurrentPhase() as MoveEffectPhase, "hitCheck");
await game.toEndOfTurn();
expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined();
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
expect(eelektross.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined();
expect(eelektross.hp).toBeLessThan(eelektross.getMaxHp());
expect(hitSpy).toHaveReturnedWith([expect.anything(), 2]);
});
it("should hit and ground targets under the effects of Magnet Rise", async () => {
@ -76,19 +79,19 @@ describe("Moves - Thousand Arrows", () => {
await game.phaseInterceptor.to("MoveEndPhase");
// ensure magnet rise suceeeded before getting knocked down
const enemyPokemon = game.field.getEnemyPokemon();
expect(enemyPokemon.getTag(BattlerTagType.FLOATING)).toBeDefined();
const eelektross = game.field.getEnemyPokemon();
expect(eelektross.getTag(BattlerTagType.FLOATING)).toBeDefined();
await game.toEndOfTurn();
expect(enemyPokemon.getTag(BattlerTagType.FLOATING)).toBeUndefined();
expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined();
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
expect(eelektross.getTag(BattlerTagType.FLOATING)).toBeUndefined();
expect(eelektross.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined();
expect(eelektross.hp).toBeLessThan(eelektross.getMaxHp());
});
it.each<{ name: string; move: MoveId }>([
{ name: "Fly", move: MoveId.FLY },
{ name: "Bounce", move: MoveId.BOUNCE },
])("should cancel the target's Fly", async ({ move }) => {
])("should hit through and cancel the target's $name", async ({ move }) => {
await game.classicMode.startBattle([SpeciesId.ILLUMISE]);
game.move.select(MoveId.THOUSAND_ARROWS);
@ -96,18 +99,19 @@ describe("Moves - Thousand Arrows", () => {
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("MoveEndPhase");
// Fly should've worked... until we smacked them
const shuckle = game.field.getEnemyPokemon();
expect(shuckle.getTag(BattlerTagType.FLYING)).toBeDefined();
expect(shuckle.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined();
// Fly should've worked until hit
const eelektross = game.field.getEnemyPokemon();
expect(eelektross.getTag(BattlerTagType.FLYING)).toBeDefined();
expect(eelektross.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined();
await game.phaseInterceptor.to("MoveEndPhase");
expect(shuckle.getTag(BattlerTagType.FLYING)).toBeUndefined();
expect(shuckle.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined();
expect(shuckle.hp).toBeLessThan(shuckle.getMaxHp());
await game.toEndOfTurn();
expect(eelektross.getTag(BattlerTagType.FLYING)).toBeUndefined();
expect(eelektross.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined();
expect(eelektross.hp).toBeLessThan(eelektross.getMaxHp());
});
it("should NOT ground semi-invulnerable targets unless already ungrounded", async () => {
// TODO: verify behavior
it.todo("should not ground semi-invulnerable targets unless already ungrounded", async () => {
await game.classicMode.startBattle([SpeciesId.ILLUMISE]);
game.move.select(MoveId.THOUSAND_ARROWS);
@ -115,10 +119,10 @@ describe("Moves - Thousand Arrows", () => {
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.toEndOfTurn();
const shuckle = game.field.getEnemyPokemon();
expect(shuckle.isGrounded()).toBe(false);
expect(shuckle.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined();
expect(shuckle.hp).toBeLessThan(shuckle.getMaxHp());
const eelektross = game.field.getEnemyPokemon();
expect(eelektross.isGrounded()).toBe(false);
expect(eelektross.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined();
expect(eelektross.hp).toBeLessThan(eelektross.getMaxHp());
});
// TODO: Sky drop is currently unimplemented