diff --git a/Assets/Prefabs/UI/SongView.prefab b/Assets/Prefabs/UI/SongView.prefab index 04405ff6e..324967e2a 100644 --- a/Assets/Prefabs/UI/SongView.prefab +++ b/Assets/Prefabs/UI/SongView.prefab @@ -88,7 +88,7 @@ GameObject: - component: {fileID: 7898971487434874996} - component: {fileID: 1200385484507932754} m_Layer: 5 - m_Name: Length Text + m_Name: Score Text m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 @@ -760,7 +760,7 @@ MonoBehaviour: m_EditorClassIdentifier: songName: {fileID: 6846856528720448031} artist: {fileID: 7006697502306556741} - lengthText: {fileID: 1200385484507932754} + scoreText: {fileID: 1200385484507932754} --- !u!225 &215129106566158253 CanvasGroup: m_ObjectHideFlags: 0 diff --git a/Assets/Scenes/MenuScene.unity b/Assets/Scenes/MenuScene.unity index 1f9a05b95..e9e521747 100644 --- a/Assets/Scenes/MenuScene.unity +++ b/Assets/Scenes/MenuScene.unity @@ -3811,7 +3811,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 1, y: 1} m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: -7.9999695, y: -30.500004} + m_AnchoredPosition: {x: -7.9999084, y: -52.5} m_SizeDelta: {x: 125, y: 11} m_Pivot: {x: 0.9999996, y: 1} --- !u!114 &1886399724 @@ -4116,7 +4116,7 @@ GameObject: - component: {fileID: 1986583193} - component: {fileID: 1986583192} m_Layer: 5 - m_Name: Length Text + m_Name: Score Text m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 @@ -4140,7 +4140,7 @@ RectTransform: m_AnchorMin: {x: 1, y: 1} m_AnchorMax: {x: 1, y: 1} m_AnchoredPosition: {x: -7.9999695, y: -12.5} - m_SizeDelta: {x: 125, y: 29} + m_SizeDelta: {x: 125, y: 20} m_Pivot: {x: 0.9999996, y: 1} --- !u!114 &1986583192 MonoBehaviour: @@ -4182,7 +4182,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: N/A + m_text: 1:23 m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: ae170e91fd29a90479e906ddffb1d8ee, type: 2} m_sharedMaterial: {fileID: 2100000, guid: 97a4ac31562f98641a2b7822989f607a, type: 2} @@ -4292,6 +4292,7 @@ RectTransform: m_Children: - {fileID: 1844302445} - {fileID: 1986583191} + - {fileID: 2086297276} - {fileID: 1886399723} - {fileID: 1330867043} - {fileID: 80151784} @@ -4358,13 +4359,170 @@ MonoBehaviour: m_EditorClassIdentifier: songName: {fileID: 149797927} artist: {fileID: 1290299072} - lengthText: {fileID: 1986583193} + scoreText: {fileID: 1986583193} + lengthText: {fileID: 2086297278} supportText: {fileID: 1886399725} albumCover: {fileID: 1330867046} albumCoverAlt: {fileID: 1876917083} difficultyContainer: {fileID: 332568989} difficultyView: {fileID: 397774209812536635, guid: 53438a07c6e5ec5409d5bde8514761ca, type: 3} +--- !u!1 &2086297275 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2086297276} + - component: {fileID: 2086297279} + - component: {fileID: 2086297278} + - component: {fileID: 2086297277} + m_Layer: 5 + m_Name: Length Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2086297276 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2086297275} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2044165073} + m_RootOrder: -1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 1, y: 1} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: -7.9999084, y: -32.5} + m_SizeDelta: {x: 125, y: 20} + m_Pivot: {x: 0.9999996, y: 1} +--- !u!114 &2086297277 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2086297275} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreLayout: 0 + m_MinWidth: -1 + m_MinHeight: -1 + m_PreferredWidth: 0 + m_PreferredHeight: -1 + m_FlexibleWidth: -1 + m_FlexibleHeight: -1 + m_LayoutPriority: 1 +--- !u!114 &2086297278 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2086297275} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: N/A + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: ae170e91fd29a90479e906ddffb1d8ee, type: 2} + m_sharedMaterial: {fileID: 2100000, guid: 97a4ac31562f98641a2b7822989f607a, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 1, g: 1, b: 1, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 16 + m_fontSizeBase: 16 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 4 + m_VerticalAlignment: 256 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_enableWordWrapping: 0 + m_wordWrappingRatios: 0.4 + m_overflowMode: 1 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 1 + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!222 &2086297279 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2086297275} + m_CullTransparentMesh: 1 --- !u!1 &2115213575 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Script/Data/SongInfo.cs b/Assets/Script/Data/SongInfo.cs index d296c70d0..d58dd0b86 100644 --- a/Assets/Script/Data/SongInfo.cs +++ b/Assets/Script/Data/SongInfo.cs @@ -27,6 +27,11 @@ public class SongInfo { [JsonConverter(typeof(DirectoryInfoConverter))] public DirectoryInfo folder; + /// + /// For remote mode only. + /// + public DirectoryInfo realFolderRemote; + public bool BassPedal2xExpertPlus { private set; get; @@ -163,5 +168,9 @@ public SongInfo(DirectoryInfo folder) { partDifficulties = new(DEFAULT_DIFFS); } + + public SongInfo Duplicate() { + return (SongInfo) MemberwiseClone(); + } } } \ No newline at end of file diff --git a/Assets/Script/Data/SongScore.cs b/Assets/Script/Data/SongScore.cs new file mode 100644 index 000000000..a5905e760 --- /dev/null +++ b/Assets/Script/Data/SongScore.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; + +namespace YARG.Data { + [JsonObject(MemberSerialization.Fields)] + public class SongScore { + public DateTime lastPlayed; + public int timesPlayed; + + public float TotalHighestPercent => highestPercent.Max(i => i.Value); + public Dictionary highestPercent; + } +} \ No newline at end of file diff --git a/Assets/Script/Data/SongScore.cs.meta b/Assets/Script/Data/SongScore.cs.meta new file mode 100644 index 000000000..5a14dc73a --- /dev/null +++ b/Assets/Script/Data/SongScore.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1b5aea9ad45657b428a02e718545b516 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Script/PlayMode/AbstractTrack.cs b/Assets/Script/PlayMode/AbstractTrack.cs index 19e50ec3c..ed47dadd7 100644 --- a/Assets/Script/PlayMode/AbstractTrack.cs +++ b/Assets/Script/PlayMode/AbstractTrack.cs @@ -109,6 +109,8 @@ private void Start() { player.inputStrategy.StarpowerEvent += StarpowerAction; Play.Instance.BeatEvent += BeatAction; + player.lastScore = null; + GameUI.Instance.AddTrackImage(trackCamera.targetTexture); // Adjust hit window diff --git a/Assets/Script/PlayMode/DrumsTrack.cs b/Assets/Script/PlayMode/DrumsTrack.cs index a0118a0fb..251d143a9 100644 --- a/Assets/Script/PlayMode/DrumsTrack.cs +++ b/Assets/Script/PlayMode/DrumsTrack.cs @@ -57,7 +57,7 @@ protected override void OnDestroy() { // Set score player.lastScore = new PlayerManager.Score { - percentage = (float) notesHit / Chart.Count, + percentage = notesHit == 0 ? 1f : (float) notesHit / Chart.Count, notesHit = notesHit, notesMissed = Chart.Count - notesHit }; diff --git a/Assets/Script/PlayMode/FiveFretTrack.cs b/Assets/Script/PlayMode/FiveFretTrack.cs index 9c5ac7b21..a90f5617d 100644 --- a/Assets/Script/PlayMode/FiveFretTrack.cs +++ b/Assets/Script/PlayMode/FiveFretTrack.cs @@ -63,7 +63,7 @@ protected override void OnDestroy() { // Set score player.lastScore = new PlayerManager.Score { - percentage = (float) notesHit / Chart.Count, + percentage = notesHit == 0 ? 1f : (float) notesHit / Chart.Count, notesHit = notesHit, notesMissed = Chart.Count - notesHit }; diff --git a/Assets/Script/PlayMode/RealGuitarTrack.cs b/Assets/Script/PlayMode/RealGuitarTrack.cs index e36cf93bc..f6664e15c 100644 --- a/Assets/Script/PlayMode/RealGuitarTrack.cs +++ b/Assets/Script/PlayMode/RealGuitarTrack.cs @@ -70,7 +70,7 @@ protected override void OnDestroy() { // Set score player.lastScore = new PlayerManager.Score { - percentage = (float) notesHit / Chart.Count, + percentage = notesHit == 0 ? 1f : (float) notesHit / Chart.Count, notesHit = notesHit, notesMissed = Chart.Count - notesHit }; diff --git a/Assets/Script/ScoreManager.cs b/Assets/Script/ScoreManager.cs new file mode 100644 index 000000000..5a2a04410 --- /dev/null +++ b/Assets/Script/ScoreManager.cs @@ -0,0 +1,102 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading; +using Newtonsoft.Json; +using YARG.Data; + +namespace YARG { + public static class ScoreManager { + /// + /// The location of the local or remote score file (depending on whether we are connected to a server). + /// + public static FileInfo ScoreFile { + get { + if (GameManager.client != null) { + return GameManager.client.remoteScore; + } + + return new(Path.Combine(SongLibrary.songFolder.ToString(), "yarg_score.json")); + } + } + + private static Dictionary scores = null; + + /// + /// Should be called before you access any scores. + /// + public static void FetchScores() { + if (scores != null) { + return; + } + + // Read from score file OR create new + if (ScoreFile.Exists) { + string json = File.ReadAllText(ScoreFile.ToString()); + scores = JsonConvert.DeserializeObject>(json); + } else { + scores = new(); + + // Create a dummy score file if one doesn't exist. + File.WriteAllText(ScoreFile.ToString(), "{}"); + } + } + + public static void PushScore(SongInfo song, SongScore score) { + string path = song.folder.ToString(); + if (GameManager.client != null) { + path = song.realFolderRemote.ToString(); + } + + if (!scores.TryGetValue(path, out var oldScore)) { + // If the score info doesn't exist, just add the new one. + scores.Add(path, score); + } else { + // Otherwise, MERGE! + oldScore.lastPlayed = score.lastPlayed; + oldScore.timesPlayed += score.timesPlayed; + + // Merge high scores + foreach (var kvp in score.highestPercent) { + if (oldScore.highestPercent.TryGetValue(kvp.Key, out var old)) { + if (old < kvp.Value) { + oldScore.highestPercent[kvp.Key] = kvp.Value; + } + } else { + oldScore.highestPercent.Add(kvp.Key, kvp.Value); + } + } + } + + // Save ASAP! + SaveScore(); + } + + public static SongScore GetScore(SongInfo song) { + string path = song.folder.ToString(); + if (song.realFolderRemote != null && GameManager.client != null) { + path = song.realFolderRemote.ToString(); + } + + if (scores.TryGetValue(path, out var o)) { + return o; + } + + return null; + } + + public static void SaveScore() { + var scoreCopy = new Dictionary(scores); + + // Prevent game lag by saving on another thread + ThreadPool.QueueUserWorkItem(_ => { + string json = JsonConvert.SerializeObject(scores, Formatting.Indented); + File.WriteAllText(ScoreFile.ToString(), json); + + // If remote, write scores on server + if (GameManager.client != null) { + GameManager.client.WriteScores(); + } + }); + } + } +} \ No newline at end of file diff --git a/Assets/Script/ScoreManager.cs.meta b/Assets/Script/ScoreManager.cs.meta new file mode 100644 index 000000000..c9338b183 --- /dev/null +++ b/Assets/Script/ScoreManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1737174bd22f1cb4cb8f01303c5e69ec +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Script/Server/Client.cs b/Assets/Script/Server/Client.cs index 21b7e1fef..c6f1b2101 100644 --- a/Assets/Script/Server/Client.cs +++ b/Assets/Script/Server/Client.cs @@ -14,6 +14,7 @@ public class Client { public event SignalAction SignalEvent; public FileInfo remoteCache; + public FileInfo remoteScore; public string remotePath; public string AlbumCoversPath => Path.Combine(remotePath, "_album_covers"); @@ -51,12 +52,15 @@ public void Start(string ip) { private void ClientThread() { var stream = client.GetStream(); - // Request cache - Send(stream, "ReqCache"); - // Read cache from server + Send(stream, "ReqCache"); remoteCache = new(Path.Combine(remotePath, "yarg_cache.json")); - ReadFile(stream, remoteCache); + Utils.ReadFile(stream, remoteCache); + + // Read score from server + Send(stream, "ReqScore"); + remoteScore = new(Path.Combine(remotePath, "yarg_score.json")); + Utils.ReadFile(stream, remoteScore); // Wait until request while (true) { @@ -66,7 +70,7 @@ private void ClientThread() { if (request.StartsWith("ReqSong,")) { // Read zipped song from server string zipPath = Path.Combine(remotePath, "download.zip"); - ReadFile(stream, new(zipPath)); + Utils.ReadFile(stream, new(zipPath)); // When done, unzip file string folderName = Utils.Hash(request[8..]); @@ -81,10 +85,32 @@ private void ClientThread() { // Read album.png from server string hash = Utils.Hash(request[14..]); string pngPath = Path.Combine(AlbumCoversPath, $"{hash}.png"); - ReadFile(stream, new(pngPath)); + Utils.ReadFile(stream, new(pngPath)); // Send signal signals.Enqueue($"AlbumCoverDone,{hash}"); + } else if (request == "WriteScores") { + // TODO: This sucks, but I'm too lazy + // Wait for proceed on the stream + while (true) { + if (stream.DataAvailable) { + // Get data from client + byte[] bytes = new byte[1024]; + int size = stream.Read(bytes, 0, bytes.Length); + + // Get request + var str = Encoding.UTF8.GetString(bytes, 0, size); + + if (str == "ProceedWriteScores") { + SendFile(stream, ScoreManager.ScoreFile); + } + + break; + } + + // Prevent CPU burn + Thread.Sleep(10); + } } } @@ -99,34 +125,14 @@ private void Send(NetworkStream stream, string str) { stream.Flush(); } - private void ReadFile(NetworkStream stream, FileInfo output) { - const int BUF_SIZE = 81920; - - // Wait until data is available - while (!stream.DataAvailable) { - Thread.Sleep(100); - } - - // Get file size - var buffer = new byte[sizeof(long)]; - stream.Read(buffer, 0, sizeof(long)); - long size = BitConverter.ToInt64(buffer); + private void SendFile(NetworkStream stream, FileInfo file) { + using var fs = file.OpenRead(); - // If the size is zero, the file did not exist on server - if (size <= 0) { - return; - } + // Send file size + stream.Write(BitConverter.GetBytes(fs.Length)); - // Copy data to disk - // We can't use CopyTo on a infinite stream (like NetworkStream) - long totalRead = 0; - var fileBuf = new byte[BUF_SIZE]; - using var fs = output.OpenWrite(); - while (totalRead < size) { - int bytesRead = stream.Read(fileBuf, 0, BUF_SIZE); - fs.Write(fileBuf, 0, bytesRead); - totalRead += bytesRead; - } + // Send file itself + fs.CopyTo(stream); } public void Stop() { @@ -183,5 +189,9 @@ public void RequestAlbumCover(string path) { // Otherwise, we have to request it requests.Enqueue($"ReqAlbumCover,{path}"); } + + public void WriteScores() { + requests.Enqueue("WriteScores"); + } } } \ No newline at end of file diff --git a/Assets/Script/Server/Host.cs b/Assets/Script/Server/Host.cs index 17c665d37..7cc664a20 100644 --- a/Assets/Script/Server/Host.cs +++ b/Assets/Script/Server/Host.cs @@ -4,8 +4,10 @@ using System.IO.Compression; using System.Net; using System.Net.Sockets; +using System.Text; using System.Threading; using UnityEngine; +using YARG.Util; namespace YARG.Server { public partial class Host : MonoBehaviour { @@ -18,8 +20,9 @@ private void Start() { GameManager.Instance.LowQualityMode = true; Application.targetFrameRate = 5; - // Fetch songs first so we have a cache file to send + // Fetch songs and scores first so we have a cache file to send SongLibrary.FetchSongs(); + ScoreManager.FetchScores(); // Create the TcpListener server = new TcpListener(IPAddress.Any, 6145); @@ -60,7 +63,7 @@ private void ServerThread(TcpClient client) { int size = stream.Read(bytes, 0, bytes.Length); // Get request - var str = System.Text.Encoding.UTF8.GetString(bytes, 0, size); + var str = Encoding.UTF8.GetString(bytes, 0, size); Log($"Received: `{str}`."); // Do something @@ -68,6 +71,8 @@ private void ServerThread(TcpClient client) { break; } else if (str == "ReqCache") { SendFile(stream, SongLibrary.CacheFile); + } else if (str == "ReqScore") { + SendFile(stream, ScoreManager.ScoreFile); } else if (str.StartsWith("ReqSong,")) { // Get the folder string path = str[8..]; @@ -106,6 +111,20 @@ private void ServerThread(TcpClient client) { } else { SendNoFile(stream); } + } else if (str == "WriteScores") { + Send(stream, "ProceedWriteScores"); + + // TODO: This sucks, but I'm too lazy + // Wait for file on the stream + while (true) { + if (stream.DataAvailable) { + Utils.ReadFile(stream, ScoreManager.ScoreFile); + break; + } + + // Prevent CPU burn + Thread.Sleep(10); + } } } else { // Prevent CPU burn @@ -136,6 +155,12 @@ private void SendNoFile(NetworkStream stream) { stream.Write(BitConverter.GetBytes(0)); } + private void Send(NetworkStream stream, string str) { + var send = Encoding.UTF8.GetBytes(str); + stream.Write(send, 0, send.Length); + stream.Flush(); + } + private void OnDestroy() { foreach (var thread in threads) { thread.Abort(); diff --git a/Assets/Script/UI/MainMenu.cs b/Assets/Script/UI/MainMenu.cs index 49c364f85..4d631ec69 100644 --- a/Assets/Script/UI/MainMenu.cs +++ b/Assets/Script/UI/MainMenu.cs @@ -1,9 +1,10 @@ +using System; +using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.UIElements; using YARG.Data; using YARG.PlayMode; -using YARG.Serialization; using YARG.Util; namespace YARG.UI { @@ -66,9 +67,12 @@ private void OnDisable() { private void SignalRecieved(string signal) { if (signal.StartsWith("DownloadDone,")) { - Play.song = SongIni.CompleteSongInfo(new SongInfo( - new(Path.Combine(GameManager.client.remotePath, signal[13..])) - )); + Play.song = chosenSong.Duplicate(); + + // Replace song folder + Play.song.realFolderRemote = Play.song.folder; + Play.song.folder = new(Path.Combine(GameManager.client.remotePath, signal[13..])); + GameManager.Instance.LoadScene(SceneIndex.PLAY); } } @@ -174,7 +178,7 @@ private void SetupPreSong() { 3 => "drums", 4 => "realGuitar", 5 => "realBass", - _ => throw new System.Exception("Unreachable.") + _ => throw new Exception("Unreachable.") }; player.chosenDifficulty = difficultyChoice.value; @@ -187,6 +191,45 @@ private void SetupPreSong() { private void SetupPostSong() { var root = postSongDocument.rootVisualElement; + // Create a score to push + + var songScore = new SongScore { + lastPlayed = DateTime.Now, + timesPlayed = 1, + highestPercent = new() + }; + var oldScore = ScoreManager.GetScore(Play.song); + + HashSet highScores = new(); + foreach (var player in PlayerManager.players) { + if (player.inputStrategy.botMode) { + continue; + } + + if (!player.lastScore.HasValue) { + continue; + } + + var lastScore = player.lastScore.GetValueOrDefault(); + + // Skip if the chart has no notes (will be viewed as 100%) + if (lastScore.notesHit == 0) { + continue; + } + + // Override or add percentage + if (oldScore == null || + !oldScore.highestPercent.TryGetValue(player.chosenInstrument, out var oldHighest) || + lastScore.percentage > oldHighest) { + + songScore.highestPercent[player.chosenInstrument] = lastScore.percentage; + highScores.Add(player); + } + } + + // Push! + ScoreManager.PushScore(Play.song, songScore); + // Setup score label var label = root.Q