diff --git a/Project.xml b/Project.xml
index fcfcfb9f3..b5630a46a 100644
--- a/Project.xml
+++ b/Project.xml
@@ -128,6 +128,7 @@
+
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/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 = difficulties.keys().array().map(function(diffId:String):Null {
- var difficulty:Null = 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 = difficulties.keys()
+ .array()
+ .map(function(diffId:String):Null {
+ var difficulty:Null = 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/migrator/SaveDataMigrator.hx b/source/funkin/save/migrator/SaveDataMigrator.hx
index 7f597b4ec..5398b2119 100644
--- a/source/funkin/save/migrator/SaveDataMigrator.hx
+++ b/source/funkin/save/migrator/SaveDataMigrator.hx
@@ -28,8 +28,9 @@ class SaveDataMigrator
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 = thx.Objects.deepCombine(Save.getDefault(), inputData);
+ var save:Save = new Save(saveDataWithDefaults);
return save;
}
else
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 2a6b345d3..000000000
--- a/source/funkin/util/StructureUtil.hx
+++ /dev/null
@@ -1,158 +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 = 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
- {
- var result:haxe.ds.Map = [];
-
- 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);
- }
-
- /**
- * Returns `true` if `a` is an anonymous structure.
- * I believe this returns `false` even for class instances and arrays.
- */
- public static function isStructure(a:Dynamic):Bool
- {
- // TODO: Is there a difference?
- // return Reflect.isObject(foo);
-
- switch (Type.typeof(a))
- {
- case TObject:
- return true;
- default:
- return false;
- }
- }
-
- /**
- * Returns true if `a` is an array.
- *
- * NOTE: isObject and isInstance also return true,
- * since they're objects of the Array<> class, so check this first!
- */
- public static function isArray(a:Dynamic):Bool
- {
- return Std.is(a, Array);
- }
-
- public static function isInstance(a:Dynamic):Bool
- {
- return Type.getClass(a) != null;
- }
-
- 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 (isArray(a) && isArray(b)) return b;
- 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 (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);
- }
- }
- if (!isStructure(a) || !isStructure(b)) return b;
-
- var result:DynamicAccess = Reflect.copy(a);
-
- for (field in Reflect.fields(b))
- {
- if (isStructure(b))
- {
- 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/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(array:Array):Array
- {
- var result:Array = [];
- 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(array:Array>):Array
- {
- var result:Array = [];
- 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(input:Array, predicate:T->Bool):Null
- {
- 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(input:Array, 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