implement test for final boss encounter phase switch

This commit is contained in:
MokaStitcher 2024-08-27 11:28:42 +02:00
parent 5b400af5b7
commit 69ab1687bc
3 changed files with 119 additions and 26 deletions

View File

@ -1,8 +1,14 @@
import { Biome } from "#app/enums/biome"; import { Biome } from "#app/enums/biome";
import { Moves } from "#app/enums/moves";
import { Species } from "#app/enums/species"; import { Species } from "#app/enums/species";
import { GameModes } from "#app/game-mode"; import { GameModes } from "#app/game-mode";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import GameManager from "./utils/gameManager"; import GameManager from "./utils/gameManager";
import { SPLASH_ONLY } from "./utils/testUtils";
import { TurnHeldItemTransferModifier } from "#app/modifier/modifier";
import { Abilities } from "#app/enums/abilities";
import { CommandPhase } from "#app/phases/command-phase";
import { StatusEffect } from "#app/data/status-effect";
const FinalWave = { const FinalWave = {
Classic: 200, Classic: 200,
@ -61,5 +67,91 @@ describe("Final Boss", () => {
expect(eternatus?.hasPassive()).toBe(false); expect(eternatus?.hasPassive()).toBe(false);
}); });
it.todo("should change form on direct hit down to last boss fragment", () => {}); it("should change form on direct hit down to last boss fragment", async () => {
game.override.startingLevel(10000);
game.override.starterSpecies(Species.KYUREM);
game.override.moveset([Moves.DRAGON_PULSE]);
game.override.enemyMoveset(SPLASH_ONLY);
game.override.enemyHeldItems([]);
// This handles skipping all dialog at the start of the battle and when switching phase
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.CLASSIC);
await game.phaseInterceptor.to(CommandPhase);
// Eternatus phase 1
let eternatus = game.scene.getEnemyPokemon()!;
const phase1Hp = eternatus.getMaxHp();
expect(eternatus.species.speciesId).toBe(Species.ETERNATUS);
expect(eternatus.formIndex).toBe(0);
expect(eternatus.bossSegments).toBe(4);
expect(eternatus.bossSegmentIndex).toBe(3);
game.move.select(Moves.DRAGON_PULSE);
await game.toNextTurn();
// Eternatus phase 2: changed form, healed and restored its shields
eternatus = game.scene.getEnemyPokemon()!;
expect(eternatus.species.speciesId).toBe(Species.ETERNATUS);
expect(eternatus.hp).toBeGreaterThan(phase1Hp);
expect(eternatus.hp).toBe(eternatus.getMaxHp());
expect(eternatus.formIndex).toBe(1);
expect(eternatus.bossSegments).toBe(5);
expect(eternatus.bossSegmentIndex).toBe(4);
// should carry a mini black hole
const miniBlackHole = eternatus.getHeldItems().find(m => m instanceof TurnHeldItemTransferModifier);
expect(miniBlackHole).toBeDefined();
expect(miniBlackHole?.stackCount).toBe(1);
});
it("should change form on status damage down to last boss fragment", async () => {
game.override.ability(Abilities.NO_GUARD);
game.override.moveset([ Moves.SPLASH, Moves.WILL_O_WISP ]);
game.override.enemyMoveset(SPLASH_ONLY);
game.override.enemyHeldItems([]);
// This handles skipping all dialog at the start of the battle and when switching phase
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.CLASSIC);
await game.phaseInterceptor.to(CommandPhase);
// Eternatus phase 1
let eternatus = game.scene.getEnemyPokemon()!;
const phase1Hp = eternatus.getMaxHp();
expect(eternatus.species.speciesId).toBe(Species.ETERNATUS);
expect(eternatus.formIndex).toBe(0);
expect(eternatus.bossSegments).toBe(4);
expect(eternatus.bossSegmentIndex).toBe(3);
// Burn the boss
game.move.select(Moves.WILL_O_WISP);
await game.toNextTurn();
expect(eternatus.status?.effect).toBe(StatusEffect.BURN);
const tickDamage = phase1Hp - eternatus.hp;
const lastShieldHp = Math.ceil(phase1Hp / eternatus.bossSegments);
// Stall until the burn is one hit away from breaking the last shield
while (eternatus.hp - tickDamage > lastShieldHp) {
game.move.select(Moves.SPLASH);
await game.toNextTurn();
}
expect(eternatus.bossSegmentIndex).toBe(1);
// Last burn should break the shield
game.move.select(Moves.SPLASH);
await game.toNextTurn();
// Eternatus phase 2: changed form, healed and restored its shields
eternatus = game.scene.getEnemyPokemon()!;
expect(eternatus.hp).toBeGreaterThan(phase1Hp);
expect(eternatus.hp).toBe(eternatus.getMaxHp());
expect(eternatus.status).toBeFalsy();
expect(eternatus.formIndex).toBe(1);
expect(eternatus.bossSegments).toBe(5);
expect(eternatus.bossSegmentIndex).toBe(4);
// should carry a mini black hole
const miniBlackHole = eternatus.getHeldItems().find(m => m instanceof TurnHeldItemTransferModifier);
expect(miniBlackHole).toBeDefined();
expect(miniBlackHole?.stackCount).toBe(1);
});
}); });

