pokerogue/src/test/moves/tera_blast.test.ts
Xavion3 90d32b886c
[Feature] Tera Rework (#5233)
* Commit old stashed changes

* Complete basic implementation of Tera

* Fix effectiveness test

* Make tera retain until forced recall or faint, regain on biome change

* Experimental sparkle fix

* Fix champion teras

* Attempted fix for double battles tera UI bug

* Fix the fix

* Fix linting and test issues

* Fix more tests

* Change int type

* Implement tera for ME trainers

* Cleanup species inclusivity check

* Make tera instant recharge if terapagos in party

* Make useless tera shards not generate

* Implement stellar tera damage boost

* Improve tera selection UI

* Tidy up animation and localisation

* Improve tera button sprite

* Fix Lance tera

* Make tera instant recharge during E4 in classic modes.

* Fix formatting in the tera common animation

The animation was also not playing due to `frameTimedEvents` being missing as well.

* Make tera effect start after animation

* Implement save migration

* Update version number for migration code

---------

Co-authored-by: Madmadness65 <blaze.the.fireman@gmail.com>
Co-authored-by: Madmadness65 <59298170+Madmadness65@users.noreply.github.com>
2025-02-16 16:20:50 -05:00

192 lines
6.4 KiB
TypeScript

import { BattlerIndex } from "#app/battle";
import { Stat } from "#enums/stat";
import { allMoves, TeraMoveCategoryAttr } from "#app/data/move";
import { Type } from "#enums/type";
import { Abilities } from "#app/enums/abilities";
import { HitResult } from "#app/field/pokemon";
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, vi } from "vitest";
describe("Moves - Tera Blast", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
const moveToCheck = allMoves[Moves.TERA_BLAST];
const teraBlastAttr = moveToCheck.getAttrs(TeraMoveCategoryAttr)[0];
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.battleType("single")
.disableCrits()
.starterSpecies(Species.FEEBAS)
.moveset([ Moves.TERA_BLAST ])
.ability(Abilities.BALL_FETCH)
.enemySpecies(Species.MAGIKARP)
.enemyMoveset(Moves.SPLASH)
.enemyAbility(Abilities.STURDY)
.enemyLevel(50);
vi.spyOn(moveToCheck, "calculateBattlePower");
});
it("changes type to match user's tera type", async () => {
game.override.enemySpecies(Species.FURRET);
await game.startBattle();
const enemyPokemon = game.scene.getEnemyPokemon()!;
vi.spyOn(enemyPokemon, "apply");
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.teraType = Type.FIGHTING;
playerPokemon.isTerastallized = true;
game.move.select(Moves.TERA_BLAST);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.phaseInterceptor.to("MoveEffectPhase");
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.SUPER_EFFECTIVE);
}, 20000);
it("increases power if user is Stellar tera type", async () => {
await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.teraType = Type.STELLAR;
playerPokemon.isTerastallized = true;
game.move.select(Moves.TERA_BLAST);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.phaseInterceptor.to("MoveEffectPhase");
expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(100);
}, 20000);
it("is super effective against terastallized targets if user is Stellar tera type", async () => {
await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.teraType = Type.STELLAR;
playerPokemon.isTerastallized = true;
const enemyPokemon = game.scene.getEnemyPokemon()!;
vi.spyOn(enemyPokemon, "apply");
enemyPokemon.isTerastallized = true;
game.move.select(Moves.TERA_BLAST);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.phaseInterceptor.to("MoveEffectPhase");
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.SUPER_EFFECTIVE);
});
it("uses the higher ATK for damage calculation", async () => {
await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.stats[Stat.ATK] = 100;
playerPokemon.stats[Stat.SPATK] = 1;
playerPokemon.isTerastallized = true;
vi.spyOn(teraBlastAttr, "apply");
game.move.select(Moves.TERA_BLAST);
await game.toNextTurn();
expect(teraBlastAttr.apply).toHaveLastReturnedWith(true);
});
it("uses the higher SPATK for damage calculation", async () => {
await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.stats[Stat.ATK] = 1;
playerPokemon.stats[Stat.SPATK] = 100;
vi.spyOn(teraBlastAttr, "apply");
game.move.select(Moves.TERA_BLAST);
await game.toNextTurn();
expect(teraBlastAttr.apply).toHaveLastReturnedWith(false);
});
it("should stay as a special move if ATK turns lower than SPATK mid-turn", async () => {
game.override.enemyMoveset([ Moves.CHARM ]);
await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.stats[Stat.ATK] = 51;
playerPokemon.stats[Stat.SPATK] = 50;
vi.spyOn(teraBlastAttr, "apply");
game.move.select(Moves.TERA_BLAST);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextTurn();
expect(teraBlastAttr.apply).toHaveLastReturnedWith(false);
});
it("does not change its move category from stat changes due to held items", async () => {
game.override
.startingHeldItems([{ name: "SPECIES_STAT_BOOSTER", type: "THICK_CLUB" }])
.starterSpecies(Species.CUBONE);
await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.stats[Stat.ATK] = 50;
playerPokemon.stats[Stat.SPATK] = 51;
vi.spyOn(teraBlastAttr, "apply");
game.move.select(Moves.TERA_BLAST);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextTurn();
expect(teraBlastAttr.apply).toHaveLastReturnedWith(false);
});
it("does not change its move category from stat changes due to abilities", async () => {
game.override.ability(Abilities.HUGE_POWER);
await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.stats[Stat.ATK] = 50;
playerPokemon.stats[Stat.SPATK] = 51;
vi.spyOn(teraBlastAttr, "apply");
game.move.select(Moves.TERA_BLAST);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextTurn();
expect(teraBlastAttr.apply).toHaveLastReturnedWith(false);
});
it("causes stat drops if user is Stellar tera type", async () => {
await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
playerPokemon.teraType = Type.STELLAR;
playerPokemon.isTerastallized = true;
game.move.select(Moves.TERA_BLAST);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.phaseInterceptor.to("MoveEndPhase");
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1);
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1);
}, 20000);
});