From c6a1f5ffeac51686b23b4764edeb569a173e910e Mon Sep 17 00:00:00 2001
From: EliteMasterEric <ericmyllyoja@gmail.com>
Date: Fri, 11 Aug 2023 14:00:38 -0400
Subject: [PATCH] Work on fixing issues with difficulty/variation handling in
 charts

---
 source/funkin/MainMenuState.hx                |  2 +-
 source/funkin/play/song/SongData.hx           | 17 ++++-----
 .../charting/ChartEditorDialogHandler.hx      |  7 +++-
 .../ui/debug/charting/ChartEditorState.hx     | 10 ++++--
 .../charting/ChartEditorToolboxHandler.hx     |  6 +++-
 source/funkin/util/SerializerUtil.hx          | 11 +++++-
 source/funkin/util/SortUtil.hx                | 35 ++++++++++++++++++-
 7 files changed, 70 insertions(+), 18 deletions(-)

diff --git a/source/funkin/MainMenuState.hx b/source/funkin/MainMenuState.hx
index fc493ef4b..5a69ea83a 100644
--- a/source/funkin/MainMenuState.hx
+++ b/source/funkin/MainMenuState.hx
@@ -54,7 +54,7 @@ class MainMenuState extends MusicBeatState
     transIn = FlxTransitionableState.defaultTransIn;
     transOut = FlxTransitionableState.defaultTransOut;
 
-    if (!FlxG.sound.music.playing)
+    if (!(FlxG?.sound?.music?.playing ?? false))
     {
       FlxG.sound.playMusic(Paths.music('freakyMenu/freakyMenu'));
     }
diff --git a/source/funkin/play/song/SongData.hx b/source/funkin/play/song/SongData.hx
index fbd7e3383..938ee0708 100644
--- a/source/funkin/play/song/SongData.hx
+++ b/source/funkin/play/song/SongData.hx
@@ -11,6 +11,7 @@ import haxe.DynamicAccess;
 import haxe.Json;
 import openfl.utils.Assets;
 import thx.semver.Version;
