mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-04-21 17:19:15 +01: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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final boss does not have passive
|
// Classic Final boss and Endless Minor/Major bosses do not have passive
|
||||||
if (this.scene.currentBattle?.battleSpec === BattleSpec.FINAL_BOSS && this instanceof EnemyPokemon) {
|
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;
|
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 { Biome } from "#app/enums/biome.js";
|
||||||
import { Species } from "#app/enums/species.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 { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
import GameManager from "./utils/gameManager";
|
import GameManager from "./utils/gameManager";
|
||||||
import { generateStarter } from "./utils/gameManagerUtils";
|
import { GameModes } from "#app/game-mode";
|
||||||
|
|
||||||
const FinalWave = {
|
const FinalWave = {
|
||||||
Classic: 200,
|
Classic: 200,
|
||||||
@ -31,7 +28,7 @@ describe("Final Boss", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should spawn Eternatus on wave 200 in END biome", async () => {
|
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.currentBattle.waveIndex).toBe(FinalWave.Classic);
|
||||||
expect(game.scene.arena.biomeType).toBe(Biome.END);
|
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 () => {
|
it("should NOT spawn Eternatus before wave 200 in END biome", async () => {
|
||||||
game.override.startingWave(FinalWave.Classic - 1);
|
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.currentBattle.waveIndex).not.toBe(FinalWave.Classic);
|
||||||
expect(game.scene.arena.biomeType).toBe(Biome.END);
|
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 () => {
|
it("should NOT spawn Eternatus outside of END biome", async () => {
|
||||||
game.override.startingBiome(Biome.FOREST);
|
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.currentBattle.waveIndex).toBe(FinalWave.Classic);
|
||||||
expect(game.scene.arena.biomeType).not.toBe(Biome.END);
|
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 () => {
|
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();
|
const eternatus = game.scene.getEnemyPokemon();
|
||||||
expect(eternatus?.species.speciesId).toBe(Species.ETERNATUS);
|
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", () => {});
|
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.
|
* Transitions to the start of a battle.
|
||||||
* @param species - Optional array of species to start the battle with.
|
* @param species - Optional array of species to start the battle with.
|
||||||
|
@ -89,6 +89,7 @@ export default class GameWrapper {
|
|||||||
frames: {},
|
frames: {},
|
||||||
});
|
});
|
||||||
Pokemon.prototype.enableMask = () => null;
|
Pokemon.prototype.enableMask = () => null;
|
||||||
|
Pokemon.prototype.updateFusionPalette = () => null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setScene(scene: BattleScene) {
|
setScene(scene: BattleScene) {
|
||||||
@ -128,7 +129,9 @@ export default class GameWrapper {
|
|||||||
manager: {
|
manager: {
|
||||||
game: this.game,
|
game: this.game,
|
||||||
},
|
},
|
||||||
|
destroy: () => null,
|
||||||
setVolume: () => null,
|
setVolume: () => null,
|
||||||
|
stop: () => null,
|
||||||
stopByKey: () => null,
|
stopByKey: () => null,
|
||||||
on: (evt, callback) => callback(),
|
on: (evt, callback) => callback(),
|
||||||
key: "",
|
key: "",
|
||||||
@ -202,6 +205,7 @@ export default class GameWrapper {
|
|||||||
};
|
};
|
||||||
const mockTextureManager = new MockTextureManager(this.scene);
|
const mockTextureManager = new MockTextureManager(this.scene);
|
||||||
this.scene.add = mockTextureManager.add;
|
this.scene.add = mockTextureManager.add;
|
||||||
|
this.scene.textures = mockTextureManager;
|
||||||
this.scene.sys.displayList = this.scene.add.displayList;
|
this.scene.sys.displayList = this.scene.add.displayList;
|
||||||
this.scene.sys.updateList = new UpdateList(this.scene);
|
this.scene.sys.updateList = new UpdateList(this.scene);
|
||||||
this.scene.systems = this.scene.sys;
|
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 MockText from "#test/utils/mocks/mocksContainer/mockText";
|
||||||
import MockPolygon from "#test/utils/mocks/mocksContainer/mockPolygon";
|
import MockPolygon from "#test/utils/mocks/mocksContainer/mockPolygon";
|
||||||
import { MockGameObject } from "./mockGameObject";
|
import { MockGameObject } from "./mockGameObject";
|
||||||
|
import MockTexture from "#test/utils/mocks/mocksContainer/mockTexture";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stub class for Phaser.Textures.TextureManager
|
||||||
|
*/
|
||||||
export default class MockTextureManager {
|
export default class MockTextureManager {
|
||||||
private textures: Map<string, any>;
|
private textures: Map<string, any>;
|
||||||
private scene;
|
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) {
|
rectangle(x, y, width, height, fillColor) {
|
||||||
const rectangle = new MockRectangle(this, x, y, width, height, fillColor);
|
const rectangle = new MockRectangle(this, x, y, width, height, fillColor);
|
||||||
this.list.push(rectangle);
|
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