From f7141e7096f25a4a3a9986afe6c26357376ae49e Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 3 Apr 2024 01:01:58 -0400 Subject: [PATCH] Fixed an issue with save data not loading defaults properly. --- source/funkin/save/Save.hx | 8 +-- .../funkin/save/migrator/SaveDataMigrator.hx | 3 +- source/funkin/ui/freeplay/FreeplayState.hx | 2 +- source/funkin/util/StructureUtil.hx | 61 +++++++++++++++++++ 4 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 source/funkin/util/StructureUtil.hx diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 73ba8efa0..dc7c5f989 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -14,7 +14,7 @@ import thx.semver.Version; class Save { // Version 2.0.2 adds attributes to `optionsChartEditor`, that should return default values if they are null. - public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.2"; + public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.3"; public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x"; // We load this version's saves from a new save path, to maintain SOME level of backwards compatibility. @@ -650,9 +650,9 @@ class Save if (legacySaveData != null) { trace('[SAVE] Found legacy save data, converting...'); - var gameSave = SaveDataMigrator.migrate(legacySaveData); + var gameSave = SaveDataMigrator.migrateFromLegacy(legacySaveData); @:privateAccess - FlxG.save.mergeData(gameSave.data); + FlxG.save.mergeData(gameSave.data, true); } else { @@ -664,7 +664,7 @@ class Save trace('[SAVE] Loaded save data.'); @:privateAccess var gameSave = SaveDataMigrator.migrate(FlxG.save.data); - FlxG.save.mergeData(gameSave.data); + FlxG.save.mergeData(gameSave.data, true); } } diff --git a/source/funkin/save/migrator/SaveDataMigrator.hx b/source/funkin/save/migrator/SaveDataMigrator.hx index 00637d52a..3ed59e726 100644 --- a/source/funkin/save/migrator/SaveDataMigrator.hx +++ b/source/funkin/save/migrator/SaveDataMigrator.hx @@ -3,6 +3,7 @@ package funkin.save.migrator; import funkin.save.Save; import funkin.save.migrator.RawSaveData_v1_0_0; import thx.semver.Version; +import funkin.util.StructureUtil; import funkin.util.VersionUtil; @:nullSafety @@ -26,7 +27,7 @@ class SaveDataMigrator if (VersionUtil.validateVersion(version, Save.SAVE_DATA_VERSION_RULE)) { // Simply import the structured data. - var save:Save = new Save(inputData); + var save:Save = new Save(StructureUtil.deepMerge(Save.getDefault(), inputData)); return save; } else diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 455805479..66c829e11 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -962,7 +962,7 @@ class FreeplayState extends MusicBeatSubState public override function destroy():Void { super.destroy(); - var daSong:Null = grpCapsules.members[curSelected]?.songData; + var daSong:Null = currentFilteredSongs[curSelected]; if (daSong != null) { clearDaCache(daSong.songName); diff --git a/source/funkin/util/StructureUtil.hx b/source/funkin/util/StructureUtil.hx new file mode 100644 index 000000000..351d0e0a8 --- /dev/null +++ b/source/funkin/util/StructureUtil.hx @@ -0,0 +1,61 @@ +package funkin.util; + +import haxe.DynamicAccess; + +/** + * Utilities for working with anonymous structures. + */ +class StructureUtil +{ + /** + * Merge two structures, with the second overwriting the first. + * Performs a SHALLOW clone, where child structures are not merged. + * @param a The base structure. + * @param b The new structure. + * @return The merged structure. + */ + public static function merge(a:Dynamic, b:Dynamic):Dynamic + { + var result:DynamicAccess = Reflect.copy(a); + + for (field in Reflect.fields(b)) + { + result.set(field, Reflect.field(b, field)); + } + + return result; + } + + /** + * Merge two structures, with the second overwriting the first. + * Performs a DEEP clone, where child structures are also merged recursively. + * @param a The base structure. + * @param b The new structure. + * @return The merged structure. + */ + public static function deepMerge(a:Dynamic, b:Dynamic):Dynamic + { + if (a == null) return b; + if (b == null) return null; + if (!Reflect.isObject(a) || !Reflect.isObject(b)) return b; + + var result:DynamicAccess = Reflect.copy(a); + + for (field in Reflect.fields(b)) + { + if (Reflect.isObject(b)) + { + // Note that isObject also returns true for class instances, + // but we just assume that's not a problem here. + result.set(field, deepMerge(Reflect.field(result, field), Reflect.field(b, field))); + } + else + { + // If we're here, b[field] is a primitive. + result.set(field, Reflect.field(b, field)); + } + } + + return result; + } +}