[Bug] Fix reloads erasing weather on first wave of biome

This commit is contained in:
Michael Li 2024-09-06 23:00:41 -04:00
parent 7288350d45
commit a7481b8c8b
7 changed files with 86 additions and 23 deletions

View File

@ -154,6 +154,7 @@ export class EncounterPhase extends BattlePhase {
this.scene.ui.setMode(Mode.MESSAGE).then(() => {
if (!this.loaded) {
this.trySetWeatherIfNewBiome(); // Set weather before session gets saved
//@ts-ignore
this.scene.gameData.saveAll(this.scene, true, battle.waveIndex % 10 === 1 || this.scene.lastSavePlayTime >= 300).then(success => { // TODO: get rid of ts-ignore
this.scene.disableMenu = false;
@ -188,10 +189,6 @@ export class EncounterPhase extends BattlePhase {
}
}
if (!this.loaded) {
this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena), false);
}
const enemyField = this.scene.getEnemyField();
this.scene.tweens.add({
targets: [this.scene.arenaEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.arenaPlayer, this.scene.trainer].flat(),
@ -387,4 +384,18 @@ export class EncounterPhase extends BattlePhase {
}
return false;
}
/**
* Set biome weather if and only if this encounter is the start of a new biome.
*
* By using function overrides, this should happen if and only if this phase
* is exactly a NewBiomeEncounterPhase or an EncounterPhase (to account for
* Wave 1 of a Daily Run), but NOT NextEncounterPhase (which starts the next
* wave in the same biome).
*/
trySetWeatherIfNewBiome(): void {
if (!this.loaded) {
this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena), false);
}
}
}

View File

@ -17,8 +17,6 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase {
}
}
this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena), false);
for (const pokemon of this.scene.getParty().filter(p => p.isOnField())) {
applyAbAttrs(PostBiomeChangeAbAttr, pokemon, null);
}
@ -35,4 +33,11 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase {
}
});
}
/**
* Set biome weather.
*/
trySetWeatherIfNewBiome(): void {
this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena), false);
}
}

View File

@ -43,4 +43,10 @@ export class NextEncounterPhase extends EncounterPhase {
}
});
}
/**
* Do nothing (since this is simply the next wave in the same biome).
*/
trySetWeatherIfNewBiome(): void {
}
}

View File

