From 038d2582330b0edcbc82cc0be61da3d896a6edab Mon Sep 17 00:00:00 2001
From: EliteMasterEric <ericmyllyoja@gmail.com>
Date: Fri, 9 Jun 2023 15:28:14 -0400
Subject: [PATCH] Chart editor changes (support for legacy import)

---
 source/funkin/PauseSubState.hx                |  2 +-
 source/funkin/play/song/Song.hx               |  2 +-
 source/funkin/play/song/SongData.hx           |  4 +-
 source/funkin/play/song/SongMigrator.hx       | 63 +++++++++++--------
 source/funkin/play/song/SongSerializer.hx     |  8 +--
 source/funkin/play/song/formats/FNFLegacy.hx  |  4 +-
 .../charting/ChartEditorDialogHandler.hx      |  2 +-
 .../ui/debug/charting/ChartEditorState.hx     |  4 +-
 .../charting/ChartEditorToolboxHandler.hx     |  4 +-
 9 files changed, 52 insertions(+), 41 deletions(-)

diff --git a/source/funkin/PauseSubState.hx b/source/funkin/PauseSubState.hx
index 7349052aa..77fdfabf1 100644
--- a/source/funkin/PauseSubState.hx
+++ b/source/funkin/PauseSubState.hx
@@ -225,7 +225,7 @@ class PauseSubState extends MusicBeatSubState
 
             if (PlayStatePlaylist.isStoryMode) openSubState(new funkin.ui.StickerSubState(null, STORY));
             else
-              openSubState(new funkin.ui.StickerSubState());
+              openSubState(new funkin.ui.StickerSubState(null, FREEPLAY));
         }
       }
 
diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx
index 7de005cb0..22b5e8f07 100644
--- a/source/funkin/play/song/Song.hx
+++ b/source/funkin/play/song/Song.hx
@@ -47,7 +47,7 @@ class Song implements IPlayStateScriptedClass
     difficultyIds = [];
     difficulties = new Map<String, SongDifficulty>();
 
