From 09a037b20b1787c3a618b1c7f66365f4cfa79ce2 Mon Sep 17 00:00:00 2001
From: Cameron Taylor <cameron.taylor.ninja@gmail.com>
Date: Mon, 26 Sep 2022 18:22:45 -0400
Subject: [PATCH 1/7] basic sort and search

---
 hmm.json                       | 159 +++++++++++++++++----------------
 source/funkin/FreeplayState.hx | 142 ++++++++++++++++++-----------
 2 files changed, 172 insertions(+), 129 deletions(-)

diff --git a/hmm.json b/hmm.json
index b32de6cc8..57467af02 100644
--- a/hmm.json
+++ b/hmm.json
@@ -1,81 +1,82 @@
 {
-    "dependencies": [{
-            "name": "discord_rpc",
-            "type": "git",
-            "dir": null,
-            "ref": "2d83fa8",
-            "url": "https://github.com/Aidan63/linc_discord-rpc"
-        },
-        {
-            "name": "firetongue",
-            "type": "git",
-            "dir": null,
-            "ref": "c5666c8",
-            "url": "https://github.com/larsiusprime/firetongue"
-        },
-        {
-            "name": "flixel",
-            "type": "git",
-            "dir": null,
-            "ref": "93a049d6",
-            "url": "https://github.com/haxeflixel/flixel"
-        },
-        {
-            "name": "flixel-addons",
-            "type": "haxelib",
-            "version": "2.11.0"
-        },
-        {
-            "name": "flixel-ui",
-            "type": "haxelib",
-            "version": "2.4.0"
-        },
-        {
-            "name": "haxeui-core",
-            "type": "haxelib",
-            "version": null
-        },
-        {
-            "name": "haxeui-flixel",
-            "type": "haxelib",
-            "version": null
-        },
-        {
-            "name": "hscript",
-            "type": "haxelib",
-            "version": "2.5.0"
-        },
-        {
-            "name": "hxcpp",
-            "type": "haxelib",
-            "version": "4.2.1"
-        },
-        {
-            "name": "hxcpp-debug-server",
-            "type": "haxelib",
-            "version": "1.2.4"
-        },
-        {
-            "name": "lime",
-            "type": "haxelib",
-            "version": "7.9.0"
-        },
-        {
-            "name": "openfl",
-            "type": "haxelib",
-            "version": "9.1.0"
-        },
-        {
-            "name": "polymod",
-            "type": "git",
-            "dir": null,
-            "ref": "c858b48",
-            "url": "https://github.com/larsiusprime/polymod"
-        },
-        {
-            "name": "thx.semver",
-            "type": "haxelib",
-            "version": "0.2.2"
-        }
-    ]
+  "dependencies": [
+    {
+      "name": "discord_rpc",
+      "type": "git",
+      "dir": null,
+      "ref": "2d83fa8",
+      "url": "https://github.com/Aidan63/linc_discord-rpc"
+    },
+    {
+      "name": "firetongue",
+      "type": "git",
+      "dir": null,
+      "ref": "c5666c8",
+      "url": "https://github.com/larsiusprime/firetongue"
+    },
+    {
+      "name": "flixel",
+      "type": "git",
+      "dir": null,
+      "ref": "93a049d6",
+      "url": "https://github.com/haxeflixel/flixel"
+    },
+    {
+      "name": "flixel-addons",
+      "type": "haxelib",
+      "version": "2.11.0"
+    },
+    {
+      "name": "flixel-ui",
+      "type": "haxelib",
+      "version": "2.4.0"
+    },
+    {
+      "name": "haxeui-core",
+      "type": "haxelib",
+      "version": "1.5.0"
+    },
+    {
+      "name": "haxeui-flixel",
+      "type": "haxelib",
+      "version": "1.5.0"
+    },
+    {
+      "name": "hscript",
+      "type": "haxelib",
+      "version": "2.5.0"
+    },
+    {
+      "name": "hxcpp",
+      "type": "haxelib",
+      "version": "4.2.1"
+    },
+    {
+      "name": "hxcpp-debug-server",
+      "type": "haxelib",
+      "version": "1.2.4"
+    },
+    {
+      "name": "lime",
+      "type": "haxelib",
+      "version": "7.9.0"
+    },
+    {
+      "name": "openfl",
+      "type": "haxelib",
+      "version": "9.1.0"
+    },
+    {
+      "name": "polymod",
+      "type": "git",
+      "dir": null,
+      "ref": "c858b48",
+      "url": "https://github.com/larsiusprime/polymod"
+    },
+    {
+      "name": "thx.semver",
+      "type": "haxelib",
+      "version": "0.2.2"
+    }
+  ]
 }
\ No newline at end of file
diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx
index 98c77f933..6423b4b0b 100644
--- a/source/funkin/FreeplayState.hx
+++ b/source/funkin/FreeplayState.hx
@@ -7,6 +7,7 @@ import flixel.FlxSprite;
 import flixel.FlxState;
 import flixel.addons.display.FlxGridOverlay;
 import flixel.addons.transition.FlxTransitionableState;
+import flixel.addons.ui.FlxInputText;
 import flixel.group.FlxGroup.FlxTypedGroup;
 import flixel.group.FlxGroup;
 import flixel.group.FlxSpriteGroup;
@@ -66,6 +67,8 @@ class FreeplayState extends MusicBeatSubstate
 
 	private var iconArray:Array<HealthIcon> = [];
 
+	var typing:FlxInputText;
+
 	override function create()
 	{
 		FlxTransitionableState.skipNextTransIn = true;
@@ -260,55 +263,7 @@ class FreeplayState extends MusicBeatSubstate
 			grpTxtScrolls.visible = true;
 		});
 