@ -24,8 +24,9 @@ import { Moves } from "#enums/moves";
import { PlayerGender } from "#enums/player-gender";
import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { SPLASH_ONLY } from "../utils/testUtils";
import { Biome } from "#app/enums/biome";
describe("Test Battle Phase", () => {
let phaserGame: Phaser.Game;
@ -291,7 +292,8 @@ describe("Test Battle Phase", () => {
expect(game.scene.currentBattle.turn).toBeGreaterThan(turn);
}, 20000);
it("to next wave with pokemon killed, single", async () => {
// Made redundant by the "same biome" test, which is copied from this test, should this be removed?
it.skip("to next wave with pokemon killed, single", async () => {
const moveToUse = Moves.SPLASH;
game.override.battleType("single");
game.override.starterSpecies(Species.MEWTWO);
@ -310,6 +312,30 @@ describe("Test Battle Phase", () => {
expect(game.scene.currentBattle.waveIndex).toBeGreaterThan(waveIndex);
}, 20000);
it("does not set new weather if staying in same biome", async () => {
const moveToUse = Moves.SPLASH;
game.override
.battleType("single")
.starterSpecies(Species.MEWTWO)
.enemySpecies(Species.RATTATA)
.enemyAbility(Abilities.HYDRATION)
.ability(Abilities.ZEN_MODE)
.startingLevel(2000)
.startingWave(3)
.startingBiome(Biome.LAKE)
.moveset([moveToUse]);
game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
await game.classicMode.startBattle();
const waveIndex = game.scene.currentBattle.waveIndex;
game.move.select(moveToUse);
vi.spyOn(game.scene.arena, "trySetWeather");
await game.doKillOpponents();
await game.toNextWave();
expect(game.scene.arena.trySetWeather).not.toHaveBeenCalled();
expect(game.scene.currentBattle.waveIndex).toBeGreaterThan(waveIndex);
}, 20000);
it("does not force switch if active pokemon faints at same time as enemy mon and is revived in post-battle", async () => {
const moveToUse = Moves.TAKE_DOWN;
game.override

View File

@ -36,19 +36,19 @@ describe("Reload", () => {
expect(preReloadRngState).toBe(postReloadRngState);
}, 20000);
it("should not have RNG inconsistencies after a biome switch", async () => {
it("should not have RNG or weather inconsistencies after a biome switch", async () => {
game.override
.startingWave(10)
.startingBiome(Biome.CAVE) // Will lead to biomes with randomly generated weather
.startingBiome(Biome.ICE_CAVE) // Will lead to Snowy Forest with randomly generated weather
.battleType("single")
.startingLevel(100)
.enemyLevel(1000)
.startingLevel(100) // Avoid levelling up
.enemyLevel(1000) // Avoid opponent dying before game.doKillOpponents()
.disableTrainerWaves()
.moveset([Moves.KOWTOW_CLEAVE])
.enemyMoveset(SPLASH_ONLY);
await game.dailyMode.startBattle();
await game.classicMode.startBattle(); // Apparently daily mode would override the biome
// Transition from Daily Run Wave 10 to Wave 11 in order to trigger biome switch
// Transition from Wave 10 to Wave 11 in order to trigger biome switch
game.move.select(Moves.KOWTOW_CLEAVE);
await game.phaseInterceptor.to("DamagePhase");
await game.doKillOpponents();
@ -56,12 +56,15 @@ describe("Reload", () => {
expect(game.phaseInterceptor.log).toContain("NewBiomeEncounterPhase");
const preReloadRngState = Phaser.Math.RND.state();
const preReloadWeather = game.scene.arena.weather;
await game.reload.reloadSession();
const postReloadRngState = Phaser.Math.RND.state();
const postReloadWeather = game.scene.arena.weather;
expect(preReloadRngState).toBe(postReloadRngState);
expect(preReloadWeather).toStrictEqual(postReloadWeather);
}, 20000);
it("should not have RNG inconsistencies at a Daily run wild Pokemon fight", async () => {

View File

@ -32,7 +32,6 @@ import TargetSelectUiHandler from "#app/ui/target-select-ui-handler";
import { Mode } from "#app/ui/ui";
import { Button } from "#enums/buttons";
import { ExpNotification } from "#enums/exp-notification";
import { GameDataType } from "#enums/game-data-type";
import { PlayerGender } from "#enums/player-gender";
import { Species } from "#enums/species";
import { generateStarter, waitUntil } from "#test/utils/gameManagerUtils";
@ -330,13 +329,11 @@ export default class GameManager {
* @returns A promise that resolves with the exported save data.
*/
exportSaveToTest(): Promise<string> {
const saveKey = "x0i2O7WRiANTqPmZ";
return new Promise(async (resolve) => {
await this.scene.gameData.saveAll(this.scene, true, true, true, true);
this.scene.reset(true);
await waitUntil(() => this.scene.ui?.getMode() === Mode.TITLE);
await this.scene.gameData.tryExportData(GameDataType.SESSION, 0);
await waitUntil(() => localStorage.hasOwnProperty("toExport"));
return resolve(localStorage.getItem("toExport")!); // TODO: is this bang correct?;
const sessionSaveData = this.scene.gameData.getSessionSaveData(this.scene);
const encryptedSaveData = AES.encrypt(JSON.stringify(sessionSaveData), saveKey).toString();
resolve(encryptedSaveData);
});
}

View File

@ -5,11 +5,27 @@ import { vi } from "vitest";
import { BattleStyle } from "#app/enums/battle-style";
import { CommandPhase } from "#app/phases/command-phase";
import { TurnInitPhase } from "#app/phases/turn-init-phase";
import { SessionSaveData } from "#app/system/game-data";
import GameManager from "../gameManager";
/**
* Helper to allow reloading sessions in unit tests.
*/
export class ReloadHelper extends GameManagerHelper {
sessionData: SessionSaveData;
constructor(game: GameManager) {
super(game);
// Whenever the game saves the session, save it to the reloadHelper instead
vi.spyOn(game.scene.gameData, "saveAll").mockImplementation((scene) => {
return new Promise<boolean>((resolve, reject) => {
this.sessionData = scene.gameData.getSessionSaveData(scene);
resolve(true);
});
});
}
/**
* Simulate reloading the session from the title screen, until reaching the
* beginning of the first turn (equivalent to running `startBattle()`) for
@ -17,7 +33,6 @@ export class ReloadHelper extends GameManagerHelper {
*/
async reloadSession() : Promise<void> {
const scene = this.game.scene;
const sessionData = scene.gameData.getSessionSaveData(scene);
const titlePhase = new TitlePhase(scene);
scene.clearPhaseQueue();
@ -25,7 +40,7 @@ export class ReloadHelper extends GameManagerHelper {
// Set the last saved session to the desired session data
vi.spyOn(scene.gameData, "getSession").mockReturnValue(
new Promise((resolve, reject) => {
resolve(sessionData);
resolve(this.sessionData);
})
);
scene.unshiftPhase(titlePhase);