From 6cb58163787b6951fa6fffed01a48e444cd5b5f5 Mon Sep 17 00:00:00 2001
From: EliteMasterEric <ericmyllyoja@gmail.com>
Date: Tue, 21 May 2024 02:49:07 -0400
Subject: [PATCH] Add freeplay favorites to the save data so they persist
 between sessions.

---
 source/funkin/input/Controls.hx            |  2 +-
 source/funkin/save/Save.hx                 | 44 ++++++++++++++++++++--
 source/funkin/save/changelog.md            |  3 ++
 source/funkin/ui/freeplay/FreeplayState.hx | 25 +++++++++++-
 4 files changed, 68 insertions(+), 6 deletions(-)

diff --git a/source/funkin/input/Controls.hx b/source/funkin/input/Controls.hx
index 1983d413b..cede0b688 100644
--- a/source/funkin/input/Controls.hx
+++ b/source/funkin/input/Controls.hx
@@ -707,7 +707,7 @@ class Controls extends FlxActionSet
           case Control.VOLUME_UP: return [PLUS, NUMPADPLUS];
           case Control.VOLUME_DOWN: return [MINUS, NUMPADMINUS];
           case Control.VOLUME_MUTE: return [ZERO, NUMPADZERO];
-          case Control.FULLSCREEN: return [FlxKey.F];
+          case Control.FULLSCREEN: return [FlxKey.F11]; // We use F for other things LOL.
 
         }
       case Duo(true):
diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx
index acbe59edd..7b2d3f511 100644
--- a/source/funkin/save/Save.hx
+++ b/source/funkin/save/Save.hx
@@ -14,8 +14,7 @@ import funkin.util.SerializerUtil;
 @:nullSafety
 class Save
 {
-  // Version 2.0.2 adds attributes to `optionsChartEditor`, that should return default values if they are null.
-  public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.3";
+  public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.4";
   public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x";
 
   // We load this version's saves from a new save path, to maintain SOME level of backwards compatibility.
@@ -53,7 +52,8 @@ class Save
   public function new(?data:RawSaveData)
   {
     if (data == null) this.data = Save.getDefault();
-    else this.data = data;
+    else
+      this.data = data;
   }
 
   public static function getDefault():RawSaveData
@@ -77,6 +77,9 @@ class Save
           levels: [],
           songs: [],
         },
+
+      favoriteSongs: [],
+
       options:
         {
           // Reasonable defaults.
@@ -554,6 +557,35 @@ class Save
     return false;
   }
 
+  public function isSongFavorited(id:String):Bool
+  {
+    if (data.favoriteSongs == null)
+    {
+      data.favoriteSongs = [];
+      flush();
+    };
+
+    return data.favoriteSongs.contains(id);
+  }
+
+  public function favoriteSong(id:String):Void
+  {
+    if (!isSongFavorited(id))
+    {
+      data.favoriteSongs.push(id);
+      flush();
+    }
+  }
+
+  public function unfavoriteSong(id:String):Void
+  {
+    if (isSongFavorited(id))
+    {
+      data.favoriteSongs.remove(id);
+      flush();
+    }
+  }
+
   public function getControls(playerId:Int, inputType:Device):Null<SaveControlsData>
   {
     switch (inputType)
@@ -740,6 +772,12 @@ typedef RawSaveData =
    */
   var options:SaveDataOptions;
 
+  /**
+   * The user's favorited songs in the Freeplay menu,
+   * as a list of song IDs.
+   */
+  var favoriteSongs:Array<String>;
+
   var mods:SaveDataMods;
 
   /**
diff --git a/source/funkin/save/changelog.md b/source/funkin/save/changelog.md
index 3fa9839d1..7c9094f2d 100644
--- a/source/funkin/save/changelog.md
+++ b/source/funkin/save/changelog.md
@@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [2.0.4] - 2024-05-21
+### Added
+- `favoriteSongs:Array<String>` to `Save`
 
 ## [2.0.3] - 2024-01-09
 ### Added
diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx
index 239068288..911d07a56 100644
--- a/source/funkin/ui/freeplay/FreeplayState.hx
+++ b/source/funkin/ui/freeplay/FreeplayState.hx
@@ -699,8 +699,8 @@ class FreeplayState extends MusicBeatSubState
       if (targetSong != null)
       {
         var realShit:Int = curSelected;
-        targetSong.isFav = !targetSong.isFav;
-        if (targetSong.isFav)
+        var isFav = targetSong.toggleFavorite();
+        if (isFav)
         {
           FlxTween.tween(grpCapsules.members[realShit], {angle: 360}, 0.4,
             {
@@ -1398,11 +1398,32 @@ class FreeplaySongData
     this.levelId = levelId;
     this.songId = songId;
     this.song = song;
+
+    this.isFav = Save.instance.isSongFavorited(songId);
+
     if (displayedVariations != null) this.displayedVariations = displayedVariations;
 
     updateValues(displayedVariations);
   }
 
+  /**
+   * Toggle whether or not the song is favorited, then flush to save data.
+   * @return Whether or not the song is now favorited.
+   */
+  public function toggleFavorite():Bool
+  {
+    isFav = !isFav;
+    if (isFav)
+    {
+      Save.instance.favoriteSong(this.songId);
+    }
+    else
+    {
+      Save.instance.unfavoriteSong(this.songId);
+    }
+    return isFav;
+  }
+
   function updateValues(variations:Array<String>):Void
   {
     this.songDifficulties = song.listDifficulties(variations, false, false);