From a523b824397aa80240fc4be706542496e73af02f Mon Sep 17 00:00:00 2001
From: Mike Welsh <mwelsh@gmail.com>
Date: Fri, 16 Feb 2024 20:48:43 -0800
Subject: [PATCH] Fix songs failing to load on HTML5 target

Do a small refactor of `LoadingState` to fix loading songs on the
when `NO_PRELOAD_ALL` is defined.

This allows the HTML5 target to progress into song gameplay again.
---
 source/funkin/InitState.hx                  | 12 ++--
 source/funkin/play/PlayState.hx             | 49 +++++++---------
 source/funkin/ui/freeplay/FreeplayState.hx  |  8 +--
 source/funkin/ui/story/StoryMenuState.hx    | 11 +---
 source/funkin/ui/transition/LoadingState.hx | 62 ++++++++++++++-------
 5 files changed, 70 insertions(+), 72 deletions(-)

diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx
index 399f52498..8072a847a 100644
--- a/source/funkin/InitState.hx
+++ b/source/funkin/InitState.hx
@@ -302,15 +302,11 @@ class InitState extends FlxState
       return;
     }
 
-    // Load and cache the song's charts.
-    // TODO: Do this in the loading state.
-    songData.cacheCharts(true);
-
-    LoadingState.loadAndSwitchState(() -> new funkin.play.PlayState(
+    LoadingState.loadPlayState(
       {
         targetSong: songData,
         targetDifficulty: difficultyId,
-      }));
+      });
   }
 
   /**
@@ -336,11 +332,11 @@ class InitState extends FlxState
 
     var targetSong:funkin.play.song.Song = SongRegistry.instance.fetchEntry(targetSongId);
 
-    LoadingState.loadAndSwitchState(() -> new funkin.play.PlayState(
+    LoadingState.loadPlayState(
       {
         targetSong: targetSong,
         targetDifficulty: difficultyId,
-      }));
+      });
   }
 
   function defineSong():String
diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx
index 66e8e0dcc..862e1ee43 100644
--- a/source/funkin/play/PlayState.hx
+++ b/source/funkin/play/PlayState.hx
@@ -123,6 +123,11 @@ typedef PlayStateParams =
    * and must be loaded externally.
    */
   ?overrideMusic:Bool,
+  /**
+   * The initial camera follow point.
+   * Used to persist the position of the `cameraFollowPosition` between levels.
+   */
+  ?cameraFollowPoint:FlxPoint,
 }
 
 /**
@@ -216,7 +221,7 @@ class PlayState extends MusicBeatSubState
    * The camera follow point from the last stage.
    * Used to persist the position of the `cameraFollowPosition` between levels.
    */
