From 3cc0232caec30301beb1ee393dde44fa8ae61a7c Mon Sep 17 00:00:00 2001 From: xsn34kzx Date: Fri, 27 Sep 2024 13:08:00 -0400 Subject: [PATCH] Clean Up & Organize Version Migration --- src/system/game-data.ts | 8 +- src/system/version-converter.ts | 141 ------------------ .../v1_0_4/session_migrators.ts | 61 ++++++++ .../v1_0_4/settings_migrators.ts | 14 ++ .../v1_0_4/system_migrators.ts | 56 +++++++ .../version_migration/version_converter.ts | 100 +++++++++++++ 6 files changed, 235 insertions(+), 145 deletions(-) delete mode 100644 src/system/version-converter.ts create mode 100644 src/system/version_migration/v1_0_4/session_migrators.ts create mode 100644 src/system/version_migration/v1_0_4/settings_migrators.ts create mode 100644 src/system/version_migration/v1_0_4/system_migrators.ts create mode 100644 src/system/version_migration/version_converter.ts diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 502a8aac2758..5251e55a7342 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -44,7 +44,7 @@ import { WeatherType } from "#app/enums/weather-type"; import { TerrainType } from "#app/data/terrain"; import { ReloadSessionPhase } from "#app/phases/reload-session-phase"; import { RUN_HISTORY_LIMIT } from "#app/ui/run-history-ui-handler"; -import { applySessionDataPatches, applySettingsDataPatches, applySystemDataPatches } from "./version-converter"; +import { SessionVersionConverter, SettingsVersionConverter, SystemVersionConverter } from "./version_migration/version_converter"; import { MysteryEncounterSaveData } from "../data/mystery-encounters/mystery-encounter-save-data"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; @@ -463,7 +463,7 @@ export class GameData { localStorage.setItem(lsItemKey, ""); } - applySystemDataPatches(systemData); + new SystemVersionConverter(systemData, systemData.gameVersion); this.trainerId = systemData.trainerId; this.secretId = systemData.secretId; @@ -838,7 +838,7 @@ export class GameData { const settings = JSON.parse(localStorage.getItem("settings")!); // TODO: is this bang correct? - applySettingsDataPatches(settings); + new SettingsVersionConverter(settings); for (const setting of Object.keys(settings)) { setSetting(this.scene, setting, settings[setting]); @@ -1270,7 +1270,7 @@ export class GameData { return v; }) as SessionSaveData; - applySessionDataPatches(sessionData); + new SessionVersionConverter(sessionData, sessionData.gameVersion); return sessionData; } diff --git a/src/system/version-converter.ts b/src/system/version-converter.ts deleted file mode 100644 index c297782ba669..000000000000 --- a/src/system/version-converter.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { allSpecies } from "#app/data/pokemon-species"; -import { AbilityAttr, defaultStarterSpecies, DexAttr, SessionSaveData, SystemSaveData } from "./game-data"; -import { SettingKeys } from "./settings/settings"; - -const LATEST_VERSION = "1.0.5"; - -export function applySessionDataPatches(data: SessionSaveData) { - const curVersion = data.gameVersion; - if (curVersion !== LATEST_VERSION) { - switch (curVersion) { - case "1.0.0": - case "1.0.1": - case "1.0.2": - case "1.0.3": - case "1.0.4": - // --- PATCHES --- - - // Fix Battle Items, Vitamins, and Lures - data.modifiers.forEach((m) => { - if (m.className === "PokemonBaseStatModifier") { - m.className = "BaseStatModifier"; - } else if (m.className === "PokemonResetNegativeStatStageModifier") { - m.className = "ResetNegativeStatStageModifier"; - } else if (m.className === "TempBattleStatBoosterModifier") { - m.className = "TempStatStageBoosterModifier"; - m.typeId = "TEMP_STAT_STAGE_BOOSTER"; - - // Migration from TempBattleStat to Stat - const newStat = m.typePregenArgs[0] + 1; - m.typePregenArgs[0] = newStat; - - // From [ stat, battlesLeft ] to [ stat, maxBattles, battleCount ] - m.args = [ newStat, 5, m.args[1] ]; - } else if (m.className === "DoubleBattleChanceBoosterModifier" && m.args.length === 1) { - let maxBattles: number; - switch (m.typeId) { - case "MAX_LURE": - maxBattles = 30; - break; - case "SUPER_LURE": - maxBattles = 15; - break; - default: - maxBattles = 10; - break; - } - - // From [ battlesLeft ] to [ maxBattles, battleCount ] - m.args = [ maxBattles, m.args[0] ]; - } - }); - - data.enemyModifiers.forEach((m) => { - if (m.className === "PokemonBaseStatModifier") { - m.className = "BaseStatModifier"; - } else if (m.className === "PokemonResetNegativeStatStageModifier") { - m.className = "ResetNegativeStatStageModifier"; - } - }); - } - - data.gameVersion = LATEST_VERSION; - } -} - -export function applySystemDataPatches(data: SystemSaveData) { - const curVersion = data.gameVersion; - if (curVersion !== LATEST_VERSION) { - switch (curVersion) { - case "1.0.0": - case "1.0.1": - case "1.0.2": - case "1.0.3": - case "1.0.4": - // --- LEGACY PATCHES --- - if (data.starterData) { - // Migrate ability starter data if empty for caught species - Object.keys(data.starterData).forEach(sd => { - if (data.dexData[sd]?.caughtAttr && (data.starterData[sd] && !data.starterData[sd].abilityAttr)) { - data.starterData[sd].abilityAttr = 1; - } - }); - } - - // Fix Legendary Stats - if (data.gameStats && (data.gameStats.legendaryPokemonCaught !== undefined && data.gameStats.subLegendaryPokemonCaught === undefined)) { - data.gameStats.subLegendaryPokemonSeen = 0; - data.gameStats.subLegendaryPokemonCaught = 0; - data.gameStats.subLegendaryPokemonHatched = 0; - allSpecies.filter(s => s.subLegendary).forEach(s => { - const dexEntry = data.dexData[s.speciesId]; - data.gameStats.subLegendaryPokemonSeen += dexEntry.seenCount; - data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen - dexEntry.seenCount, 0); - data.gameStats.subLegendaryPokemonCaught += dexEntry.caughtCount; - data.gameStats.legendaryPokemonCaught = Math.max(data.gameStats.legendaryPokemonCaught - dexEntry.caughtCount, 0); - data.gameStats.subLegendaryPokemonHatched += dexEntry.hatchedCount; - data.gameStats.legendaryPokemonHatched = Math.max(data.gameStats.legendaryPokemonHatched - dexEntry.hatchedCount, 0); - }); - data.gameStats.subLegendaryPokemonSeen = Math.max(data.gameStats.subLegendaryPokemonSeen, data.gameStats.subLegendaryPokemonCaught); - data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen, data.gameStats.legendaryPokemonCaught); - data.gameStats.mythicalPokemonSeen = Math.max(data.gameStats.mythicalPokemonSeen, data.gameStats.mythicalPokemonCaught); - } - - // --- PATCHES --- - - // Fix Starter Data - for (const starterId of defaultStarterSpecies) { - if (data.starterData[starterId]?.abilityAttr) { - data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1; - } - if (data.dexData[starterId]?.caughtAttr) { - data.dexData[starterId].caughtAttr |= DexAttr.FEMALE; - } - } - } - - data.gameVersion = LATEST_VERSION; - } -} - -export function applySettingsDataPatches(settings: Object) { - const curVersion = settings.hasOwnProperty("gameVersion") ? settings["gameVersion"] : "1.0.0"; - if (curVersion !== LATEST_VERSION) { - switch (curVersion) { - case "1.0.0": - case "1.0.1": - case "1.0.2": - case "1.0.3": - case "1.0.4": - // --- PATCHES --- - - // Fix Reward Cursor Target - if (settings.hasOwnProperty("REROLL_TARGET") && !settings.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) { - settings[SettingKeys.Shop_Cursor_Target] = settings["REROLL_TARGET"]; - delete settings["REROLL_TARGET"]; - localStorage.setItem("settings", JSON.stringify(settings)); - } - } - // Note that the current game version will be written at `saveSettings` - } -} diff --git a/src/system/version_migration/v1_0_4/session_migrators.ts b/src/system/version_migration/v1_0_4/session_migrators.ts new file mode 100644 index 000000000000..40bb18c3b6a0 --- /dev/null +++ b/src/system/version_migration/v1_0_4/session_migrators.ts @@ -0,0 +1,61 @@ +import { SessionSaveData } from "../../game-data"; + +/** + * Converts old lapsing modifiers (battle items, lures, and Dire Hit) and + * other miscellaneous modifiers (vitamins, White Herb) to any new class + * names and/or change in reload arguments. + * @param data {@linkcode SessionSaveData} + */ +export function migrateModifiers(data: SessionSaveData) { + data.modifiers.forEach((m) => { + if (m.className === "PokemonBaseStatModifier") { + m.className = "BaseStatModifier"; + } else if (m.className === "PokemonResetNegativeStatStageModifier") { + m.className = "ResetNegativeStatStageModifier"; + } else if (m.className === "TempBattleStatBoosterModifier") { + const maxBattles = 5; + // Dire Hit no longer a part of the TempBattleStatBoosterModifierTypeGenerator + if (m.typeId !== "DIRE_HIT") { + m.className = "TempStatStageBoosterModifier"; + m.typeId = "TEMP_STAT_STAGE_BOOSTER"; + + // Migration from TempBattleStat to Stat + const newStat = m.typePregenArgs[0] + 1; + m.typePregenArgs[0] = newStat; + + // From [ stat, battlesLeft ] to [ stat, maxBattles, battleCount ] + m.args = [ newStat, maxBattles, Math.min(m.args[1], maxBattles) ]; + } else { + m.className = "TempCritBoosterModifier"; + m.typePregenArgs = []; + + // From [ stat, battlesLeft ] to [ maxBattles, battleCount ] + m.args = [ maxBattles, Math.min(m.args[1], maxBattles) ]; + } + } else if (m.className === "DoubleBattleChanceBoosterModifier" && m.args.length === 1) { + let maxBattles: number; + switch (m.typeId) { + case "MAX_LURE": + maxBattles = 30; + break; + case "SUPER_LURE": + maxBattles = 15; + break; + default: + maxBattles = 10; + break; + } + + // From [ battlesLeft ] to [ maxBattles, battleCount ] + m.args = [ maxBattles, m.args[0] ]; + } + }); + + data.enemyModifiers.forEach((m) => { + if (m.className === "PokemonBaseStatModifier") { + m.className = "BaseStatModifier"; + } else if (m.className === "PokemonResetNegativeStatStageModifier") { + m.className = "ResetNegativeStatStageModifier"; + } + }); +} diff --git a/src/system/version_migration/v1_0_4/settings_migrators.ts b/src/system/version_migration/v1_0_4/settings_migrators.ts new file mode 100644 index 000000000000..59955d53352c --- /dev/null +++ b/src/system/version_migration/v1_0_4/settings_migrators.ts @@ -0,0 +1,14 @@ +import { SettingKeys } from "../../settings/settings"; + +/** + * Migrate from "REROLL_TARGET" property to {@linkcode + * SettingKeys.Shop_Cursor_Target}. + * @param data the `settings` object + */ +export function fixRerollTarget(data: Object) { + if (data.hasOwnProperty("REROLL_TARGET") && !data.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) { + data[SettingKeys.Shop_Cursor_Target] = data["REROLL_TARGET"]; + delete data["REROLL_TARGET"]; + localStorage.setItem("settings", JSON.stringify(data)); + } +} diff --git a/src/system/version_migration/v1_0_4/system_migrators.ts b/src/system/version_migration/v1_0_4/system_migrators.ts new file mode 100644 index 000000000000..8d40c0985b9a --- /dev/null +++ b/src/system/version_migration/v1_0_4/system_migrators.ts @@ -0,0 +1,56 @@ +import { AbilityAttr, defaultStarterSpecies, DexAttr, SystemSaveData } from "../../game-data"; +import { allSpecies } from "../../../data/pokemon-species"; + +/** + * Migrate ability starter data if empty for caught species + * @param data {@linkcode SystemSaveData} + */ +export function migrateAbilityData(data: SystemSaveData) { + if (data.starterData && data.dexData) { + // Migrate ability starter data if empty for caught species + Object.keys(data.starterData).forEach(sd => { + if (data.dexData[sd]?.caughtAttr && (data.starterData[sd] && !data.starterData[sd].abilityAttr)) { + data.starterData[sd].abilityAttr = 1; + } + }); + } +} + +/** + * Populate legendary Pokémon statistics if they are missing + * @param data {@linkcode SystemSaveData} + */ +export function fixLegendaryStats(data: SystemSaveData) { + if (data.gameStats && (data.gameStats.legendaryPokemonCaught !== undefined && data.gameStats.subLegendaryPokemonCaught === undefined)) { + data.gameStats.subLegendaryPokemonSeen = 0; + data.gameStats.subLegendaryPokemonCaught = 0; + data.gameStats.subLegendaryPokemonHatched = 0; + allSpecies.filter(s => s.subLegendary).forEach(s => { + const dexEntry = data.dexData[s.speciesId]; + data.gameStats.subLegendaryPokemonSeen += dexEntry.seenCount; + data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen - dexEntry.seenCount, 0); + data.gameStats.subLegendaryPokemonCaught += dexEntry.caughtCount; + data.gameStats.legendaryPokemonCaught = Math.max(data.gameStats.legendaryPokemonCaught - dexEntry.caughtCount, 0); + data.gameStats.subLegendaryPokemonHatched += dexEntry.hatchedCount; + data.gameStats.legendaryPokemonHatched = Math.max(data.gameStats.legendaryPokemonHatched - dexEntry.hatchedCount, 0); + }); + data.gameStats.subLegendaryPokemonSeen = Math.max(data.gameStats.subLegendaryPokemonSeen, data.gameStats.subLegendaryPokemonCaught); + data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen, data.gameStats.legendaryPokemonCaught); + data.gameStats.mythicalPokemonSeen = Math.max(data.gameStats.mythicalPokemonSeen, data.gameStats.mythicalPokemonCaught); + } +} + +/** + * Unlock all starters' first ability and female gender option + * @param data {@linkcode SystemSaveData} + */ +export function fixStarterData(data: SystemSaveData) { + for (const starterId of defaultStarterSpecies) { + if (data.starterData[starterId]?.abilityAttr) { + data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1; + } + if (data.dexData[starterId]?.caughtAttr) { + data.dexData[starterId].caughtAttr |= DexAttr.FEMALE; + } + } +} diff --git a/src/system/version_migration/version_converter.ts b/src/system/version_migration/version_converter.ts new file mode 100644 index 000000000000..d97e1fa97fa2 --- /dev/null +++ b/src/system/version_migration/version_converter.ts @@ -0,0 +1,100 @@ +import { SessionSaveData, SystemSaveData } from "../game-data"; +import { version } from "../../../package.json"; + +// --- v1.0.4 (and below) PATCHES --- // +import * as v1_0_4SessionData from "./v1_0_4/session_migrators"; +import * as v1_0_4SystemData from "./v1_0_4/system_migrators"; +import * as v1_0_4SettingsData from "./v1_0_4/settings_migrators"; + +const LATEST_VERSION = version.split(".").map(value => parseInt(value)); + +export abstract class VersionConverter { + constructor(data: any, gameVersion: string) { + const curVersion = gameVersion.split(".").map(value => parseInt(value)); + if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) { + this.applyPatches(data, curVersion); + } + } + + callMigrators(data: any, migrationObject: Object) { + const migrators = Object.values(migrationObject); + for (const migrate of migrators) { + migrate(data); + } + } + + abstract applyPatches(data: any, curVersion: number[]): void; +} + +export class SessionVersionConverter extends VersionConverter { + constructor(data: SessionSaveData, gameVersion: string) { + super(data, gameVersion); + } + + applyPatches(data: SessionSaveData, curVersion: number[]): void { + const [ curMajor, curMinor, curPatch ] = curVersion; + + switch (curMajor) { + case 1: + switch (curMinor) { + case 0: + if (curPatch <= 4) { + console.log("Applying v1.0.4 session data migration!"); + this.callMigrators(data, v1_0_4SessionData); + } + default: + } + default: + } + console.log(`Session data successfully migrated to v${version}!`); + } +} + +export class SystemVersionConverter extends VersionConverter { + constructor(data: SystemSaveData, gameVersion: string) { + super(data, gameVersion); + } + + applyPatches(data: SystemSaveData, curVersion: number[]): void { + const [ curMajor, curMinor, curPatch ] = curVersion; + + switch (curMajor) { + case 1: + switch (curMinor) { + case 0: + if (curPatch <= 4) { + console.log("Applying v1.0.4 system data migraton!"); + this.callMigrators(data, v1_0_4SystemData); + } + default: + } + default: + } + console.log(`System data successfully migrated to v${version}!`); + } +} + +export class SettingsVersionConverter extends VersionConverter { + constructor(data: SystemSaveData) { + const gameVersion = data.hasOwnProperty("gameVersion") ? data["gameVersion"] : "1.0.0"; + super(data, gameVersion); + } + + applyPatches(data: Object, curVersion: number[]): void { + const [ curMajor, curMinor, curPatch ] = curVersion; + + switch (curMajor) { + case 1: + switch (curMinor) { + case 0: + if (curPatch <= 4) { + console.log("Applying v1.0.4 settings data migraton!"); + this.callMigrators(data, v1_0_4SettingsData); + } + default: + } + default: + } + console.log(`System data successfully migrated to v${version}!`); + } +}