[Bug] Fix reloads erasing weather on first wave of biome (#4078)
* [Bug] Fix reloads erasing weather on first wave of biome * Minor revisions * Minor revisions 2 * Update test --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
parent
128df1b6d2
commit
009fd3fc5c
|
@ -216,8 +216,8 @@ export class EncounterPhase extends BattlePhase {
|
||||||
|
|
||||||
this.scene.ui.setMode(Mode.MESSAGE).then(() => {
|
this.scene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||||
if (!this.loaded) {
|
if (!this.loaded) {
|
||||||
//@ts-ignore
|
this.trySetWeatherIfNewBiome(); // Set weather before session gets saved
|
||||||
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.gameData.saveAll(this.scene, true, battle.waveIndex % 10 === 1 || (this.scene.lastSavePlayTime ?? 0) >= 300).then(success => {
|
||||||
this.scene.disableMenu = false;
|
this.scene.disableMenu = false;
|
||||||
if (!success) {
|
if (!success) {
|
||||||
return this.scene.reset(true);
|
return this.scene.reset(true);
|
||||||
|
@ -250,10 +250,6 @@ export class EncounterPhase extends BattlePhase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.loaded) {
|
|
||||||
this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
const enemyField = this.scene.getEnemyField();
|
const enemyField = this.scene.getEnemyField();
|
||||||
this.scene.tweens.add({
|
this.scene.tweens.add({
|
||||||
targets: [this.scene.arenaEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.arenaPlayer, this.scene.trainer].flat(),
|
targets: [this.scene.arenaEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.arenaPlayer, this.scene.trainer].flat(),
|
||||||
|
@ -519,4 +515,18 @@ export class EncounterPhase extends BattlePhase {
|
||||||
}
|
}
|
||||||
return false;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())) {
|
for (const pokemon of this.scene.getParty().filter(p => p.isOnField())) {
|
||||||
applyAbAttrs(PostBiomeChangeAbAttr, pokemon, null);
|
applyAbAttrs(PostBiomeChangeAbAttr, pokemon, null);
|
||||||
}
|
}
|
||||||
|
@ -41,4 +39,11 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set biome weather.
|
||||||
|
*/
|
||||||
|
trySetWeatherIfNewBiome(): void {
|
||||||
|
this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena), false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,4 +67,10 @@ export class NextEncounterPhase extends EncounterPhase {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do nothing (since this is simply the next wave in the same biome).
|
||||||
|
*/
|
||||||
|
trySetWeatherIfNewBiome(): void {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,8 @@ import { Moves } from "#enums/moves";
|
||||||
import { PlayerGender } from "#enums/player-gender";
|
import { PlayerGender } from "#enums/player-gender";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { Biome } from "#app/enums/biome";
|
||||||
|
|
||||||
describe("Test Battle Phase", () => {
|
describe("Test Battle Phase", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
|
@ -290,22 +291,27 @@ describe("Test Battle Phase", () => {
|
||||||
expect(game.scene.currentBattle.turn).toBeGreaterThan(turn);
|
expect(game.scene.currentBattle.turn).toBeGreaterThan(turn);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
it("to next wave with pokemon killed, single", async () => {
|
it("does not set new weather if staying in same biome", async () => {
|
||||||
const moveToUse = Moves.SPLASH;
|
const moveToUse = Moves.SPLASH;
|
||||||
game.override.battleType("single");
|
game.override
|
||||||
game.override.starterSpecies(Species.MEWTWO);
|
.battleType("single")
|
||||||
game.override.enemySpecies(Species.RATTATA);
|
.starterSpecies(Species.MEWTWO)
|
||||||
game.override.enemyAbility(Abilities.HYDRATION);
|
.enemySpecies(Species.RATTATA)
|
||||||
game.override.ability(Abilities.ZEN_MODE);
|
.enemyAbility(Abilities.HYDRATION)
|
||||||
game.override.startingLevel(2000);
|
.ability(Abilities.ZEN_MODE)
|
||||||
game.override.startingWave(3);
|
.startingLevel(2000)
|
||||||
game.override.moveset([moveToUse]);
|
.startingWave(3)
|
||||||
|
.startingBiome(Biome.LAKE)
|
||||||
|
.moveset([moveToUse]);
|
||||||
game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
|
game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
|
||||||
await game.startBattle();
|
await game.classicMode.startBattle();
|
||||||
const waveIndex = game.scene.currentBattle.waveIndex;
|
const waveIndex = game.scene.currentBattle.waveIndex;
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
|
|
||||||
|
vi.spyOn(game.scene.arena, "trySetWeather");
|
||||||
await game.doKillOpponents();
|
await game.doKillOpponents();
|
||||||
await game.toNextWave();
|
await game.toNextWave();
|
||||||
|
expect(game.scene.arena.trySetWeather).not.toHaveBeenCalled();
|
||||||
expect(game.scene.currentBattle.waveIndex).toBeGreaterThan(waveIndex);
|
expect(game.scene.currentBattle.waveIndex).toBeGreaterThan(waveIndex);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
|
|
|
@ -38,16 +38,15 @@ describe("Reload", () => {
|
||||||
it("should not have RNG inconsistencies after a biome switch", async () => {
|
it("should not have RNG inconsistencies after a biome switch", async () => {
|
||||||
game.override
|
game.override
|
||||||
.startingWave(10)
|
.startingWave(10)
|
||||||
.startingBiome(Biome.CAVE) // Will lead to biomes with randomly generated weather
|
|
||||||
.battleType("single")
|
.battleType("single")
|
||||||
.startingLevel(100)
|
.startingLevel(100) // Avoid levelling up
|
||||||
.enemyLevel(1000)
|
.enemyLevel(1000) // Avoid opponent dying before game.doKillOpponents()
|
||||||
.disableTrainerWaves()
|
.disableTrainerWaves()
|
||||||
.moveset([Moves.KOWTOW_CLEAVE])
|
.moveset([Moves.KOWTOW_CLEAVE])
|
||||||
.enemyMoveset(Moves.SPLASH);
|
.enemyMoveset(Moves.SPLASH);
|
||||||
await game.dailyMode.startBattle();
|
await game.dailyMode.startBattle();
|
||||||
|
|
||||||
// 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);
|
game.move.select(Moves.KOWTOW_CLEAVE);
|
||||||
await game.phaseInterceptor.to("DamagePhase");
|
await game.phaseInterceptor.to("DamagePhase");
|
||||||
await game.doKillOpponents();
|
await game.doKillOpponents();
|
||||||
|
@ -63,6 +62,34 @@ describe("Reload", () => {
|
||||||
expect(preReloadRngState).toBe(postReloadRngState);
|
expect(preReloadRngState).toBe(postReloadRngState);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
|
it("should not have weather inconsistencies after a biome switch", async () => {
|
||||||
|
game.override
|
||||||
|
.startingWave(10)
|
||||||
|
.startingBiome(Biome.ICE_CAVE) // Will lead to Snowy Forest with randomly generated weather
|
||||||
|
.battleType("single")
|
||||||
|
.startingLevel(100) // Avoid levelling up
|
||||||
|
.enemyLevel(1000) // Avoid opponent dying before game.doKillOpponents()
|
||||||
|
.disableTrainerWaves()
|
||||||
|
.moveset([Moves.KOWTOW_CLEAVE])
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
await game.classicMode.startBattle(); // Apparently daily mode would override the biome
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
await game.toNextWave();
|
||||||
|
expect(game.phaseInterceptor.log).toContain("NewBiomeEncounterPhase");
|
||||||
|
|
||||||
|
const preReloadWeather = game.scene.arena.weather;
|
||||||
|
|
||||||
|
await game.reload.reloadSession();
|
||||||
|
|
||||||
|
const postReloadWeather = game.scene.arena.weather;
|
||||||
|
|
||||||
|
expect(postReloadWeather).toStrictEqual(preReloadWeather);
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
it("should not have RNG inconsistencies at a Daily run wild Pokemon fight", async () => {
|
it("should not have RNG inconsistencies at a Daily run wild Pokemon fight", async () => {
|
||||||
await game.dailyMode.startBattle();
|
await game.dailyMode.startBattle();
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,6 @@ import TargetSelectUiHandler from "#app/ui/target-select-ui-handler";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import { Button } from "#enums/buttons";
|
import { Button } from "#enums/buttons";
|
||||||
import { ExpNotification } from "#enums/exp-notification";
|
import { ExpNotification } from "#enums/exp-notification";
|
||||||
import { GameDataType } from "#enums/game-data-type";
|
|
||||||
import { PlayerGender } from "#enums/player-gender";
|
import { PlayerGender } from "#enums/player-gender";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import { generateStarter, waitUntil } from "#test/utils/gameManagerUtils";
|
import { generateStarter, waitUntil } from "#test/utils/gameManagerUtils";
|
||||||
|
@ -371,13 +370,11 @@ export default class GameManager {
|
||||||
* @returns A promise that resolves with the exported save data.
|
* @returns A promise that resolves with the exported save data.
|
||||||
*/
|
*/
|
||||||
exportSaveToTest(): Promise<string> {
|
exportSaveToTest(): Promise<string> {
|
||||||
|
const saveKey = "x0i2O7WRiANTqPmZ";
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
await this.scene.gameData.saveAll(this.scene, true, true, true, true);
|
const sessionSaveData = this.scene.gameData.getSessionSaveData(this.scene);
|
||||||
this.scene.reset(true);
|
const encryptedSaveData = AES.encrypt(JSON.stringify(sessionSaveData), saveKey).toString();
|
||||||
await waitUntil(() => this.scene.ui?.getMode() === Mode.TITLE);
|
resolve(encryptedSaveData);
|
||||||
await this.scene.gameData.tryExportData(GameDataType.SESSION, 0);
|
|
||||||
await waitUntil(() => localStorage.hasOwnProperty("toExport"));
|
|
||||||
return resolve(localStorage.getItem("toExport")!); // TODO: is this bang correct?;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,27 @@ import { vi } from "vitest";
|
||||||
import { BattleStyle } from "#app/enums/battle-style";
|
import { BattleStyle } from "#app/enums/battle-style";
|
||||||
import { CommandPhase } from "#app/phases/command-phase";
|
import { CommandPhase } from "#app/phases/command-phase";
|
||||||
import { TurnInitPhase } from "#app/phases/turn-init-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.
|
* Helper to allow reloading sessions in unit tests.
|
||||||
*/
|
*/
|
||||||
export class ReloadHelper extends GameManagerHelper {
|
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
|
* Simulate reloading the session from the title screen, until reaching the
|
||||||
* beginning of the first turn (equivalent to running `startBattle()`) for
|
* beginning of the first turn (equivalent to running `startBattle()`) for
|
||||||
|
@ -17,7 +33,6 @@ export class ReloadHelper extends GameManagerHelper {
|
||||||
*/
|
*/
|
||||||
async reloadSession() : Promise<void> {
|
async reloadSession() : Promise<void> {
|
||||||
const scene = this.game.scene;
|
const scene = this.game.scene;
|
||||||
const sessionData = scene.gameData.getSessionSaveData(scene);
|
|
||||||
const titlePhase = new TitlePhase(scene);
|
const titlePhase = new TitlePhase(scene);
|
||||||
|
|
||||||
scene.clearPhaseQueue();
|
scene.clearPhaseQueue();
|
||||||
|
@ -25,7 +40,7 @@ export class ReloadHelper extends GameManagerHelper {
|
||||||
// Set the last saved session to the desired session data
|
// Set the last saved session to the desired session data
|
||||||
vi.spyOn(scene.gameData, "getSession").mockReturnValue(
|
vi.spyOn(scene.gameData, "getSession").mockReturnValue(
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
resolve(sessionData);
|
resolve(this.sessionData);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
scene.unshiftPhase(titlePhase);
|
scene.unshiftPhase(titlePhase);
|
||||||
|
|
Loading…
Reference in New Issue