pokerogue/src/test/boss-pokemon.test.ts
flx-sta 0aac6c6647
[Dev][Test] Add "Mystery Encounter" type for create-test script + minor timeout fixes (#4350)
* add: mystery encounter to create-test

* remove: leftover `TIMEOUT`

in boss-pokemon.test

* add regex to replace ` ` with `_`

This was never done before. for any

* remove: manual test timeout overwrites
2024-09-20 17:54:37 -04:00

235 lines
8.9 KiB
TypeScript

import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import GameManager from "./utils/gameManager";
import { Species } from "#app/enums/species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Abilities } from "#app/enums/abilities";
import { Moves } from "#app/enums/moves";
import { EFFECTIVE_STATS } from "#app/enums/stat";
import { EnemyPokemon } from "#app/field/pokemon";
import { toDmgValue } from "#app/utils";
describe("Boss Pokemon / Shields", () => {
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")
.disableTrainerWaves()
.disableCrits()
.enemySpecies(Species.RATTATA)
.enemyMoveset(Moves.SPLASH)
.enemyHeldItems([])
.startingLevel(1000)
.moveset([Moves.FALSE_SWIPE, Moves.SUPER_FANG, Moves.SPLASH, Moves.PSYCHIC])
.ability(Abilities.NO_GUARD);
});
it("Pokemon should get shields based on their Species and level and the current wave", async () => {
let level = 50;
let wave = 5;
// On normal waves, no shields...
expect(game.scene.getEncounterBossSegments(wave, level, getPokemonSpecies(Species.RATTATA))).toBe(0);
// ... expect (sub)-legendary and mythical Pokemon who always get shields
expect(game.scene.getEncounterBossSegments(wave, level, getPokemonSpecies(Species.MEW))).toBe(2);
// Pokemon with 670+ BST get an extra shield
expect(game.scene.getEncounterBossSegments(wave, level, getPokemonSpecies(Species.MEWTWO))).toBe(3);
// Every 10 waves will always be a boss Pokemon with shield(s)
wave = 50;
expect(game.scene.getEncounterBossSegments(wave, level, getPokemonSpecies(Species.RATTATA))).toBe(2);
// Every extra 250 waves adds a shield
wave += 250;
expect(game.scene.getEncounterBossSegments(wave, level, getPokemonSpecies(Species.RATTATA))).toBe(3);
wave += 750;
expect(game.scene.getEncounterBossSegments(wave, level, getPokemonSpecies(Species.RATTATA))).toBe(6);
// Pokemon above level 100 get an extra shield
level = 100;
expect(game.scene.getEncounterBossSegments(wave, level, getPokemonSpecies(Species.RATTATA))).toBe(7);
});
it("should reduce the number of shields if we are in a double battle", async () => {
game.override
.battleType("double")
.startingWave(150); // Floor 150 > 2 shields / 3 health segments
await game.classicMode.startBattle([ Species.MEWTWO ]);
const boss1: EnemyPokemon = game.scene.getEnemyParty()[0]!;
const boss2: EnemyPokemon = game.scene.getEnemyParty()[1]!;
expect(boss1.isBoss()).toBe(true);
expect(boss1.bossSegments).toBe(2);
expect(boss2.isBoss()).toBe(true);
expect(boss2.bossSegments).toBe(2);
});
it("shields should stop overflow damage and give stat stage boosts when broken", async () => {
game.override.startingWave(150); // Floor 150 > 2 shields / 3 health segments
await game.classicMode.startBattle([ Species.MEWTWO ]);
const enemyPokemon = game.scene.getEnemyPokemon()!;
const segmentHp = enemyPokemon.getMaxHp() / enemyPokemon.bossSegments;
expect(enemyPokemon.isBoss()).toBe(true);
expect(enemyPokemon.bossSegments).toBe(3);
expect(getTotalStatStageBoosts(enemyPokemon)).toBe(0);
game.move.select(Moves.SUPER_FANG); // Enough to break the first shield
await game.toNextTurn();
// Broke 1st of 2 shields, health at 2/3rd
expect(enemyPokemon.bossSegmentIndex).toBe(1);
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp() - toDmgValue(segmentHp));
// Breaking the shield gives a +1 boost to ATK, DEF, SP ATK, SP DEF or SPD
expect(getTotalStatStageBoosts(enemyPokemon)).toBe(1);
game.move.select(Moves.FALSE_SWIPE); // Enough to break last shield but not kill
await game.toNextTurn();
expect(enemyPokemon.bossSegmentIndex).toBe(0);
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp() - toDmgValue(2 * segmentHp));
// Breaking the last shield gives a +2 boost to ATK, DEF, SP ATK, SP DEF or SPD
expect(getTotalStatStageBoosts(enemyPokemon)).toBe(3);
});
it("breaking multiple shields at once requires extra damage", async () => {
game.override
.battleType("double")
.enemyHealthSegments(5);
await game.classicMode.startBattle([ Species.MEWTWO ]);
// In this test we want to break through 3 shields at once
const brokenShields = 3;
const boss1: EnemyPokemon = game.scene.getEnemyParty()[0]!;
const boss1SegmentHp = boss1.getMaxHp() / boss1.bossSegments;
const requiredDamageBoss1 = boss1SegmentHp * (1 + Math.pow(2, brokenShields));
expect(boss1.isBoss()).toBe(true);
expect(boss1.bossSegments).toBe(5);
expect(boss1.bossSegmentIndex).toBe(4);
// Not enough damage to break through all shields
boss1.damageAndUpdate(Math.floor(requiredDamageBoss1 - 5));
expect(boss1.bossSegmentIndex).toBe(1);
expect(boss1.hp).toBe(boss1.getMaxHp() - toDmgValue(boss1SegmentHp * 3));
const boss2: EnemyPokemon = game.scene.getEnemyParty()[1]!;
const boss2SegmentHp = boss2.getMaxHp() / boss2.bossSegments;
const requiredDamageBoss2 = boss2SegmentHp * (1 + Math.pow(2, brokenShields));
expect(boss2.isBoss()).toBe(true);
expect(boss2.bossSegments).toBe(5);
// Enough damage to break through all shields
boss2.damageAndUpdate(Math.ceil(requiredDamageBoss2));
expect(boss2.bossSegmentIndex).toBe(0);
expect(boss2.hp).toBe(boss2.getMaxHp() - toDmgValue(boss2SegmentHp * 4));
});
it("the number of stat stage boosts is consistent when several shields are broken at once", async () => {
const shieldsToBreak = 4;
game.override
.battleType("double")
.enemyHealthSegments(shieldsToBreak + 1);
await game.classicMode.startBattle([ Species.MEWTWO ]);
const boss1: EnemyPokemon = game.scene.getEnemyParty()[0]!;
const boss1SegmentHp = boss1.getMaxHp() / boss1.bossSegments;
const singleShieldDamage = Math.ceil(boss1SegmentHp);
expect(boss1.isBoss()).toBe(true);
expect(boss1.bossSegments).toBe(shieldsToBreak + 1);
expect(boss1.bossSegmentIndex).toBe(shieldsToBreak);
expect(getTotalStatStageBoosts(boss1)).toBe(0);
let totalStatStages = 0;
// Break the shields one by one
for (let i = 1; i <= shieldsToBreak; i++) {
boss1.damageAndUpdate(singleShieldDamage);
expect(boss1.bossSegmentIndex).toBe(shieldsToBreak - i);
expect(boss1.hp).toBe(boss1.getMaxHp() - toDmgValue(boss1SegmentHp * i));
// Do nothing and go to next turn so that the StatStageChangePhase gets applied
game.move.select(Moves.SPLASH);
await game.toNextTurn();
// All broken shields give +1 stat boost, except the last two that gives +2
totalStatStages += i >= shieldsToBreak -1? 2 : 1;
expect(getTotalStatStageBoosts(boss1)).toBe(totalStatStages);
}
const boss2: EnemyPokemon = game.scene.getEnemyParty()[1]!;
const boss2SegmentHp = boss2.getMaxHp() / boss2.bossSegments;
const requiredDamage = boss2SegmentHp * (1 + Math.pow(2, shieldsToBreak - 1));
expect(boss2.isBoss()).toBe(true);
expect(boss2.bossSegments).toBe(shieldsToBreak + 1);
expect(boss2.bossSegmentIndex).toBe(shieldsToBreak);
expect(getTotalStatStageBoosts(boss2)).toBe(0);
// Enough damage to break all shields at once
boss2.damageAndUpdate(Math.ceil(requiredDamage));
expect(boss2.bossSegmentIndex).toBe(0);
expect(boss2.hp).toBe(boss2.getMaxHp() - toDmgValue(boss2SegmentHp * shieldsToBreak));
// Do nothing and go to next turn so that the StatStageChangePhase gets applied
game.move.select(Moves.SPLASH);
await game.toNextTurn();
expect(getTotalStatStageBoosts(boss2)).toBe(totalStatStages);
});
it("the boss enduring does not proc an extra stat boost", async () => {
game.override
.enemyHealthSegments(2)
.enemyAbility(Abilities.STURDY);
await game.classicMode.startBattle([ Species.MEWTWO ]);
const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon.isBoss()).toBe(true);
expect(enemyPokemon.bossSegments).toBe(2);
expect(getTotalStatStageBoosts(enemyPokemon)).toBe(0);
game.move.select(Moves.PSYCHIC);
await game.toNextTurn();
// Enemy survived with Sturdy
expect(enemyPokemon.bossSegmentIndex).toBe(0);
expect(enemyPokemon.hp).toBe(1);
expect(getTotalStatStageBoosts(enemyPokemon)).toBe(1);
});
/**
* Gets the sum of the effective stat stage boosts for the given Pokemon
* @param enemyPokemon the pokemon to get stats from
* @returns the total stats boosts
*/
function getTotalStatStageBoosts(enemyPokemon: EnemyPokemon): number {
let boosts = 0;
for (const s of EFFECTIVE_STATS) {
boosts += enemyPokemon.getStatStage(s);
}
return boosts;
}
});