mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-01-16 05:51:56 +00:00
0aac6c6647
* 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
235 lines
8.9 KiB
TypeScript
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;
|
|
}
|
|
});
|
|
|