From 6fc5e8cc2f7e5c9855be1950a75be6b0c4c15c05 Mon Sep 17 00:00:00 2001
From: EliteMasterEric <ericmyllyoja@gmail.com>
Date: Thu, 9 May 2024 22:36:39 -0400
Subject: [PATCH 1/6] Fixed up some metadata

---
 example_mods/introMod/_polymod_meta.json   |   2 +-
 example_mods/testing123/_polymod_meta.json |   2 +-
 tests/unit/assets/shared/images/arrows.png | Bin 4806 -> 0 bytes
 tests/unit/assets/shared/images/arrows.xml |  27 ---------------------
 4 files changed, 2 insertions(+), 29 deletions(-)
 delete mode 100644 tests/unit/assets/shared/images/arrows.png
 delete mode 100644 tests/unit/assets/shared/images/arrows.xml

diff --git a/example_mods/introMod/_polymod_meta.json b/example_mods/introMod/_polymod_meta.json
index e0b03f1cd..4dc0cd804 100644
--- a/example_mods/introMod/_polymod_meta.json
+++ b/example_mods/introMod/_polymod_meta.json
@@ -3,7 +3,7 @@
   "description": "An introductory mod.",
   "contributors": [
     {
-      "name": "MasterEric"
+      "name": "EliteMasterEric"
     }
   ],
   "api_version": "0.1.0",
diff --git a/example_mods/testing123/_polymod_meta.json b/example_mods/testing123/_polymod_meta.json
index 4c0f177f9..0a2ed042c 100644
--- a/example_mods/testing123/_polymod_meta.json
+++ b/example_mods/testing123/_polymod_meta.json
@@ -3,7 +3,7 @@
   "description": "Newgrounds? More like OLDGROUNDS lol.",
   "contributors": [
     {
-      "name": "MasterEric"
+      "name": "EliteMasterEric"
     }
   ],
   "api_version": "0.1.0",
diff --git a/tests/unit/assets/shared/images/arrows.png b/tests/unit/assets/shared/images/arrows.png
deleted file mode 100644
index a443684327b409e3297d9456cb28afddd889b499..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 4806
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
Iso@a*KZ!k4q5uE@

diff --git a/tests/unit/assets/shared/images/arrows.xml b/tests/unit/assets/shared/images/arrows.xml
deleted file mode 100644
index 96a73a388..000000000
--- a/tests/unit/assets/shared/images/arrows.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<TextureAtlas imagePath="arrows.png">
-	<SubTexture name="staticLeft0001" x="0" y="0" width="17" height="17" />
-	<SubTexture name="staticDown0001" x="17" y="0" width="17" height="17" />
-	<SubTexture name="staticUp0001" x="34" y="0" width="17" height="17" />
-	<SubTexture name="staticRight0001" x="51" y="0" width="17" height="17" />
-	<SubTexture name="noteLeft0001" x="0" y="17" width="17" height="17" />
-	<SubTexture name="noteDown0001" x="17" y="17" width="17" height="17" />
-	<SubTexture name="noteUp0001" x="34" y="17" width="17" height="17" />
-	<SubTexture name="noteRight0001" x="51" y="17" width="17" height="17" />
-	<SubTexture name="pressedLeft0001" x="0" y="17" width="17" height="17" />
-	<SubTexture name="pressedDown0001" x="17" y="17" width="17" height="17" />
-	<SubTexture name="pressedUp0001" x="34" y="17" width="17" height="17" />
-	<SubTexture name="pressedRight0001" x="51" y="17" width="17" height="17" />
-	<SubTexture name="pressedLeft0002" x="0" y="34" width="17" height="17" />
-	<SubTexture name="pressedDown0002" x="17" y="34" width="17" height="17" />
-	<SubTexture name="pressedUp0002" x="34" y="34" width="17" height="17" />
-	<SubTexture name="pressedRight0002" x="51" y="34" width="17" height="17" />
-	<SubTexture name="confirmLeft0001" x="0" y="51" width="17" height="17" />
-	<SubTexture name="confirmDown0001" x="17" y="51" width="17" height="17" />
-	<SubTexture name="confirmUp0001" x="34" y="51" width="17" height="17" />
-	<SubTexture name="confirmRight0001" x="51" y="51" width="17" height="17" />
-	<SubTexture name="confirmLeft0002" x="0" y="68" width="17" height="17" />
-	<SubTexture name="confirmDown0002" x="17" y="68" width="17" height="17" />
-	<SubTexture name="confirmUp0002" x="34" y="68" width="17" height="17" />
-	<SubTexture name="confirmRight0002" x="51" y="68" width="17" height="17" />
-</TextureAtlas>

From a243b167b2b1a76c70a7b68296d7e27f4410cff4 Mon Sep 17 00:00:00 2001
From: EliteMasterEric <ericmyllyoja@gmail.com>
Date: Thu, 9 May 2024 22:37:01 -0400
Subject: [PATCH 2/6] Fix up more credits

---
 source/funkin/play/components/HealthIcon.hx         | 2 +-
 source/funkin/ui/debug/charting/ChartEditorState.hx | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/source/funkin/play/components/HealthIcon.hx b/source/funkin/play/components/HealthIcon.hx
index 957daa43c..2d7099e8a 100644
--- a/source/funkin/play/components/HealthIcon.hx
+++ b/source/funkin/play/components/HealthIcon.hx
@@ -24,7 +24,7 @@ import funkin.util.MathUtil;
  *     - i.e. `PlayState.instance.iconP1.playAnimation("losing")`
  *   - Scripts can also utilize all functionality that a normal FlxSprite would have access to, such as adding supplimental animations.
  *     - i.e. `PlayState.instance.iconP1.animation.addByPrefix("jumpscare", "jumpscare", 24, false);`
- * @author MasterEric
+ * @author EliteMasterEric
  */
 @:nullSafety
 class HealthIcon extends FunkinSprite
diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx
index b75cd8bf1..a313981f4 100644
--- a/source/funkin/ui/debug/charting/ChartEditorState.hx
+++ b/source/funkin/ui/debug/charting/ChartEditorState.hx
@@ -137,7 +137,7 @@ using Lambda;
  *
  * Some functionality is split into handler classes to help maintain my sanity.
  *
- * @author MasterEric
+ * @author EliteMasterEric
  */
 // @:nullSafety
 

From 5d5cf740204ac0b57712483a5e2a9fb0491b6eba Mon Sep 17 00:00:00 2001
From: EliteMasterEric <ericmyllyoja@gmail.com>
Date: Thu, 9 May 2024 22:37:21 -0400
Subject: [PATCH 3/6] Reimplement rank-based results animations.

---
 README.md                         |   2 +-
 assets                            |   2 +-
 source/funkin/play/ResultState.hx | 322 +++++++++++++++++++++---------
 source/funkin/util/Constants.hx   |  11 +
 4 files changed, 240 insertions(+), 97 deletions(-)