View File

@ -156,14 +156,8 @@ export default class GameManager {
selectStarterPhase.initBattle(starters); selectStarterPhase.initBattle(starters);
}); });
game.onNextPrompt("EncounterPhase", Mode.MESSAGE, async () => { // This will consider all battle entry dialog as seens and skip them
// This will skip all entry dialogue (I can't figure out a way to sequentially handle the 8 chained messages via 1 prompt handler) vi.spyOn(game.scene.ui, "shouldSkipDialogue").mockReturnValue(true);
game.setMode(Mode.MESSAGE);
const encounterPhase = game.scene.getCurrentPhase() as EncounterPhase;
// No need to end phase, this will do it for you
encounterPhase.doEncounterCommon(false);
});
await game.phaseInterceptor.to(EncounterPhase, true); await game.phaseInterceptor.to(EncounterPhase, true);
console.log("===finished run to final boss encounter==="); console.log("===finished run to final boss encounter===");

View File

@ -295,26 +295,20 @@ export default class UI extends Phaser.GameObjects.Container {
} }
showDialogue(text: string, name: string | undefined, delay: integer | null = 0, callback: Function, callbackDelay?: integer, promptDelay?: integer): void { showDialogue(text: string, name: string | undefined, delay: integer | null = 0, callback: Function, callbackDelay?: integer, promptDelay?: integer): void {
// First get the gender of the player (default male) (also used if UNSET) // Skip dialogue if the player has enabled the option and the dialogue has been already seen
let playerGenderPrefix = "PGM"; if (this.shouldSkipDialogue(text)) {
if ((this.scene as BattleScene).gameData.gender === PlayerGender.FEMALE) { console.log(`Dialogue ${text} skipped`);
playerGenderPrefix = "PGF"; callback();
return;
} }
// Add the prefix to the text
const localizationKey: string = playerGenderPrefix + text;
// Get localized dialogue (if available) // Get the localization key corresponding to the player's gender
const localizationKey: string = this.getGenderedLocalizationKey(text);
let hasi18n = false; let hasi18n = false;
if (i18next.exists(localizationKey) ) { if (i18next.exists(localizationKey) ) {
text = i18next.t(localizationKey as ParseKeys); text = i18next.t(localizationKey as ParseKeys);
hasi18n = true; hasi18n = true;
// Skip dialogue if the player has enabled the option and the dialogue has been already seen
if ((this.scene as BattleScene).skipSeenDialogues && (this.scene as BattleScene).gameData.getSeenDialogues()[localizationKey] === true) {
console.log(`Dialogue ${localizationKey} skipped`);
callback();
return;
}
} }
let showMessageAndCallback = () => { let showMessageAndCallback = () => {
hasi18n && (this.scene as BattleScene).gameData.saveSeenDialogue(localizationKey); hasi18n && (this.scene as BattleScene).gameData.saveSeenDialogue(localizationKey);
@ -337,14 +331,27 @@ export default class UI extends Phaser.GameObjects.Container {
} }
} }
shouldSkipDialogue(text): boolean { /**
* Adds the appropriate gender marker to a localization key based on the player's selected gender
* @param baseKey the localization key to change
* @returns the gendered version of the key
*/
getGenderedLocalizationKey(baseKey: string): string {
let playerGenderPrefix = "PGM"; let playerGenderPrefix = "PGM";
if ((this.scene as BattleScene).gameData.gender === PlayerGender.FEMALE) { if ((this.scene as BattleScene).gameData.gender === PlayerGender.FEMALE) {
playerGenderPrefix = "PGF"; playerGenderPrefix = "PGF";
} }
return playerGenderPrefix + baseKey;
}
const key = playerGenderPrefix + text; /**
* Checks if a dialogue should be skipped based on whether the "skipping seen dialog"
* option is enabled and if the given dialog has been seen already
* @param text the localization key to use, without their gendered marker
* @returns true if the dialog should be skipped
*/
shouldSkipDialogue(text: string): boolean {
const key = this.getGenderedLocalizationKey(text);
if (i18next.exists(key) ) { if (i18next.exists(key) ) {
if ((this.scene as BattleScene).skipSeenDialogues && (this.scene as BattleScene).gameData.getSeenDialogues()[key] === true) { if ((this.scene as BattleScene).skipSeenDialogues && (this.scene as BattleScene).gameData.getSeenDialogues()[key] === true) {
return true; return true;