diff --git a/Project.xml b/Project.xml index 24cdac270..f19a19e8c 100644 --- a/Project.xml +++ b/Project.xml @@ -128,6 +128,7 @@ <haxelib name="json2object" /> <!-- JSON parsing --> + <haxelib name="thx.core" /> <!-- General utility library, "the lodash of Haxe" --> <haxelib name="thx.semver" /> <!-- Version string handling --> <haxelib name="hxcpp-debug-server" if="desktop debug" /> <!-- VSCode debug support --> diff --git a/hmm.json b/hmm.json index 288aa80b8..716754b59 100644 --- a/hmm.json +++ b/hmm.json @@ -153,7 +153,7 @@ "name": "polymod", "type": "git", "dir": null, - "ref": "8553b800965f225bb14c7ab8f04bfa9cdec362ac", + "ref": "bfbe30d81601b3543d80dce580108ad6b7e182c7", "url": "https://github.com/larsiusprime/polymod" }, { diff --git a/source/funkin/import.hx b/source/funkin/import.hx index 250de99cb..c8431be33 100644 --- a/source/funkin/import.hx +++ b/source/funkin/import.hx @@ -11,6 +11,7 @@ import flixel.system.debug.watch.Tracker; // These are great. using Lambda; using StringTools; +using thx.Arrays; using funkin.util.tools.ArraySortTools; using funkin.util.tools.ArrayTools; using funkin.util.tools.FloatTools; diff --git a/source/funkin/input/Controls.hx b/source/funkin/input/Controls.hx index 31551dec9..345791eef 100644 --- a/source/funkin/input/Controls.hx +++ b/source/funkin/input/Controls.hx @@ -997,7 +997,7 @@ class Controls extends FlxActionSet for (control in Control.createAll()) { var inputs:Array<Int> = Reflect.field(data, control.getName()); - inputs = inputs.unique(); + inputs = inputs.distinct(); if (inputs != null) { if (inputs.length == 0) { @@ -1050,7 +1050,7 @@ class Controls extends FlxActionSet if (inputs.length == 0) { inputs = [FlxKey.NONE]; } else { - inputs = inputs.unique(); + inputs = inputs.distinct(); } Reflect.setField(data, control.getName(), inputs); diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index 23d8d2198..53408fb34 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -439,12 +439,16 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta // so we have to map it to the actual difficulty names. // We also filter out difficulties that don't match the variation or that don't exist. - var diffFiltered:Array<String> = difficulties.keys().array().map(function(diffId:String):Null<String> { - var difficulty:Null<SongDifficulty> = difficulties.get(diffId); - if (difficulty == null) return null; - if (variationIds.length > 0 && !variationIds.contains(difficulty.variation)) return null; - return difficulty.difficulty; - }).nonNull().unique(); + var diffFiltered:Array<String> = difficulties.keys() + .array() + .map(function(diffId:String):Null<String> { + var difficulty:Null<SongDifficulty> = difficulties.get(diffId); + if (difficulty == null) return null; + if (variationIds.length > 0 && !variationIds.contains(difficulty.variation)) return null; + return difficulty.difficulty; + }) + .filterNull() + .distinct(); diffFiltered = diffFiltered.filter(function(diffId:String):Bool { if (showHidden) return true; diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 711b9fcf4..934d6a4aa 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -746,6 +746,7 @@ class Save /** * An anonymous structure containingg all the user's save data. + * Isn't stored with JSON, stored with some sort of Haxe built-in serialization? */ typedef RawSaveData = { @@ -756,8 +757,6 @@ typedef RawSaveData = /** * A semantic versioning string for the save data format. */ - @:jcustomparse(funkin.data.DataParse.semverVersion) - @:jcustomwrite(funkin.data.DataWrite.semverVersion) var version:Version; var api:SaveApiData; diff --git a/source/funkin/save/migrator/SaveDataMigrator.hx b/source/funkin/save/migrator/SaveDataMigrator.hx index 9e308cb10..4fa9dd6b3 100644 --- a/source/funkin/save/migrator/SaveDataMigrator.hx +++ b/source/funkin/save/migrator/SaveDataMigrator.hx @@ -3,7 +3,6 @@ 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 @@ -24,16 +23,20 @@ class SaveDataMigrator } else { + // Sometimes the Haxe serializer has issues with the version so we fix it here. + version = VersionUtil.repairVersion(version); if (VersionUtil.validateVersion(version, Save.SAVE_DATA_VERSION_RULE)) { - // Simply import the structured data. - var save:Save = new Save(StructureUtil.deepMerge(Save.getDefault(), inputData)); + // Import the structured data. + var saveDataWithDefaults:RawSaveData = cast thx.Objects.deepCombine(Save.getDefault(), inputData); + var save:Save = new Save(saveDataWithDefaults); return save; } else { - trace('[SAVE] Invalid save data version! Returning blank data.'); - trace(inputData); + var message:String = 'Error migrating save data, expected ${Save.SAVE_DATA_VERSION}.'; + lime.app.Application.current.window.alert(message, "Save Data Failure"); + trace('[SAVE] ' + message); return new Save(Save.getDefault()); } } diff --git a/source/funkin/ui/story/LevelProp.hx b/source/funkin/ui/story/LevelProp.hx index ffc756e1c..5a3efc36a 100644 --- a/source/funkin/ui/story/LevelProp.hx +++ b/source/funkin/ui/story/LevelProp.hx @@ -13,11 +13,10 @@ class LevelProp extends Bopper // Only reset the prop if the asset path has changed. if (propData == null || value?.assetPath != propData?.assetPath) { - this.visible = (value != null); - this.propData = value; - danceEvery = this.propData?.danceEvery ?? 0; applyData(); } + this.visible = (value != null); + danceEvery = this.propData?.danceEvery ?? 0; return this.propData; } diff --git a/source/funkin/util/StructureUtil.hx b/source/funkin/util/StructureUtil.hx deleted file mode 100644 index 2f0c3818a..000000000 --- a/source/funkin/util/StructureUtil.hx +++ /dev/null @@ -1,136 +0,0 @@ -package funkin.util; - -import funkin.util.tools.MapTools; -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<Dynamic> = Reflect.copy(a); - - for (field in Reflect.fields(b)) - { - result.set(field, Reflect.field(b, field)); - } - - return result; - } - - public static function toMap(a:Dynamic):haxe.ds.Map<String, Dynamic> - { - var result:haxe.ds.Map<String, Dynamic> = []; - - for (field in Reflect.fields(a)) - { - result.set(field, Reflect.field(a, field)); - } - - return result; - } - - public static function isMap(a:Dynamic):Bool - { - return Std.isOfType(a, haxe.Constraints.IMap); - } - - public static function isObject(a:Dynamic):Bool - { - switch (Type.typeof(a)) - { - case TObject: - return true; - default: - return false; - } - } - - public static function isPrimitive(a:Dynamic):Bool - { - switch (Type.typeof(a)) - { - case TInt | TFloat | TBool: - return true; - case TClass(c): - return false; - case TEnum(e): - return false; - case TObject: - return false; - case TFunction: - return false; - case TNull: - return true; - case TUnknown: - return false; - default: - return false; - } - } - - /** - * 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 (isPrimitive(a) && isPrimitive(b)) return b; - if (isMap(b)) - { - if (isMap(a)) - { - return MapTools.merge(a, b); - } - else - { - return StructureUtil.toMap(a).merge(b); - } - } - if (!Reflect.isObject(a) || !Reflect.isObject(b)) return b; - if (Std.isOfType(b, haxe.ds.StringMap)) - { - if (Std.isOfType(a, haxe.ds.StringMap)) - { - return MapTools.merge(a, b); - } - else - { - return StructureUtil.toMap(a).merge(b); - } - } - - var result:DynamicAccess<Dynamic> = 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; - } -} diff --git a/source/funkin/util/VersionUtil.hx b/source/funkin/util/VersionUtil.hx index 247ba19db..18d7eafa6 100644 --- a/source/funkin/util/VersionUtil.hx +++ b/source/funkin/util/VersionUtil.hx @@ -32,6 +32,25 @@ class VersionUtil } } + public static function repairVersion(version:thx.semver.Version):thx.semver.Version + { + var versionData:thx.semver.Version.SemVer = version; + + if (thx.Types.isAnonymousObject(versionData.version)) + { + // This is bad! versionData.version should be an array! + versionData.version = [versionData.version[0], versionData.version[1], versionData.version[2]]; + + var fixedVersion:thx.semver.Version = versionData; + return fixedVersion; + } + else + { + // No need for repair. + return version; + } + } + /** * Checks that a given verison number satisisfies a given version rule. * Version rule can be complex, e.g. "1.0.x" or ">=1.0.0,<1.1.0", or anything NPM supports. diff --git a/source/funkin/util/macro/InlineMacro.hx b/source/funkin/util/macro/InlineMacro.hx index b0e7ed184..c40257409 100644 --- a/source/funkin/util/macro/InlineMacro.hx +++ b/source/funkin/util/macro/InlineMacro.hx @@ -23,7 +23,7 @@ class InlineMacro var fields:Array<haxe.macro.Expr.Field> = haxe.macro.Context.getBuildFields(); // Find the field with the given name. - var targetField:Null<haxe.macro.Expr.Field> = fields.find(function(f) return f.name == field + var targetField:Null<haxe.macro.Expr.Field> = thx.Arrays.find(fields, function(f) return f.name == field && (MacroUtil.isFieldStatic(f) == isStatic)); // If the field was not found, throw an error. diff --git a/source/funkin/util/tools/ArrayTools.hx b/source/funkin/util/tools/ArrayTools.hx index caf8e8aab..0fe245e3a 100644 --- a/source/funkin/util/tools/ArrayTools.hx +++ b/source/funkin/util/tools/ArrayTools.hx @@ -5,72 +5,6 @@ package funkin.util.tools; */ class ArrayTools { - /** - * Returns a copy of the array with all duplicate elements removed. - * @param array The array to remove duplicates from. - * @return A copy of the array with all duplicate elements removed. - */ - public static function unique<T>(array:Array<T>):Array<T> - { - var result:Array<T> = []; - for (element in array) - { - if (!result.contains(element)) - { - result.push(element); - } - } - return result; - } - - /** - * Returns a copy of the array with all `null` elements removed. - * @param array The array to remove `null` elements from. - * @return A copy of the array with all `null` elements removed. - */ - public static function nonNull<T>(array:Array<Null<T>>):Array<T> - { - var result:Array<T> = []; - for (element in array) - { - if (element != null) - { - result.push(element); - } - } - return result; - } - - /** - * Return the first element of the array that satisfies the predicate, or null if none do. - * @param input The array to search - * @param predicate The predicate to call - * @return The result - */ - public static function find<T>(input:Array<T>, predicate:T->Bool):Null<T> - { - for (element in input) - { - if (predicate(element)) return element; - } - return null; - } - - /** - * Return the index of the first element of the array that satisfies the predicate, or `-1` if none do. - * @param input The array to search - * @param predicate The predicate to call - * @return The index of the result - */ - public static function findIndex<T>(input:Array<T>, predicate:T->Bool):Int - { - for (index in 0...input.length) - { - if (predicate(input[index])) return index; - } - return -1; - } - /* * Push an element to the array if it is not already present. * @param input The array to push to