mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-02-16 17:27:41 +00:00
Disable endless boss passives (#3451)
* fix strict null broken * disable endless boss passives * jsdocs on mock objects and move helper function to gameManager.ts * Apply suggestions from flx's review Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * fix broken test * fix lint --------- Co-authored-by: ImperialSympathizer <imperialsympathizer@gmail.com> Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>
This commit is contained in:
parent
b20314b45f
commit
988ec664e9
@ -1082,8 +1082,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Final boss does not have passive
|
||||
if (this.scene.currentBattle?.battleSpec === BattleSpec.FINAL_BOSS && this instanceof EnemyPokemon) {
|
||||
// Classic Final boss and Endless Minor/Major bosses do not have passive
|
||||
const { currentBattle, gameMode } = this.scene;
|
||||
const waveIndex = currentBattle?.waveIndex;
|
||||
if (this instanceof EnemyPokemon &&
|
||||
(currentBattle?.battleSpec === BattleSpec.FINAL_BOSS ||
|
||||
gameMode.isEndlessMinorBoss(waveIndex) ||
|
||||
gameMode.isEndlessMajorBoss(waveIndex))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
88
src/test/endless_boss.test.ts
Normal file
88
src/test/endless_boss.test.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { Biome } from "#app/enums/biome";
|
||||
import { Species } from "#app/enums/species";
|
||||
import { GameModes } from "#app/game-mode";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import GameManager from "./utils/gameManager";
|
||||
|
||||
const EndlessBossWave = {
|
||||
Minor: 250,
|
||||
Major: 1000
|
||||
};
|
||||
|
||||
describe("Endless Boss", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.startingBiome(Biome.END)
|
||||
.disableCrits();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
it(`should spawn a minor boss every ${EndlessBossWave.Minor} waves in END biome in Endless`, async () => {
|
||||
game.override.startingWave(EndlessBossWave.Minor);
|
||||
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.ENDLESS);
|
||||
|
||||
expect(game.scene.currentBattle.waveIndex).toBe(EndlessBossWave.Minor);
|
||||
expect(game.scene.arena.biomeType).toBe(Biome.END);
|
||||
const eternatus = game.scene.getEnemyPokemon();
|
||||
expect(eternatus?.species.speciesId).toBe(Species.ETERNATUS);
|
||||
expect(eternatus?.hasPassive()).toBe(false);
|
||||
expect(eternatus?.formIndex).toBe(0);
|
||||
});
|
||||
|
||||
it(`should spawn a major boss every ${EndlessBossWave.Major} waves in END biome in Endless`, async () => {
|
||||
game.override.startingWave(EndlessBossWave.Major);
|
||||
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.ENDLESS);
|
||||
|
||||
expect(game.scene.currentBattle.waveIndex).toBe(EndlessBossWave.Major);
|
||||
expect(game.scene.arena.biomeType).toBe(Biome.END);
|
||||
const eternatus = game.scene.getEnemyPokemon();
|
||||
expect(eternatus?.species.speciesId).toBe(Species.ETERNATUS);
|
||||
expect(eternatus?.hasPassive()).toBe(false);
|
||||
expect(eternatus?.formIndex).toBe(1);
|
||||
});
|
||||
|
||||
it(`should spawn a minor boss every ${EndlessBossWave.Minor} waves in END biome in Spliced Endless`, async () => {
|
||||
game.override.startingWave(EndlessBossWave.Minor);
|
||||
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.SPLICED_ENDLESS);
|
||||
|
||||
expect(game.scene.currentBattle.waveIndex).toBe(EndlessBossWave.Minor);
|
||||
expect(game.scene.arena.biomeType).toBe(Biome.END);
|
||||
const eternatus = game.scene.getEnemyPokemon();
|
||||
expect(eternatus?.species.speciesId).toBe(Species.ETERNATUS);
|
||||
expect(eternatus?.hasPassive()).toBe(false);
|
||||
expect(eternatus?.formIndex).toBe(0);
|
||||
});
|
||||
|
||||
it(`should spawn a major boss every ${EndlessBossWave.Major} waves in END biome in Spliced Endless`, async () => {
|
||||
game.override.startingWave(EndlessBossWave.Major);
|
||||
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.SPLICED_ENDLESS);
|
||||
|
||||
expect(game.scene.currentBattle.waveIndex).toBe(EndlessBossWave.Major);
|
||||
expect(game.scene.arena.biomeType).toBe(Biome.END);
|
||||
const eternatus = game.scene.getEnemyPokemon();
|
||||
expect(eternatus?.species.speciesId).toBe(Species.ETERNATUS);
|
||||
expect(eternatus?.hasPassive()).toBe(false);
|
||||
expect(eternatus?.formIndex).toBe(1);
|
||||
});
|
||||
|
||||
it(`should NOT spawn major or minor boss outside wave ${EndlessBossWave.Minor}s in END biome`, async () => {
|
||||
game.override.startingWave(EndlessBossWave.Minor - 1);
|
||||
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.ENDLESS);
|
||||
|
||||
expect(game.scene.currentBattle.waveIndex).not.toBe(EndlessBossWave.Minor);
|
||||
expect(game.scene.getEnemyPokemon()!.species.speciesId).not.toBe(Species.ETERNATUS);
|
||||
});
|
||||
});
|
@ -1,11 +1,8 @@
|
||||
import { Biome } from "#app/enums/biome.js";
|
||||
import { Species } from "#app/enums/species.js";
|
||||
import { GameModes, getGameMode } from "#app/game-mode.js";
|
||||
import { EncounterPhase, SelectStarterPhase } from "#app/phases.js";
|
||||
import { Mode } from "#app/ui/ui.js";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import GameManager from "./utils/gameManager";
|
||||
import { generateStarter } from "./utils/gameManagerUtils";
|
||||
import { GameModes } from "#app/game-mode";
|
||||
|
||||
const FinalWave = {
|
||||
Classic: 200,
|
||||
@ -31,7 +28,7 @@ describe("Final Boss", () => {
|
||||
});
|
||||
|
||||
it("should spawn Eternatus on wave 200 in END biome", async () => {
|
||||
await runToFinalBossEncounter(game, [Species.BIDOOF]);
|
||||
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.CLASSIC);
|
||||
|
||||
expect(game.scene.currentBattle.waveIndex).toBe(FinalWave.Classic);
|
||||
expect(game.scene.arena.biomeType).toBe(Biome.END);
|
||||
@ -40,7 +37,7 @@ describe("Final Boss", () => {
|
||||
|
||||
it("should NOT spawn Eternatus before wave 200 in END biome", async () => {
|
||||
game.override.startingWave(FinalWave.Classic - 1);
|
||||
await runToFinalBossEncounter(game, [Species.BIDOOF]);
|
||||
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.CLASSIC);
|
||||
|
||||
expect(game.scene.currentBattle.waveIndex).not.toBe(FinalWave.Classic);
|
||||
expect(game.scene.arena.biomeType).toBe(Biome.END);
|
||||
@ -49,7 +46,7 @@ describe("Final Boss", () => {
|
||||
|
||||
it("should NOT spawn Eternatus outside of END biome", async () => {
|
||||
game.override.startingBiome(Biome.FOREST);
|
||||
await runToFinalBossEncounter(game, [Species.BIDOOF]);
|
||||
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.CLASSIC);
|
||||
|
||||
expect(game.scene.currentBattle.waveIndex).toBe(FinalWave.Classic);
|
||||
expect(game.scene.arena.biomeType).not.toBe(Biome.END);
|
||||
@ -57,7 +54,7 @@ describe("Final Boss", () => {
|
||||
});
|
||||
|
||||
it("should not have passive enabled on Eternatus", async () => {
|
||||
await runToFinalBossEncounter(game, [Species.BIDOOF]);
|
||||
await game.runToFinalBossEncounter(game, [Species.BIDOOF], GameModes.CLASSIC);
|
||||
|
||||
const eternatus = game.scene.getEnemyPokemon();
|
||||
expect(eternatus?.species.speciesId).toBe(Species.ETERNATUS);
|
||||
@ -66,32 +63,3 @@ describe("Final Boss", () => {
|
||||
|
||||
it.todo("should change form on direct hit down to last boss fragment", () => {});
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper function to run to the final boss encounter as it's a bit tricky due to extra dialogue
|
||||
* @param game - The game manager
|
||||
*/
|
||||
async function runToFinalBossEncounter(game: GameManager, species: Species[]) {
|
||||
console.log("===to final boss encounter===");
|
||||
await game.runToTitle();
|
||||
|
||||
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
|
||||
game.scene.gameMode = getGameMode(GameModes.CLASSIC);
|
||||
const starters = generateStarter(game.scene, species);
|
||||
const selectStarterPhase = new SelectStarterPhase(game.scene);
|
||||
game.scene.pushPhase(new EncounterPhase(game.scene, false));
|
||||
selectStarterPhase.initBattle(starters);
|
||||
});
|
||||
|
||||
game.onNextPrompt("EncounterPhase", Mode.MESSAGE, async () => {
|
||||
// This will skip all entry dialogue (I can't figure out a way to sequentially handle the 8 chained messages via 1 prompt handler)
|
||||
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);
|
||||
console.log("===finished run to final boss encounter===");
|
||||
}
|
||||
|
@ -141,6 +141,38 @@ export default class GameManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to run to the final boss encounter as it's a bit tricky due to extra dialogue
|
||||
* Also handles Major/Minor bosses from endless modes
|
||||
* @param game - The game manager
|
||||
* @param species
|
||||
* @param mode
|
||||
*/
|
||||
async runToFinalBossEncounter(game: GameManager, species: Species[], mode: GameModes) {
|
||||
console.log("===to final boss encounter===");
|
||||
await game.runToTitle();
|
||||
|
||||
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
|
||||
game.scene.gameMode = getGameMode(mode);
|
||||
const starters = generateStarter(game.scene, species);
|
||||
const selectStarterPhase = new SelectStarterPhase(game.scene);
|
||||
game.scene.pushPhase(new EncounterPhase(game.scene, false));
|
||||
selectStarterPhase.initBattle(starters);
|
||||
});
|
||||
|
||||
game.onNextPrompt("EncounterPhase", Mode.MESSAGE, async () => {
|
||||
// This will skip all entry dialogue (I can't figure out a way to sequentially handle the 8 chained messages via 1 prompt handler)
|
||||
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);
|
||||
console.log("===finished run to final boss encounter===");
|
||||
}
|
||||
|
||||
/**
|
||||
* Transitions to the start of a battle.
|
||||
* @param species - Optional array of species to start the battle with.
|
||||
|
@ -89,6 +89,7 @@ export default class GameWrapper {
|
||||
frames: {},
|
||||
});
|
||||
Pokemon.prototype.enableMask = () => null;
|
||||
Pokemon.prototype.updateFusionPalette = () => null;
|
||||
}
|
||||
|
||||
setScene(scene: BattleScene) {
|
||||
@ -128,7 +129,9 @@ export default class GameWrapper {
|
||||
manager: {
|
||||
game: this.game,
|
||||
},
|
||||
destroy: () => null,
|
||||
setVolume: () => null,
|
||||
stop: () => null,
|
||||
stopByKey: () => null,
|
||||
on: (evt, callback) => callback(),
|
||||
key: "",
|
||||
@ -202,6 +205,7 @@ export default class GameWrapper {
|
||||
};
|
||||
const mockTextureManager = new MockTextureManager(this.scene);
|
||||
this.scene.add = mockTextureManager.add;
|
||||
this.scene.textures = mockTextureManager;
|
||||
this.scene.sys.displayList = this.scene.add.displayList;
|
||||
this.scene.sys.updateList = new UpdateList(this.scene);
|
||||
this.scene.systems = this.scene.sys;
|
||||
|
@ -6,8 +6,11 @@ import MockImage from "#test/utils/mocks/mocksContainer/mockImage";
|
||||
import MockText from "#test/utils/mocks/mocksContainer/mockText";
|
||||
import MockPolygon from "#test/utils/mocks/mocksContainer/mockPolygon";
|
||||
import { MockGameObject } from "./mockGameObject";
|
||||
import MockTexture from "#test/utils/mocks/mocksContainer/mockTexture";
|
||||
|
||||
|
||||
/**
|
||||
* Stub class for Phaser.Textures.TextureManager
|
||||
*/
|
||||
export default class MockTextureManager {
|
||||
private textures: Map<string, any>;
|
||||
private scene;
|
||||
@ -54,6 +57,14 @@ export default class MockTextureManager {
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mock texture
|
||||
* @param key
|
||||
*/
|
||||
get(key) {
|
||||
return new MockTexture(this, key, null);
|
||||
}
|
||||
|
||||
rectangle(x, y, width, height, fillColor) {
|
||||
const rectangle = new MockRectangle(this, x, y, width, height, fillColor);
|
||||
this.list.push(rectangle);
|
||||
|
42
src/test/utils/mocks/mocksContainer/mockTexture.ts
Normal file
42
src/test/utils/mocks/mocksContainer/mockTexture.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { MockGameObject } from "../mockGameObject";
|
||||
import MockTextureManager from "#test/utils/mocks/mockTextureManager";
|
||||
|
||||
|
||||
/**
|
||||
* Stub for Phaser.Textures.Texture object
|
||||
* Just mocks the function calls and data required for use in tests
|
||||
*/
|
||||
export default class MockTexture implements MockGameObject {
|
||||
public manager: MockTextureManager;
|
||||
public key: string;
|
||||
public source;
|
||||
public frames: object;
|
||||
public firstFrame: string;
|
||||
|
||||
constructor(manager, key: string, source) {
|
||||
this.manager = manager;
|
||||
this.key = key;
|
||||
this.source = source;
|
||||
|
||||
const mockFrame = {
|
||||
width: 100,
|
||||
height: 100,
|
||||
cutX: 0,
|
||||
cutY: 0
|
||||
};
|
||||
this.frames = {
|
||||
firstFrame: mockFrame,
|
||||
0: mockFrame,
|
||||
1: mockFrame,
|
||||
2: mockFrame,
|
||||
3: mockFrame,
|
||||
4: mockFrame
|
||||
};
|
||||
this.firstFrame = "firstFrame";
|
||||
}
|
||||
|
||||
/** Mocks the function call that gets an HTMLImageElement, see {@link Pokemon.updateFusionPalette} */
|
||||
getSourceImage() {
|
||||
return null;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user