diff --git a/README.md b/README.md
index 39c098af5..5728a6cb3 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ Please check out our [Contributor's guide](./CONTRIBUTORS.md) on how you can act
 
 ## Programming
 - [ninjamuffin99](https://twitter.com/ninja_muffin99) - Lead Programmer
-- [MasterEric](https://twitter.com/EliteMasterEric) - Programmer
+- [EliteMasterEric](https://twitter.com/EliteMasterEric) - Programmer
 - [MtH](https://twitter.com/emmnyaa) - Charting and Additional Programming
 - [GeoKureli](https://twitter.com/Geokureli/) - Additional Programming
 - Our contributors on GitHub
diff --git a/assets b/assets
index fe52d20de..6115eb683 160000
--- a/assets
+++ b/assets
@@ -1 +1 @@
-Subproject commit fe52d20de7025d90cadb429dbdedf6d986727088
+Subproject commit 6115eb6837e97b8b3ad82f3ccd2a49a4383ed35b
diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx
index 56dd1e80f..7f8bdd77a 100644
--- a/source/funkin/play/ResultState.hx
+++ b/source/funkin/play/ResultState.hx
@@ -26,6 +26,7 @@ import funkin.play.components.TallyCounter;
 /**
  * The state for the results screen after a song or week is finished.
  */
+@:nullSafety
 class ResultState extends MusicBeatSubState
 {
   final params:ResultsStateParams;
@@ -42,91 +43,45 @@ class ResultState extends MusicBeatSubState
     super();
 
     this.params = params;
+
+    resultsVariation = calculateVariation(params);
+
+    var fontLetters:String = "AaBbCcDdEeFfGgHhiIJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz:1234567890";
+    songName = new FlxBitmapText(FlxBitmapFont.fromMonospace(Paths.image("resultScreen/tardlingSpritesheet"), fontLetters, FlxPoint.get(49, 62)));
+    songName.text = params.title;
+    songName.letterSpacing = -15;
+    songName.angle = -4.4;
+    songName.zIndex = 1000;
+
+    difficulty = new FlxSprite(555);
+    difficulty.zIndex = 1000;
   }
 
   override function create():Void
   {
-    /*
-      if (params.scoreData.sick == params.scoreData.totalNotesHit
-        && params.scoreData.maxCombo == params.scoreData.totalNotesHit) resultsVariation = PERFECT;
-      else if (params.scoreData.missed + params.scoreData.bad + params.scoreData.shit >= params.scoreData.totalNotes * 0.50)
-        resultsVariation = SHIT; // if more than half of your song was missed, bad, or shit notes, you get shit ending!
-      else
-        resultsVariation = NORMAL;
-     */
-    resultsVariation = NORMAL;
-
-    FunkinSound.playMusic('results$resultsVariation',
+    FunkinSound.playMusic(resultsVariation.getMusicPath(),
       {
         startingVolume: 1.0,
         overrideExisting: true,
         restartTrack: true,
-        loop: resultsVariation != SHIT
+        loop: resultsVariation.shouldMusicLoop()
       });
 
     // Reset the camera zoom on the results screen.
     FlxG.camera.zoom = 1.0;
 
-    // TEMP-ish, just used to sorta "cache" the 3000x3000 image!
-    var cacheBullShit:FlxSprite = new FlxSprite().loadGraphic(Paths.image("resultScreen/soundSystem"));
-    add(cacheBullShit);
-
-    var dumb:FlxSprite = new FlxSprite().loadGraphic(Paths.image("resultScreen/scorePopin"));
-    add(dumb);
-
     var bg:FlxSprite = FlxGradient.createGradientFlxSprite(FlxG.width, FlxG.height, [0xFFFECC5C, 0xFFFDC05C], 90);
     bg.scrollFactor.set();
+    bg.zIndex = 10;
     add(bg);
 
     var bgFlash:FlxSprite = FlxGradient.createGradientFlxSprite(FlxG.width, FlxG.height, [0xFFFFEB69, 0xFFFFE66A], 90);
     bgFlash.scrollFactor.set();
     bgFlash.visible = false;
+    bg.zIndex = 20;
     add(bgFlash);
 
-    // var bfGfExcellent:FlxAtlasSprite = new FlxAtlasSprite(380, -170, Paths.animateAtlas("resultScreen/resultsBoyfriendExcellent", "shared"));
-    // bfGfExcellent.visible = false;
-    // add(bfGfExcellent);
-    //
-    // var bfPerfect:FlxAtlasSprite = new FlxAtlasSprite(370, -180, Paths.animateAtlas("resultScreen/resultsBoyfriendPerfect", "shared"));
-    // bfPerfect.visible = false;
-    // add(bfPerfect);
-    //
-    // var bfSHIT:FlxAtlasSprite = new FlxAtlasSprite(0, 20, Paths.animateAtlas("resultScreen/resultsBoyfriendSHIT", "shared"));
-    // bfSHIT.visible = false;
-    // add(bfSHIT);
-    //
-    // bfGfExcellent.anim.onComplete = () -> {
-    // bfGfExcellent.anim.curFrame = 28;
-    // bfGfExcellent.anim.play(); // unpauses this anim, since it's on PlayOnce!
-    // };
-    //
-    // bfPerfect.anim.onComplete = () -> {
-    //  bfPerfect.anim.curFrame = 136;
-    //  bfPerfect.anim.play(); // unpauses this anim, since it's on PlayOnce!
-    // };
-    //
-    // bfSHIT.anim.onComplete = () -> {
-    //  bfSHIT.anim.curFrame = 150;
-    //  bfSHIT.anim.play(); // unpauses this anim, since it's on PlayOnce!
-    // };
-
-    var gf:FlxSprite = FunkinSprite.createSparrow(625, 325, 'resultScreen/resultGirlfriendGOOD');
-    gf.animation.addByPrefix("clap", "Girlfriend Good Anim", 24, false);
-    gf.visible = false;
-    gf.animation.finishCallback = _ -> {
-      gf.animation.play('clap', true, false, 9);
-    };
-    add(gf);
-
-    var boyfriend:FlxSprite = FunkinSprite.createSparrow(640, -200, 'resultScreen/resultBoyfriendGOOD');
-    boyfriend.animation.addByPrefix("fall", "Boyfriend Good Anim0", 24, false);
-    boyfriend.visible = false;
-    boyfriend.animation.finishCallback = function(_) {
-      boyfriend.animation.play('fall', true, false, 14);
-    };
-
-    add(boyfriend);
-
+    // The sound system which falls into place behind the score text. Plays every time!
     var soundSystem:FlxSprite = FunkinSprite.createSparrow(-15, -180, 'resultScreen/soundSystem');
     soundSystem.animation.addByPrefix("idle", "sound system", 24, false);
     soundSystem.visible = false;
@@ -134,9 +89,66 @@ class ResultState extends MusicBeatSubState
       soundSystem.animation.play("idle");
       soundSystem.visible = true;
     });
+    soundSystem.zIndex = 1100;
     add(soundSystem);
 
-    difficulty = new FlxSprite(555);
+    var bfPerfect:Null<FlxAtlasSprite> = null;
+    var bfExcellent:Null<FlxAtlasSprite> = null;
+    var bfGood:Null<FlxSprite> = null;
+    var gfGood:Null<FlxSprite> = null;
+    var bfShit:Null<FlxAtlasSprite> = null;
+
+    switch (resultsVariation)
+    {
+      case PERFECT | PERFECT_GOLD | PERFECT_PLATINUM:
+        bfPerfect = new FlxAtlasSprite(370, -180, Paths.animateAtlas("resultScreen/results-bf/resultsPERFECT", "shared"));
+        bfPerfect.visible = false;
+        bfPerfect.zIndex = 500;
+        add(bfPerfect);
+
+        bfPerfect.anim.onComplete = () -> {
+          bfPerfect.anim.curFrame = 136;
+          bfPerfect.anim.play(); // unpauses this anim, since it's on PlayOnce!
+        };
+
+      case EXCELLENT:
+        bfExcellent = new FlxAtlasSprite(380, -170, Paths.animateAtlas("resultScreen/results-bf/resultsEXCELLENT", "shared"));
+        bfExcellent.visible = false;
+        bfExcellent.zIndex = 500;
+        add(bfExcellent);
+
+        bfExcellent.onAnimationFinish.add((animName) -> {
+          bfExcellent.playAnimation('Loop Start');
+        });
+
+      case GOOD | GREAT:
+        gfGood = FunkinSprite.createSparrow(625, 325, 'resultScreen/results-bf/resultsGOOD/resultGirlfriendGOOD');
+        gfGood.animation.addByPrefix("clap", "Girlfriend Good Anim", 24, false);
+        gfGood.visible = false;
+        gfGood.zIndex = 500;
+        gfGood.animation.finishCallback = _ -> {
+          gfGood.animation.play('clap', true, false, 9);
+        };
+        add(gfGood);
+
+        bfGood = FunkinSprite.createSparrow(640, -200, 'resultScreen/results-bf/resultsGOOD/resultBoyfriendGOOD');
+        bfGood.animation.addByPrefix("fall", "Boyfriend Good Anim0", 24, false);
+        bfGood.visible = false;
+        bfGood.zIndex = 501;
+        bfGood.animation.finishCallback = function(_) {
+          bfGood.animation.play('fall', true, false, 14);
+        };
+        add(bfGood);
+
+      case SHIT:
+        bfShit = new FlxAtlasSprite(0, 20, Paths.animateAtlas("resultScreen/results-bf/resultsSHIT", "shared"));
+        bfShit.visible = false;
+        bfShit.zIndex = 500;
+        add(bfShit);
+        bfShit.onAnimationFinish.add((animName) -> {
+          bfShit.playAnimation('Loop Start');
+        });
+    }
 
     var diffSpr:String = switch (PlayState.instance.currentDifficulty)
     {
@@ -157,11 +169,6 @@ class ResultState extends MusicBeatSubState
     difficulty.loadGraphic(Paths.image("resultScreen/" + diffSpr));
     add(difficulty);
 
-    var fontLetters:String = "AaBbCcDdEeFfGgHhiIJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz:1234567890";
-    songName = new FlxBitmapText(FlxBitmapFont.fromMonospace(Paths.image("resultScreen/tardlingSpritesheet"), fontLetters, FlxPoint.get(49, 62)));
-    songName.text = params.title;
-    songName.letterSpacing = -15;
-    songName.angle = -4.4;
     add(songName);
 
     var angleRad = songName.angle * Math.PI / 180;
@@ -179,21 +186,25 @@ class ResultState extends MusicBeatSubState
     var blackTopBar:FlxSprite = new FlxSprite().loadGraphic(Paths.image("resultScreen/topBarBlack"));
     blackTopBar.y = -blackTopBar.height;
     FlxTween.tween(blackTopBar, {y: 0}, 0.4, {ease: FlxEase.quartOut, startDelay: 0.5});
+    blackTopBar.zIndex = 1010;
     add(blackTopBar);
 
     var resultsAnim:FunkinSprite = FunkinSprite.createSparrow(-200, -10, "resultScreen/results");
     resultsAnim.animation.addByPrefix("result", "results instance 1", 24, false);
     resultsAnim.animation.play("result");
+    resultsAnim.zIndex = 1200;
     add(resultsAnim);
 
     var ratingsPopin:FunkinSprite = FunkinSprite.createSparrow(-150, 120, "resultScreen/ratingsPopin");
     ratingsPopin.animation.addByPrefix("idle", "Categories", 24, false);
     ratingsPopin.visible = false;
+    ratingsPopin.zIndex = 1200;
     add(ratingsPopin);
 
     var scorePopin:FunkinSprite = FunkinSprite.createSparrow(-180, 520, "resultScreen/scorePopin");
     scorePopin.animation.addByPrefix("score", "tally score", 24, false);
     scorePopin.visible = false;
+    scorePopin.zIndex = 1200;
     add(scorePopin);
 
     var highscoreNew:FlxSprite = new FlxSprite(310, 570);
@@ -202,11 +213,13 @@ class ResultState extends MusicBeatSubState
     highscoreNew.visible = false;
     highscoreNew.setGraphicSize(Std.int(highscoreNew.width * 0.8));
     highscoreNew.updateHitbox();
+    highscoreNew.zIndex = 1200;
     add(highscoreNew);
 
     var hStuf:Int = 50;
 
     var ratingGrp:FlxTypedGroup<TallyCounter> = new FlxTypedGroup<TallyCounter>();
+    ratingGrp.zIndex = 1200;
     add(ratingGrp);
 
     /**
@@ -238,6 +251,7 @@ class ResultState extends MusicBeatSubState
 
     var score:ResultScore = new ResultScore(35, 305, 10, params.scoreData.score);
     score.visible = false;
+    score.zIndex = 1200;
     add(score);
 
     for (ind => rating in ratingGrp.members)
@@ -275,40 +289,72 @@ class ResultState extends MusicBeatSubState
 
       switch (resultsVariation)
       {
-        // case SHIT:
-        // bfSHIT.visible = true;
-        // bfSHIT.playAnimation("");
+        case PERFECT | PERFECT_GOLD | PERFECT_PLATINUM:
+          if (bfPerfect == null)
+          {
+            trace("Could not build PERFECT animation!");
+          }
+          else
+          {
+            bfPerfect.visible = true;
+            bfPerfect.playAnimation('');
+          }
 
-        case NORMAL:
-          boyfriend.animation.play('fall');
-          boyfriend.visible = true;
+        case EXCELLENT:
+          if (bfExcellent == null)
+          {
+            trace("Could not build EXCELLENT animation!");
+          }
+          else
+          {
+            bfExcellent.visible = true;
+            bfExcellent.playAnimation('Intro');
+          }
 
-          new FlxTimer().start((1 / 24) * 12, _ -> {
-            bgFlash.visible = true;
-            FlxTween.tween(bgFlash, {alpha: 0}, 0.4);
-            new FlxTimer().start((1 / 24) * 2, _ ->
-              {
-                // bgFlash.alpha = 0.5;
+        case SHIT:
+          if (bfShit == null)
+          {
+            trace("Could not build SHIT animation!");
+          }
+          else
+          {
+            bfShit.visible = true;
+            bfShit.playAnimation('Intro');
+          }
 
-                // bgFlash.visible = false;
-              });
-          });
+        case GREAT | GOOD:
+          if (bfGood == null || gfGood == null)
+          {
+            trace("Could not build GOOD animation!");
+          }
+          else
+          {
+            bfGood.animation.play('fall');
+            bfGood.visible = true;
 
-          new FlxTimer().start((1 / 24) * 22, _ -> {
-            // plays about 22 frames (at 24fps timing) after bf spawns in
-            gf.animation.play('clap', true);
-            gf.visible = true;
-          });
-        // case PERFECT:
-        //          bfPerfect.visible = true;
-        //          bfPerfect.playAnimation("");
+            new FlxTimer().start((1 / 24) * 12, _ -> {
+              bgFlash.visible = true;
+              FlxTween.tween(bgFlash, {alpha: 0}, 0.4);
+              new FlxTimer().start((1 / 24) * 2, _ ->
+                {
+                  // bgFlash.alpha = 0.5;
 
-        // bfGfExcellent.visible = true;
-        // bfGfExcellent.playAnimation("");
+                  // bgFlash.visible = false;
+                });
+            });
+
+            new FlxTimer().start((1 / 24) * 22, _ -> {
+              // plays about 22 frames (at 24fps timing) after bf spawns in
+              gfGood.animation.play('clap', true);
+              gfGood.visible = true;
+            });
+          }
         default:
       }
     });
 
+    refresh();
+
     super.create();
   }
 
@@ -401,14 +447,100 @@ class ResultState extends MusicBeatSubState
 
     super.update(elapsed);
   }
+
+  public static function calculateVariation(params:ResultsStateParams):ResultVariations
+  {
+    // Perfect (Platinum) is a Sick Full Clear
+    var isPerfectPlat = (params.scoreData.tallies.sick + params.scoreData.tallies.good) == params.scoreData.tallies.totalNotes
+      && params.scoreData.tallies.sick / params.scoreData.tallies.totalNotes >= Constants.RANK_PERFECT_PLAT_THRESHOLD;
+    if (isPerfectPlat) return ResultVariations.PERFECT_PLATINUM;
+
+    // Perfect (Gold) is an 85% Sick Full Clear
+    var isPerfectGold = (params.scoreData.tallies.sick + params.scoreData.tallies.good) == params.scoreData.tallies.totalNotes
+      && params.scoreData.tallies.sick / params.scoreData.tallies.totalNotes >= Constants.RANK_PERFECT_GOLD_THRESHOLD;
+    if (isPerfectGold) return ResultVariations.PERFECT_GOLD;
+
+    // Else, use the standard grades
+
+    // Clear % (including bad and shit). 1.00 is a full clear but not a full combo
+    var clear = (params.scoreData.tallies.totalNotesHit) / params.scoreData.tallies.totalNotes;
+
+    if (clear == Constants.RANK_PERFECT_THRESHOLD)
+    {
+      return ResultVariations.PERFECT;
+    }
+    else if (clear >= Constants.RANK_EXCELLENT_THRESHOLD)
+    {
+      return ResultVariations.EXCELLENT;
+    }
+    else if (clear >= Constants.RANK_GREAT_THRESHOLD)
+    {
+      return ResultVariations.GREAT;
+    }
+    else if (clear >= Constants.RANK_GOOD_THRESHOLD)
+    {
+      return ResultVariations.GOOD;
+    }
+    else
+    {
+      return ResultVariations.SHIT;
+    }
+  }
 }
 
 enum abstract ResultVariations(String)
 {
+  var PERFECT_PLATINUM;
+  var PERFECT_GOLD;
   var PERFECT;
   var EXCELLENT;
-  var NORMAL;
+  var GREAT;
+  var GOOD;
   var SHIT;
+
+  public function getMusicPath():String
+  {
+    switch (abstract)
+    {
+      case PERFECT_PLATINUM:
+        return 'resultsPERFECT';
+      case PERFECT_GOLD:
+        return 'resultsPERFECT';
+      case PERFECT:
+        return 'resultsPERFECT';
+      case EXCELLENT:
+        return 'resultsNORMAL';
+      case GREAT:
+        return 'resultsNORMAL';
+      case GOOD:
+        return 'resultsNORMAL';
+      case SHIT:
+        return 'resultsSHIT';
+    }
+  }
+
+  public function shouldMusicLoop():Bool
+  {
+    switch (abstract)
+    {
+      case PERFECT_PLATINUM:
+        return true;
+      case PERFECT_GOLD:
+        return true;
+      case PERFECT:
+        return true;
+      case EXCELLENT:
+        return true;
+      case GREAT:
+        return true;
+      case GOOD:
+        return true;
+      case SHIT:
+        return false;
+      default:
+        return false;
+    }
+  }
 }
 
 typedef ResultsStateParams =
diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx
index c50f17697..2f3b570b3 100644
--- a/source/funkin/util/Constants.hx
+++ b/source/funkin/util/Constants.hx
@@ -455,6 +455,17 @@ class Constants
   public static final JUDGEMENT_BAD_COMBO_BREAK:Bool = true;
   public static final JUDGEMENT_SHIT_COMBO_BREAK:Bool = true;
 
+  // % Sick
+  public static final RANK_PERFECT_PLAT_THRESHOLD:Float = 1.0; // % Sick
+  public static final RANK_PERFECT_GOLD_THRESHOLD:Float = 0.85; // % Sick
+
+  // % Hit
+  public static final RANK_PERFECT_THRESHOLD:Float = 1.00;
+  public static final RANK_EXCELLENT_THRESHOLD:Float = 0.90;
+  public static final RANK_GREAT_THRESHOLD:Float = 0.75;
+  public static final RANK_GOOD_THRESHOLD:Float = 0.60;
+
+  // public static final RANK_SHIT_THRESHOLD:Float = 0.00;
   /**
    * FILE EXTENSIONS
    */

From 98cf37b642292fcd7eb8a5913db5c7e08a43b82a Mon Sep 17 00:00:00 2001
From: EliteMasterEric <ericmyllyoja@gmail.com>
Date: Thu, 9 May 2024 22:38:01 -0400
Subject: [PATCH 4/6] Update assets to add music.

---
 assets | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/assets b/assets
index 6115eb683..7df2e5527 160000
--- a/assets
+++ b/assets
@@ -1 +1 @@
-Subproject commit 6115eb6837e97b8b3ad82f3ccd2a49a4383ed35b
+Subproject commit 7df2e552738f8f2278538513a7495cb96d5ed118

From 83c3ff478c27bdc096e7dfcea190753df97526b5 Mon Sep 17 00:00:00 2001
From: EliteMasterEric <ericmyllyoja@gmail.com>
Date: Fri, 10 May 2024 22:09:09 -0400
Subject: [PATCH 5/6] Added Clear % tally to results.

---
 assets                            |   2 +-
 source/funkin/play/ResultState.hx | 381 +++++++++++++++++++++---------
 2 files changed, 266 insertions(+), 117 deletions(-)

diff --git a/assets b/assets
index 7df2e5527..927578f48 160000
--- a/assets
+++ b/assets
@@ -1 +1 @@
-Subproject commit 7df2e552738f8f2278538513a7495cb96d5ed118
+Subproject commit 927578f482b23dc4511fd8203560d631442d91a8
diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx
index 7f8bdd77a..df3134b9d 100644
--- a/source/funkin/play/ResultState.hx
+++ b/source/funkin/play/ResultState.hx
@@ -12,6 +12,8 @@ import funkin.ui.MusicBeatSubState;
 import flixel.math.FlxRect;
 import flixel.text.FlxBitmapText;
 import funkin.ui.freeplay.FreeplayScore;
+import flixel.text.FlxText;
+import flixel.util.FlxColor;
 import flixel.tweens.FlxEase;
 import funkin.ui.freeplay.FreeplayState;
 import flixel.tweens.FlxTween;
@@ -31,12 +33,27 @@ class ResultState extends MusicBeatSubState
 {
   final params:ResultsStateParams;
 
-  var resultsVariation:ResultVariations;
-  var songName:FlxBitmapText;
-  var difficulty:FlxSprite;
+  final rank:ResultRank;
+  final songName:FlxBitmapText;
+  final difficulty:FlxSprite;
 
-  var maskShaderSongName:LeftMaskShader = new LeftMaskShader();
-  var maskShaderDifficulty:LeftMaskShader = new LeftMaskShader();
+  final maskShaderSongName:LeftMaskShader = new LeftMaskShader();
+  final maskShaderDifficulty:LeftMaskShader = new LeftMaskShader();
+
+  final resultsAnim:FunkinSprite;
+  final ratingsPopin:FunkinSprite;
+  final scorePopin:FunkinSprite;
+
+  final bgFlash:FlxSprite;
+
+  final highscoreNew:FlxSprite;
+  final score:ResultScore;
+
+  var bfPerfect:Null<FlxAtlasSprite> = null;
+  var bfExcellent:Null<FlxAtlasSprite> = null;
+  var bfGood:Null<FlxSprite> = null;
+  var gfGood:Null<FlxSprite> = null;
+  var bfShit:Null<FlxAtlasSprite> = null;
 
   public function new(params:ResultsStateParams)
   {
@@ -44,7 +61,11 @@ class ResultState extends MusicBeatSubState
 
     this.params = params;
 
-    resultsVariation = calculateVariation(params);
+    rank = calculateRank(params);
+    // rank = SHIT;
+
+    // We build a lot of this stuff in the constructor, then place it in create().
+    // This prevents having to do `null` checks everywhere.
 
     var fontLetters:String = "AaBbCcDdEeFfGgHhiIJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz:1234567890";
     songName = new FlxBitmapText(FlxBitmapFont.fromMonospace(Paths.image("resultScreen/tardlingSpritesheet"), fontLetters, FlxPoint.get(49, 62)));
@@ -55,18 +76,22 @@ class ResultState extends MusicBeatSubState
 
     difficulty = new FlxSprite(555);
     difficulty.zIndex = 1000;
+
+    bgFlash = FlxGradient.createGradientFlxSprite(FlxG.width, FlxG.height, [0xFFFFEB69, 0xFFFFE66A], 90);
+
+    resultsAnim = FunkinSprite.createSparrow(-200, -10, "resultScreen/results");
+
+    ratingsPopin = FunkinSprite.createSparrow(-150, 120, "resultScreen/ratingsPopin");
+
+    scorePopin = FunkinSprite.createSparrow(-180, 520, "resultScreen/scorePopin");
+
+    highscoreNew = new FlxSprite(310, 570);
+
+    score = new ResultScore(35, 305, 10, params.scoreData.score);
   }
 
   override function create():Void
   {
-    FunkinSound.playMusic(resultsVariation.getMusicPath(),
-      {
-        startingVolume: 1.0,
-        overrideExisting: true,
-        restartTrack: true,
-        loop: resultsVariation.shouldMusicLoop()
-      });
-
     // Reset the camera zoom on the results screen.
     FlxG.camera.zoom = 1.0;
 
@@ -75,10 +100,9 @@ class ResultState extends MusicBeatSubState
     bg.zIndex = 10;
     add(bg);
 
-    var bgFlash:FlxSprite = FlxGradient.createGradientFlxSprite(FlxG.width, FlxG.height, [0xFFFFEB69, 0xFFFFE66A], 90);
     bgFlash.scrollFactor.set();
     bgFlash.visible = false;
-    bg.zIndex = 20;
+    bgFlash.zIndex = 20;
     add(bgFlash);
 
     // The sound system which falls into place behind the score text. Plays every time!
@@ -92,13 +116,7 @@ class ResultState extends MusicBeatSubState
     soundSystem.zIndex = 1100;
     add(soundSystem);
 
-    var bfPerfect:Null<FlxAtlasSprite> = null;
-    var bfExcellent:Null<FlxAtlasSprite> = null;
-    var bfGood:Null<FlxSprite> = null;
-    var gfGood:Null<FlxSprite> = null;
-    var bfShit:Null<FlxAtlasSprite> = null;
-
-    switch (resultsVariation)
+    switch (rank)
     {
       case PERFECT | PERFECT_GOLD | PERFECT_PLATINUM:
         bfPerfect = new FlxAtlasSprite(370, -180, Paths.animateAtlas("resultScreen/results-bf/resultsPERFECT", "shared"));
@@ -107,8 +125,11 @@ class ResultState extends MusicBeatSubState
         add(bfPerfect);
 
         bfPerfect.anim.onComplete = () -> {
-          bfPerfect.anim.curFrame = 136;
-          bfPerfect.anim.play(); // unpauses this anim, since it's on PlayOnce!
+          if (bfPerfect != null)
+          {
+            bfPerfect.anim.curFrame = 137;
+            bfPerfect.anim.play(); // unpauses this anim, since it's on PlayOnce!
+          }
         };
 
       case EXCELLENT:
@@ -118,7 +139,10 @@ class ResultState extends MusicBeatSubState
         add(bfExcellent);
 
         bfExcellent.onAnimationFinish.add((animName) -> {
-          bfExcellent.playAnimation('Loop Start');
+          if (bfExcellent != null)
+          {
+            bfExcellent.playAnimation('Loop Start');
+          }
         });
 
       case GOOD | GREAT:
@@ -127,7 +151,10 @@ class ResultState extends MusicBeatSubState
         gfGood.visible = false;
         gfGood.zIndex = 500;
         gfGood.animation.finishCallback = _ -> {
-          gfGood.animation.play('clap', true, false, 9);
+          if (gfGood != null)
+          {
+            gfGood.animation.play('clap', true, false, 9);
+          }
         };
         add(gfGood);
 
@@ -136,7 +163,10 @@ class ResultState extends MusicBeatSubState
         bfGood.visible = false;
         bfGood.zIndex = 501;
         bfGood.animation.finishCallback = function(_) {
-          bfGood.animation.play('fall', true, false, 14);
+          if (bfGood != null)
+          {
+            bfGood.animation.play('fall', true, false, 14);
+          }
         };
         add(bfGood);
 
@@ -146,7 +176,10 @@ class ResultState extends MusicBeatSubState
         bfShit.zIndex = 500;
         add(bfShit);
         bfShit.onAnimationFinish.add((animName) -> {
-          bfShit.playAnimation('Loop Start');
+          if (bfShit != null)
+          {
+            bfShit.playAnimation('Loop Start');
+          }
         });
     }
 
@@ -189,25 +222,21 @@ class ResultState extends MusicBeatSubState
     blackTopBar.zIndex = 1010;
     add(blackTopBar);
 
-    var resultsAnim:FunkinSprite = FunkinSprite.createSparrow(-200, -10, "resultScreen/results");
     resultsAnim.animation.addByPrefix("result", "results instance 1", 24, false);
     resultsAnim.animation.play("result");
     resultsAnim.zIndex = 1200;
     add(resultsAnim);
 
-    var ratingsPopin:FunkinSprite = FunkinSprite.createSparrow(-150, 120, "resultScreen/ratingsPopin");
     ratingsPopin.animation.addByPrefix("idle", "Categories", 24, false);
     ratingsPopin.visible = false;
     ratingsPopin.zIndex = 1200;
     add(ratingsPopin);
 
-    var scorePopin:FunkinSprite = FunkinSprite.createSparrow(-180, 520, "resultScreen/scorePopin");
     scorePopin.animation.addByPrefix("score", "tally score", 24, false);
     scorePopin.visible = false;
     scorePopin.zIndex = 1200;
     add(scorePopin);
 
-    var highscoreNew:FlxSprite = new FlxSprite(310, 570);
     highscoreNew.frames = Paths.getSparrowAtlas("resultScreen/highscoreNew");
     highscoreNew.animation.addByPrefix("new", "NEW HIGHSCORE", 24);
     highscoreNew.visible = false;
@@ -249,7 +278,6 @@ class ResultState extends MusicBeatSubState
     var tallyMissed:TallyCounter = new TallyCounter(260, (hStuf * 9) + extraYOffset, params.scoreData.tallies.missed, 0xFFC68AE6);
     ratingGrp.add(tallyMissed);
 
-    var score:ResultScore = new ResultScore(35, 305, 10, params.scoreData.score);
     score.visible = false;
     score.zIndex = 1200;
     add(score);
@@ -263,7 +291,68 @@ class ResultState extends MusicBeatSubState
       });
     }
 
-    new FlxTimer().start(0.5, _ -> {
+    startRankTallySequence();
+
+    refresh();
+
+    super.create();
+  }
+
+  var rankTallyTimer:Null<FlxTimer> = null;
+  var clearPercentTarget:Int = 100;
+  var clearPercentLerp:Int = 0;
+
+  function startRankTallySequence():Void
+  {
+    clearPercentTarget = Math.floor((params.scoreData.tallies.totalNotesHit) / params.scoreData.tallies.totalNotes * 100);
+    // clearPercentTarget = 97;
+
+    var clearPercentText = new FlxText(FlxG.width / 2, FlxG.height / 2, 0, 'CLEAR: ${clearPercentLerp}%');
+    clearPercentText.setFormat(Paths.font('vcr.ttf'), 64, FlxColor.BLACK, FlxTextAlign.RIGHT);
+    clearPercentText.zIndex = 1000;
+    add(clearPercentText);
+
+    rankTallyTimer = new FlxTimer().start(1 / 24, _ -> {
+      // Tick up.
+      if (clearPercentLerp < clearPercentTarget)
+      {
+        clearPercentLerp++;
+
+        clearPercentText.text = 'CLEAR: ${clearPercentLerp}%';
+        FunkinSound.playOnce(Paths.sound('scrollMenu'));
+      }
+
+      // Don't overshoot.
+      if (clearPercentLerp > clearPercentTarget)
+      {
+        clearPercentLerp = clearPercentTarget;
+      }
+
+      if (clearPercentLerp == clearPercentTarget)
+      {
+        if (rankTallyTimer != null)
+        {
+          rankTallyTimer.destroy();
+          rankTallyTimer = null;
+        }
+
+        // Play confirm sound.
+        FunkinSound.playOnce(Paths.sound('confirmMenu'));
+
+        new FlxTimer().start(1.0, _ -> {
+          remove(clearPercentText);
+
+          afterRankTallySequence();
+        });
+      }
+    }, 0); // 0 = Loop until stopped
+
+    if (ratingsPopin == null)
+    {
+      trace("Could not build ratingsPopin!");
+    }
+    else
+    {
       ratingsPopin.animation.play("idle");
       ratingsPopin.visible = true;
 
@@ -286,76 +375,139 @@ class ResultState extends MusicBeatSubState
           highscoreNew.visible = false;
         }
       };
-
-      switch (resultsVariation)
-      {
-        case PERFECT | PERFECT_GOLD | PERFECT_PLATINUM:
-          if (bfPerfect == null)
-          {
-            trace("Could not build PERFECT animation!");
-          }
-          else
-          {
-            bfPerfect.visible = true;
-            bfPerfect.playAnimation('');
-          }
-
-        case EXCELLENT:
-          if (bfExcellent == null)
-          {
-            trace("Could not build EXCELLENT animation!");
-          }
-          else
-          {
-            bfExcellent.visible = true;
-            bfExcellent.playAnimation('Intro');
-          }
-
-        case SHIT:
-          if (bfShit == null)
-          {
-            trace("Could not build SHIT animation!");
-          }
-          else
-          {
-            bfShit.visible = true;
-            bfShit.playAnimation('Intro');
-          }
-
-        case GREAT | GOOD:
-          if (bfGood == null || gfGood == null)
-          {
-            trace("Could not build GOOD animation!");
-          }
-          else
-          {
-            bfGood.animation.play('fall');
-            bfGood.visible = true;
-
-            new FlxTimer().start((1 / 24) * 12, _ -> {
-              bgFlash.visible = true;
-              FlxTween.tween(bgFlash, {alpha: 0}, 0.4);
-              new FlxTimer().start((1 / 24) * 2, _ ->
-                {
-                  // bgFlash.alpha = 0.5;
-
-                  // bgFlash.visible = false;
-                });
-            });
-
-            new FlxTimer().start((1 / 24) * 22, _ -> {
-              // plays about 22 frames (at 24fps timing) after bf spawns in
-              gfGood.animation.play('clap', true);
-              gfGood.visible = true;
-            });
-          }
-        default:
-      }
-    });
+    }
 
     refresh();
+  }
 
-    super.create();
+  function afterRankTallySequence():Void
+  {
+    FunkinSound.playMusic(rank.getMusicPath(),
+      {
+        startingVolume: 1.0,
+        overrideExisting: true,
+        restartTrack: true,
+        loop: rank.shouldMusicLoop()
+      });
+
+    FlxG.sound.music.onComplete = () -> {
+      if (rank == SHIT)
+      {
+        FunkinSound.playMusic('bluu',
+          {
+            startingVolume: 0.0,
+            overrideExisting: true,
+            restartTrack: true,
+            loop: true
+          });
+        FlxG.sound.music.fadeIn(10.0, 0.0, 1.0);
+      }
+    }
+
+    switch (rank)
+    {
+      case PERFECT | PERFECT_GOLD | PERFECT_PLATINUM:
+        if (bfPerfect == null)
+        {
+          trace("Could not build PERFECT animation!");
+        }
+        else
+        {
+          bfPerfect.visible = true;
+          bfPerfect.playAnimation('');
+
+          new FlxTimer().start((1 / 24) * 12, _ -> {
+            bgFlash.visible = true;
+            FlxTween.tween(bgFlash, {alpha: 0}, 0.4);
+            new FlxTimer().start((1 / 24) * 2, _ ->
+              {
+                // bgFlash.alpha = 0.5;
+
+                // bgFlash.visible = false;
+              });
+          });
+        }
+
+      case EXCELLENT:
+        if (bfExcellent == null)
+        {
+          trace("Could not build EXCELLENT animation!");
+        }
+        else
+        {
+          bfExcellent.visible = true;
+          bfExcellent.playAnimation('Intro');
+
+          new FlxTimer().start((1 / 24) * 12, _ -> {
+            bgFlash.visible = true;
+            FlxTween.tween(bgFlash, {alpha: 0}, 0.4);
+            new FlxTimer().start((1 / 24) * 2, _ ->
+              {
+                // bgFlash.alpha = 0.5;
+
+                // bgFlash.visible = false;
+              });
+          });
+        }
+
+      case SHIT:
+        if (bfShit == null)
+        {
+          trace("Could not build SHIT animation!");
+        }
+        else
+        {
+          bfShit.visible = true;
+          bfShit.playAnimation('Intro');
+
+          new FlxTimer().start((1 / 24) * 12, _ -> {
+            bgFlash.visible = true;
+            FlxTween.tween(bgFlash, {alpha: 0}, 0.4);
+            new FlxTimer().start((1 / 24) * 2, _ ->
+              {
+                // bgFlash.alpha = 0.5;
+
+                // bgFlash.visible = false;
+              });
+          });
+        }
+
+      case GREAT | GOOD:
+        if (bfGood == null)
+        {
+          trace("Could not build GOOD animation!");
+        }
+        else
+        {
+          bfGood.animation.play('fall');
+          bfGood.visible = true;
+
+          new FlxTimer().start((1 / 24) * 12, _ -> {
+            bgFlash.visible = true;
+            FlxTween.tween(bgFlash, {alpha: 0}, 0.4);
+            new FlxTimer().start((1 / 24) * 2, _ ->
+              {
+                // bgFlash.alpha = 0.5;
+
+                // bgFlash.visible = false;
+              });
+          });
+
+          new FlxTimer().start((1 / 24) * 22, _ -> {
+            // plays about 22 frames (at 24fps timing) after bf spawns in
+            if (gfGood != null)
+            {
+              gfGood.animation.play('clap', true);
+              gfGood.visible = true;
+            }
+            else
+            {
+              trace("Could not build GOOD animation!");
+            }
+          });
+        }
+      default:
+    }
   }
 
   function timerThenSongName():Void
@@ -391,11 +543,8 @@ class ResultState extends MusicBeatSubState
   {
     super.draw();
 
-    if (songName != null)
-    {
-      songName.clipRect = FlxRect.get(Math.max(0, 540 - songName.x), 0, FlxG.width, songName.height);
-      // PROBABLY SHOULD FIX MEMORY FREE OR WHATEVER THE PUT() FUNCTION DOES !!!! FEELS LIKE IT STUTTERS!!!
-    }
+    songName.clipRect = FlxRect.get(Math.max(0, 540 - songName.x), 0, FlxG.width, songName.height);
+    // PROBABLY SHOULD FIX MEMORY FREE OR WHATEVER THE PUT() FUNCTION DOES !!!! FEELS LIKE IT STUTTERS!!!
 
     // if (songName != null && songName.frame != null)
     // maskShaderSongName.frameUV = songName.frame.uv;
@@ -448,17 +597,17 @@ class ResultState extends MusicBeatSubState
     super.update(elapsed);
   }
 
-  public static function calculateVariation(params:ResultsStateParams):ResultVariations
+  public static function calculateRank(params:ResultsStateParams):ResultRank
   {
     // Perfect (Platinum) is a Sick Full Clear
     var isPerfectPlat = (params.scoreData.tallies.sick + params.scoreData.tallies.good) == params.scoreData.tallies.totalNotes
       && params.scoreData.tallies.sick / params.scoreData.tallies.totalNotes >= Constants.RANK_PERFECT_PLAT_THRESHOLD;
-    if (isPerfectPlat) return ResultVariations.PERFECT_PLATINUM;
+    if (isPerfectPlat) return ResultRank.PERFECT_PLATINUM;
 
     // Perfect (Gold) is an 85% Sick Full Clear
     var isPerfectGold = (params.scoreData.tallies.sick + params.scoreData.tallies.good) == params.scoreData.tallies.totalNotes
       && params.scoreData.tallies.sick / params.scoreData.tallies.totalNotes >= Constants.RANK_PERFECT_GOLD_THRESHOLD;
-    if (isPerfectGold) return ResultVariations.PERFECT_GOLD;
+    if (isPerfectGold) return ResultRank.PERFECT_GOLD;
 
     // Else, use the standard grades
 
@@ -467,28 +616,28 @@ class ResultState extends MusicBeatSubState
 
     if (clear == Constants.RANK_PERFECT_THRESHOLD)
     {
-      return ResultVariations.PERFECT;
+      return ResultRank.PERFECT;
     }
     else if (clear >= Constants.RANK_EXCELLENT_THRESHOLD)
     {
-      return ResultVariations.EXCELLENT;
+      return ResultRank.EXCELLENT;
     }
     else if (clear >= Constants.RANK_GREAT_THRESHOLD)
     {
-      return ResultVariations.GREAT;
+      return ResultRank.GREAT;
     }
     else if (clear >= Constants.RANK_GOOD_THRESHOLD)
     {
-      return ResultVariations.GOOD;
+      return ResultRank.GOOD;
     }
     else
     {
-      return ResultVariations.SHIT;
+      return ResultRank.SHIT;
     }
   }
 }
 
-enum abstract ResultVariations(String)
+enum abstract ResultRank(String)
 {
   var PERFECT_PLATINUM;
   var PERFECT_GOLD;

From 6c2d18c72c7350ccefc4d9b15577c6092d663bed Mon Sep 17 00:00:00 2001
From: EliteMasterEric <ericmyllyoja@gmail.com>
Date: Sat, 11 May 2024 01:05:51 -0400
Subject: [PATCH 6/6] Resurrected difficulty stars, fixed flame animation,
 fixed Random showing an album.

---
 assets                                       |   2 +-
 source/funkin/play/song/Song.hx              |  21 ++++
 source/funkin/ui/freeplay/AlbumRoll.hx       |  44 ++++----
 source/funkin/ui/freeplay/DifficultyStars.hx | 106 +++++++++++++++++++
 source/funkin/ui/freeplay/FreeplayFlames.hx  |  21 +++-
 source/funkin/ui/freeplay/FreeplayState.hx   |  23 ++--
 source/funkin/ui/freeplay/SongMenuItem.hx    |   2 +-
 7 files changed, 183 insertions(+), 36 deletions(-)
 create mode 100644 source/funkin/ui/freeplay/DifficultyStars.hx

diff --git a/assets b/assets
index 927578f48..fd112e293 160000
--- a/assets
+++ b/assets
@@ -1 +1 @@
-Subproject commit 927578f482b23dc4511fd8203560d631442d91a8
+Subproject commit fd112e293ee0f823ee98d5b8bd8a85e934f772f6
diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx
index e71ae3213..23d8d2198 100644
--- a/source/funkin/play/song/Song.hx
+++ b/source/funkin/play/song/Song.hx
@@ -399,6 +399,27 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry<SongMeta
     return null;
   }
 
+  /**
+   * Given that this character is selected in the Freeplay menu,
+   * which variations should be available?
+   * @param charId The character ID to query.
+   * @return An array of available variations.
+   */
+  public function getVariationsByCharId(?charId:String):Array<String>
+  {
+    if (charId == null) charId = Constants.DEFAULT_CHARACTER;
+
+    if (variations.contains(charId))
+    {
+      return [charId];
+    }
+    else
+    {
+      // TODO: How to exclude character variations while keeping other custom variations?
+      return variations;
+    }
+  }
+
   /**
    * List all the difficulties in this song.
    *
diff --git a/source/funkin/ui/freeplay/AlbumRoll.hx b/source/funkin/ui/freeplay/AlbumRoll.hx
index 35facf131..50f4a432c 100644
--- a/source/funkin/ui/freeplay/AlbumRoll.hx
+++ b/source/funkin/ui/freeplay/AlbumRoll.hx
@@ -38,7 +38,7 @@ class AlbumRoll extends FlxSpriteGroup
 
   var newAlbumArt:FlxAtlasSprite;
 
-  // var difficultyStars:DifficultyStars;
+  var difficultyStars:DifficultyStars;
   var _exitMovers:Null<FreeplayState.ExitMoverData>;
 
   var albumData:Album;
@@ -65,9 +65,9 @@ class AlbumRoll extends FlxSpriteGroup
 
     add(newAlbumArt);
 
-    // difficultyStars = new DifficultyStars(140, 39);
-    // difficultyStars.stars.visible = false;
-    // add(difficultyStars);
+    difficultyStars = new DifficultyStars(140, 39);
+    difficultyStars.stars.visible = false;
+    add(difficultyStars);
   }
 
   function onAlbumFinish(animName:String):Void
@@ -86,9 +86,14 @@ class AlbumRoll extends FlxSpriteGroup
   {
     if (albumId == null)
     {
-      // difficultyStars.stars.visible = false;
+      this.visible = false;
+      difficultyStars.stars.visible = false;
       return;
     }
+    else
+    {
+      this.visible = true;
+    }
 
     albumData = AlbumRegistry.instance.fetchEntry(albumId);
 
@@ -144,10 +149,10 @@ class AlbumRoll extends FlxSpriteGroup
     newAlbumArt.visible = true;
     newAlbumArt.playAnimation(animNames.get('$albumId-active'), false, false, false);
 
-    // difficultyStars.stars.visible = false;
+    difficultyStars.stars.visible = false;
     new FlxTimer().start(0.75, function(_) {
       // showTitle();
-      // showStars();
+      showStars();
     });
   }
 
@@ -156,16 +161,17 @@ class AlbumRoll extends FlxSpriteGroup
     newAlbumArt.playAnimation(animNames.get('$albumId-trans'), false, false, false);
   }
 
-  // public function setDifficultyStars(?difficulty:Int):Void
-  // {
-  //   if (difficulty == null) return;
-  //   difficultyStars.difficulty = difficulty;
-  // }
-  // /**
-  //  * Make the album stars visible.
-  //  */
-  // public function showStars():Void
-  // {
-  //   difficultyStars.stars.visible = false; // true;
-  // }
+  public function setDifficultyStars(?difficulty:Int):Void
+  {
+    if (difficulty == null) return;
+    difficultyStars.difficulty = difficulty;
+  }
+
+  /**
+   * Make the album stars visible.
+   */
+  public function showStars():Void
+  {
+    difficultyStars.stars.visible = true; // true;
+  }
 }
diff --git a/source/funkin/ui/freeplay/DifficultyStars.hx b/source/funkin/ui/freeplay/DifficultyStars.hx
new file mode 100644
index 000000000..51526bcbe
--- /dev/null
+++ b/source/funkin/ui/freeplay/DifficultyStars.hx
@@ -0,0 +1,106 @@
+package funkin.ui.freeplay;
+
+import flixel.group.FlxSpriteGroup;
+import funkin.graphics.adobeanimate.FlxAtlasSprite;
+import funkin.graphics.shaders.HSVShader;
+
+class DifficultyStars extends FlxSpriteGroup
+{
+  /**
+   * Internal handler var for difficulty... ranges from 0... to 15
+   * 0 is 1 star... 15 is 0 stars!
+   */
+  var curDifficulty(default, set):Int = 0;
+
+  /**
+   * Range between 0 and 15
+   */
+  public var difficulty(default, set):Int = 1;
+
+  public var stars:FlxAtlasSprite;
+
+  var flames:FreeplayFlames;
+
+  var hsvShader:HSVShader;
+
+  public function new(x:Float, y:Float)
+  {
+    super(x, y);
+
+    hsvShader = new HSVShader();
+
+    flames = new FreeplayFlames(0, 0);
+    add(flames);
+
+    stars = new FlxAtlasSprite(0, 0, Paths.animateAtlas("freeplay/freeplayStars"));
+    stars.anim.play("diff stars");
+    add(stars);
+
+    stars.shader = hsvShader;
+
+    for (memb in flames.members)
+      memb.shader = hsvShader;
+  }
+
+  override function update(elapsed:Float):Void
+  {
+    super.update(elapsed);
+
+    // "loops" the current animation
+    // for clarity, the animation file looks like
+    // frame : stars
+    // 0-99: 1 star
+    // 100-199: 2 stars
+    // ......
+    // 1300-1499: 15 stars
+    // 1500 : 0 stars
+    if (curDifficulty < 15 && stars.anim.curFrame >= (curDifficulty + 1) * 100)
+    {
+      stars.anim.play("diff stars", true, false, curDifficulty * 100);
+    }
+  }
+
+  function set_difficulty(value:Int):Int
+  {
+    difficulty = value;
+
+    if (difficulty <= 0)
+    {
+      difficulty = 0;
+      curDifficulty = 15;
+    }
+    else if (difficulty <= 15)
+    {
+      difficulty = value;
+      curDifficulty = difficulty - 1;
+    }
+    else
+    {
+      difficulty = 15;
+      curDifficulty = difficulty - 1;
+    }
+
+    if (difficulty > 10) flames.flameCount = difficulty - 10;
+    else
+      flames.flameCount = 0;
+
+    return difficulty;
+  }
+
+  function set_curDifficulty(value:Int):Int
+  {
+    curDifficulty = value;
+    if (curDifficulty == 15)
+    {
+      stars.anim.play("diff stars", true, false, 1500);
+      stars.anim.pause();
+    }
+    else
+    {
+      stars.anim.curFrame = Std.int(curDifficulty * 100);
+      stars.anim.play("diff stars", true, false, curDifficulty * 100);
+    }
+
+    return curDifficulty;
+  }
+}
diff --git a/source/funkin/ui/freeplay/FreeplayFlames.hx b/source/funkin/ui/freeplay/FreeplayFlames.hx
index c20d85898..f6b6f5c3d 100644
--- a/source/funkin/ui/freeplay/FreeplayFlames.hx
+++ b/source/funkin/ui/freeplay/FreeplayFlames.hx
@@ -50,8 +50,19 @@ class FreeplayFlames extends FlxSpriteGroup
     }
   }
 
