2024-09-13 19:14:10 -07:00
|
|
|
import { BattlerIndex } from "#app/battle";
|
2024-11-08 14:44:34 -08:00
|
|
|
import { Type } from "#enums/type";
|
2024-08-22 06:49:33 -07:00
|
|
|
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
|
|
|
import { Moves } from "#app/enums/moves";
|
|
|
|
import { Species } from "#app/enums/species";
|
|
|
|
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
|
|
|
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
2025-02-22 22:52:07 -06:00
|
|
|
import GameManager from "#test/testUtils/gameManager";
|
2024-07-25 16:10:38 -07:00
|
|
|
import Phaser from "phaser";
|
|
|
|
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
2024-06-23 09:48:49 -07:00
|
|
|
|
2024-09-20 14:05:45 -07:00
|
|
|
|
2024-06-23 09:48:49 -07:00
|
|
|
describe("Moves - Roost", () => {
|
|
|
|
let phaserGame: Phaser.Game;
|
|
|
|
let game: GameManager;
|
|
|
|
|
|
|
|
beforeAll(() => {
|
|
|
|
phaserGame = new Phaser.Game({
|
|
|
|
type: Phaser.HEADLESS,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
game.phaseInterceptor.restoreOg();
|
|
|
|
});
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
game = new GameManager(phaserGame);
|
2024-07-25 14:48:48 -07:00
|
|
|
game.override.battleType("single");
|
2024-09-12 12:27:47 -07:00
|
|
|
game.override.enemySpecies(Species.RELICANTH);
|
2024-07-25 15:29:19 -07:00
|
|
|
game.override.startingLevel(100);
|
2024-09-13 19:14:10 -07:00
|
|
|
game.override.enemyLevel(100);
|
2024-09-12 12:27:47 -07:00
|
|
|
game.override.enemyMoveset(Moves.EARTHQUAKE);
|
2024-10-04 13:08:31 +08:00
|
|
|
game.override.moveset([ Moves.ROOST, Moves.BURN_UP, Moves.DOUBLE_SHOCK ]);
|
2024-06-23 09:48:49 -07:00
|
|
|
});
|
|
|
|
|
2024-09-12 12:27:47 -07:00
|
|
|
/**
|
|
|
|
* Roost's behavior should be defined as:
|
|
|
|
* The pokemon loses its flying type for a turn. If the pokemon was ungroundd solely due to being a flying type, it will be grounded until end of turn.
|
|
|
|
* 1. Pure Flying type pokemon -> become normal type until end of turn
|
|
|
|
* 2. Dual Flying/X type pokemon -> become type X until end of turn
|
|
|
|
* 3. Pokemon that use burn up into roost (ex. Moltres) -> become flying due to burn up, then typeless until end of turn after using roost
|
|
|
|
* 4. If a pokemon is afflicted with Forest's Curse or Trick or treat, dual type pokemon will become 3 type pokemon after the flying type is regained
|
|
|
|
* Pure flying types become (Grass or Ghost) and then back to flying/ (Grass or Ghost),
|
|
|
|
* and pokemon post Burn up become ()
|
|
|
|
* 5. If a pokemon is also ungrounded due to other reasons (such as levitate), it will stay ungrounded post roost, despite not being flying type.
|
|
|
|
* 6. Non flying types using roost (such as dunsparce) are already grounded, so this move will only heal and have no other effects.
|
|
|
|
*/
|
|
|
|
|
|
|
|
test(
|
|
|
|
"Non flying type uses roost -> no type change, took damage",
|
|
|
|
async () => {
|
2024-10-04 13:08:31 +08:00
|
|
|
await game.classicMode.startBattle([ Species.DUNSPARCE ]);
|
2024-09-12 12:27:47 -07:00
|
|
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
|
|
|
const playerPokemonStartingHP = playerPokemon.hp;
|
|
|
|
game.move.select(Moves.ROOST);
|
2024-10-04 13:08:31 +08:00
|
|
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
2024-09-12 12:27:47 -07:00
|
|
|
await game.phaseInterceptor.to(MoveEffectPhase);
|
|
|
|
|
|
|
|
// Should only be normal type, and NOT flying type
|
|
|
|
let playerPokemonTypes = playerPokemon.getTypes();
|
|
|
|
expect(playerPokemonTypes[0] === Type.NORMAL).toBeTruthy();
|
|
|
|
expect(playerPokemonTypes.length === 1).toBeTruthy();
|
|
|
|
expect(playerPokemon.isGrounded()).toBeTruthy();
|
|
|
|
|
|
|
|
await game.phaseInterceptor.to(TurnEndPhase);
|
|
|
|
|
|
|
|
// Lose HP, still normal type
|
|
|
|
playerPokemonTypes = playerPokemon.getTypes();
|
|
|
|
expect(playerPokemon.hp).toBeLessThan(playerPokemonStartingHP);
|
|
|
|
expect(playerPokemonTypes[0] === Type.NORMAL).toBeTruthy();
|
|
|
|
expect(playerPokemonTypes.length === 1).toBeTruthy();
|
|
|
|
expect(playerPokemon.isGrounded()).toBeTruthy();
|
2024-09-20 14:05:45 -07:00
|
|
|
}
|
2024-09-12 12:27:47 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
test(
|
|
|
|
"Pure flying type -> becomes normal after roost and takes damage from ground moves -> regains flying",
|
|
|
|
async () => {
|
2024-10-04 13:08:31 +08:00
|
|
|
await game.classicMode.startBattle([ Species.TORNADUS ]);
|
2024-09-12 12:27:47 -07:00
|
|
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
|
|
|
const playerPokemonStartingHP = playerPokemon.hp;
|
|
|
|
game.move.select(Moves.ROOST);
|
2024-10-04 13:08:31 +08:00
|
|
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
2024-09-12 12:27:47 -07:00
|
|
|
await game.phaseInterceptor.to(MoveEffectPhase);
|
|
|
|
|
|
|
|
// Should only be normal type, and NOT flying type
|
|
|
|
let playerPokemonTypes = playerPokemon.getTypes();
|
|
|
|
expect(playerPokemonTypes[0] === Type.NORMAL).toBeTruthy();
|
|
|
|
expect(playerPokemonTypes[0] === Type.FLYING).toBeFalsy();
|
|
|
|
expect(playerPokemon.isGrounded()).toBeTruthy();
|
|
|
|
|
|
|
|
await game.phaseInterceptor.to(TurnEndPhase);
|
|
|
|
|
|
|
|
// Should have lost HP and is now back to being pure flying
|
|
|
|
playerPokemonTypes = playerPokemon.getTypes();
|
|
|
|
expect(playerPokemon.hp).toBeLessThan(playerPokemonStartingHP);
|
|
|
|
expect(playerPokemonTypes[0] === Type.NORMAL).toBeFalsy();
|
|
|
|
expect(playerPokemonTypes[0] === Type.FLYING).toBeTruthy();
|
|
|
|
expect(playerPokemon.isGrounded()).toBeFalsy();
|
|
|
|
|
2024-09-20 14:05:45 -07:00
|
|
|
}
|
2024-09-12 12:27:47 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
test(
|
|
|
|
"Dual X/flying type -> becomes type X after roost and takes damage from ground moves -> regains flying",
|
|
|
|
async () => {
|
2024-10-04 13:08:31 +08:00
|
|
|
await game.classicMode.startBattle([ Species.HAWLUCHA ]);
|
2024-09-12 12:27:47 -07:00
|
|
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
|
|
|
const playerPokemonStartingHP = playerPokemon.hp;
|
|
|
|
game.move.select(Moves.ROOST);
|
2024-10-04 13:08:31 +08:00
|
|
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
2024-09-12 12:27:47 -07:00
|
|
|
await game.phaseInterceptor.to(MoveEffectPhase);
|
|
|
|
|
|
|
|
// Should only be pure fighting type and grounded
|
|
|
|
let playerPokemonTypes = playerPokemon.getTypes();
|
|
|
|
expect(playerPokemonTypes[0] === Type.FIGHTING).toBeTruthy();
|
|
|
|
expect(playerPokemonTypes.length === 1).toBeTruthy();
|
|
|
|
expect(playerPokemon.isGrounded()).toBeTruthy();
|
|
|
|
|
|
|
|
await game.phaseInterceptor.to(TurnEndPhase);
|
|
|
|
|
|
|
|
// Should have lost HP and is now back to being fighting/flying
|
|
|
|
playerPokemonTypes = playerPokemon.getTypes();
|
|
|
|
expect(playerPokemon.hp).toBeLessThan(playerPokemonStartingHP);
|
|
|
|
expect(playerPokemonTypes[0] === Type.FIGHTING).toBeTruthy();
|
|
|
|
expect(playerPokemonTypes[1] === Type.FLYING).toBeTruthy();
|
|
|
|
expect(playerPokemon.isGrounded()).toBeFalsy();
|
|
|
|
|
2024-09-20 14:05:45 -07:00
|
|
|
}
|
2024-09-12 12:27:47 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
test(
|
|
|
|
"Pokemon with levitate after using roost should lose flying type but still be unaffected by ground moves",
|
|
|
|
async () => {
|
2024-09-13 19:14:10 -07:00
|
|
|
game.override.starterForms({ [Species.ROTOM]: 4 });
|
2024-10-04 13:08:31 +08:00
|
|
|
await game.classicMode.startBattle([ Species.ROTOM ]);
|
2024-09-12 12:27:47 -07:00
|
|
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
|
|
|
const playerPokemonStartingHP = playerPokemon.hp;
|
|
|
|
game.move.select(Moves.ROOST);
|
2024-10-04 13:08:31 +08:00
|
|
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
2024-09-12 12:27:47 -07:00
|
|
|
await game.phaseInterceptor.to(MoveEffectPhase);
|
|
|
|
|
2024-09-13 19:14:10 -07:00
|
|
|
// Should only be pure eletric type and grounded
|
2024-09-12 12:27:47 -07:00
|
|
|
let playerPokemonTypes = playerPokemon.getTypes();
|
|
|
|
expect(playerPokemonTypes[0] === Type.ELECTRIC).toBeTruthy();
|
|
|
|
expect(playerPokemonTypes.length === 1).toBeTruthy();
|
|
|
|
expect(playerPokemon.isGrounded()).toBeFalsy();
|
|
|
|
|
|
|
|
await game.phaseInterceptor.to(TurnEndPhase);
|
|
|
|
|
2024-09-13 19:14:10 -07:00
|
|
|
// Should have lost HP and is now back to being electric/flying
|
2024-09-12 12:27:47 -07:00
|
|
|
playerPokemonTypes = playerPokemon.getTypes();
|
|
|
|
expect(playerPokemon.hp).toBe(playerPokemonStartingHP);
|
|
|
|
expect(playerPokemonTypes[0] === Type.ELECTRIC).toBeTruthy();
|
|
|
|
expect(playerPokemonTypes[1] === Type.FLYING).toBeTruthy();
|
|
|
|
expect(playerPokemon.isGrounded()).toBeFalsy();
|
|
|
|
|
2024-09-20 14:05:45 -07:00
|
|
|
}
|
2024-09-12 12:27:47 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
test(
|
|
|
|
"A fire/flying type that uses burn up, then roost should be typeless until end of turn",
|
|
|
|
async () => {
|
2024-10-04 13:08:31 +08:00
|
|
|
await game.classicMode.startBattle([ Species.MOLTRES ]);
|
2024-09-12 12:27:47 -07:00
|
|
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
|
|
|
const playerPokemonStartingHP = playerPokemon.hp;
|
|
|
|
game.move.select(Moves.BURN_UP);
|
2024-10-04 13:08:31 +08:00
|
|
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
2024-09-12 12:27:47 -07:00
|
|
|
await game.phaseInterceptor.to(MoveEffectPhase);
|
|
|
|
|
|
|
|
// Should only be pure flying type after burn up
|
|
|
|
let playerPokemonTypes = playerPokemon.getTypes();
|
|
|
|
expect(playerPokemonTypes[0] === Type.FLYING).toBeTruthy();
|
|
|
|
expect(playerPokemonTypes.length === 1).toBeTruthy();
|
|
|
|
|
|
|
|
await game.phaseInterceptor.to(TurnEndPhase);
|
|
|
|
game.move.select(Moves.ROOST);
|
2024-10-04 13:08:31 +08:00
|
|
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
2024-09-12 12:27:47 -07:00
|
|
|
await game.phaseInterceptor.to(MoveEffectPhase);
|
|
|
|
|
|
|
|
// Should only be typeless type after roost and is grounded
|
|
|
|
playerPokemonTypes = playerPokemon.getTypes();
|
|
|
|
expect(playerPokemon.getTag(BattlerTagType.ROOSTED)).toBeDefined();
|
|
|
|
expect(playerPokemonTypes[0] === Type.UNKNOWN).toBeTruthy();
|
|
|
|
expect(playerPokemonTypes.length === 1).toBeTruthy();
|
|
|
|
expect(playerPokemon.isGrounded()).toBeTruthy();
|
|
|
|
|
|
|
|
await game.phaseInterceptor.to(TurnEndPhase);
|
|
|
|
|
|
|
|
// Should go back to being pure flying and have taken damage from earthquake, and is ungrounded again
|
|
|
|
playerPokemonTypes = playerPokemon.getTypes();
|
|
|
|
expect(playerPokemon.hp).toBeLessThan(playerPokemonStartingHP);
|
|
|
|
expect(playerPokemonTypes[0] === Type.FLYING).toBeTruthy();
|
|
|
|
expect(playerPokemonTypes.length === 1).toBeTruthy();
|
|
|
|
expect(playerPokemon.isGrounded()).toBeFalsy();
|
|
|
|
|
2024-09-20 14:05:45 -07:00
|
|
|
}
|
2024-09-12 12:27:47 -07:00
|
|
|
);
|
|
|
|
|
2024-06-23 09:48:49 -07:00
|
|
|
test(
|
2024-09-12 12:27:47 -07:00
|
|
|
"An electric/flying type that uses double shock, then roost should be typeless until end of turn",
|
2024-06-23 09:48:49 -07:00
|
|
|
async () => {
|
2024-09-12 12:27:47 -07:00
|
|
|
game.override.enemySpecies(Species.ZEKROM);
|
2024-10-04 13:08:31 +08:00
|
|
|
await game.classicMode.startBattle([ Species.ZAPDOS ]);
|
2024-09-12 12:27:47 -07:00
|
|
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
|
|
|
const playerPokemonStartingHP = playerPokemon.hp;
|
|
|
|
game.move.select(Moves.DOUBLE_SHOCK);
|
2024-10-04 13:08:31 +08:00
|
|
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
2024-09-12 12:27:47 -07:00
|
|
|
await game.phaseInterceptor.to(MoveEffectPhase);
|
|
|
|
|
|
|
|
// Should only be pure flying type after burn up
|
|
|
|
let playerPokemonTypes = playerPokemon.getTypes();
|
|
|
|
expect(playerPokemonTypes[0] === Type.FLYING).toBeTruthy();
|
|
|
|
expect(playerPokemonTypes.length === 1).toBeTruthy();
|
|
|
|
|
|
|
|
await game.phaseInterceptor.to(TurnEndPhase);
|
|
|
|
game.move.select(Moves.ROOST);
|
2024-10-04 13:08:31 +08:00
|
|
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
2024-09-12 12:27:47 -07:00
|
|
|
await game.phaseInterceptor.to(MoveEffectPhase);
|
|
|
|
|
|
|
|
// Should only be typeless type after roost and is grounded
|
|
|
|
playerPokemonTypes = playerPokemon.getTypes();
|
|
|
|
expect(playerPokemon.getTag(BattlerTagType.ROOSTED)).toBeDefined();
|
|
|
|
expect(playerPokemonTypes[0] === Type.UNKNOWN).toBeTruthy();
|
|
|
|
expect(playerPokemonTypes.length === 1).toBeTruthy();
|
|
|
|
expect(playerPokemon.isGrounded()).toBeTruthy();
|
2024-06-23 09:48:49 -07:00
|
|
|
|
2024-09-12 12:27:47 -07:00
|
|
|
await game.phaseInterceptor.to(TurnEndPhase);
|
2024-06-23 09:48:49 -07:00
|
|
|
|
2024-09-12 12:27:47 -07:00
|
|
|
// Should go back to being pure flying and have taken damage from earthquake, and is ungrounded again
|
|
|
|
playerPokemonTypes = playerPokemon.getTypes();
|
|
|
|
expect(playerPokemon.hp).toBeLessThan(playerPokemonStartingHP);
|
|
|
|
expect(playerPokemonTypes[0] === Type.FLYING).toBeTruthy();
|
|
|
|
expect(playerPokemonTypes.length === 1).toBeTruthy();
|
|
|
|
expect(playerPokemon.isGrounded()).toBeFalsy();
|
2024-06-23 09:48:49 -07:00
|
|
|
|
2024-09-20 14:05:45 -07:00
|
|
|
}
|
2024-09-12 12:27:47 -07:00
|
|
|
);
|
2024-06-23 09:48:49 -07:00
|
|
|
|
2024-09-12 12:27:47 -07:00
|
|
|
test(
|
|
|
|
"Dual Type Pokemon afflicted with Forests Curse/Trick or Treat and post roost will become dual type and then become 3 type at end of turn",
|
|
|
|
async () => {
|
2024-10-04 13:08:31 +08:00
|
|
|
game.override.enemyMoveset([ Moves.TRICK_OR_TREAT, Moves.TRICK_OR_TREAT, Moves.TRICK_OR_TREAT, Moves.TRICK_OR_TREAT ]);
|
|
|
|
await game.classicMode.startBattle([ Species.MOLTRES ]);
|
2024-09-12 12:27:47 -07:00
|
|
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
|
|
|
game.move.select(Moves.ROOST);
|
2024-06-23 09:48:49 -07:00
|
|
|
await game.phaseInterceptor.to(MoveEffectPhase);
|
|
|
|
|
2024-09-12 12:27:47 -07:00
|
|
|
let playerPokemonTypes = playerPokemon.getTypes();
|
|
|
|
expect(playerPokemonTypes[0] === Type.FIRE).toBeTruthy();
|
|
|
|
expect(playerPokemonTypes.length === 1).toBeTruthy();
|
|
|
|
expect(playerPokemon.isGrounded()).toBeTruthy();
|
2024-06-23 09:48:49 -07:00
|
|
|
|
|
|
|
await game.phaseInterceptor.to(TurnEndPhase);
|
|
|
|
|
2024-09-12 12:27:47 -07:00
|
|
|
// Should be fire/flying/ghost
|
|
|
|
playerPokemonTypes = playerPokemon.getTypes();
|
|
|
|
expect(playerPokemonTypes.filter(type => type === Type.FLYING)).toHaveLength(1);
|
|
|
|
expect(playerPokemonTypes.filter(type => type === Type.FIRE)).toHaveLength(1);
|
|
|
|
expect(playerPokemonTypes.filter(type => type === Type.GHOST)).toHaveLength(1);
|
|
|
|
expect(playerPokemonTypes.length === 3).toBeTruthy();
|
|
|
|
expect(playerPokemon.isGrounded()).toBeFalsy();
|
|
|
|
|
2024-09-20 14:05:45 -07:00
|
|
|
}
|
2024-06-23 09:48:49 -07:00
|
|
|
);
|
2024-09-12 12:27:47 -07:00
|
|
|
|
2024-06-23 09:48:49 -07:00
|
|
|
});
|