-    _metadata = SongDataParser.parseSongMetadata(songId);
+    _metadata = SongDataParser.loadSongMetadata(songId);
     if (_metadata == null || _metadata.length == 0)
     {
       throw 'Could not find song data for songId: $songId';
diff --git a/source/funkin/play/song/SongData.hx b/source/funkin/play/song/SongData.hx
index 2ae38156a..a6d19454f 100644
--- a/source/funkin/play/song/SongData.hx
+++ b/source/funkin/play/song/SongData.hx
@@ -377,7 +377,7 @@ abstract SongNoteData(RawSongNoteData)
   function get_stepTime():Float
   {
     // TODO: Account for changes in BPM.
-    return this.t / Conductor.stepLengthMs;
+    return this.t / Conductor.stepCrochet;
   }
 
   /**
@@ -563,7 +563,7 @@ abstract SongEventData(RawSongEventData)
   function get_stepTime():Float
   {
     // TODO: Account for changes in BPM.
-    return this.t / Conductor.stepLengthMs;
+    return this.t / Conductor.stepCrochet;
   }
 
   public var event(get, set):String;
diff --git a/source/funkin/play/song/SongMigrator.hx b/source/funkin/play/song/SongMigrator.hx
index 05adf2457..81500406d 100644
--- a/source/funkin/play/song/SongMigrator.hx
+++ b/source/funkin/play/song/SongMigrator.hx
@@ -103,11 +103,28 @@ class SongMigrator
    * @param songId The ID of the song (only used for error reporting).
    * @return The migrated song metadata, or null if the migration failed.
    */
-  public static function migrateSongMetadataFromLegacy(jsonData:Dynamic):SongMetadata
+  public static function migrateSongMetadataFromLegacy(jsonData:Dynamic, difficulty:String = 'normal'):SongMetadata
   {
     trace('Migrating song metadata from FNF Legacy.');
 
     var songData:FNFLegacy = cast jsonData;
+    // Some cleanup
+    if (Std.isOfType(jsonData.song.notes, Array))
+    {
+      jsonData.song.notes = haxe.ds.Either.Left(jsonData.song.notes);
+    }
+    else
+    {
+      jsonData.song.notes = haxe.ds.Either.Right(jsonData.song.notes);
+    }
+    if (Std.isOfType(jsonData.song.speed, Float))
+    {
+      jsonData.song.speed = haxe.ds.Either.Left(jsonData.song.speed);
+    }
+    else
+    {
+      jsonData.song.speed = haxe.ds.Either.Right(jsonData.song.speed);
+    }
 
     var songMetadata:SongMetadata = new SongMetadata('Import', 'Kawai Sprite', 'default');
 
@@ -152,9 +169,20 @@ class SongMigrator
     songMetadata.playData.difficulties = [];
     if (songData.song != null && songData.song.notes != null)
     {
-      if (songData.song.notes.easy != null) songMetadata.playData.difficulties.push('easy');
-      if (songData.song.notes.normal != null) songMetadata.playData.difficulties.push('normal');
-      if (songData.song.notes.hard != null) songMetadata.playData.difficulties.push('hard');
+      if (Std.isOfType(songData.song.notes, Array))
+      {
+        // One difficulty of notes.
+        songMetadata.playData.difficulties.push(difficulty);
+      }
+      else
+      {
+        // Multiple difficulties of notes.
+        var songNoteDataDynamic:haxe.DynamicAccess<Dynamic> = cast songData.song.notes;
+        for (difficultyKey in songNoteDataDynamic.keys())
+        {
+          songMetadata.playData.difficulties.push(difficultyKey);
+        }
+      }
     }
     else
     {
@@ -186,7 +214,7 @@ class SongMigrator
    * @param difficulty The difficulty to migrate.
    * @return The migrated song chart data, or null if the migration failed.
    */
-  public static function migrateSongChartDataFromLegacy(jsonData:Dynamic):SongChartData
+  public static function migrateSongChartDataFromLegacy(jsonData:Dynamic, difficulty:String = 'normal'):SongChartData
   {
     trace('Migrating song chart data from FNF Legacy.');
 
@@ -194,27 +222,10 @@ class SongMigrator
 
     var songChartData:SongChartData = new SongChartData(1.0, [], []);
 
-    if (songData.song.notes.normal != null)
-    {
-      var songEventsEmpty:Bool = songChartData.getEvents() == null || songChartData.getEvents().length == 0;
-      if (songEventsEmpty) songChartData.setEvents(migrateSongEventDataFromLegacy(songData.song.notes.normal));
-      songChartData.setNotes(migrateSongNoteDataFromLegacy(songData.song.notes.normal), 'normal');
-      songChartData.setScrollSpeed(songData.song.speed.normal, 'normal');
-    }
-    if (songData.song.notes.easy != null)
-    {
-      var songEventsEmpty:Bool = songChartData.getEvents() == null || songChartData.getEvents().length == 0;
-      if (songEventsEmpty) songChartData.setEvents(migrateSongEventDataFromLegacy(songData.song.notes.easy));
-      songChartData.setNotes(migrateSongNoteDataFromLegacy(songData.song.notes.easy), 'easy');
-      songChartData.setScrollSpeed(songData.song.speed.easy, 'easy');
-    }
-    if (songData.song.notes.hard != null)
-    {
-      var songEventsEmpty:Bool = songChartData.getEvents() == null || songChartData.getEvents().length == 0;
-      if (songEventsEmpty) songChartData.setEvents(migrateSongEventDataFromLegacy(songData.song.notes.hard));
-      songChartData.setNotes(migrateSongNoteDataFromLegacy(songData.song.notes.hard), 'hard');
-      songChartData.setScrollSpeed(songData.song.speed.hard, 'hard');
-    }
+    var songEventsEmpty:Bool = songChartData.getEvents() == null || songChartData.getEvents().length == 0;
+    if (songEventsEmpty) songChartData.setEvents(migrateSongEventDataFromLegacy(songData.song.notes));
+    songChartData.setNotes(migrateSongNoteDataFromLegacy(songData.song.notes), difficulty);
+    songChartData.setScrollSpeed(songData.song.speed, difficulty);
 
     return songChartData;
   }
diff --git a/source/funkin/play/song/SongSerializer.hx b/source/funkin/play/song/SongSerializer.hx
index 968a7a1f5..a08b722da 100644
--- a/source/funkin/play/song/SongSerializer.hx
+++ b/source/funkin/play/song/SongSerializer.hx
@@ -82,9 +82,9 @@ class SongSerializer
    * Save a SongChartData object as a JSON file to an automatically generated path.
    * Works great on HTML5 and desktop.
    */
-  public static function exportSongChartData(data:SongChartData)
+  public static function exportSongChartData(data:SongChartData, songId:String)
   {
-    var path = 'chart.json';
+    var path = '${songId}-chart.json';
     exportSongChartDataAs(path, data);
   }
 
@@ -92,9 +92,9 @@ class SongSerializer
    * Save a SongMetadata object as a JSON file to an automatically generated path.
    * Works great on HTML5 and desktop.
    */
-  public static function exportSongMetadata(data:SongMetadata)
+  public static function exportSongMetadata(data:SongMetadata, songId:String)
   {
-    var path = 'metadata.json';
+    var path = '${songId}-metadata.json';
     exportSongMetadataAs(path, data);
   }
 
diff --git a/source/funkin/play/song/formats/FNFLegacy.hx b/source/funkin/play/song/formats/FNFLegacy.hx
index 51dc602f3..a64e461bd 100644
--- a/source/funkin/play/song/formats/FNFLegacy.hx
+++ b/source/funkin/play/song/formats/FNFLegacy.hx
@@ -10,10 +10,10 @@ typedef LegacySongData =
   var player1:String; // Boyfriend
   var player2:String; // Opponent
 
-  var speed:LegacyScrollSpeeds;
+  var speed:Float;
   var stageDefault:String;
   var bpm:Float;
-  var notes:LegacyNoteData;
+  var notes:Array<LegacyNoteSection>;
   var song:String; // Song name
 };
 
diff --git a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx
index 4044df5d8..ec1b0754d 100644
--- a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx
+++ b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx
@@ -601,7 +601,7 @@ class ChartEditorDialogHandler
     {
       trace('Adding vocal upload for character ${charKey}');
       var charMetadata:CharacterData = CharacterDataParser.fetchCharacterData(charKey);
-      var charName:String = charMetadata.name;
+      var charName:String = charMetadata != null ? charMetadata.name : charKey;
 
       var vocalsEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT);
 
diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx
index 173e64d24..0769ee800 100644
--- a/source/funkin/ui/debug/charting/ChartEditorState.hx
+++ b/source/funkin/ui/debug/charting/ChartEditorState.hx
@@ -3367,10 +3367,10 @@ class ChartEditorState extends HaxeUIState
       audioVocalTrackGroup.clear();
     }
     // Add player vocals.
-    if (currentSongCharacterPlayer != null) audioVocalTrackGroup.addPlayerVocals(new FlxSound().loadEmbedded(Assets.getSound(Paths.voices(songId,
+    if (currentSongCharacterPlayer != null) audioVocalTrackGroup.addPlayerVoice(new FlxSound().loadEmbedded(Assets.getSound(Paths.voices(songId,
       '-$currentSongCharacterPlayer'))));
     // Add opponent vocals.
-    if (currentSongCharacterOpponent != null) audioVocalTrackGroup.addOpponentVocals(new FlxSound().loadEmbedded(Assets.getSound(Paths.voices(songId,
+    if (currentSongCharacterOpponent != null) audioVocalTrackGroup.addOpponentVoice(new FlxSound().loadEmbedded(Assets.getSound(Paths.voices(songId,
       '-$currentSongCharacterOpponent'))));
 
     postLoadInstrumental();
diff --git a/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx b/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx
index 9f976df6b..6784b9cfc 100644
--- a/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx
+++ b/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx
@@ -374,11 +374,11 @@ class ChartEditorToolboxHandler
     var difficultyToolboxLoadChart:Button = toolbox.findComponent('difficultyToolboxLoadChart', Button);
 
     difficultyToolboxSaveMetadata.onClick = function(event:UIEvent) {
-      SongSerializer.exportSongMetadata(state.currentSongMetadata);
+      SongSerializer.exportSongMetadata(state.currentSongMetadata, state);
     };
 
     difficultyToolboxSaveChart.onClick = function(event:UIEvent) {
-      SongSerializer.exportSongChartData(state.currentSongChartData);
+      SongSerializer.exportSongChartData(state.currentSongChartData, state);
     };
 
     difficultyToolboxSaveAll.onClick = function(event:UIEvent) {