diff --git a/hmm.json b/hmm.json index 8ff572b30..50879416b 100644 --- a/hmm.json +++ b/hmm.json @@ -11,7 +11,7 @@ "name": "flixel", "type": "git", "dir": null, - "ref": "f2b090d6c608471e730b051c8ee22b8b378964b1", + "ref": "ffa691cb2d2d81de35b900a4411e4062ac84ab58", "url": "https://github.com/FunkinCrew/flixel" }, { diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 1d723d086..52cf18b7f 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -1,25 +1,23 @@ package funkin.save; import flixel.util.FlxSave; -import funkin.util.FileUtil; import funkin.input.Controls.Device; import funkin.play.scoring.Scoring; import funkin.play.scoring.Scoring.ScoringRank; import funkin.save.migrator.RawSaveData_v1_0_0; import funkin.save.migrator.SaveDataMigrator; -import funkin.save.migrator.SaveDataMigrator; import funkin.ui.debug.charting.ChartEditorState.ChartEditorLiveInputStyle; import funkin.ui.debug.charting.ChartEditorState.ChartEditorTheme; import funkin.ui.debug.stageeditor.StageEditorState.StageEditorTheme; +import funkin.util.FileUtil; import funkin.util.SerializerUtil; import thx.semver.Version; -import thx.semver.Version; @:nullSafety class Save { - public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.6"; - public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x"; + public static final SAVE_DATA_VERSION:thx.semver.Version = "2.1.0"; + public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=2.1.0 <2.2.0"; // We load this version's saves from a new save path, to maintain SOME level of backwards compatibility. static final SAVE_PATH:String = 'FunkinCrew'; @@ -965,32 +963,57 @@ class Save FlxG.save.bind('$SAVE_NAME${slot}', SAVE_PATH); - if (FlxG.save.isEmpty()) + switch (FlxG.save.status) { - trace('[SAVE] Save data is empty, checking for legacy save data...'); - var legacySaveData = fetchLegacySaveData(); - if (legacySaveData != null) - { - trace('[SAVE] Found legacy save data, converting...'); - var gameSave = SaveDataMigrator.migrateFromLegacy(legacySaveData); + case EMPTY: + trace('[SAVE] Save data in slot ${slot} is empty, checking for legacy save data...'); + var legacySaveData = fetchLegacySaveData(); + if (legacySaveData != null) + { + trace('[SAVE] Found legacy save data, converting...'); + var gameSave = SaveDataMigrator.migrateFromLegacy(legacySaveData); + FlxG.save.mergeData(gameSave.data, true); + return gameSave; + } + else + { + trace('[SAVE] No legacy save data found.'); + var gameSave = new Save(); + FlxG.save.mergeData(gameSave.data, true); + return gameSave; + } + case ERROR(_): + return handleSaveDataError(slot); + case BOUND(_, _): + trace('[SAVE] Loaded existing save data in slot ${slot}.'); + var gameSave = SaveDataMigrator.migrate(FlxG.save.data); FlxG.save.mergeData(gameSave.data, true); + return gameSave; - } - else - { - trace('[SAVE] No legacy save data found.'); - var gameSave = new Save(); - FlxG.save.mergeData(gameSave.data, true); - return gameSave; - } + } + } + + /** + * Call this when there is an error loading the save data in slot X. + */ + static function handleSaveDataError(slot:Int):Save + { + var msg = 'There was an error loading your save data in slot ${slot}.'; + msg += '\nPlease report this issue to the developers.'; + lime.app.Application.current.window.alert(msg, "Save Data Failure"); + + // Don't touch that slot anymore. + // Instead, load the next available slot. + + var nextSlot = slot + 1; + + if (nextSlot < 1000) + { + return loadFromSlot(nextSlot); } else { - trace('[SAVE] Found existing save data.'); - var gameSave = SaveDataMigrator.migrate(FlxG.save.data); - FlxG.save.mergeData(gameSave.data, true); - - return gameSave; + throw "End of save data slots. Can't load any more."; } } @@ -1055,7 +1078,15 @@ class Save { var targetSaveData = new FlxSave(); targetSaveData.bind('$SAVE_NAME${slot}', SAVE_PATH); - return !targetSaveData.isEmpty(); + switch (targetSaveData.status) + { + case EMPTY: + return false; + case ERROR(_): + return false; + case BOUND(_, _): + return true; + } } /** diff --git a/source/funkin/save/changelog.md b/source/funkin/save/changelog.md index 72fb7fc03..aa98d3096 100644 --- a/source/funkin/save/changelog.md +++ b/source/funkin/save/changelog.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.1.0] - 2024-10-18 +This version introduces changes to save data loading in order to improve compatibility with older versions. +### Changed +- `optionsStageEditor.theme` converted from an Enum to a String to fix save data compatibility issues. + - In the future, Enum values should not be used in order to prevent incompatibilities caused by introducing new types to the save data that older versions cannot parse. +- `optionsChartEditor.theme` converted from an Enum to a String to fix save data compatibility issues. +- `optionsChartEditor.chartEditorLiveInputStyle` converted from an Enum to a String to fix save data compatibility issues. + ## [2.0.6] - 2024-10-11 ### Added - `optionsStageEditor` to `Save` for storing user preferences for the stage editor. diff --git a/source/funkin/save/migrator/SaveDataMigrator.hx b/source/funkin/save/migrator/SaveDataMigrator.hx index 7a929322a..228669f66 100644 --- a/source/funkin/save/migrator/SaveDataMigrator.hx +++ b/source/funkin/save/migrator/SaveDataMigrator.hx @@ -32,6 +32,10 @@ class SaveDataMigrator var save:Save = new Save(saveDataWithDefaults); return save; } + else if (VersionUtil.validateVersion(version, "2.0.x")) + { + return migrate_v2_0_0(inputData); + } else { var message:String = 'Error migrating save data, expected ${Save.SAVE_DATA_VERSION}.'; @@ -43,6 +47,20 @@ class SaveDataMigrator } } + static function migrate_v2_0_0(inputData:Dynamic):Save + { + // Import the structured data. + var saveDataWithDefaults:RawSaveData = cast thx.Objects.deepCombine(Save.getDefault(), inputData); + + // Reset these values to valid ones. + saveDataWithDefaults.optionsChartEditor.chartEditorLiveInputStyle = funkin.ui.debug.charting.ChartEditorLiveInputStyle.None; + saveDataWithDefaults.optionsChartEditor.theme = funkin.ui.debug.charting.ChartEditorTheme.Light; + saveDataWithDefaults.optionsStageEditor.theme = funkin.ui.debug.stageeditor.StageEditorTheme.Light; + + var save:Save = new Save(saveDataWithDefaults); + return save; + } + /** * Migrate from 1.x to the latest version. */ diff --git a/source/funkin/save/migrator/SaveData_v2_0_0.hx b/source/funkin/save/migrator/SaveData_v2_0_0.hx new file mode 100644 index 000000000..5369d8737 --- /dev/null +++ b/source/funkin/save/migrator/SaveData_v2_0_0.hx @@ -0,0 +1,26 @@ +package funkin.save.migrator; + +// Internal enums used to ensure old save data can be parsed by the default Haxe unserializer. +// In the future, only primitive types and abstract enums should be used in save data! + +@:native("funkin.ui.debug.stageeditor.StageEditorTheme") +enum StageEditorTheme +{ + Light; + Dark; +} + +@:native("funkin.ui.debug.charting.ChartEditorTheme") +enum ChartEditorTheme +{ + Light; + Dark; +} + +@:native("funkin.ui.debug.charting.ChartEditorLiveInputStyle") +enum ChartEditorLiveInputStyle +{ + None; + NumberKeys; + WASDKeys; +} diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 3fb63a4f1..c11f13342 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -5654,7 +5654,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function handleHelpKeybinds():Void { // F1 = Open Help - if (FlxG.keys.justPressed.F1 && !isHaxeUIDialogOpen) { + if (FlxG.keys.justPressed.F1 && !isHaxeUIDialogOpen) + { this.openUserGuideDialog(); } } @@ -6543,22 +6544,22 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState /** * Available input modes for the chart editor state. Numbers/arrows/WASD available for other keybinds. */ -enum ChartEditorLiveInputStyle +enum abstract ChartEditorLiveInputStyle(String) { /** * No hotkeys to place notes at the playbar. */ - None; + var None; /** * 1/2/3/4 to place notes on opponent's side, 5/6/7/8 to place notes on player's side. */ - NumberKeys; + var NumberKeys; /** * WASD to place notes on opponent's side, Arrow keys to place notes on player's side. */ - WASDKeys; + var WASDKeys; } typedef ChartEditorParams = @@ -6577,15 +6578,15 @@ typedef ChartEditorParams = /** * Available themes for the chart editor state. */ -enum ChartEditorTheme +enum abstract ChartEditorTheme(String) { /** * The default theme for the chart editor. */ - Light; + var Light; /** * A theme which introduces darker colors. */ - Dark; + var Dark; } diff --git a/source/funkin/ui/debug/stageeditor/StageEditorState.hx b/source/funkin/ui/debug/stageeditor/StageEditorState.hx index 98190498f..a536a09ee 100644 --- a/source/funkin/ui/debug/stageeditor/StageEditorState.hx +++ b/source/funkin/ui/debug/stageeditor/StageEditorState.hx @@ -1458,17 +1458,17 @@ class StageEditorState extends UIState /** * Available themes for the stage editor state. */ -enum StageEditorTheme +enum abstract StageEditorTheme(String) { /** * The default theme for the stage editor. */ - Light; + var Light; /** * A theme which introduces stage colors. */ - Dark; + var Dark; } enum StageEditorDialogType