-  public var previousCameraFollowPoint:FlxSprite = null;
+  public var previousCameraFollowPoint:FlxPoint = null;
 
   /**
    * The current camera zoom level.
@@ -544,6 +549,7 @@ class PlayState extends MusicBeatSubState
     isMinimalMode = params.minimalMode ?? false;
     startTimestamp = params.startTimestamp ?? 0.0;
     overrideMusic = params.overrideMusic ?? false;
+    previousCameraFollowPoint = params.cameraFollowPoint;
 
     // Don't do anything else here! Wait until create() when we attach to the camera.
   }
@@ -2621,38 +2627,25 @@ class PlayState extends MusicBeatSubState
           FlxG.sound.play(Paths.sound('Lights_Shut_off'), function() {
             // no camFollow so it centers on horror tree
             var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId);
-            // Load and cache the song's charts.
-            // TODO: Do this in the loading state.
-            targetSong.cacheCharts(true);
-
-            LoadingState.loadAndSwitchState(() -> {
-              var nextPlayState:PlayState = new PlayState(
-                {
-                  targetSong: targetSong,
-                  targetDifficulty: PlayStatePlaylist.campaignDifficulty,
-                  targetVariation: currentVariation,
-                });
-              nextPlayState.previousCameraFollowPoint = new FlxSprite(cameraFollowPoint.x, cameraFollowPoint.y);
-              return nextPlayState;
-            });
+            LoadingState.loadPlayState(
+              {
+                targetSong: targetSong,
+                targetDifficulty: PlayStatePlaylist.campaignDifficulty,
+                targetVariation: currentVariation,
+                cameraFollowPoint: cameraFollowPoint.getPosition(),
+              });
           });
         }
         else
         {
           var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId);
-          // Load and cache the song's charts.
-          // TODO: Do this in the loading state.
-          targetSong.cacheCharts(true);
-          LoadingState.loadAndSwitchState(() -> {
-            var nextPlayState:PlayState = new PlayState(
-              {
-                targetSong: targetSong,
-                targetDifficulty: PlayStatePlaylist.campaignDifficulty,
-                targetVariation: currentVariation,
-              });
-            nextPlayState.previousCameraFollowPoint = new FlxSprite(cameraFollowPoint.x, cameraFollowPoint.y);
-            return nextPlayState;
-          });
+          LoadingState.loadPlayState(
+            {
+              targetSong: targetSong,
+              targetDifficulty: PlayStatePlaylist.campaignDifficulty,
+              targetVariation: currentVariation,
+              cameraFollowPoint: cameraFollowPoint.getPosition(),
+            });
         }
       }
     }
diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx
index e7c615313..39cab8759 100644
--- a/source/funkin/ui/freeplay/FreeplayState.hx
+++ b/source/funkin/ui/freeplay/FreeplayState.hx
@@ -1135,18 +1135,14 @@ class FreeplayState extends MusicBeatSubState
     FlxG.sound.play(Paths.sound('confirmMenu'));
     dj.confirm();
 
-    // Load and cache the song's charts.
-    // TODO: Do this in the loading state.
-    targetSong.cacheCharts(true);
-
     new FlxTimer().start(1, function(tmr:FlxTimer) {
       Paths.setCurrentLevel(cap.songData.levelId);
-      LoadingState.loadAndSwitchState(() -> new PlayState(
+      LoadingState.loadPlayState(
         {
           targetSong: targetSong,
           targetDifficulty: targetDifficulty,
           targetVariation: targetVariation,
-        }), true);
+        }, true);
     });
   }
 
diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx
index 1905a7c57..54e16e917 100644
--- a/source/funkin/ui/story/StoryMenuState.hx
+++ b/source/funkin/ui/story/StoryMenuState.hx
@@ -554,22 +554,15 @@ class StoryMenuState extends MusicBeatState
     PlayStatePlaylist.campaignTitle = currentLevel.getTitle();
     PlayStatePlaylist.campaignDifficulty = currentDifficultyId;
 
-    if (targetSong != null)
-    {
-      // Load and cache the song's charts.
-      // TODO: Do this in the loading state.
-      targetSong.cacheCharts(true);
-    }
-
     new FlxTimer().start(1, function(tmr:FlxTimer) {
       FlxTransitionableState.skipNextTransIn = false;
       FlxTransitionableState.skipNextTransOut = false;
 
-      LoadingState.loadAndSwitchState(() -> new PlayState(
+      LoadingState.loadPlayState(
         {
           targetSong: targetSong,
           targetDifficulty: PlayStatePlaylist.campaignDifficulty,
-        }), true);
+        }, true);
     });
   }
 
diff --git a/source/funkin/ui/transition/LoadingState.hx b/source/funkin/ui/transition/LoadingState.hx
index 63dcb8f68..86f443d1d 100644
--- a/source/funkin/ui/transition/LoadingState.hx
+++ b/source/funkin/ui/transition/LoadingState.hx
@@ -9,7 +9,6 @@ import funkin.graphics.shaders.ScreenWipeShader;
 import funkin.play.PlayState;
 import funkin.play.PlayStatePlaylist;
 import funkin.play.song.Song.SongDifficulty;
-import funkin.ui.mainmenu.MainMenuState;
 import funkin.ui.MusicBeatState;
 import haxe.io.Path;
 import funkin.graphics.FunkinSprite;
@@ -27,17 +26,19 @@ class LoadingState extends MusicBeatState
   inline static var MIN_TIME = 1.0;
 
   var target:NextState;
-  var stopMusic = false;
+  var playParams:Null<PlayStateParams>;
+  var stopMusic:Bool = false;
   var callbacks:MultiCallback;
-  var danceLeft = false;
+  var danceLeft:Bool = false;
 
   var loadBar:FlxSprite;
   var funkay:FlxSprite;
 
-  function new(target:NextState, stopMusic:Bool)
+  function new(target:NextState, stopMusic:Bool, playParams:Null<PlayStateParams> = null)
   {
     super();
     this.target = target;
+    this.playParams = playParams;
     this.stopMusic = stopMusic;
   }
 
@@ -62,10 +63,18 @@ class LoadingState extends MusicBeatState
       callbacks = new MultiCallback(onLoad);
       var introComplete = callbacks.add('introComplete');
 
-      if (Std.isOfType(target, PlayState))
+      if (playParams != null)
       {
-        var targetPlayState:PlayState = cast target;
-        var targetChart:SongDifficulty = targetPlayState.currentChart;
+        // Load and cache the song's charts.
+        if (playParams.targetSong != null)
+        {
+          playParams.targetSong.cacheCharts(true);
+        }
+
+        // Preload the song for the play state.
+        var difficulty:String = playParams.targetDifficulty ?? Constants.DEFAULT_DIFFICULTY;
+        var variation:String = playParams.targetVariation ?? Constants.DEFAULT_VARIATION;
+        var targetChart:SongDifficulty = playParams.targetSong?.getDifficulty(difficulty, variation);
         var instPath:String = Paths.inst(targetChart.song.id);
         var voicesPaths:Array<String> = targetChart.buildVoiceList();
 
@@ -172,25 +181,36 @@ class LoadingState extends MusicBeatState
     return Paths.inst(PlayState.instance.currentSong.id);
   }
 
-  inline static public function loadAndSwitchState(nextState:NextState, shouldStopMusic = false):Void
-  {
-    FlxG.switchState(getNextState(nextState, shouldStopMusic));
-  }
-
-  static function getNextState(nextState:NextState, shouldStopMusic = false):NextState
+  /**
+   * Starts the transition to a new `PlayState` to start a new song.
+   * First switches to the `LoadingState` if assets need to be loaded.
+   * @param params The parameters for the next `PlayState`.
+   * @param shouldStopMusic Whether to stop the current music while loading.
+   */
+  public static function loadPlayState(params:PlayStateParams, shouldStopMusic = false):Void
   {
     Paths.setCurrentLevel(PlayStatePlaylist.campaignId);
+    var playStateCtor:NextState = () -> new PlayState(params);
 
     #if NO_PRELOAD_ALL
-    // var loaded = isSoundLoaded(getSongPath())
-    //  && (!PlayState.currentSong.needsVoices || isSoundLoaded(getVocalPath()))
-    //  && isLibraryLoaded('shared');
-    //
-    if (true) return () -> new LoadingState(nextState, shouldStopMusic);
-    #end
-    if (shouldStopMusic && FlxG.sound.music != null) FlxG.sound.music.stop();
+    // Switch to loading state while we load assets (default on HTML5 target).
+    var loadStateCtor:NextState = () -> new LoadingState(playStateCtor, shouldStopMusic, params);
+    FlxG.switchState(loadStateCtor);
+    #else
+    // All assets preloaded, switch directly to play state (defualt on other targets).
+    if (shouldStopMusic && FlxG.sound.music != null)
+    {
+      FlxG.sound.music.stop();
+    }
 
-    return nextState;
+    // Load and cache the song's charts.
+    if (params?.targetSong != null)
+    {
+      params.targetSong.cacheCharts(true);
+    }
+
+    FlxG.switchState(playStateCtor);
+    #end
   }
 
   #if NO_PRELOAD_ALL