-		for (i in 0...songs.length)
-		{
-			var funnyMenu:SongMenuItem = new SongMenuItem(FlxG.width, (i * 150) + 160, songs[i].songName);
-			funnyMenu.targetPos.x = funnyMenu.x;
-			funnyMenu.ID = i;
-			funnyMenu.alpha = 0.5;
-			funnyMenu.songText.visible = false;
-
-			fp.updateScore(0);
-
-			new FlxTimer().start((1 / 24) * i, function(doShit)
-			{
-				funnyMenu.doJumpIn = true;
-			});
-
-			new FlxTimer().start((0.09 * i) + 0.85, function(lerpTmr)
-			{
-				funnyMenu.doLerp = true;
-			});
-
-			new FlxTimer().start(((0.20 * i) / (1 + i)) + 0.75, function(swagShi)
-			{
-				funnyMenu.songText.visible = true;
-				funnyMenu.alpha = 1;
-			});
-
-			grpCapsules.add(funnyMenu);
-
-			var songText:Alphabet = new Alphabet(0, (70 * i) + 30, songs[i].songName, true, false);
-			songText.x += 100;
-			songText.isMenuItem = true;
-			songText.targetY = i;
-
-			// grpSongs.add(songText);
-
-			var icon:HealthIcon = new HealthIcon(songs[i].songCharacter);
-			// icon.sprTracker = songText;
-
-			// using a FlxGroup is too much fuss!
-			iconArray.push(icon);
-			// add(icon);
-
-			// songText.x += 40;
-			// DONT PUT X IN THE FIRST PARAMETER OF new ALPHABET() !!
-			// songText.screenCenter(X);
-		}
-
-		changeSelection();
-		changeDiff();
+		generateSongList();
 
 		// FlxG.sound.playMusic(Paths.music('title'), 0);
 		// FlxG.sound.music.fadeIn(2, 0, 0.8);
@@ -339,6 +294,15 @@ class FreeplayState extends MusicBeatSubstate
 		funnyCam.bgColor = FlxColor.TRANSPARENT;
 		FlxG.cameras.add(funnyCam);
 