+import funkin.util.SerializerUtil;
 
 /**
  * Contains utilities for loading and parsing stage data.
@@ -138,13 +139,8 @@ class SongDataParser
   {
     var result:Array<SongMetadata> = [];
 
-    var rawJson:String = loadSongMetadataFile(songId);
-    var jsonData:Dynamic = null;
-    try
-    {
-      jsonData = Json.parse(rawJson);
-    }
-    catch (e) {}
+    var jsonStr:String = loadSongMetadataFile(songId);
+    var jsonData:Dynamic = SerializerUtil.fromJSON(jsonStr);
 
     var songMetadata:SongMetadata = SongMigrator.migrateSongMetadata(jsonData, songId);
     songMetadata = SongValidator.validateSongMetadata(songMetadata, songId);
@@ -160,9 +156,10 @@ class SongDataParser
 
     for (variation in variations)
     {
-      var variationRawJson:String = loadSongMetadataFile(songId, variation);
-      var variationSongMetadata:SongMetadata = SongMigrator.migrateSongMetadata(variationRawJson, '${songId}_${variation}');
-      variationSongMetadata = SongValidator.validateSongMetadata(variationSongMetadata, '${songId}_${variation}');
+      var variationJsonStr:String = loadSongMetadataFile(songId, variation);
+      var variationJsonData:Dynamic = SerializerUtil.fromJSON(variationJsonStr);
+      var variationSongMetadata:SongMetadata = SongMigrator.migrateSongMetadata(variationJsonData, '${songId}:${variation}');
+      variationSongMetadata = SongValidator.validateSongMetadata(variationSongMetadata, '${songId}:${variation}');
       if (variationSongMetadata != null)
       {
         variationSongMetadata.variation = variation;
diff --git a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx
index 6641a16c0..43e8ac96c 100644
--- a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx
+++ b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx
@@ -115,7 +115,12 @@ class ChartEditorDialogHandler
 
       if (songData == null) continue;
 
-      var songName:String = songData.getDifficulty().songName;
+      var songName:Null<String> = songData.getDifficulty('normal') ?.songName;
+      if (songName == null) songName = songData.getDifficulty() ?.songName;
+      if (songName == null)
+      {
+        trace('[WARN] Could not fetch song name for ${targetSongId}');
+      }
 
       var linkTemplateSong:Link = new Link();
       linkTemplateSong.text = songName;
diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx
index 26b001d7e..da8dd2d86 100644
--- a/source/funkin/ui/debug/charting/ChartEditorState.hx
+++ b/source/funkin/ui/debug/charting/ChartEditorState.hx
@@ -2804,7 +2804,10 @@ class ChartEditorState extends HaxeUIState
       var treeSong:TreeViewNode = treeView.addNode({id: 'stv_song', text: 'S: $currentSongName', icon: 'haxeui-core/styles/default/haxeui_tiny.png'});
       treeSong.expanded = true;
 
-      for (curVariation in availableVariations)
+      var variations = Reflect.copy(availableVariations);
+      variations.sort(SortUtil.alphabetically);
+
+      for (curVariation in variations)
       {
         var variationMetadata:SongMetadata = songMetadata.get(curVariation);
 
@@ -2940,13 +2943,14 @@ class ChartEditorState extends HaxeUIState
 
   function getCurrentTreeDifficultyNode():TreeViewNode
   {
-    var treeView:TreeView = findComponent('difficultyToolboxTree');
+    var difficultyToolbox:CollapsibleDialog = ChartEditorToolboxHandler.getToolbox(this, CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
+    if (difficultyToolbox == null) return null;
 
+    var treeView:TreeView = difficultyToolbox.findComponent('difficultyToolboxTree');
     if (treeView == null) return null;
 
     var result:TreeViewNode = treeView.findNodeByPath('stv_song/stv_variation_$selectedVariation/stv_difficulty_${selectedVariation}_$selectedDifficulty',
       'id');
-
     if (result == null) return null;
 
     return result;
diff --git a/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx b/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx
index 5cace2ff6..d35323f1b 100644
--- a/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx
+++ b/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx
@@ -489,6 +489,8 @@ class ChartEditorToolboxHandler
     };
     inputBPM.value = state.currentSongMetadata.timeChanges[0].bpm;
 
+    var labelScrollSpeed:Label = toolbox.findComponent('labelScrollSpeed', Label);
+
     var inputScrollSpeed:Slider = toolbox.findComponent('inputScrollSpeed', Slider);
     inputScrollSpeed.onChange = function(event:UIEvent) {
       var valid:Bool = event.target.value != null && event.target.value > 0;
@@ -502,8 +504,10 @@ class ChartEditorToolboxHandler
       {
         state.currentSongChartScrollSpeed = 1.0;
       }
+      labelScrollSpeed.text = 'Scroll Speed: ${state.currentSongChartScrollSpeed}x';
     };
-    inputScrollSpeed.value = state.currentSongChartData.scrollSpeed;
+    inputScrollSpeed.value = state.currentSongChartScrollSpeed;
+    labelScrollSpeed.text = 'Scroll Speed: ${state.currentSongChartScrollSpeed}x';
 
     return toolbox;
   }
diff --git a/source/funkin/util/SerializerUtil.hx b/source/funkin/util/SerializerUtil.hx
index 9452b7785..d731aee8f 100644
--- a/source/funkin/util/SerializerUtil.hx
+++ b/source/funkin/util/SerializerUtil.hx
@@ -36,7 +36,16 @@ class SerializerUtil
    */
   public static function fromJSON(input:String):Dynamic
   {
-    return Json.parse(input);
+    try
+    {
+      return Json.parse(input);
+    }
+    catch (e)
+    {
+      trace('An error occurred while parsing JSON from string data');
+      trace(e);
+      return null;
+    }
   }
 
   /**
diff --git a/source/funkin/util/SortUtil.hx b/source/funkin/util/SortUtil.hx
index 61418c299..082de4b41 100644
--- a/source/funkin/util/SortUtil.hx
+++ b/source/funkin/util/SortUtil.hx
@@ -30,8 +30,10 @@ class SortUtil
 
   /**
    * Sort predicate for sorting strings alphabetically.
+   * @param a The first string to compare.
+   * @param b The second string to compare.
    */
-  public static function alphabetically(a:String, b:String)
+  public static function alphabetically(a:String, b:String):Int
   {
     a = a.toUpperCase();
     b = b.toUpperCase();
@@ -39,4 +41,35 @@ class SortUtil
     // Sort alphabetically. Yes that's how this works.
     return a == b ? 0 : a > b ? 1 : -1;
   }
+
+  /**
+   * Sort predicate which sorts two strings alphabetically, but prioritizes a specific string first.
+   * Example usage: `array.sort(defaultThenAlphabetical.bind('test'))` will sort the array so that the string 'test' is first.
+   * @param a The first string to compare.
+   * @param b The second string to compare.
+   * @param defaultValue The value to prioritize. Defaults to `default`.
+   */
+  public static function defaultThenAlphabetically(a:String, b:String, defaultValue:String):Int
+  {
+    if (a == b) return 0;
+    if (a == defaultValue) return 1;
+    if (b == defaultValue) return -1;
+    return alphabetically(a, b);
+  }
+
+  /**
+   * Sort predicate which sorts two strings alphabetically, but prioritizes a specific string first.
+   * Example usage: `array.sort(defaultsThenAlphabetical.bind(['test']))` will sort the array so that the string 'test' is first.
+   * @param a The first string to compare.
+   * @param b The second string to compare.
+   * @param defaultValue The value to prioritize. Defaults to `default`.
+   */
+  public static function defaultsThenAlphabetically(a:String, b:String, defaultValues:Array<String>):Int
+  {
+    if (a == b) return 0;
+    if (defaultValues.contains(a) && defaultValues.contains(b)) return alphabetically(a, b);
+    if (defaultValues.contains(a)) return 1;
+    if (defaultValues.contains(b)) return -1;
+    return alphabetically(a, b);
+  }
 }