Fixed migrate script, re-added deprecated attributes out of necessity

This commit is contained in:
Bertie690 2025-04-22 09:05:24 -04:00
parent 9e65d0a07b
commit 68adbcbe83
7 changed files with 69 additions and 33 deletions

View File

@ -7,17 +7,21 @@ import type { Nature } from "#enums/nature";
* Includes abilities, nature, changed types, etc.
*/
export class CustomPokemonData {
public spriteScale: number;
public spriteScale = 1;
public ability: Abilities | -1;
public passive: Abilities | -1;
public nature: Nature | -1;
public types: PokemonType[];
/** Deprecated but needed for session save migration */
// TODO: Remove this once pre-session migration is implemented
public hitsRecCount: number | null = null;
constructor(data?: CustomPokemonData | Partial<CustomPokemonData>) {
this.spriteScale = data?.spriteScale ?? -1;
this.spriteScale = data?.spriteScale ?? 1;
this.ability = data?.ability ?? -1;
this.passive = data?.passive ?? -1;
this.nature = data?.nature ?? -1;
this.types = data?.types ?? [];
this.hitsRecCount = data?.hitsRecCount ?? null;
}
}

View File

@ -1183,12 +1183,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
formKey === "ruchbah-starmobile" ||
formKey === "caph-starmobile"
) {
// G-Max and starmobiles have flat 1.5x scale
return 1.5;
}
if (this.customPokemonData.spriteScale > 0) {
return this.customPokemonData.spriteScale;
}
return 1;
return this.customPokemonData.spriteScale;
}
/** Resets the pokemon's field sprite properties, including position, alpha, and scale */

View File