+		typing = new FlxInputText(100, 100);
+		add(typing);
+
+		typing.callback = function(txt, action)
+		{
+			generateSongList(new EReg(txt.trim(), "ig"));
+			trace(action);
+		};
+
 		forEach(function(bs)
 		{
 			bs.cameras = [funnyCam];
@@ -347,6 +311,81 @@ class FreeplayState extends MusicBeatSubstate
 		super.create();
 	}
 
+	public function generateSongList(?regexp:EReg)
+	{
+		curSelected = 0;
+
+		grpCapsules.clear();
+
+		var regexp:EReg = regexp;
+		var tempSongs:Array<SongMetadata> = songs;
+		if (regexp != null)
+			tempSongs = songs.filter(item -> regexp.match(item.songName));
+
+		tempSongs.sort(function(a, b):Int
+		{
+			var tempA = a.songName.toUpperCase();
+			var tempB = b.songName.toUpperCase();
+
+			if (tempA < tempB)
+				return -1;
+			else if (tempA > tempB)
+				return 1;
+			else
+				return 0;
+		});
+
+		for (i in 0...tempSongs.length)
+		{
+			var funnyMenu:SongMenuItem = new SongMenuItem(FlxG.width, (i * 150) + 160, tempSongs[i].songName);
+			funnyMenu.targetPos.x = funnyMenu.x;
+			funnyMenu.ID = i;
+			funnyMenu.alpha = 0.5;
+			funnyMenu.songText.visible = false;
+
+			fp.updateScore(0);
+
+			new FlxTimer().start((1 / 24) * i, function(doShit)
+			{
+				funnyMenu.doJumpIn = true;
+			});
+
+			new FlxTimer().start((0.09 * i) + 0.85, function(lerpTmr)
+			{
+				funnyMenu.doLerp = true;
+			});
+
+			new FlxTimer().start(((0.20 * i) / (1 + i)) + 0.75, function(swagShi)
+			{
+				funnyMenu.songText.visible = true;
+				funnyMenu.alpha = 1;
+			});
+
+			grpCapsules.add(funnyMenu);
+
+			var songText:Alphabet = new Alphabet(0, (70 * i) + 30, tempSongs[i].songName, true, false);
+			songText.x += 100;
+			songText.isMenuItem = true;
+			songText.targetY = i;
+
+			// grpSongs.add(songText);
+
+			var icon:HealthIcon = new HealthIcon(tempSongs[i].songCharacter);
+			// icon.sprTracker = songText;
+
+			// using a FlxGroup is too much fuss!
+			iconArray.push(icon);
+			// add(icon);
+
+			// songText.x += 40;
+			// DONT PUT X IN THE FIRST PARAMETER OF new ALPHABET() !!
+			// songText.screenCenter(X);
+		}
+
+		changeSelection();
+		changeDiff();
+	}
+
 	public function addSong(songName:String, weekNum:Int, songCharacter:String)
 	{
 		songs.push(new SongMetadata(songName, weekNum, songCharacter));
@@ -382,6 +421,9 @@ class FreeplayState extends MusicBeatSubstate
 	{
 		super.update(elapsed);
 
+		if (FlxG.keys.justPressed.T)
+			typing.hasFocus = true;
+
 		if (FlxG.sound.music != null)
 		{
 			if (FlxG.sound.music.volume < 0.7)
@@ -496,7 +538,7 @@ class FreeplayState extends MusicBeatSubstate
 		if (controls.UI_RIGHT_P)
 			changeDiff(1);
 
-		if (controls.BACK)
+		if (controls.BACK && !typing.hasFocus)
 		{
 			FlxG.sound.play(Paths.sound('cancelMenu'));
 

From 4918c4f6453fd84068cf1503288cd7b5c80e8445 Mon Sep 17 00:00:00 2001
From: Cameron Taylor <cameron.taylor.ninja@gmail.com>
Date: Tue, 27 Sep 2022 04:37:42 -0400
Subject: [PATCH 2/7] alphabetical sorting stuuuuf

---
 source/funkin/FreeplayState.hx            |  77 ++++++++++++----
 source/funkin/freeplayStuff/LetterSort.hx | 106 ++++++++++++++++++++++
 2 files changed, 163 insertions(+), 20 deletions(-)
 create mode 100644 source/funkin/freeplayStuff/LetterSort.hx

diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx
index 6423b4b0b..c2c0e5c8c 100644
--- a/source/funkin/FreeplayState.hx
+++ b/source/funkin/FreeplayState.hx
@@ -25,6 +25,7 @@ import funkin.Controls.Control;
 import funkin.freeplayStuff.BGScrollingText;
 import funkin.freeplayStuff.DJBoyfriend;
 import funkin.freeplayStuff.FreeplayScore;
+import funkin.freeplayStuff.LetterSort;
 import funkin.freeplayStuff.SongMenuItem;
 import funkin.play.HealthIcon;
 import funkin.play.PlayState;
@@ -242,6 +243,14 @@ class FreeplayState extends MusicBeatSubstate
 			add(new DifficultySelector(20, grpDifficulties.y - 10, false, controls));
 			add(new DifficultySelector(325, grpDifficulties.y - 10, true, controls));
 
+			var letterSort:LetterSort = new LetterSort(300, 100);
+			add(letterSort);
+
+			letterSort.changeSelectionCallback = (str) ->
+			{
+				generateSongList(str, true);
+			};
+
 			new FlxTimer().start(1 / 24, function(handShit)
 			{
 				fnfFreeplay.visible = true;
@@ -299,7 +308,7 @@ class FreeplayState extends MusicBeatSubstate
 
 		typing.callback = function(txt, action)
 		{
-			generateSongList(new EReg(txt.trim(), "ig"));
+			// generateSongList(new EReg(txt.trim(), "ig"));
 			trace(action);
 		};
 
@@ -311,29 +320,48 @@ class FreeplayState extends MusicBeatSubstate
 		super.create();
 	}
 
-	public function generateSongList(?regexp:EReg)
+	public function generateSongList(?startsWith:String, ?force:Bool = false)
 	{
 		curSelected = 0;
 
 		grpCapsules.clear();
 
-		var regexp:EReg = regexp;
+		// var regexp:EReg = regexp;
 		var tempSongs:Array<SongMetadata> = songs;
-		if (regexp != null)
-			tempSongs = songs.filter(item -> regexp.match(item.songName));
 
-		tempSongs.sort(function(a, b):Int
+		if (startsWith != null)
 		{
-			var tempA = a.songName.toUpperCase();
-			var tempB = b.songName.toUpperCase();
+			trace("STARTS WITH: " + startsWith);
+			switch (startsWith)
+			{
+				case "ALL":
+				case "#":
+				default:
+					trace(tempSongs.length);
 
-			if (tempA < tempB)
-				return -1;
-			else if (tempA > tempB)
-				return 1;
-			else
-				return 0;
-		});
+					tempSongs = tempSongs.filter(str ->
+					{
+						return str.songName.toLowerCase().startsWith(startsWith);
+					});
+					trace(tempSongs.length);
+			}
+		}
+
+		// if (regexp != null)
+		// 	tempSongs = songs.filter(item -> regexp.match(item.songName));
+
+		// tempSongs.sort(function(a, b):Int
+		// {
+		// 	var tempA = a.songName.toUpperCase();
+		// 	var tempB = b.songName.toUpperCase();
+
+		// 	if (tempA < tempB)
+		// 		return -1;
+		// 	else if (tempA > tempB)
+		// 		return 1;
+		// 	else
+		// 		return 0;
+		// });
 
 		for (i in 0...tempSongs.length)
 		{
@@ -355,11 +383,19 @@ class FreeplayState extends MusicBeatSubstate
 				funnyMenu.doLerp = true;
 			});
 
-			new FlxTimer().start(((0.20 * i) / (1 + i)) + 0.75, function(swagShi)
+			if (!force)
+			{
+				new FlxTimer().start(((0.20 * i) / (1 + i)) + 0.75, function(swagShi)
+				{
+					funnyMenu.songText.visible = true;
+					funnyMenu.alpha = 1;
+				});
+			}
+			else
 			{
 				funnyMenu.songText.visible = true;
 				funnyMenu.alpha = 1;
-			});
+			}
 
 			grpCapsules.add(funnyMenu);
 
@@ -649,8 +685,8 @@ class FreeplayState extends MusicBeatSubstate
 		curSelected += change;
 
 		if (curSelected < 0)
-			curSelected = songs.length - 1;
-		if (curSelected >= songs.length)
+			curSelected = grpCapsules.members.length - 1;
+		if (curSelected >= grpCapsules.members.length)
 			curSelected = 0;
 
 		// selector.y = (70 * curSelected) + 30;
@@ -684,7 +720,8 @@ class FreeplayState extends MusicBeatSubstate
 				capsule.targetPos.y -= 100; // another 100 for good measure
 		}
 
-		grpCapsules.members[curSelected].selected = true;
+		if (grpCapsules.members.length > 0)
+			grpCapsules.members[curSelected].selected = true;
 	}
 }
 
diff --git a/source/funkin/freeplayStuff/LetterSort.hx b/source/funkin/freeplayStuff/LetterSort.hx
new file mode 100644
index 000000000..b263bc357
--- /dev/null
+++ b/source/funkin/freeplayStuff/LetterSort.hx
@@ -0,0 +1,106 @@
+package funkin.freeplayStuff;
+
+import flixel.FlxSprite;
+import flixel.group.FlxGroup.FlxTypedGroup;
+import flixel.group.FlxGroup;
+import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
+
+class LetterSort extends FlxTypedSpriteGroup<FreeplayLetter>
+{
+	public var letters:Array<FreeplayLetter> = [];
+
+	var curSelection:Int = 0;
+
+	public var changeSelectionCallback:String->Void;
+
+	public function new(x, y)
+	{
+		super(x, y);
+
+		var leftArrow:FreeplayLetter = new FreeplayLetter(-20, 0);
+		leftArrow.animation.play("arrow");
+		add(leftArrow);
+
+		for (i in 0...6)
+		{
+			var letter:FreeplayLetter = new FreeplayLetter(i * 80, 0, i);
+			add(letter);
+
+			letters.push(letter);
+
+			if (i == 3)
+				letter.alpha = 0.6;
+
+			var sep:FreeplayLetter = new FreeplayLetter((i * 80) + 50, 0);
+			sep.animation.play("seperator");
+			add(sep);
+		}
+
+		// changeSelection(-3);
+	}
+
+	override function update(elapsed:Float)
+	{
+		super.update(elapsed);
+
+		if (FlxG.keys.justPressed.E)
+			changeSelection(1);
+		if (FlxG.keys.justPressed.Q)
+			changeSelection(-1);
+	}
+
+	public function changeSelection(diff:Int = 0)
+	{
+		for (letter in letters)
+			letter.changeLetter(diff);
+
+		if (changeSelectionCallback != null)
+			changeSelectionCallback(letters[3].arr[letters[3].curLetter]); // bullshit and long lol!
+	}
+}
+
+class FreeplayLetter extends FlxSprite
+{
+	public var arr:Array<String> = [];
+
+	public var curLetter:Int = 0;
+
+	public function new(x:Float, y:Float, ?letterInd:Int)
+	{
+		super(x, y);
+
+		frames = Paths.getSparrowAtlas("freeplay/letterStuff");
+
+		var alphabet:String = "abcdefghijklmnopqrstuvwxyz";
+		arr = alphabet.split("");
+		arr.insert(0, "#");
+		arr.insert(0, "ALL");
+		arr.insert(0, "fav");
+
+		for (str in arr)
+		{
+			animation.addByPrefix(str, str + " "); // string followed by a space! intentional!
+		}
+
+		animation.addByPrefix("arrow", "mini arrow");
+		animation.addByPrefix("seperator", "seperator");
+
+		if (letterInd != null)
+		{
+			animation.play(arr[letterInd]);
+			curLetter = letterInd;
+		}
+	}
+
+	public function changeLetter(diff:Int = 0)
+	{
+		curLetter += diff;
+
+		if (curLetter < 0)
+			curLetter = arr.length - 1;
+		if (curLetter >= arr.length)
+			curLetter = 0;
+
+		animation.play(arr[curLetter]);
+	}
+}

From bbdd750d3bc0f1bb8e31d0c3457e3f0a172f53d8 Mon Sep 17 00:00:00 2001
From: Cameron Taylor <cameron.taylor.ninja@gmail.com>
Date: Tue, 27 Sep 2022 04:53:34 -0400
Subject: [PATCH 3/7] hold infinite scroll stuff

---
 source/funkin/FreeplayState.hx | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx
index c2c0e5c8c..53b9302e5 100644
--- a/source/funkin/FreeplayState.hx
+++ b/source/funkin/FreeplayState.hx
@@ -453,6 +453,9 @@ class FreeplayState extends MusicBeatSubstate
 
 	var initTouchPos:FlxPoint = new FlxPoint();
 
+	var spamTimer:Float = 0;
+	var spamming:Bool = false;
+
 	override function update(elapsed:Float)
 	{
 		super.update(elapsed);
@@ -561,6 +564,31 @@ class FreeplayState extends MusicBeatSubstate
 		}
 		#end
 
+		if (controls.UI_UP || controls.UI_DOWN)
+		{
+			spamTimer += elapsed;
+
+			if (spamming)
+			{
+				if (spamTimer >= 0.07)
+				{
+					spamTimer = 0;
+
+					if (controls.UI_UP)
+						changeSelection(-1);
+					else
+						changeSelection(1);
+				}
+			}
+			else if (spamTimer >= 0.9)
+				spamming = true;
+		}
+		else
+		{
+			spamming = false;
+			spamTimer = 0;
+		}
+
 		if (upP)
 			changeSelection(-1);
 		if (downP)

From 9c2efc4d0ea48d848c02b1ccf4399a86ff5a12d3 Mon Sep 17 00:00:00 2001
From: Cameron Taylor <cameron.taylor.ninja@gmail.com>
Date: Wed, 28 Sep 2022 02:50:53 -0400
Subject: [PATCH 4/7] fav in progress

---
 source/funkin/FreeplayState.hx | 61 ++++++++++++++++++++++++++--------
 1 file changed, 48 insertions(+), 13 deletions(-)

diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx
index 53b9302e5..6462aa196 100644
--- a/source/funkin/FreeplayState.hx
+++ b/source/funkin/FreeplayState.hx
@@ -248,7 +248,15 @@ class FreeplayState extends MusicBeatSubstate
 
 			letterSort.changeSelectionCallback = (str) ->
 			{
-				generateSongList(str, true);
+				switch (str)
+				{
+					case "fav":
+						generateSongList({filterType: FAVORITE}, true);
+					case "ALL":
+						generateSongList(null, true);
+					default:
+						generateSongList({filterType: STARTSWITH, filterData: str}, true);
+				}
 			};
 
 			new FlxTimer().start(1 / 24, function(handShit)
@@ -320,7 +328,7 @@ class FreeplayState extends MusicBeatSubstate
 		super.create();
 	}
 
-	public function generateSongList(?startsWith:String, ?force:Bool = false)
+	public function generateSongList(?filterStuff:SongFilter, ?force:Bool = false)
 	{
 		curSelected = 0;
 
@@ -329,21 +337,24 @@ class FreeplayState extends MusicBeatSubstate
 		// var regexp:EReg = regexp;
 		var tempSongs:Array<SongMetadata> = songs;
 
-		if (startsWith != null)
+		if (filterStuff != null)
 		{
-			trace("STARTS WITH: " + startsWith);
-			switch (startsWith)
+			switch (filterStuff.filterType)
 			{
-				case "ALL":
-				case "#":
-				default:
-					trace(tempSongs.length);
-
+				case STARTSWITH:
 					tempSongs = tempSongs.filter(str ->
 					{
-						return str.songName.toLowerCase().startsWith(startsWith);
+						return str.songName.toLowerCase().startsWith(filterStuff.filterData);
 					});
-					trace(tempSongs.length);
+				case ALL:
+				// no filter!
+				case FAVORITE:
+					tempSongs = tempSongs.filter(str ->
+					{
+						return str.isFav;
+					});
+				default:
+					// return all on default
 			}
 		}
 
@@ -460,6 +471,15 @@ class FreeplayState extends MusicBeatSubstate
 	{
 		super.update(elapsed);
 
+		if (FlxG.keys.justPressed.F)
+		{
+			songs[curSelected].isFav = !songs[curSelected].isFav;
+			if (songs[curSelected].isFav)
+				FlxTween.tween(grpCapsules.members[curSelected], {angle: 360}, 0.4, {ease: FlxEase.elasticOut});
+			else
+				FlxTween.tween(grpCapsules.members[curSelected], {angle: 0}, 0.4, {ease: FlxEase.elasticOut});
+		}
+
 		if (FlxG.keys.justPressed.T)
 			typing.hasFocus = true;
 
@@ -799,16 +819,31 @@ class DifficultySelector extends FlxSprite
 	}
 }
 
+typedef SongFilter =
+{
+	var filterType:FilterType;
+	var ?filterData:Dynamic;
+}
+
+enum abstract FilterType(String)
+{
+	var STARTSWITH;
+	var FAVORITE;
+	var ALL;
+}
+
 class SongMetadata
 {
 	public var songName:String = "";
 	public var week:Int = 0;
 	public var songCharacter:String = "";
+	public var isFav:Bool = false;
 
-	public function new(song:String, week:Int, songCharacter:String)
+	public function new(song:String, week:Int, songCharacter:String, ?isFav:Bool = false)
 	{
 		this.songName = song;
 		this.week = week;
 		this.songCharacter = songCharacter;
+		this.isFav = isFav;
 	}
 }

From 085cc452534df530ce5fdbdecde8c6fa40c03dd7 Mon Sep 17 00:00:00 2001
From: Cameron Taylor <cameron.taylor.ninja@gmail.com>
Date: Wed, 28 Sep 2022 03:40:51 -0400
Subject: [PATCH 5/7] cute hearts

---
 source/funkin/FreeplayState.hx              | 26 +++++++++++++++++++--
 source/funkin/freeplayStuff/SongMenuItem.hx |  9 +++++++
 2 files changed, 33 insertions(+), 2 deletions(-)

diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx
index 6462aa196..672464202 100644
--- a/source/funkin/FreeplayState.hx
+++ b/source/funkin/FreeplayState.hx
@@ -381,6 +381,7 @@ class FreeplayState extends MusicBeatSubstate
 			funnyMenu.ID = i;
 			funnyMenu.alpha = 0.5;
 			funnyMenu.songText.visible = false;
+			funnyMenu.favIcon.visible = tempSongs[i].isFav;
 
 			fp.updateScore(0);
 
@@ -473,11 +474,32 @@ class FreeplayState extends MusicBeatSubstate
 
 		if (FlxG.keys.justPressed.F)
 		{
+			var realShit = curSelected;
 			songs[curSelected].isFav = !songs[curSelected].isFav;
 			if (songs[curSelected].isFav)
-				FlxTween.tween(grpCapsules.members[curSelected], {angle: 360}, 0.4, {ease: FlxEase.elasticOut});
+			{
+				
+				FlxTween.tween(grpCapsules.members[realShit], {angle: 360}, 0.4, {
+					ease: FlxEase.elasticOut,
+					onComplete: _ ->
+					{
+						grpCapsules.members[realShit].favIcon.visible = true;
+						grpCapsules.members[realShit].favIcon.animation.play("fav");
+					}
+				});
+			}
 			else
-				FlxTween.tween(grpCapsules.members[curSelected], {angle: 0}, 0.4, {ease: FlxEase.elasticOut});
+			{
+				grpCapsules.members[realShit].favIcon.animation.play('fav', false, true);
+				new FlxTimer().start((1 / 24) * 14, _ ->
+				{
+					grpCapsules.members[realShit].favIcon.visible = false;
+				});
+				new FlxTimer().start((1 / 24) * 24, _ ->
+				{
+					FlxTween.tween(grpCapsules.members[realShit], {angle: 0}, 0.4, {ease: FlxEase.elasticOut});
+				});
+			}
 		}
 
 		if (FlxG.keys.justPressed.T)
diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx
index 7eb862bef..d98668937 100644
--- a/source/funkin/freeplayStuff/SongMenuItem.hx
+++ b/source/funkin/freeplayStuff/SongMenuItem.hx
@@ -17,6 +17,7 @@ class SongMenuItem extends FlxSpriteGroup
 	public var songTitle:String = "Test";
 
 	public var songText:FlxText;
+	public var favIcon:FlxSprite;
 
 	public var targetPos:FlxPoint = new FlxPoint();
 	public var doLerp:Bool = false;
@@ -40,6 +41,14 @@ class SongMenuItem extends FlxSpriteGroup
 		songText.color = 0xFF43C1EA;
 		add(songText);
 
+		favIcon = new FlxSprite(440, 40);
+		favIcon.frames = Paths.getSparrowAtlas('freeplay/favHeart');
+		favIcon.animation.addByPrefix('fav', "favorite heart", 24, false);
+		favIcon.animation.play('fav');
+		favIcon.antialiasing = true;
+		favIcon.setGraphicSize(60, 60);
+		add(favIcon);
+
 		selected = selected; // just to kickstart the set_selected
 	}
 

