diff --git a/src/field/arena.ts b/src/field/arena.ts index 2d20abeedd1..ddb3499b3ae 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -384,6 +384,10 @@ export class Arena { return weatherMultiplier * terrainMultiplier; } + /** + * Gets the denominator for the chance for a trainer spawn + * @returns n where 1/n is the chance of a trainer battle + */ getTrainerChance(): integer { switch (this.biomeType) { case Biome.METROPOLIS: diff --git a/src/game-mode.ts b/src/game-mode.ts index 0a472e223e3..dd22e69d719 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -107,22 +107,37 @@ export class GameMode implements GameModeConfig { } } + /** + * Determines whether or not to generate a trainer + * @param waveIndex the current floor the player is on (trainer sprites fail to generate on X1 floors) + * @param arena the arena that contains the scene and functions + * @returns true if a trainer should be generated, false otherwise + */ isWaveTrainer(waveIndex: integer, arena: Arena): boolean { + /** + * Daily spawns trainers on floors 5, 15, 20, 25, 30, 35, 40, and 45 + */ if (this.isDaily) { return waveIndex % 10 === 5 || (!(waveIndex % 10) && waveIndex > 10 && !this.isWaveFinal(waveIndex)); } if ((waveIndex % 30) === (arena.scene.offsetGym ? 0 : 20) && !this.isWaveFinal(waveIndex)) { return true; } else if (waveIndex % 10 !== 1 && waveIndex % 10) { + /** + * Do not check X1 floors since there's a bug that stops trainer sprites from appearing + * after a X0 full party heal + */ + const trainerChance = arena.getTrainerChance(); let allowTrainerBattle = true; if (trainerChance) { const waveBase = Math.floor(waveIndex / 10) * 10; + // Stop generic trainers from spawning in within 3 waves of a trainer battle for (let w = Math.max(waveIndex - 3, waveBase + 2); w <= Math.min(waveIndex + 3, waveBase + 9); w++) { if (w === waveIndex) { continue; } - if ((w % 30) === (arena.scene.offsetGym ? 0 : 20) || this.isFixedBattle(waveIndex)) { + if ((w % 30) === (arena.scene.offsetGym ? 0 : 20) || this.isFixedBattle(w)) { allowTrainerBattle = false; break; } else if (w < waveIndex) { @@ -138,7 +153,7 @@ export class GameMode implements GameModeConfig { } } } - return allowTrainerBattle && trainerChance && !Utils.randSeedInt(trainerChance); + return Boolean(allowTrainerBattle && trainerChance && !Utils.randSeedInt(trainerChance)); } return false; } diff --git a/src/test/game-mode.test.ts b/src/test/game-mode.test.ts new file mode 100644 index 00000000000..04376c20361 --- /dev/null +++ b/src/test/game-mode.test.ts @@ -0,0 +1,52 @@ +import { GameMode, GameModes, getGameMode } from "#app/game-mode.js"; +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from "vitest"; +import GameManager from "./utils/gameManager"; +import * as Utils from "../utils"; +describe("game-mode", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + afterEach(() => { + game.phaseInterceptor.restoreOg(); + vi.resetAllMocks(); + }); + beforeEach(() => { + game = new GameManager(phaserGame); + }); + describe("classic", () => { + let classicGameMode: GameMode; + beforeEach(() => { + classicGameMode = getGameMode(GameModes.CLASSIC); + }); + it("does NOT spawn trainers within 3 waves of fixed battle", () => { + const { arena } = game.scene; + /** set wave 16 to be a fixed trainer fight meaning wave 13-19 don't allow trainer spawns */ + vi.spyOn(classicGameMode, "isFixedBattle").mockImplementation( + (n: number) => (n === 16 ? true : false) + ); + vi.spyOn(arena, "getTrainerChance").mockReturnValue(1); + vi.spyOn(Utils, "randSeedInt").mockReturnValue(0); + expect(classicGameMode.isWaveTrainer(11, arena)).toBeFalsy(); + expect(classicGameMode.isWaveTrainer(12, arena)).toBeTruthy(); + expect(classicGameMode.isWaveTrainer(13, arena)).toBeFalsy(); + expect(classicGameMode.isWaveTrainer(14, arena)).toBeFalsy(); + expect(classicGameMode.isWaveTrainer(15, arena)).toBeFalsy(); + // Wave 16 is a fixed trainer battle + expect(classicGameMode.isWaveTrainer(17, arena)).toBeFalsy(); + expect(classicGameMode.isWaveTrainer(18, arena)).toBeFalsy(); + expect(classicGameMode.isWaveTrainer(19, arena)).toBeFalsy(); + }); + }); +});