+  var timers:Array<FlxTimer> = [];
+
   function set_flameCount(value:Int):Int
   {
+    // Stop all existing timers.
+    // This fixes a bug where quickly switching difficulties would show flames.
+    for (timer in timers)
+    {
+      timer.active = false;
+      timer.destroy();
+      timers.remove(timer);
+    }
+
     this.flameCount = value;
     var visibleCount:Int = 0;
     for (i in 0...5)
@@ -62,10 +73,18 @@ class FreeplayFlames extends FlxSpriteGroup
       {
         if (!flame.visible)
         {
-          new FlxTimer().start(flameTimer * visibleCount, function(_) {
+          var nextTimer:FlxTimer = new FlxTimer().start(flameTimer * visibleCount, function(currentTimer:FlxTimer) {
+            if (i >= this.flameCount)
+            {
+              trace('EARLY EXIT');
+              return;
+            }
+            timers.remove(currentTimer);
             flame.animation.play("flame", true);
             flame.visible = true;
           });
+          timers.push(nextTimer);
+
           visibleCount++;
         }
       }
diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx
index 7b7543845..239068288 100644
--- a/source/funkin/ui/freeplay/FreeplayState.hx
+++ b/source/funkin/ui/freeplay/FreeplayState.hx
@@ -120,8 +120,6 @@ class FreeplayState extends MusicBeatSubState
   var curCapsule:SongMenuItem;
   var curPlaying:Bool = false;
 
-  var displayedVariations:Array<String>;
-
   var dj:DJBoyfriend;
 
   var ostName:FlxText;
@@ -184,10 +182,6 @@ class FreeplayState extends MusicBeatSubState
     // Add a null entry that represents the RANDOM option
     songs.push(null);
 
-    // TODO: This makes custom variations disappear from Freeplay. Figure out a better solution later.
-    // Default character (BF) shows default and Erect variations. Pico shows only Pico variations.
-    displayedVariations = (currentCharacter == 'bf') ? [Constants.DEFAULT_VARIATION, 'erect'] : [currentCharacter];
-
     // programmatically adds the songs via LevelRegistry and SongRegistry
     for (levelId in LevelRegistry.instance.listSortedLevelIds())
     {
@@ -195,7 +189,8 @@ class FreeplayState extends MusicBeatSubState
       {
         var song:Song = SongRegistry.instance.fetchEntry(songId);
 
-        // Only display songs which actually have available charts for the current character.
+        // Only display songs which actually have available difficulties for the current character.
+        var displayedVariations = song.getVariationsByCharId(currentCharacter);
         var availableDifficultiesForSong:Array<String> = song.listDifficulties(displayedVariations, false);
         if (availableDifficultiesForSong.length == 0) continue;
 
@@ -488,10 +483,6 @@ class FreeplayState extends MusicBeatSubState
 
       albumRoll.playIntro();
 
-      new FlxTimer().start(0.75, function(_) {
-        // albumRoll.showTitle();
-      });
-
       FlxTween.tween(grpDifficulties, {x: 90}, 0.6, {ease: FlxEase.quartOut});
 
       diffSelLeft.visible = true;
@@ -1072,6 +1063,9 @@ class FreeplayState extends MusicBeatSubState
       albumRoll.albumId = newAlbumId;
       albumRoll.skipIntro();
     }
+
+    // Set difficulty star count.
+    albumRoll.setDifficultyStars(daSong?.difficultyRating);
   }
 
   // Clears the cache of songs, frees up memory, they' ll have to be loaded in later tho function clearDaCache(actualSongTho:String)
@@ -1383,11 +1377,12 @@ class FreeplaySongData
 
   public var songName(default, null):String = '';
   public var songCharacter(default, null):String = '';
-  public var songRating(default, null):Int = 0;
+  public var difficultyRating(default, null):Int = 0;
   public var albumId(default, null):Null<String> = null;
 
   public var currentDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY;
-  public var displayedVariations(default, null):Array<String> = [Constants.DEFAULT_VARIATION];
+
+  var displayedVariations:Array<String> = [Constants.DEFAULT_VARIATION];
 
   function set_currentDifficulty(value:String):String
   {
@@ -1417,7 +1412,7 @@ class FreeplaySongData
     if (songDifficulty == null) return;
     this.songName = songDifficulty.songName;
     this.songCharacter = songDifficulty.characters.opponent;
-    this.songRating = songDifficulty.difficultyRating;
+    this.difficultyRating = songDifficulty.difficultyRating;
     if (songDifficulty.album == null)
     {
       FlxG.log.warn('No album for: ${songDifficulty.songName}');
diff --git a/source/funkin/ui/freeplay/SongMenuItem.hx b/source/funkin/ui/freeplay/SongMenuItem.hx
index f6d85e56e..cf9b52482 100644
--- a/source/funkin/ui/freeplay/SongMenuItem.hx
+++ b/source/funkin/ui/freeplay/SongMenuItem.hx
@@ -168,7 +168,7 @@ class SongMenuItem extends FlxSpriteGroup
     songText.text = songData?.songName ?? 'Random';
     // Update capsule character.
     if (songData?.songCharacter != null) setCharacter(songData.songCharacter);
-    updateDifficultyRating(songData?.songRating ?? 0);
+    updateDifficultyRating(songData?.difficultyRating ?? 0);
     // Update opacity, offsets, etc.
     updateSelected();
   }