From 4af59051000f3e449ddabfb1b6b0671cd0bb5f3f Mon Sep 17 00:00:00 2001
From: Cameron Taylor <cameron.taylor.ninja@gmail.com>
Date: Wed, 14 Dec 2022 12:25:12 -0500
Subject: [PATCH 6/7] better freeplay number

---
 source/funkin/freeplayStuff/FreeplayScore.hx | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/source/funkin/freeplayStuff/FreeplayScore.hx b/source/funkin/freeplayStuff/FreeplayScore.hx
index cef363cd0..5fb529485 100644
--- a/source/funkin/freeplayStuff/FreeplayScore.hx
+++ b/source/funkin/freeplayStuff/FreeplayScore.hx
@@ -51,7 +51,7 @@ class FreeplayScore extends FlxTypedSpriteGroup<ScoreNum>
 
 		for (i in 0...7)
 		{
-			add(new ScoreNum(x + (45 * i), y, 0));
+			add(new ScoreNum(x + (35 * i), y, 0));
 		}
 
 		this.scoreShit = scoreShit;
@@ -69,13 +69,15 @@ class ScoreNum extends FlxSprite
 
 	function set_digit(val):Int
 	{
-		if (animation.curAnim != null && animation.curAnim.name != Std.string(val))
+		if (animation.curAnim != null && animation.curAnim.name != numToString[val])
 		{
-			animation.play(Std.string(val), true, false, 0);
+			animation.play(numToString[val], true, false, 0);
 			updateHitbox();
 
 			switch (val)
 			{
+				case 1:
+					offset.x -= 15;
 				case 5:
 				// set offsets
 				// offset.x += 0;
@@ -98,6 +100,8 @@ class ScoreNum extends FlxSprite
 	public var baseY:Float = 0;
 	public var baseX:Float = 0;
 
+	var numToString:Array<String> = ["ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE"];
+
 	public function new(x:Float, y:Float, ?initDigit:Int = 0)
 	{
 		super(x, y);
@@ -105,17 +109,17 @@ class ScoreNum extends FlxSprite
 		baseY = y;
 		baseX = x;
 
-		frames = Paths.getSparrowAtlas('noteComboNumbers');
+		frames = Paths.getSparrowAtlas('digital_numbers');
 
 		for (i in 0...10)
 		{
-			var stringNum:String = Std.string(i);
+			var stringNum:String = numToString[i];
 			animation.addByPrefix(stringNum, stringNum, 24, false);
 		}
 
 		this.digit = initDigit;
 
-		animation.play(Std.string(digit), true);
+		animation.play(numToString[digit], true);
 		antialiasing = true;
 
 		setGraphicSize(Std.int(width * 0.3));

From e1f53d288d2601b48579cf1b20165fc00a7974ab Mon Sep 17 00:00:00 2001
From: Cameron Taylor <cameron.taylor.ninja@gmail.com>
Date: Fri, 16 Dec 2022 17:10:22 -0500
Subject: [PATCH 7/7] freeplay highscore stuff

---
 source/funkin/FreeplayState.hx               | 48 +++++++++++++++---
 source/funkin/Highscore.hx                   | 53 ++++++++++++++++++++
 source/funkin/PauseSubState.hx               |  3 ++
 source/funkin/freeplayStuff/FreeplayScore.hx |  4 +-
 source/funkin/play/PlayState.hx              | 11 +++-
 source/funkin/play/ResultState.hx            |  4 +-
 6 files changed, 112 insertions(+), 11 deletions(-)

diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx
index 9bbe54497..5c9fb1a99 100644
--- a/source/funkin/FreeplayState.hx
+++ b/source/funkin/FreeplayState.hx
@@ -47,6 +47,9 @@ class FreeplayState extends MusicBeatSubstate
 	var curDifficulty:Int = 1;
 
 	var fp:FreeplayScore;
+	var txtCompletion:FlxText;
+	var lerpCompletion:Float = 0;
+	var intendedCompletion:Float = 0;
 	var lerpScore:Float = 0;
 	var intendedScore:Int = 0;
 
@@ -233,10 +236,30 @@ class FreeplayState extends MusicBeatSubstate
 		fnfFreeplay.shader = sillyStroke;
 		add(fnfFreeplay);
 
-		fp = new FreeplayScore(420, 40, 100);
+		var fnfHighscoreSpr:FlxSprite = new FlxSprite(890, 70);
+		fnfHighscoreSpr.frames = Paths.getSparrowAtlas('freeplay/highscore');
+		fnfHighscoreSpr.animation.addByPrefix("highscore", "highscore", 24, false);
+		fnfHighscoreSpr.visible = false;
+		fnfHighscoreSpr.setGraphicSize(0, Std.int(fnfHighscoreSpr.height * 1));
+		fnfHighscoreSpr.antialiasing = true;
+		fnfHighscoreSpr.updateHitbox();
+		add(fnfHighscoreSpr);
+
+		new FlxTimer().start(FlxG.random.float(12, 50), function(tmr)
+		{
+			fnfHighscoreSpr.animation.play("highscore");
+			tmr.time = FlxG.random.float(20, 60);
+		}, 0);
+
+		fp = new FreeplayScore(460, 60, 100);
 		fp.visible = false;
 		add(fp);
 
+		txtCompletion = new FlxText(1200, 77, 0, "0", 32);
+		txtCompletion.font = "VCR OSD Mono";
+		txtCompletion.visible = false;
+		add(txtCompletion);
+
 		dj.onIntroDone.add(function()
 		{
 			FlxTween.tween(grpDifficulties, {x: 90}, 0.6, {ease: FlxEase.quartOut});
@@ -262,9 +285,13 @@ class FreeplayState extends MusicBeatSubstate
 
 			new FlxTimer().start(1 / 24, function(handShit)
 			{
+				fnfHighscoreSpr.visible = true;
 				fnfFreeplay.visible = true;
 				fp.visible = true;
-				fp.updateScore(FlxG.random.int(0, 1000));
+				fp.updateScore(0);
+
+				txtCompletion.visible = true;
+				intendedCompletion = 0;
 
 				new FlxTimer().start(1.5 / 24, function(bold)
 				{
@@ -384,7 +411,7 @@ class FreeplayState extends MusicBeatSubstate
 			funnyMenu.songText.visible = false;
 			funnyMenu.favIcon.visible = tempSongs[i].isFav;
 
-			fp.updateScore(0);
+			// fp.updateScore(0);
 
 			new FlxTimer().start((1 / 24) * i, function(doShit)
 			{
@@ -479,7 +506,6 @@ class FreeplayState extends MusicBeatSubstate
 			songs[curSelected].isFav = !songs[curSelected].isFav;
 			if (songs[curSelected].isFav)
 			{
-				
 				FlxTween.tween(grpCapsules.members[realShit], {angle: 360}, 0.4, {
 					ease: FlxEase.elasticOut,
 					onComplete: _ ->
@@ -515,8 +541,16 @@ class FreeplayState extends MusicBeatSubstate
 		}
 
 		lerpScore = CoolUtil.coolLerp(lerpScore, intendedScore, 0.2);
+		lerpCompletion = CoolUtil.coolLerp(lerpCompletion, intendedCompletion, 0.9);
 
-		fp.scoreShit = Std.int(lerpScore);
+		fp.updateScore(Std.int(lerpScore));
+
+		txtCompletion.text = Math.floor(lerpCompletion * 100) + "%";
+		trace(Highscore.getCompletion(songs[curSelected].songName, curDifficulty));
+
+		// trace(intendedScore);
+		// trace(lerpScore);
+		// Highscore.getAllScores();
 
 		var upP = controls.UI_UP_P;
 		var downP = controls.UI_DOWN_P;
@@ -736,6 +770,7 @@ class FreeplayState extends MusicBeatSubstate
 
 		// intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty);
 		intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty);
+		intendedCompletion = Highscore.getCompletion(songs[curSelected].songName, curDifficulty);
 
 		PlayState.storyDifficulty = curDifficulty;
 		PlayState.storyDifficulty_NEW = switch (curDifficulty)
@@ -799,7 +834,8 @@ class FreeplayState extends MusicBeatSubstate
 		// selector.y = (70 * curSelected) + 30;
 
 		// intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty);
-		intendedScore = FlxG.random.int(0, 1000000);
+		intendedScore = Highscore.getScore(songs[curSelected].songName, curDifficulty);
+		intendedCompletion = Highscore.getCompletion(songs[curSelected].songName, curDifficulty);
 		// lerpScore = 0;
 
 		#if PRELOAD_ALL
diff --git a/source/funkin/Highscore.hx b/source/funkin/Highscore.hx
index ff0f36424..9a878bc40 100644
--- a/source/funkin/Highscore.hx
+++ b/source/funkin/Highscore.hx
@@ -8,6 +8,12 @@ class Highscore
 	public static var songScores:Map<String, Int> = new Map<String, Int>();
 	#end
 
+	#if (haxe >= "4.0.0")
+	public static var songCompletion:Map<String, Float> = new Map();
+	#else
+	public static var songCompletion:Map<String, Float> = new Map<String, Float>();
+	#end
+
 	public static var tallies:Tallies = new Tallies();
 
 	public static function saveScore(song:String, score:Int = 0, ?diff:Int = 0):Bool
@@ -33,6 +39,24 @@ class Highscore
 		return false;
 	}
 
+	public static function saveCompletion(song:String, completion:Float, ?diff:Int = 0):Bool
+	{
+		var formattedSong:String = formatSong(song, diff);
+
+		if (songCompletion.exists(formattedSong))
+		{
+			if (songCompletion.get(formattedSong) < completion)
+			{
+				setCompletion(formattedSong, completion);
+				return true;
+			}
+		}
+		else
+			setCompletion(formattedSong, completion);
+
+		return false;
+	}
+
 	public static function saveWeekScore(week:Int = 1, score:Int = 0, ?diff:Int = 0):Void
 	{
 		#if newgrounds
@@ -50,6 +74,13 @@ class Highscore
 			setScore(formattedSong, score);
 	}
 
+	static function setCompletion(formattedSong:String, completion:Float):Void
+	{
+		songCompletion.set(formattedSong, completion);
+		FlxG.save.data.songCompletion = songCompletion;
+		FlxG.save.flush();
+	}
+
 	/**
 	 * YOU SHOULD FORMAT SONG WITH formatSong() BEFORE TOSSING IN SONG VARIABLE
 	 */
@@ -88,6 +119,19 @@ class Highscore
 		return songScores.get(formatSong(song, diff));
 	}
 
+	public static function getCompletion(song, diff):Float
+	{
+		if (!songCompletion.exists(formatSong(song, diff)))
+			setCompletion(formatSong(song, diff), 0);
+
+		return songCompletion.get(formatSong(song, diff));
+	}
+
+	public static function getAllScores()
+	{
+		trace(songScores.toString());
+	}
+
 	public static function getWeekScore(week:Int, diff:Int):Int
 	{
 		if (!songScores.exists(formatSong('week' + week, diff)))
@@ -102,6 +146,9 @@ class Highscore
 		{
 			songScores = FlxG.save.data.songScores;
 		}
+
+		if (FlxG.save.data.songCompletion != null)
+			songCompletion = FlxG.save.data.songCompletion;
 	}
 }
 
@@ -120,6 +167,7 @@ abstract Tallies(RawTallies)
 			good: 0,
 			sick: 0,
 			totalNotes: 0,
+			totalNotesHit: 0,
 			maxCombo: 0,
 			isNewHighscore: false
 		}
@@ -145,5 +193,10 @@ typedef RawTallies =
 	/**
 	 * How many notes total that you hit. (NOT how many notes total in the song!)
 	 */
+	var totalNotesHit:Int;
+
+	/**
+	 * How many notes PASSED BY AND/OR HIT!!!
+	 */
 	var totalNotes:Int;
 }
diff --git a/source/funkin/PauseSubState.hx b/source/funkin/PauseSubState.hx
index 34dffd377..42e33624d 100644
--- a/source/funkin/PauseSubState.hx
+++ b/source/funkin/PauseSubState.hx
@@ -81,6 +81,9 @@ class PauseSubState extends MusicBeatSubstate
 
 		var deathCounter:FlxText = new FlxText(20, 15 + 64, 0, "", 32);
 		deathCounter.text = "Blue balled: " + PlayState.deathCounter;
+		deathCounter.text += "\n" + Highscore.tallies.totalNotesHit;
+		deathCounter.text += "\n" + Highscore.tallies.totalNotes;
+		deathCounter.text += "\n" + Std.string(Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes);
 		deathCounter.scrollFactor.set();
 		deathCounter.setFormat(Paths.font('vcr.ttf'), 32);
 		deathCounter.updateHitbox();
diff --git a/source/funkin/freeplayStuff/FreeplayScore.hx b/source/funkin/freeplayStuff/FreeplayScore.hx
index 5fb529485..0a062f6b6 100644
--- a/source/funkin/freeplayStuff/FreeplayScore.hx
+++ b/source/funkin/freeplayStuff/FreeplayScore.hx
@@ -51,7 +51,7 @@ class FreeplayScore extends FlxTypedSpriteGroup<ScoreNum>
 
 		for (i in 0...7)
 		{
-			add(new ScoreNum(x + (35 * i), y, 0));
+			add(new ScoreNum(x + (45 * i), y, 0));
 		}
 
 		this.scoreShit = scoreShit;
@@ -122,7 +122,7 @@ class ScoreNum extends FlxSprite
 		animation.play(numToString[digit], true);
 		antialiasing = true;
 
-		setGraphicSize(Std.int(width * 0.3));
+		setGraphicSize(Std.int(width * 0.4));
 		updateHitbox();
 	}
 }
diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx
index 73e54e238..8cdb28931 100644
--- a/source/funkin/play/PlayState.hx
+++ b/source/funkin/play/PlayState.hx
@@ -1196,6 +1196,9 @@ class PlayState extends MusicBeatState
 
 	function regenNoteData_NEW():Void
 	{
+		Highscore.tallies.combo = 0;
+		Highscore.tallies = new Tallies();
+
 		// Reset song events.
 		songEvents = currentChart.getEvents();
 		SongEventHandler.resetEvents(songEvents);
@@ -1637,6 +1640,10 @@ class PlayState extends MusicBeatState
 		while (inactiveNotes[0] != null && inactiveNotes[0].data.strumTime - Conductor.songPosition < 1800 / SongLoad.getSpeed())
 		{
 			var dunceNote:Note = inactiveNotes[0];
+
+			if (dunceNote.mustPress && !dunceNote.isSustainNote)
+				Highscore.tallies.totalNotes++;
+
 			activeNotes.add(dunceNote);
 
 			inactiveNotes.shift();
@@ -1834,6 +1841,8 @@ class PlayState extends MusicBeatState
 		{
 			// crackhead double thingie, sets whether was new highscore, AND saves the song!
 			Highscore.tallies.isNewHighscore = Highscore.saveScore(currentSong.song, songScore, storyDifficulty);
+
+			Highscore.saveCompletion(currentSong.song, Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes, storyDifficulty);
 		}
 
 		if (isStoryMode)
@@ -2261,7 +2270,7 @@ class PlayState extends MusicBeatState
 			if (!note.isSustainNote)
 			{
 				Highscore.tallies.combo++;
-				Highscore.tallies.totalNotes++;
+				Highscore.tallies.totalNotesHit++;
 
 				if (Highscore.tallies.combo > Highscore.tallies.maxCombo)
 					Highscore.tallies.maxCombo = Highscore.tallies.combo;
diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx
index 59dc67c47..1adac9300 100644
--- a/source/funkin/play/ResultState.hx
+++ b/source/funkin/play/ResultState.hx
@@ -28,7 +28,7 @@ class ResultState extends MusicBeatSubstate
 
 	override function create()
 	{
-		if (Highscore.tallies.sick == Highscore.tallies.totalNotes && Highscore.tallies.maxCombo == Highscore.tallies.totalNotes)
+		if (Highscore.tallies.sick == Highscore.tallies.totalNotesHit && Highscore.tallies.maxCombo == Highscore.tallies.totalNotesHit)
 			resultsVariation = PERFECT;
 		else if (Highscore.tallies.missed + Highscore.tallies.bad + Highscore.tallies.shit >= Highscore.tallies.totalNotes * 0.50)
 			resultsVariation = SHIT; // if more than half of your song was missed, bad, or shit notes, you get shit ending!
@@ -165,7 +165,7 @@ class ResultState extends MusicBeatSubstate
 		var ratingGrp:FlxTypedGroup<TallyCounter> = new FlxTypedGroup<TallyCounter>();
 		add(ratingGrp);
 
-		var totalHit:TallyCounter = new TallyCounter(375, hStuf * 3, Highscore.tallies.totalNotes);
+		var totalHit:TallyCounter = new TallyCounter(375, hStuf * 3, Highscore.tallies.totalNotesHit);
 		ratingGrp.add(totalHit);
 
 		var maxCombo:TallyCounter = new TallyCounter(375, hStuf * 4, Highscore.tallies.maxCombo);