@ -1194,13 +1194,18 @@ export class GameData {
}
}
// load modifier data
if (globalScene.modifiers.length) {
console.warn("Existing modifiers not cleared on session load, deleting...");
globalScene.modifiers = [];
}
for (const modifierData of sessionData.modifiers) {
const modifier = modifierData.toModifier(Modifier[modifierData.className]);
if (modifier) {
globalScene.addModifier(modifier, true);
}
}
globalScene.updateModifiers(true);
for (const enemyModifierData of sessionData.enemyModifiers) {
@ -1338,12 +1343,12 @@ export class GameData {
}
parseSessionData(dataStr: string): SessionSaveData {
// TODO: Add add `null`/`undefined` to the corresponding type signatures for this
// TODO: Add `null`/`undefined` to the corresponding type signatures for this
// (or prevent them from being null)
// If the value is able to *not exist*, it should say so in the code
const sessionData = JSON.parse(dataStr, (k: string, v: any) => {
// TODO: Move this into migrate script
// TODO: Add pre-parse migrate scripts
switch (k) {
case "party":
case "enemyParty": {
@ -1452,7 +1457,7 @@ export class GameData {
encrypt(JSON.stringify(sessionData), bypassLogin),
);
console.debug("Session data saved");
console.debug("Session data saved!");
if (!bypassLogin && sync) {
pokerogueApi.savedata.updateAll(request).then(error => {

View File

@ -69,6 +69,12 @@ export default class PokemonData {
public customPokemonData: CustomPokemonData;
public fusionCustomPokemonData: CustomPokemonData;
// Deprecated attributes, needed for now to allow SessionData migration (see PR#4619 comments)
// TODO: Remove these once pre-session migration is implemented
public natureOverride: Nature | -1;
public mysteryEncounterPokemonData: CustomPokemonData | null;
public fusionMysteryEncounterPokemonData: CustomPokemonData | null;
/**
* Construct a new {@linkcode PokemonData} instance out of a {@linkcode Pokemon}
* or JSON representation thereof.
@ -114,6 +120,15 @@ export default class PokemonData {
this.isTerastallized = !!source.isTerastallized;
this.stellarTypesBoosted = source.stellarTypesBoosted ?? [];
// Deprecated, but needed for session data migration
this.natureOverride = source.natureOverride;
this.mysteryEncounterPokemonData = source.mysteryEncounterPokemonData
? new CustomPokemonData(source.mysteryEncounterPokemonData)
: null;
this.fusionMysteryEncounterPokemonData = source.fusionMysteryEncounterPokemonData
? new CustomPokemonData(source.fusionMysteryEncounterPokemonData)
: null;
this.fusionSpecies = sourcePokemon?.fusionSpecies?.speciesId ?? source.fusionSpecies;
this.fusionFormIndex = source.fusionFormIndex;
this.fusionAbilityIndex = source.fusionAbilityIndex;

View File

@ -27,14 +27,17 @@ const migratePartyData: SessionSaveMigrator = {
// only edit summondata moveset if exists
pkmnData.summonData.moveset &&= pkmnData.summonData.moveset.filter(m => !!m);
if (
pkmnData.customPokemonData &&
"hitsRecCount" in pkmnData.customPokemonData &&
typeof pkmnData.customPokemonData["hitsRecCount"] === "number"
) {
// transfer old hit count stat to battleData.
// No need to reset it as new Pokemon
pkmnData.battleData.hitCount = pkmnData.customPokemonData["hitsRecCount"];
if (pkmnData.customPokemonData) {
// revert all "-1" sprite scales to a minimum value of 1
pkmnData.customPokemonData.spriteScale = Math.max(1, pkmnData.customPokemonData.spriteScale);
if (
"hitsRecCount" in pkmnData.customPokemonData &&
typeof pkmnData.customPokemonData["hitsRecCount"] === "number"
) {
// transfer old hit count stat to battleData.
pkmnData.battleData.hitCount = pkmnData.customPokemonData["hitsRecCount"];
pkmnData.customPokemonData["hitsRecCount"] = null;
}
}
return pkmnData;
};

View File

@ -82,14 +82,15 @@ describe("Abilities - Harvest", () => {
.weather(WeatherType.NONE); // clear weather so we can control when harvest rolls succeed
await game.classicMode.startBattle([Species.MILOTIC]);
const player = game.scene.getPlayerPokemon();
const milotic = game.scene.getPlayerPokemon()!;
expect(milotic).toBeDefined();
// Chug a few berries without harvest (should get tracked)
game.move.select(Moves.SPLASH);
await game.forceEnemyMove(Moves.NUZZLE);
await game.toNextTurn();
expect(player?.battleData.berriesEaten).toEqual(expect.arrayContaining([BerryType.ENIGMA, BerryType.LUM]));
expect(milotic.battleData.berriesEaten).toEqual(expect.arrayContaining([BerryType.ENIGMA, BerryType.LUM]));
expect(getPlayerBerries()).toHaveLength(2);
// Give ourselves harvest and disable enemy neut gas,
@ -97,10 +98,12 @@ describe("Abilities - Harvest", () => {
game.override.ability(Abilities.HARVEST);
game.move.select(Moves.GASTRO_ACID);
await game.forceEnemyMove(Moves.NUZZLE);
await game.phaseInterceptor.to("TurnEndPhase", false);
vi.spyOn(Phaser.Math.RND, "realInRange").mockReturnValue(0);
await game.phaseInterceptor.to("TurnEndPhase");
expect(player?.battleData.berriesEaten).toEqual(
expect(milotic.battleData.berriesEaten).toEqual(
expect.arrayContaining([BerryType.ENIGMA, BerryType.LUM, BerryType.ENIGMA, BerryType.LUM]),
);
expect(getPlayerBerries()).toHaveLength(0);
@ -112,11 +115,11 @@ describe("Abilities - Harvest", () => {
vi.spyOn(Phaser.Math.RND, "realInRange").mockReturnValue(1);
await game.toNextTurn();
expect(player?.battleData.berriesEaten).toHaveLength(3);
expect(milotic?.battleData.berriesEaten).toHaveLength(3);
expect(getPlayerBerries()).toHaveLength(1);
});
it("remembers berries eaten array across waves and save/reload", async () => {
it("remembers berries eaten array across waves", async () => {
game.override
.startingHeldItems([{ name: "BERRY", type: BerryType.PETAYA, count: 2 }])
.ability(Abilities.BALL_FETCH); // don't actually need harvest for this test
@ -132,20 +135,17 @@ describe("Abilities - Harvest", () => {
// ate 1 berry without recovering (no harvest)
expect(regieleki.battleData.berriesEaten).toEqual([BerryType.PETAYA]);
expect(getPlayerBerries()).toEqual([expect.objectContaining({ berryType: BerryType.PETAYA, stackCount: 1 })]);
expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.SPATK)).toBe(1);
expectBerriesContaining({ name: "BERRY", count: 1, type: BerryType.PETAYA });
expect(regieleki.getStatStage(Stat.SPATK)).toBe(1);
await game.toNextWave();
expect(regieleki.battleData.berriesEaten).toEqual([BerryType.PETAYA]);
await game.reload.reloadSession();
const regielekiReloaded = game.scene.getPlayerPokemon()!;
expect(regielekiReloaded.battleData.berriesEaten).toEqual([BerryType.PETAYA]);
expectBerriesContaining({ name: "BERRY", count: 1, type: BerryType.PETAYA });
expect(regieleki.getStatStage(Stat.SPATK)).toBe(1);
});
it("keeps berries eaten across reloads", async () => {
it("keeps harvested berries across reloads", async () => {
game.override
.startingHeldItems([{ name: "BERRY", type: BerryType.PETAYA, count: 1 }])
.moveset([Moves.SPLASH, Moves.EARTHQUAKE])
@ -172,12 +172,13 @@ describe("Abilities - Harvest", () => {
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.toNextWave();
expect(getPlayerBerries()).toEqual([expect.objectContaining({ berryType: BerryType.PETAYA, stackCount: 1 })]);
expectBerriesContaining({ name: "BERRY", count: 1, type: BerryType.PETAYA });
expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.SPATK)).toBe(1);
await game.reload.reloadSession();
expect(getPlayerBerries()).toEqual([expect.objectContaining({ berryType: BerryType.PETAYA, stackCount: 1 })]);
expect(regieleki.battleData.berriesEaten).toEqual([]);
expectBerriesContaining({ name: "BERRY", count: 1, type: BerryType.PETAYA });
expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.SPATK)).toBe(1);
});

View File

@ -46,6 +46,16 @@ export class ReloadHelper extends GameManagerHelper {
scene.unshiftPhase(titlePhase);
this.game.endPhase(); // End the currently ongoing battle
// remove all persistent mods before loading
// TODO: Look into why these aren't removed before load
if (this.game.scene.modifiers.length) {
console.log(
"Removing %d modifiers from scene on load...",
this.game.scene.modifiers.length,
this.game.scene.modifiers,
);
this.game.scene.modifiers = [];
}
titlePhase.loadSaveSlot(-1); // Load the desired session data
this.game.phaseInterceptor.shift(); // Loading the save slot also ended TitlePhase, clean it up