From b7924d36848805bcadd3e6781ea1099b038a5912 Mon Sep 17 00:00:00 2001 From: Warp Date: Sun, 18 Jun 2023 03:23:59 +0200 Subject: [PATCH 001/249] Fix editor not scrolling due to rounding errors --- Quaver.Shared/Audio/AudioEngine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Audio/AudioEngine.cs b/Quaver.Shared/Audio/AudioEngine.cs index d318d38ead..9f8743d36c 100644 --- a/Quaver.Shared/Audio/AudioEngine.cs +++ b/Quaver.Shared/Audio/AudioEngine.cs @@ -173,7 +173,7 @@ public static double GetNearestSnapTimeFromTime(Qua map, Direction direction, in var nearestTick = Math.Round((pointToSnap - point.StartTime) / snapTimePerBeat) * snapTimePerBeat + point.StartTime; - if ((int) Math.Abs(nearestTick - time) <= (int) snapTimePerBeat) + if ((int)Math.Abs(nearestTick - time) <= (int)snapTimePerBeat || Math.Abs(nearestTick - time) - snapTimePerBeat <= 0.1) return nearestTick; if (direction == Direction.Backward) From 24150a6c1f055d6c8e9da833805576eb6bd6b801 Mon Sep 17 00:00:00 2001 From: Warp Date: Sat, 5 Aug 2023 23:08:11 +0200 Subject: [PATCH 002/249] Change GetNearestSnapTimeFromTime to not round to ints --- Quaver.Shared/Audio/AudioEngine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Audio/AudioEngine.cs b/Quaver.Shared/Audio/AudioEngine.cs index 9f8743d36c..c074881608 100644 --- a/Quaver.Shared/Audio/AudioEngine.cs +++ b/Quaver.Shared/Audio/AudioEngine.cs @@ -173,7 +173,7 @@ public static double GetNearestSnapTimeFromTime(Qua map, Direction direction, in var nearestTick = Math.Round((pointToSnap - point.StartTime) / snapTimePerBeat) * snapTimePerBeat + point.StartTime; - if ((int)Math.Abs(nearestTick - time) <= (int)snapTimePerBeat || Math.Abs(nearestTick - time) - snapTimePerBeat <= 0.1) + if (Math.Abs(nearestTick - time) - snapTimePerBeat <= snapTimePerBeat / 2) return nearestTick; if (direction == Direction.Backward) From 1ed276ad5766da561b75cd142643f87017226f45 Mon Sep 17 00:00:00 2001 From: Swan Date: Tue, 8 Aug 2023 08:40:53 -0400 Subject: [PATCH 003/249] Fix crash when seeking to bookmark w/ no bookmarks --- Quaver.Shared/Screens/Edit/EditScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index e868f7dc06..2f753ba9e6 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -1404,6 +1404,9 @@ public void ExitToTestPlay(bool fromStart = false) /// public void SeekToNearestBookmark(Direction direction) { + if (WorkingMap.Bookmarks.Count == 0) + return; + BookmarkInfo nextBookmark = null; var closest = WorkingMap.Bookmarks.OrderBy(x => Math.Abs(x.StartTime - Track.Time)).First(); From 6f71b4fbea0db56acae87f12968a99e07743c0a1 Mon Sep 17 00:00:00 2001 From: Swan Date: Tue, 8 Aug 2023 09:58:54 -0400 Subject: [PATCH 004/249] Create dotnet.yml --- .github/workflows/dotnet.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/dotnet.yml diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000000..ee3a285226 --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,28 @@ +# This workflow will build a .NET project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net + +name: .NET + +on: + push: + branches: [ "ui-redesign" ] + pull_request: + branches: [ "ui-redesign" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore + - name: Test + run: dotnet test --no-build --verbosity normal From 836f993e10f66e470762a1cca5574ae9007db721 Mon Sep 17 00:00:00 2001 From: Swan Date: Tue, 8 Aug 2023 09:59:35 -0400 Subject: [PATCH 005/249] Create FUNDING.yml --- .github/FUNDING.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..1d2ab8238d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: ['https://quavergame.com/donate'] From de51b3a35605e91e6b3bfd840e1dea01867305f5 Mon Sep 17 00:00:00 2001 From: Swan Date: Tue, 8 Aug 2023 10:01:58 -0400 Subject: [PATCH 006/249] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 07bbcbbf25..4b1b9db1d2 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -20,10 +20,8 @@ A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] +**Log Files** +These are located in the /Logs/ directory in the game folder. Your issue will be closed if you are posting a bug report without this. **Additional context** Add any other context about the problem here. From 0840678655f580fc9b97a8979a072b80072e04c4 Mon Sep 17 00:00:00 2001 From: Swan Date: Tue, 8 Aug 2023 10:04:48 -0400 Subject: [PATCH 007/249] Update dotnet.yml --- .github/workflows/dotnet.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index ee3a285226..11170254c4 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -20,6 +20,8 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: 6.0.x + - name: Checkout submodules + run: git submodule update --init --recursive - name: Restore dependencies run: dotnet restore - name: Build From f7b538fd3bf1048c3741e601e8ace9d607fb5b85 Mon Sep 17 00:00:00 2001 From: Swan Date: Tue, 8 Aug 2023 10:09:02 -0400 Subject: [PATCH 008/249] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d0bd10ed58..a398acc1af 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ It is also available on [Steam](https://store.steampowered.com/app/980610/Quaver Getting started with **Quaver** development is extremely easy. -* Install the [.NET Core 3.1 SDK](https://dotnet.microsoft.com/download/dotnet-core/3.1) +* Install the [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) * Clone the Quaver repository and its submodules `git clone --recurse-submodules https://github.com/Quaver/Quaver` * **Have Steam open and running** * Build & run Quaver with `dotnet run --project Quaver` From 09b9fb491047c4258b6098849e9e860c390ba166 Mon Sep 17 00:00:00 2001 From: Swan Date: Tue, 8 Aug 2023 10:48:38 -0400 Subject: [PATCH 009/249] Display users that are spectating you --- .../Screens/Gameplay/UI/SpectatorCount.cs | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/Quaver.Shared/Screens/Gameplay/UI/SpectatorCount.cs b/Quaver.Shared/Screens/Gameplay/UI/SpectatorCount.cs index 61b5745e0d..c4f2628c11 100644 --- a/Quaver.Shared/Screens/Gameplay/UI/SpectatorCount.cs +++ b/Quaver.Shared/Screens/Gameplay/UI/SpectatorCount.cs @@ -5,6 +5,8 @@ using Quaver.Shared.Online; using Wobble.Graphics; using Wobble.Graphics.Sprites; +using Wobble.Graphics.Sprites.Text; +using Wobble.Managers; namespace Quaver.Shared.Screens.Gameplay.UI { @@ -26,19 +28,15 @@ public SpectatorCount() SetChildrenVisibility = true }; - SpectatorsText = new SpriteTextBitmap(FontsBitmap.GothamRegular, - $" ") + SpectatorsText = new SpriteTextBitmap(FontsBitmap.GothamRegular, $" ", false) { - Parent = Eye, - Alignment = Alignment.MidLeft, - FontSize = 14, + Parent = this, + FontSize = 16, X = Eye.X + Eye.Width + 10 }; - + UpdateSpectatorText(); - - Size = new ScalableVector2(Eye.X + Eye.Width + 10 + SpectatorsText.Width, SpectatorsText.Height); - + if (OnlineManager.Client != null) { OnlineManager.Client.OnSpectatorJoined += OnSpectatorJoined; @@ -76,7 +74,13 @@ private void OnSpectatorLeft(object sender, SpectatorLeftEventArgs e) private void UpdateSpectatorText() { - SpectatorsText.Text = $"Spectators ({OnlineManager.Spectators.Count})"; + var text = $"Spectators ({OnlineManager.Spectators.Count})\n\n"; + + foreach (var spectator in OnlineManager.Spectators) + text += $"- {spectator.Value.OnlineUser?.Username ?? $"User {spectator.Key}"}\n"; + + SpectatorsText.Text = text; + Size = new ScalableVector2(Eye.X + Eye.Width + 10 + SpectatorsText.Width, SpectatorsText.Height); } } } \ No newline at end of file From 8b02b106b0fcc661d542a14a7566c9d3edabf8f8 Mon Sep 17 00:00:00 2001 From: Swan Date: Tue, 8 Aug 2023 10:51:23 -0400 Subject: [PATCH 010/249] Remove ruleset dropdown in multiplayer lobby --- .../MultiplayerLobby/UI/Filter/MultiplayerLobbyFilterPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/MultiplayerLobby/UI/Filter/MultiplayerLobbyFilterPanel.cs b/Quaver.Shared/Screens/MultiplayerLobby/UI/Filter/MultiplayerLobbyFilterPanel.cs index 01b63de45b..d481ec544b 100644 --- a/Quaver.Shared/Screens/MultiplayerLobby/UI/Filter/MultiplayerLobbyFilterPanel.cs +++ b/Quaver.Shared/Screens/MultiplayerLobby/UI/Filter/MultiplayerLobbyFilterPanel.cs @@ -75,7 +75,7 @@ public MultiplayerLobbyFilterPanel(Bindable> visibleGames) FilterTask = new TaskHandler(StartFilterTask); CreateBanner(); - CreateRulesetDropdown(); + //CreateRulesetDropdown(); CreateModeDropdown(); CreateMapDropdown(); CreateVisibilityDropdown(); From adb14db115ff46ad49a3fcf5067cff13614968cf Mon Sep 17 00:00:00 2001 From: Swan Date: Wed, 9 Aug 2023 08:02:08 -0400 Subject: [PATCH 011/249] Clear old online data when logging in --- Quaver.Shared/Online/OnlineManager.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Quaver.Shared/Online/OnlineManager.cs b/Quaver.Shared/Online/OnlineManager.cs index 342017c220..72f8e45531 100644 --- a/Quaver.Shared/Online/OnlineManager.cs +++ b/Quaver.Shared/Online/OnlineManager.cs @@ -412,6 +412,13 @@ private static void OnLoginSuccess(object sender, LoginReplyEventArgs e) { OnlineUsers.Clear(); OnlineUsers[e.Self.OnlineUser.Id] = e.Self; + Spectators.Clear(); + SpectatorClients.Clear(); + MultiplayerGames.Clear(); + ListeningParty = null; + FriendsList.Clear(); + SpectatorClients = new Dictionary(); + Spectators = new Dictionary(); } // Make sure the config username is changed. From 6810e4cd3719c2ceaf99350d5ede21a119d4dcbb Mon Sep 17 00:00:00 2001 From: Swan Date: Wed, 9 Aug 2023 08:20:31 -0400 Subject: [PATCH 012/249] Fix skinned results/song select grades not displaying --- .../UI/Header/Contents/ResultsScreenHeaderContentContainer.cs | 4 ++-- .../Screens/Selection/UI/Maps/DrawableMapContainer.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Quaver.Shared/Screens/Results/UI/Header/Contents/ResultsScreenHeaderContentContainer.cs b/Quaver.Shared/Screens/Results/UI/Header/Contents/ResultsScreenHeaderContentContainer.cs index b91f08d35f..906feef06a 100644 --- a/Quaver.Shared/Screens/Results/UI/Header/Contents/ResultsScreenHeaderContentContainer.cs +++ b/Quaver.Shared/Screens/Results/UI/Header/Contents/ResultsScreenHeaderContentContainer.cs @@ -187,8 +187,8 @@ private void UpdateGradeTextureAndSize() GradeSprite.Image = SkinManager.Skin.GradesLarge[grade]; const int width = 110; - - GradeSprite.Size = new ScalableVector2(width, GradeSprite.Image.Height / GradeSprite.Image.Width * width); + + GradeSprite.Size = new ScalableVector2(width, (float) GradeSprite.Image.Height / GradeSprite.Image.Width * width); GradeSprite.Y = -TabSelector.Height - 22; } diff --git a/Quaver.Shared/Screens/Selection/UI/Maps/DrawableMapContainer.cs b/Quaver.Shared/Screens/Selection/UI/Maps/DrawableMapContainer.cs index 17956f38b6..dcf6f7924f 100644 --- a/Quaver.Shared/Screens/Selection/UI/Maps/DrawableMapContainer.cs +++ b/Quaver.Shared/Screens/Selection/UI/Maps/DrawableMapContainer.cs @@ -123,7 +123,7 @@ public void UpdateContent(Map map, int index) OnlineGrade.Visible = true; OnlineGrade.Image = SkinManager.Skin.Grades[map.OnlineGrade]; - OnlineGrade.Size = new ScalableVector2(width, OnlineGrade.Image.Height / OnlineGrade.Image.Width * width); + OnlineGrade.Size = new ScalableVector2(width, (float) OnlineGrade.Image.Height / OnlineGrade.Image.Width * width); Name.X = OnlineGrade.X + OnlineGrade.Width + 16; ByText.X = Name.X; From f26fd0b6c43ab26f35c7e0cdbe1921f4bfddaf68 Mon Sep 17 00:00:00 2001 From: Warp Date: Wed, 9 Aug 2023 15:28:28 +0200 Subject: [PATCH 013/249] add hiterror alpha skin ini config --- .../Rulesets/Keys/Playfield/GameplayPlayfieldKeysStage.cs | 3 ++- Quaver.Shared/Screens/Gameplay/UI/HitErrorBar.cs | 7 ++++++- Quaver.Shared/Skinning/SkinKeys.cs | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Screens/Gameplay/Rulesets/Keys/Playfield/GameplayPlayfieldKeysStage.cs b/Quaver.Shared/Screens/Gameplay/Rulesets/Keys/Playfield/GameplayPlayfieldKeysStage.cs index 960c5eca7c..2a0b925023 100644 --- a/Quaver.Shared/Screens/Gameplay/Rulesets/Keys/Playfield/GameplayPlayfieldKeysStage.cs +++ b/Quaver.Shared/Screens/Gameplay/Rulesets/Keys/Playfield/GameplayPlayfieldKeysStage.cs @@ -534,7 +534,8 @@ private void UpdateComboDisplay(GameTime gameTime) { Parent = Playfield.ForegroundContainer, Alignment = Alignment.MidCenter, - Position = new ScalableVector2(Skin.HitErrorPosX, Skin.HitErrorPosY) + Position = new ScalableVector2(Skin.HitErrorPosX, Skin.HitErrorPosY), + Alpha = Skin.HitErrorAlpha }; /// diff --git a/Quaver.Shared/Screens/Gameplay/UI/HitErrorBar.cs b/Quaver.Shared/Screens/Gameplay/UI/HitErrorBar.cs index b0a863d081..d980c735ac 100644 --- a/Quaver.Shared/Screens/Gameplay/UI/HitErrorBar.cs +++ b/Quaver.Shared/Screens/Gameplay/UI/HitErrorBar.cs @@ -49,6 +49,11 @@ public class HitErrorBar : Container /// public Sprite LastHitCheveron { get; } + /// + /// The initial alpha of the hit error lines. + /// + public float Alpha { get; set; } = 0.5f; + /// /// /// Ctor - @@ -125,7 +130,7 @@ public void AddJudgement(Judgement j, double hitTime) LineObjectPool[CurrentLinePoolIndex].Tint = SkinManager.Skin.Keys[MapManager.Selected.Value.Mode].JudgeColors[j]; LineObjectPool[CurrentLinePoolIndex].X = -(float)hitTime / ModHelper.GetRateFromMods(ModManager.Mods); - LineObjectPool[CurrentLinePoolIndex].Alpha = 0.5f; + LineObjectPool[CurrentLinePoolIndex].Alpha = Alpha; } } } diff --git a/Quaver.Shared/Skinning/SkinKeys.cs b/Quaver.Shared/Skinning/SkinKeys.cs index 69429cf80e..00c89358bb 100644 --- a/Quaver.Shared/Skinning/SkinKeys.cs +++ b/Quaver.Shared/Skinning/SkinKeys.cs @@ -178,6 +178,8 @@ public class SkinKeys [FixedScale] internal float HitErrorPosY { get; private set; } + internal float HitErrorAlpha { get; private set; } = 0.5f; + [FixedScale] internal float HitErrorHeight { get; private set; } @@ -494,6 +496,7 @@ private void ReadConfig(bool loadFromResources) HealthBarScale = ConfigHelper.ReadInt32((int) HealthBarScale, ini["HealthBarScale"]); HitErrorPosX = ConfigHelper.ReadInt32((int) HitErrorPosX, ini["HitErrorPosX"]); HitErrorPosY = ConfigHelper.ReadInt32((int) HitErrorPosY, ini["HitErrorPosY"]); + HitErrorAlpha = ConfigHelper.ReadFloat(HitErrorAlpha, ini["HitErrorAlpha"]); HitErrorHeight = ConfigHelper.ReadInt32((int) HitErrorHeight, ini["HitErrorHeight"]); HitErrorChevronSize = ConfigHelper.ReadInt32((int) HitErrorChevronSize, ini["HitErrorChevronSize"]); TimingLineColor = ConfigHelper.ReadColor(TimingLineColor, ini["TimingLineColor"]); From 59f30ab6eaf36b181b70545010570a9aea683afb Mon Sep 17 00:00:00 2001 From: Swan Date: Wed, 9 Aug 2023 09:39:26 -0400 Subject: [PATCH 014/249] Fix multiplayer ghost players --- .../Multi/UI/Players/MultiplayerPlayerList.cs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/Quaver.Shared/Screens/Multi/UI/Players/MultiplayerPlayerList.cs b/Quaver.Shared/Screens/Multi/UI/Players/MultiplayerPlayerList.cs index 1047d85fbf..beeb933ce6 100644 --- a/Quaver.Shared/Screens/Multi/UI/Players/MultiplayerPlayerList.cs +++ b/Quaver.Shared/Screens/Multi/UI/Players/MultiplayerPlayerList.cs @@ -115,7 +115,7 @@ public override void Destroy() public void AddPlayer(User user, bool sort = false) { // Player already exists - if (Players.Any(x => x is MultiplayerPlayer p && p.User == user)) + if (Players.Any(x => x is MultiplayerPlayer p && p.User.OnlineUser.Id == user.OnlineUser.Id)) return; var player = new MultiplayerPlayer(Game, this, user); @@ -323,8 +323,11 @@ private void CreateScrollbar() /// private void OnUserJoinedGame(object sender, UserJoinedGameEventArgs e) { - if (OnlineManager.OnlineUsers.ContainsKey(e.UserId)) - AddScheduledUpdate(() => AddPlayer(OnlineManager.OnlineUsers[e.UserId], true)); + AddScheduledUpdate(() => + { + if (OnlineManager.OnlineUsers.ContainsKey(e.UserId)) + AddPlayer(OnlineManager.OnlineUsers[e.UserId], true); + }); } /// @@ -333,13 +336,15 @@ private void OnUserJoinedGame(object sender, UserJoinedGameEventArgs e) /// private void OnUserLeftGame(object sender, UserLeftGameEventArgs e) { - var player = Players.Find(x => x is MultiplayerPlayer p && p.User.OnlineUser.Id == e.UserId) - as MultiplayerPlayer; - - if (player == null) - return; + AddScheduledUpdate(() => + { + var player = Players.Find(x => x is MultiplayerPlayer p && p.User.OnlineUser.Id == e.UserId) as MultiplayerPlayer; - AddScheduledUpdate(() => RemovePlayer(player.User)); + if (player == null) + return; + + RemovePlayer(player.User); + }); } private void OnUserDisconnected(object sender, UserDisconnectedEventArgs e) => From f66393512b322ddd1781f25d0ed72dae7be9dc8e Mon Sep 17 00:00:00 2001 From: Swan Date: Fri, 11 Aug 2023 07:08:15 -0400 Subject: [PATCH 015/249] Don't display combo alerts in map previews --- Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs index 77d02aff48..64b67672b7 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs @@ -224,7 +224,7 @@ public GameplayScreenView(Screen screen) : base(screen) CreateRatingDisplay(); CreateAccuracyDisplay(); - if (ConfigManager.DisplayComboAlerts.Value) + if (ConfigManager.DisplayComboAlerts.Value && !Screen.IsSongSelectPreview) ComboAlert = new ComboAlert(Screen.Ruleset.ScoreProcessor) { Parent = Container }; // Create judgement status display From a46446e031f26abc04e4882be01db6a9def66c26 Mon Sep 17 00:00:00 2001 From: Swan Date: Fri, 11 Aug 2023 07:08:58 -0400 Subject: [PATCH 016/249] Add right click to add map to playlist in multiplayer --- .../Selected/SelectedGamePanelMatchBanner.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Quaver.Shared/Screens/MultiplayerLobby/UI/Selected/SelectedGamePanelMatchBanner.cs b/Quaver.Shared/Screens/MultiplayerLobby/UI/Selected/SelectedGamePanelMatchBanner.cs index ada4170b74..58bd4c9ed6 100644 --- a/Quaver.Shared/Screens/MultiplayerLobby/UI/Selected/SelectedGamePanelMatchBanner.cs +++ b/Quaver.Shared/Screens/MultiplayerLobby/UI/Selected/SelectedGamePanelMatchBanner.cs @@ -6,6 +6,7 @@ using Quaver.Shared.Assets; using Quaver.Shared.Config; using Quaver.Shared.Database.Maps; +using Quaver.Shared.Database.Playlists; using Quaver.Shared.Graphics.Backgrounds; using Quaver.Shared.Graphics.Notifications; using Quaver.Shared.Graphics.Overlays.Hub; @@ -20,6 +21,9 @@ using Quaver.Shared.Screens.MultiplayerLobby.UI.Games; using Quaver.Shared.Screens.Selection; using Quaver.Shared.Screens.Selection.UI.FilterPanel.MapInformation.Metadata; +using Quaver.Shared.Screens.Selection.UI.Maps; +using Quaver.Shared.Screens.Selection.UI.Playlists.Dialogs.Create; +using Quaver.Shared.Screens.Selection.UI.Playlists.Management.Maps; using Wobble; using Wobble.Bindables; using Wobble.Graphics; @@ -27,6 +31,7 @@ using Wobble.Graphics.Sprites; using Wobble.Graphics.Sprites.Text; using Wobble.Graphics.UI.Buttons; +using Wobble.Graphics.UI.Dialogs; using Wobble.Logging; using Wobble.Managers; @@ -343,6 +348,29 @@ private void CreateButton() else if (SelectedGame.Value.MapId == -1) NotificationManager.Show(NotificationLevel.Warning, "Cannot view online listing because this map is not submitted online!"); }; + + Button.RightClicked += (sender, args) => + { + if (!IsMultiplayer) + return; + + if (MapManager.Selected.Value == null) + return; + + if (MapManager.Selected.Value.Md5Checksum != SelectedGame.Value.MapMd5 && + MapManager.Selected.Value.Md5Checksum != SelectedGame.Value.AlternativeMd5) + return; + + var game = (QuaverGame) GameBase.Game; + + if (PlaylistManager.Playlists.FindAll(x => x.PlaylistGame == MapGame.Quaver).Count == 0) + { + DialogManager.Show(new CreatePlaylistDialog()); + return; + } + + game.CurrentScreen?.ActivateCheckboxContainer(new AddMapToPlaylistCheckboxContainer(MapManager.Selected.Value)); + }; } /// From 40ff74ce1fc9c8d61f9c0429513324dc293d73be Mon Sep 17 00:00:00 2001 From: Swan Date: Fri, 11 Aug 2023 07:48:21 -0400 Subject: [PATCH 017/249] Don't boot player to main menu on multiplayer disconnect --- Quaver.Shared/Online/OnlineManager.cs | 34 +++++++++++++------ .../Screens/Gameplay/GameplayScreenView.cs | 2 +- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/Quaver.Shared/Online/OnlineManager.cs b/Quaver.Shared/Online/OnlineManager.cs index 72f8e45531..ea087d109f 100644 --- a/Quaver.Shared/Online/OnlineManager.cs +++ b/Quaver.Shared/Online/OnlineManager.cs @@ -312,16 +312,17 @@ private static void OnConnectionStatusChanged(object sender, ConnectionStatusCha if (Status.Value == ConnectionStatus.Connected) return; + ClearOnlineData(); + var game = (QuaverGame) GameBase.Game; - if (game.CurrentScreen?.Type == QuaverScreenType.Lobby || CurrentGame != null) + switch (game.CurrentScreen?.Type) { - LeaveLobby(); - CurrentGame = null; - game.CurrentScreen?.Exit(() => new MainMenuScreen()); + case QuaverScreenType.Multiplayer: + case QuaverScreenType.Lobby: + game.CurrentScreen?.Exit(() => new MainMenuScreen()); + break; } - - ListeningParty = null; } /// @@ -392,9 +393,8 @@ private static void OnDisconnection(object sender, DisconnectedEventArgs e) NotificationManager.Show(NotificationLevel.Error, "Failed to authenticate to the server"); return; } - - // Remove the active listening party - ListeningParty = null; + + ClearOnlineData(); } /// @@ -410,7 +410,7 @@ private static void OnLoginSuccess(object sender, LoginReplyEventArgs e) lock (OnlineUsers) { - OnlineUsers.Clear(); + ClearOnlineData(); OnlineUsers[e.Self.OnlineUser.Id] = e.Self; Spectators.Clear(); SpectatorClients.Clear(); @@ -1964,5 +1964,19 @@ private static List GetScoresFromMultiplayerUsers() ScoresHelper.SetRatingProcessors(scores); return scores; } + + private static void ClearOnlineData() + { + OnlineUsers.Clear(); + Spectators.Clear(); + SpectatorClients.Clear(); + MultiplayerGames.Clear(); + ListeningParty = null; + FriendsList.Clear(); + SpectatorClients = new Dictionary(); + Spectators = new Dictionary(); + CurrentGame = null; + ListeningParty = null; + } } } \ No newline at end of file diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs index 77d02aff48..ffb0c1e2ad 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs @@ -775,7 +775,7 @@ private void HandlePlayCompletion(GameTime gameTime) Screen.Ruleset.UpdateStandardizedScoreProcessor(true); Screen.SendJudgementsToServer(true); - OnlineManager.Client.FinishMultiplayerGameSession(); + OnlineManager.Client?.FinishMultiplayerGameSession(); ResultsScreenLoadInitiated = true; } catch (Exception e) From bde9b720429d347b9c1a3ce87a44d9fe16aba499 Mon Sep 17 00:00:00 2001 From: Warp Date: Fri, 11 Aug 2023 22:04:26 +0200 Subject: [PATCH 018/249] Create bug_report.yml --- .github/ISSUE_TEMPLATE/bug_report.yml | 51 +++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..9856675fdb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,51 @@ +name: Bug Report Form +description: File a bug report +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + - type: textarea + id: describe-bug + attributes: + label: Describe the bug + description: A clear and concise description of what the bug is. + placeholder: Tell us what you see! + validations: + required: true + - type: textarea + id: reproduce-bug + attributes: + label: To Reproduce + description: Steps to reproduce the behavior + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + validations: + required: true + - type: textarea + id: expected-behavior + attributes: + label: Expected behavior + description: A clear and concise description of what you expected to happen. + - type: textarea + id: log-files + attributes: + label: Log Files + description: | + These are located in the /Logs/ directory in the game folder. + + Tip: You can attach log files by clicking this area to highlight it and then dragging files in. + validations: + required: true + - type: textarea + id: screenshots + attributes: + label: Screenshots + description: | + If applicable, add screenshots to help explain your problem. + + Tip: You can attach images by clicking this area to highlight it and then dragging files in. From c8f2f622821c4ef31c3ef1293430333a505320aa Mon Sep 17 00:00:00 2001 From: Warp Date: Fri, 11 Aug 2023 22:05:19 +0200 Subject: [PATCH 019/249] Delete bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 4b1b9db1d2..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Log Files** -These are located in the /Logs/ directory in the game folder. Your issue will be closed if you are posting a bug report without this. - -**Additional context** -Add any other context about the problem here. From ac6ba4d6ce1fce51a134c6da29bf736c23f61816 Mon Sep 17 00:00:00 2001 From: Warp Date: Fri, 11 Aug 2023 22:12:07 +0200 Subject: [PATCH 020/249] Create feature_request.yml --- .github/ISSUE_TEMPLATE/feature_request.yml | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000000..e169e04e1e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,24 @@ +name: Feature request +description: Suggest an idea for this project +labels: ["New Feature"] +body: + - type: textarea + id: related-problem + attributes: + label: Is your feature request related to a problem? Please describe. + description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + - type: textarea + id: solution-description + attributes: + label: Describe the solution you'd like + description: A clear and concise description of what you want to happen. + - type: textarea + id: alternatives + attributes: + label: Describe alternatives you've considered + description: A clear and concise description of any alternative solutions or features you've considered. + - type: textarea + id: additional-context + attributes: + label: Additional context + description: Add any other context or screenshots about the feature request here. From 3a7d8c5b79a9d3be734d25cfeebf3402f2ddef31 Mon Sep 17 00:00:00 2001 From: Warp Date: Fri, 11 Aug 2023 22:12:20 +0200 Subject: [PATCH 021/249] Delete feature_request.md --- .github/ISSUE_TEMPLATE/feature_request.md | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 066b2d920a..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. From bfbb7d1162841f5374321e90d76ce4affbe0e487 Mon Sep 17 00:00:00 2001 From: Warp Date: Fri, 11 Aug 2023 22:13:34 +0200 Subject: [PATCH 022/249] Update bug_report.yml --- .github/ISSUE_TEMPLATE/bug_report.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 9856675fdb..eaa33b9525 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,11 +1,7 @@ -name: Bug Report Form -description: File a bug report +name: Bug report +description: Create a report to help us improve labels: ["bug"] body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out this bug report! - type: textarea id: describe-bug attributes: From dae909509bf21a4fc5d0b4f57938352974cc638e Mon Sep 17 00:00:00 2001 From: Warp Date: Fri, 11 Aug 2023 22:14:12 +0200 Subject: [PATCH 023/249] Update bug_report.yml --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index eaa33b9525..c65c41690d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,6 @@ name: Bug report description: Create a report to help us improve -labels: ["bug"] +labels: ["Bug"] body: - type: textarea id: describe-bug From 90b3af75c6ffed72f4ecf1ad20b26c5813b7ef24 Mon Sep 17 00:00:00 2001 From: Swan Date: Tue, 15 Aug 2023 09:41:25 -0400 Subject: [PATCH 024/249] Gracefully disconnect if game crashes --- Quaver/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Quaver/Program.cs b/Quaver/Program.cs index 470599da12..080ee072c8 100644 --- a/Quaver/Program.cs +++ b/Quaver/Program.cs @@ -81,6 +81,7 @@ private static void Run() var exception = args.ExceptionObject as Exception; Logger.Error(exception, LogType.Runtime); SendCrashLog(exception); + OnlineManager.Client?.Disconnect(); }; // Change the working directory to where the executable is. From 75d3743dd2815b47413b94849c48649a3c7fbd50 Mon Sep 17 00:00:00 2001 From: Warp Date: Thu, 17 Aug 2023 16:43:58 +0200 Subject: [PATCH 025/249] leave multiplayer game when pressing home button --- .../Menu/Border/Components/Buttons/IconTextButtonHome.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Quaver.Shared/Graphics/Menu/Border/Components/Buttons/IconTextButtonHome.cs b/Quaver.Shared/Graphics/Menu/Border/Components/Buttons/IconTextButtonHome.cs index c576f5190a..517623ae4d 100644 --- a/Quaver.Shared/Graphics/Menu/Border/Components/Buttons/IconTextButtonHome.cs +++ b/Quaver.Shared/Graphics/Menu/Border/Components/Buttons/IconTextButtonHome.cs @@ -2,6 +2,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Quaver.Shared.Assets; +using Quaver.Shared.Online; using Quaver.Shared.Screens; using Quaver.Shared.Screens.Main; using Wobble; @@ -21,6 +22,8 @@ public IconTextButtonHome() : base(FontAwesome.Get(FontAwesomeIcon.fa_home), Fon public override void OnClick() { var game = (QuaverGame) GameBase.Game; + if (OnlineManager.CurrentGame != null) + OnlineManager.LeaveGame(); game.CurrentScreen.Exit(() => new MainMenuScreen()); } } From 6fdb9fb3eedb5c4e971dc0af0d4163a5ac4fcc3c Mon Sep 17 00:00:00 2001 From: Swan Date: Fri, 18 Aug 2023 14:37:37 -0400 Subject: [PATCH 026/249] Update for new screen management system --- Quaver.Shared/Screens/QuaverScreenManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Quaver.Shared/Screens/QuaverScreenManager.cs b/Quaver.Shared/Screens/QuaverScreenManager.cs index 9b7ca6aa31..17b04ee635 100644 --- a/Quaver.Shared/Screens/QuaverScreenManager.cs +++ b/Quaver.Shared/Screens/QuaverScreenManager.cs @@ -55,7 +55,7 @@ public static void ScheduleScreenChange(Func newScreen, bool switc if (LastScreen == QuaverScreenType.None || switchImmediately) { - ChangeScreen(newScreen()); + ChangeScreen(newScreen(), true); return; } @@ -91,14 +91,14 @@ private static void OnCompleted(object sender, TaskCompleteEventArgs ChangeScreen(e.Result)); + game.ScheduledRenderTargetDraws.Add(() => ChangeScreen(e.Result, false)); } - private static void ChangeScreen(QuaverScreen screen) + private static void ChangeScreen(QuaverScreen screen, bool switchImmediately) { var game = (QuaverGame) GameBase.Game; - ScreenManager.ChangeScreen(screen); + ScreenManager.ChangeScreen(screen, switchImmediately); game.CurrentScreen = screen; // Update client status on the server. From af9902e10db6bb834df859333c36628b70adf325 Mon Sep 17 00:00:00 2001 From: Swan Date: Sat, 19 Aug 2023 08:17:39 -0400 Subject: [PATCH 027/249] Handle potential null reference --- Quaver.Shared/Screens/QuaverScreenManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/QuaverScreenManager.cs b/Quaver.Shared/Screens/QuaverScreenManager.cs index 17b04ee635..2317df6ac5 100644 --- a/Quaver.Shared/Screens/QuaverScreenManager.cs +++ b/Quaver.Shared/Screens/QuaverScreenManager.cs @@ -87,7 +87,7 @@ private static void OnCompleted(object sender, TaskCompleteEventArgs Date: Sat, 19 Aug 2023 21:27:08 +0200 Subject: [PATCH 028/249] add option to change custom fps --- Quaver.Resources | 2 +- Quaver.Shared/Assets/UserInterface.cs | 2 + .../Graphics/Dialogs/YesNoTextDialog.cs | 72 +++++++++++++++++ Quaver.Shared/QuaverGame.cs | 1 + .../Items/Custom/OptionsItemCustomFps.cs | 77 +++++++++++++++++++ .../Items/Custom/OptionsItemKeybind.cs | 6 +- .../Items/Custom/OptionsItemKeybindGeneric.cs | 8 +- .../Custom/OptionsItemKeybindMultiple.cs | 6 +- .../Screens/Options/Items/OptionsItem.cs | 5 ++ .../Screens/Options/OptionsDialog.cs | 2 +- .../Screens/Options/OptionsHeader.cs | 10 +-- Quaver.Shared/Screens/Options/OptionsMenu.cs | 16 ++-- .../Options/Search/OptionsHeaderSearch.cs | 12 +-- 13 files changed, 182 insertions(+), 37 deletions(-) create mode 100644 Quaver.Shared/Graphics/Dialogs/YesNoTextDialog.cs create mode 100644 Quaver.Shared/Screens/Options/Items/Custom/OptionsItemCustomFps.cs diff --git a/Quaver.Resources b/Quaver.Resources index ae39ef5a9e..0d70fea060 160000 --- a/Quaver.Resources +++ b/Quaver.Resources @@ -1 +1 @@ -Subproject commit ae39ef5a9ebc7de3d4e004f588e2e90d2c0b0cc3 +Subproject commit 0d70fea06099ae5c7ed2d9c454154eb89a40b017 diff --git a/Quaver.Shared/Assets/UserInterface.cs b/Quaver.Shared/Assets/UserInterface.cs index d63d2ea3b8..bb95cf7f89 100644 --- a/Quaver.Shared/Assets/UserInterface.cs +++ b/Quaver.Shared/Assets/UserInterface.cs @@ -115,6 +115,7 @@ public static class UserInterface public static Texture2D CreateButton => TextureManager.Load(@"Quaver.Resources/Textures/UI/create-button.png"); public static Texture2D SureButton => TextureManager.Load(@"Quaver.Resources/Textures/UI/sure-button.png"); public static Texture2D AcceptButton => TextureManager.Load(@"Quaver.Resources/Textures/UI/accept-button.png"); + public static Texture2D SaveButton => TextureManager.Load(@"Quaver.Resources/Textures/UI/save-button.png"); public static Texture2D DeclineButton => TextureManager.Load(@"Quaver.Resources/Textures/UI/decline-button.png"); public static Texture2D LegalPanel => TextureManager.Load(@"Quaver.Resources/Textures/UI/legal-panel.png"); public static Texture2D CancelButton => TextureManager.Load(@"Quaver.Resources/Textures/UI/cancel-button.png"); @@ -195,6 +196,7 @@ public static class UserInterface public static Texture2D ReplayControllerInactiveBar => TextureManager.Load(@"Quaver.Resources/Textures/UI/Replay/replay-controller-inactive-bar.png"); public static Texture2D ReplayControllerPanel => TextureManager.Load(@"Quaver.Resources/Textures/UI/Replay/replay-controller-panel.png"); public static Texture2D ReplayControllerSpeedPanel => TextureManager.Load(@"Quaver.Resources/Textures/UI/Replay/replay-controller-speed-panel.png"); + public static Texture2D OptionsCustomFpsButton => TextureManager.Load(@"Quaver.Resources/Textures/UI/Options/custom-fps-button.png"); public static Texture2D OptionsCalibrateOffsetButton => TextureManager.Load(@"Quaver.Resources/Textures/UI/Options/calibrate-offset-button.png"); public static Texture2D OptionsUpdateButton => TextureManager.Load(@"Quaver.Resources/Textures/UI/Options/options-update-button.png"); public static Texture2D OptionsExportSkinButton => TextureManager.Load(@"Quaver.Resources/Textures/UI/Options/export-skin-button.png"); diff --git a/Quaver.Shared/Graphics/Dialogs/YesNoTextDialog.cs b/Quaver.Shared/Graphics/Dialogs/YesNoTextDialog.cs new file mode 100644 index 0000000000..407d917cb1 --- /dev/null +++ b/Quaver.Shared/Graphics/Dialogs/YesNoTextDialog.cs @@ -0,0 +1,72 @@ +using System; +using Microsoft.Xna.Framework; +using Quaver.Server.Common.Objects.Multiplayer; +using Quaver.Shared.Assets; +using Quaver.Shared.Graphics; +using Quaver.Shared.Helpers; +using Wobble.Graphics; +using Wobble.Graphics.Animations; +using Wobble.Graphics.UI.Dialogs; +using Wobble.Graphics.UI.Form; +using Wobble.Managers; + +namespace Quaver.Shared.Graphics.Dialogs +{ + public class YesNoTextDialog : YesNoDialog + { + /// + /// + protected Textbox Textbox { get; set; } + + public Action SubmitAction { get; set; } + + /// + /// + /// + public YesNoTextDialog(string header, string confirmationText, string initialText, string placeHolderText, + Action submitAction = null, Action cancelAction = null) : base(header, confirmationText, null, cancelAction) + { + SubmitAction = submitAction; + YesAction += () => + { + SubmitAction?.Invoke(Textbox.RawText); + }; + Panel.Height += 50; + YesButton.Image = UserInterface.SaveButton; + YesButton.Y += 10; + NoButton.Y += 10; + + CreateTextbox(initialText, placeHolderText); + } + + /// + /// + private void CreateTextbox(string initialText, string placeHolderText) + { + Textbox = new Textbox(new ScalableVector2(Panel.Width * 0.90f, 50), FontManager.GetWobbleFont(Fonts.LatoBlack), + 20, initialText, placeHolderText, s => + { + SubmitAction?.Invoke(s); + Close(); + }) + { + Parent = Panel, + Alignment = Alignment.TopCenter, + Y = 180, + Tint = ColorHelper.HexToColor("#2F2F2F"), + AlwaysFocused = true + }; + + Textbox.AddBorder(ColorHelper.HexToColor("#363636"), 2); + } + + /// + /// + /// + public override void Close() + { + Textbox.Visible = false; + base.Close(); + } + } +} \ No newline at end of file diff --git a/Quaver.Shared/QuaverGame.cs b/Quaver.Shared/QuaverGame.cs index e15d9a8de4..0abf6f620d 100644 --- a/Quaver.Shared/QuaverGame.cs +++ b/Quaver.Shared/QuaverGame.cs @@ -446,6 +446,7 @@ public void PerformGameSetup() }; ConfigManager.FpsLimiterType.ValueChanged += (sender, e) => InitializeFpsLimiting(); + ConfigManager.CustomFpsLimit.ValueChanged += (sender, e) => InitializeFpsLimiting(); ConfigManager.WindowFullScreen.ValueChanged += (sender, e) => Graphics.IsFullScreen = e.Value; ConfigManager.WindowBorderless.ValueChanged += (sender, e) => Window.IsBorderless = e.Value; ConfigManager.SelectedGameMode.ValueChanged += (sender, args) => diff --git a/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemCustomFps.cs b/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemCustomFps.cs new file mode 100644 index 0000000000..1139df7b7e --- /dev/null +++ b/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemCustomFps.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.Linq; +using MonoGame.Extended; +using Quaver.API.Enums; +using Quaver.API.Maps; +using Quaver.Shared.Assets; +using Quaver.Shared.Audio; +using Quaver.Shared.Config; +using Quaver.Shared.Database.Maps; +using Quaver.Shared.Database.Scores; +using Quaver.Shared.Graphics; +using Quaver.Shared.Graphics.Backgrounds; +using Quaver.Shared.Graphics.Dialogs; +using Quaver.Shared.Graphics.Notifications; +using Quaver.Shared.Modifiers; +using Quaver.Shared.Screens.Gameplay; +using Quaver.Shared.Screens.Gameplay.UI.Offset; +using Quaver.Shared.Screens.Menu.UI.Jukebox; +using Quaver.Shared.Screens.Selection.UI.Profile; +using Wobble; +using Wobble.Assets; +using Wobble.Bindables; +using Wobble.Graphics; +using Wobble.Graphics.UI.Buttons; +using Wobble.Graphics.UI.Dialogs; +using Wobble.Graphics.UI.Form; +using Wobble.Managers; + +namespace Quaver.Shared.Screens.Options.Items.Custom +{ + public class OptionsItemCustomFps : OptionsItem + { + /// + /// The button to open the dialog. + /// + private IconButton Button { get; } + + /// + /// + /// + /// + /// + public OptionsItemCustomFps(RectangleF containerRect, string name) : base(containerRect, name) + { + const float scale = 0.85f; + + Button = new IconButton(UserInterface.OptionsCustomFpsButton) + { + Parent = this, + Alignment = Alignment.MidRight, + X = -Name.X, + Size = new ScalableVector2(215 * scale, 36 * scale), + UsePreviousSpriteBatchOptions = true + }; + + Button.Clicked += (sender, args) => + { + Focused = true; + DialogManager.Show(new YesNoTextDialog("Custom FPS", "Enter a custom FPS value.", ConfigManager.CustomFpsLimit.Value.ToString(), "", (s) => + { + if (!int.TryParse(s, out var fps)) + { + NotificationManager.Show(NotificationLevel.Error, "Please enter a valid FPS value."); + return; + } + ConfigManager.CustomFpsLimit.Value = fps; + NotificationManager.Show(NotificationLevel.Success, $"Custom FPS set to {ConfigManager.CustomFpsLimit.Value}."); + + Focused = false; + }, () => + { + Focused = false; + })); + }; + } + } +} \ No newline at end of file diff --git a/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemKeybind.cs b/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemKeybind.cs index 37d2222005..4a9c6dea0b 100644 --- a/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemKeybind.cs +++ b/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemKeybind.cs @@ -33,10 +33,6 @@ public class OptionsItemKeybind : OptionsItem /// private SpriteTextPlus Text { get; } - /// - /// - public bool Focused { get; private set; } - /// /// private Keys[] PreviousPressedKeys { get; set; } @@ -47,7 +43,7 @@ public class OptionsItemKeybind : OptionsItem /// /// /// - /// + /// public OptionsItemKeybind(RectangleF containerRect, string name, Bindable bindedKey) : base(containerRect, name) { BindedKey = bindedKey; diff --git a/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemKeybindGeneric.cs b/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemKeybindGeneric.cs index 1ee0a3256c..b156dab2e3 100644 --- a/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemKeybindGeneric.cs +++ b/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemKeybindGeneric.cs @@ -26,11 +26,7 @@ public class OptionsItemKeybindGeneric : OptionsItem /// /// private SpriteTextPlus Text { get; } - - /// - /// - public bool Focused { get; private set; } - + /// /// private List PreviousPressedKeys { get; set; } @@ -41,7 +37,7 @@ public class OptionsItemKeybindGeneric : OptionsItem /// /// /// - /// + /// public OptionsItemKeybindGeneric(RectangleF containerRect, string name, Bindable bindedKey) : base(containerRect, name) { BindedKey = bindedKey; diff --git a/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemKeybindMultiple.cs b/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemKeybindMultiple.cs index 50f4dcdee9..45e2001aa8 100644 --- a/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemKeybindMultiple.cs +++ b/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemKeybindMultiple.cs @@ -27,10 +27,6 @@ public class OptionsItemKeybindMultiple : OptionsItem /// private SpriteTextPlus Text { get; } - /// - /// - public bool Focused { get; private set; } - /// /// private List PreviousPressedKeys { get; set; } @@ -50,7 +46,7 @@ public class OptionsItemKeybindMultiple : OptionsItem /// /// /// - /// + /// public OptionsItemKeybindMultiple(RectangleF containerRect, string name, List> keys) : base(containerRect, name) { BindedKeys = keys; diff --git a/Quaver.Shared/Screens/Options/Items/OptionsItem.cs b/Quaver.Shared/Screens/Options/Items/OptionsItem.cs index 9600d8fdb9..3c4dc4230d 100644 --- a/Quaver.Shared/Screens/Options/Items/OptionsItem.cs +++ b/Quaver.Shared/Screens/Options/Items/OptionsItem.cs @@ -28,6 +28,11 @@ public class OptionsItem : Sprite /// public List Tags { get; set; } = new List(); + /// + /// Used to determine if the options search bar should be focused. + /// + public bool Focused { get; protected set; } + /// /// /// diff --git a/Quaver.Shared/Screens/Options/OptionsDialog.cs b/Quaver.Shared/Screens/Options/OptionsDialog.cs index 50a987a0a5..7b674aa95d 100644 --- a/Quaver.Shared/Screens/Options/OptionsDialog.cs +++ b/Quaver.Shared/Screens/Options/OptionsDialog.cs @@ -63,7 +63,7 @@ public override void Destroy() /// private void Close() { - if (Menu.IsKeybindFocused.Value) + if (Menu.IsOptionFocused.Value) return; Menu.Destroy(); diff --git a/Quaver.Shared/Screens/Options/OptionsHeader.cs b/Quaver.Shared/Screens/Options/OptionsHeader.cs index dcd4b114fe..30da94e87a 100644 --- a/Quaver.Shared/Screens/Options/OptionsHeader.cs +++ b/Quaver.Shared/Screens/Options/OptionsHeader.cs @@ -19,7 +19,7 @@ public class OptionsHeader : Sprite /// /// - private Bindable IsKeybindFocused { get; } + private Bindable IsOptionFocused { get; } /// /// @@ -51,12 +51,12 @@ public class OptionsHeader : Sprite /// /// /// - /// + /// public OptionsHeader(Bindable selectedSection, float width, float sidebarWidth, - Bindable searchQuery, Bindable isKeybindFocused) + Bindable searchQuery, Bindable isOptionFocused) { SelectedSection = selectedSection; - IsKeybindFocused = isKeybindFocused; + IsOptionFocused = isOptionFocused; SidebarWidth = sidebarWidth; CurrentSearchQuery = searchQuery; @@ -117,7 +117,7 @@ private void UpdateActiveSectionText() => ScheduleUpdate(() => /// private void CreateSearchBox() { - SearchBox = new OptionsHeaderSearch(CurrentSearchQuery, IsKeybindFocused) + SearchBox = new OptionsHeaderSearch(CurrentSearchQuery, IsOptionFocused) { Parent = this, Alignment = Alignment.MidRight, diff --git a/Quaver.Shared/Screens/Options/OptionsMenu.cs b/Quaver.Shared/Screens/Options/OptionsMenu.cs index 9953a2d67b..5ed0db92dc 100644 --- a/Quaver.Shared/Screens/Options/OptionsMenu.cs +++ b/Quaver.Shared/Screens/Options/OptionsMenu.cs @@ -58,7 +58,7 @@ public class OptionsMenu : Sprite /// /// - public Bindable IsKeybindFocused { get; } = new Bindable(false) {Value = false}; + public Bindable IsOptionFocused { get; } = new Bindable(false) {Value = false}; /// /// @@ -87,7 +87,7 @@ public OptionsMenu() /// public override void Update(GameTime gameTime) { - SetKeybindFocusedState(); + SetOptionFocusedState(); SkinManager.HandleSkinReloading(); base.Update(gameTime); @@ -102,7 +102,7 @@ public override void Destroy() SelectedSection.ValueChanged -= OnSectionChanged; SelectedSection?.Dispose(); CurrentSearchQuery?.Dispose(); - IsKeybindFocused?.Dispose(); + IsOptionFocused?.Dispose(); // Make sure to destroy everything that's not visible foreach (var section in Sections) @@ -141,6 +141,7 @@ private void CreateSections() { Tags = new List {"fps", "limited", "unlimited", "vsync", "wayland"} }, + new OptionsItemCustomFps(containerRect, "Set Custom FPS"), new OptionsItemCheckbox(containerRect, "Display FPS Counter", ConfigManager.FpsCounter), new OptionsItemCheckbox(containerRect, "Lower FPS On Inactive Window", ConfigManager.LowerFpsOnWindowInactive), new OptionsItemCheckbox(containerRect, "Enable High Process Priority", ConfigManager.EnableHighProcessPriority) @@ -441,7 +442,7 @@ private void CreateSections() /// /// private void CreateHeader() => Header = new OptionsHeader(SelectedSection, Width, Sidebar.Width, CurrentSearchQuery, - IsKeybindFocused) + IsOptionFocused) { Parent = this, Alignment = Alignment.TopLeft @@ -583,7 +584,7 @@ private void UpdateSection(OptionsSection section) => section?.Subcategories.For /// Looks through each section and checks if any of the keybinds are currently focused. /// This sets the bindable, so that the search textbox knows when to become always active or not /// - private void SetKeybindFocusedState() + private void SetOptionFocusedState() { var isFocused = false; @@ -593,8 +594,7 @@ private void SetKeybindFocusedState() { foreach (var item in category.Items) { - if (item is OptionsItemKeybind keybind && keybind.Focused - || item is OptionsItemKeybindMultiple keybindMultiple && keybindMultiple.Focused) + if (item.Focused) { isFocused = true; } @@ -602,7 +602,7 @@ private void SetKeybindFocusedState() } } - IsKeybindFocused.Value = isFocused; + IsOptionFocused.Value = isFocused; } } } diff --git a/Quaver.Shared/Screens/Options/Search/OptionsHeaderSearch.cs b/Quaver.Shared/Screens/Options/Search/OptionsHeaderSearch.cs index d942400299..a6bf232847 100644 --- a/Quaver.Shared/Screens/Options/Search/OptionsHeaderSearch.cs +++ b/Quaver.Shared/Screens/Options/Search/OptionsHeaderSearch.cs @@ -23,7 +23,7 @@ public class OptionsHeaderSearch : Textbox /// /// - private Bindable IsKeybindFocused { get; } + private Bindable IsOptionFocused { get; } /// /// @@ -33,13 +33,13 @@ public class OptionsHeaderSearch : Textbox /// /// /// - /// - public OptionsHeaderSearch(Bindable currentSearchQuery, Bindable isKeybindFocused) + /// + public OptionsHeaderSearch(Bindable currentSearchQuery, Bindable isOptionFocused) : base(new ScalableVector2(300, 34), FontManager.GetWobbleFont(Fonts.LatoBlack),20, "", "Search for options...") { CurrentSearchQuery = currentSearchQuery; - IsKeybindFocused = isKeybindFocused; + IsOptionFocused = isOptionFocused; AllowSubmission = false; Tint = ColorHelper.HexToColor("#2F2F2F"); @@ -58,8 +58,8 @@ public OptionsHeaderSearch(Bindable currentSearchQuery, Bindable i /// public override void Update(GameTime gameTime) { - AlwaysFocused = !IsKeybindFocused.Value; - Focused = !IsKeybindFocused.Value; + AlwaysFocused = !IsOptionFocused.Value; + Focused = !IsOptionFocused.Value; HandleSearchIconAnimations(gameTime); base.Update(gameTime); From 4c6c6243d289a0f0ebe83001e458d47d0b058b54 Mon Sep 17 00:00:00 2001 From: Warp Date: Sat, 19 Aug 2023 21:31:31 +0200 Subject: [PATCH 029/249] add comments --- Quaver.Shared/Graphics/Dialogs/YesNoTextDialog.cs | 3 +++ .../Screens/Options/Items/Custom/OptionsItemCustomFps.cs | 3 +++ Quaver.Shared/Screens/Options/OptionsHeader.cs | 1 + Quaver.Shared/Screens/Options/OptionsMenu.cs | 1 + Quaver.Shared/Screens/Options/Search/OptionsHeaderSearch.cs | 1 + 5 files changed, 9 insertions(+) diff --git a/Quaver.Shared/Graphics/Dialogs/YesNoTextDialog.cs b/Quaver.Shared/Graphics/Dialogs/YesNoTextDialog.cs index 407d917cb1..8921ad81da 100644 --- a/Quaver.Shared/Graphics/Dialogs/YesNoTextDialog.cs +++ b/Quaver.Shared/Graphics/Dialogs/YesNoTextDialog.cs @@ -18,6 +18,9 @@ public class YesNoTextDialog : YesNoDialog /// protected Textbox Textbox { get; set; } + /// + /// The action to be performed when the user submits the text. + /// public Action SubmitAction { get; set; } /// diff --git a/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemCustomFps.cs b/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemCustomFps.cs index 1139df7b7e..fd2f335d7c 100644 --- a/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemCustomFps.cs +++ b/Quaver.Shared/Screens/Options/Items/Custom/OptionsItemCustomFps.cs @@ -28,6 +28,9 @@ namespace Quaver.Shared.Screens.Options.Items.Custom { + /// + /// The options item for custom FPS. + /// public class OptionsItemCustomFps : OptionsItem { /// diff --git a/Quaver.Shared/Screens/Options/OptionsHeader.cs b/Quaver.Shared/Screens/Options/OptionsHeader.cs index 30da94e87a..4d9c299c2a 100644 --- a/Quaver.Shared/Screens/Options/OptionsHeader.cs +++ b/Quaver.Shared/Screens/Options/OptionsHeader.cs @@ -18,6 +18,7 @@ public class OptionsHeader : Sprite private Bindable CurrentSearchQuery { get; } /// + /// Whether or not an option is currently focused /// private Bindable IsOptionFocused { get; } diff --git a/Quaver.Shared/Screens/Options/OptionsMenu.cs b/Quaver.Shared/Screens/Options/OptionsMenu.cs index 5ed0db92dc..17730e60b8 100644 --- a/Quaver.Shared/Screens/Options/OptionsMenu.cs +++ b/Quaver.Shared/Screens/Options/OptionsMenu.cs @@ -57,6 +57,7 @@ public class OptionsMenu : Sprite private Dictionary ContentContainers { get; set; } /// + /// Whether or not an option is currently focused /// public Bindable IsOptionFocused { get; } = new Bindable(false) {Value = false}; diff --git a/Quaver.Shared/Screens/Options/Search/OptionsHeaderSearch.cs b/Quaver.Shared/Screens/Options/Search/OptionsHeaderSearch.cs index a6bf232847..9d62eb8d7e 100644 --- a/Quaver.Shared/Screens/Options/Search/OptionsHeaderSearch.cs +++ b/Quaver.Shared/Screens/Options/Search/OptionsHeaderSearch.cs @@ -22,6 +22,7 @@ public class OptionsHeaderSearch : Textbox private Bindable CurrentSearchQuery { get; } /// + /// Whether or not an option is currently focused /// private Bindable IsOptionFocused { get; } From 94d3d725beedd7625fe3b4c12d835bdebce23cfc Mon Sep 17 00:00:00 2001 From: Swan Date: Sat, 19 Aug 2023 19:52:17 -0400 Subject: [PATCH 030/249] Update Wobble --- Wobble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wobble b/Wobble index c22cecdc35..543e1804c5 160000 --- a/Wobble +++ b/Wobble @@ -1 +1 @@ -Subproject commit c22cecdc350c56b3f3c71dd46b7c77584bb3a7ad +Subproject commit 543e1804c53c2753b7e2f1be5409b8066d447d5e From bb425990a4286ef28033c4f7c67ad93f3ad055fd Mon Sep 17 00:00:00 2001 From: Warp Date: Mon, 21 Aug 2023 23:27:32 +0200 Subject: [PATCH 031/249] check if the user is in game to not add twice --- Quaver.Shared/Online/OnlineManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Online/OnlineManager.cs b/Quaver.Shared/Online/OnlineManager.cs index ea087d109f..5afe347882 100644 --- a/Quaver.Shared/Online/OnlineManager.cs +++ b/Quaver.Shared/Online/OnlineManager.cs @@ -737,7 +737,8 @@ private static void OnJoinedMultiplayerGame(object sender, JoinedGameEventArgs e CurrentGame = MultiplayerGames[e.GameId]; CurrentGame.Players.Add(Self.OnlineUser); - CurrentGame.PlayerIds.Add(Self.OnlineUser.Id); + if (!CurrentGame.PlayerIds.Contains(Self.OnlineUser.Id)) + CurrentGame.PlayerIds.Add(Self.OnlineUser.Id); CurrentGame.PlayerMods.Add(new MultiplayerPlayerMods { UserId = Self.OnlineUser.Id, Modifiers = "0"}); // Get the current screen From 16a5eec1c33983a72beee79195945361fa8563cb Mon Sep 17 00:00:00 2001 From: Warp Date: Tue, 22 Aug 2023 17:57:34 +0200 Subject: [PATCH 032/249] check players and playermods too --- Quaver.Shared/Online/OnlineManager.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Online/OnlineManager.cs b/Quaver.Shared/Online/OnlineManager.cs index 5afe347882..e3c3af2dbb 100644 --- a/Quaver.Shared/Online/OnlineManager.cs +++ b/Quaver.Shared/Online/OnlineManager.cs @@ -736,10 +736,12 @@ private static void OnJoinedMultiplayerGame(object sender, JoinedGameEventArgs e } CurrentGame = MultiplayerGames[e.GameId]; - CurrentGame.Players.Add(Self.OnlineUser); + if (!CurrentGame.Players.Contains(Self.OnlineUser)) + CurrentGame.Players.Add(Self.OnlineUser); if (!CurrentGame.PlayerIds.Contains(Self.OnlineUser.Id)) CurrentGame.PlayerIds.Add(Self.OnlineUser.Id); - CurrentGame.PlayerMods.Add(new MultiplayerPlayerMods { UserId = Self.OnlineUser.Id, Modifiers = "0"}); + if (!CurrentGame.PlayerMods.Any(x => x.UserId == Self.OnlineUser.Id)) + CurrentGame.PlayerMods.Add(new MultiplayerPlayerMods { UserId = Self.OnlineUser.Id, Modifiers = "0"}); // Get the current screen var game = (QuaverGame) GameBase.Game; From 1b80e6e7ce580309611c3f995622f206c70aae9e Mon Sep 17 00:00:00 2001 From: Swan Date: Tue, 22 Aug 2023 12:07:56 -0400 Subject: [PATCH 033/249] Update Quaver.Resources --- Quaver.Resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Resources b/Quaver.Resources index 0d70fea060..ffd6a13dd2 160000 --- a/Quaver.Resources +++ b/Quaver.Resources @@ -1 +1 @@ -Subproject commit 0d70fea06099ae5c7ed2d9c454154eb89a40b017 +Subproject commit ffd6a13dd2472834a51528b5a796acabfc4a66ce From a075de4aa5f5e8378c6bc604c6f5a1fa907ce477 Mon Sep 17 00:00:00 2001 From: Warp Date: Thu, 24 Aug 2023 11:54:25 +0200 Subject: [PATCH 034/249] add textbox cursor textbox movement --- .../Screens/Downloading/UI/Search/DownloadSearchPanel.cs | 5 ++++- .../Screens/Selection/UI/FilterPanel/SelectFilterPanel.cs | 3 ++- Wobble | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Quaver.Shared/Screens/Downloading/UI/Search/DownloadSearchPanel.cs b/Quaver.Shared/Screens/Downloading/UI/Search/DownloadSearchPanel.cs index 5468537b13..215a443f9a 100644 --- a/Quaver.Shared/Screens/Downloading/UI/Search/DownloadSearchPanel.cs +++ b/Quaver.Shared/Screens/Downloading/UI/Search/DownloadSearchPanel.cs @@ -128,7 +128,10 @@ private void CreateMode() /// private void CreateSearchBar() { - SearchBox = new DownloadSearchBox(SearchQuery, new ScalableVector2(280, 40)); + SearchBox = new DownloadSearchBox(SearchQuery, new ScalableVector2(280, 40)) + { + AllowCursorMovement = false + }; RightItems.Add(SearchBox); } diff --git a/Quaver.Shared/Screens/Selection/UI/FilterPanel/SelectFilterPanel.cs b/Quaver.Shared/Screens/Selection/UI/FilterPanel/SelectFilterPanel.cs index 4237f455f5..adbd56003a 100644 --- a/Quaver.Shared/Screens/Selection/UI/FilterPanel/SelectFilterPanel.cs +++ b/Quaver.Shared/Screens/Selection/UI/FilterPanel/SelectFilterPanel.cs @@ -220,7 +220,8 @@ private void CreateSearchBox() SearchBox = new FilterPanelSearchBox(CurrentSearchQuery, AvailableMapsets, IsPlayTesting, ActiveLeftPanel, "Type to search...") { - Parent = this + Parent = this, + AllowCursorMovement = false, }; RightItems.Add(SearchBox); diff --git a/Wobble b/Wobble index 543e1804c5..2cd9875571 160000 --- a/Wobble +++ b/Wobble @@ -1 +1 @@ -Subproject commit 543e1804c53c2753b7e2f1be5409b8066d447d5e +Subproject commit 2cd9875571dd5ad9da1f9647c821fb92e554e10c From 2cea887dd532e11167146bc82e1c5109c831747a Mon Sep 17 00:00:00 2001 From: Warp Date: Thu, 24 Aug 2023 12:53:49 +0200 Subject: [PATCH 035/249] update wobble --- Wobble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wobble b/Wobble index 2cd9875571..23d64b54b2 160000 --- a/Wobble +++ b/Wobble @@ -1 +1 @@ -Subproject commit 2cd9875571dd5ad9da1f9647c821fb92e554e10c +Subproject commit 23d64b54b23ad42507b1cab36e89257df3bfdb7c From 8ab0a00b339d375e7346ae22ea9eb6df6b4cfd4d Mon Sep 17 00:00:00 2001 From: Warp Date: Thu, 24 Aug 2023 15:06:50 +0200 Subject: [PATCH 036/249] dont allow cursor movement in downloadfilter --- .../Screens/Downloading/UI/Filter/DownloadFilterTextbox.cs | 1 + Wobble | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Downloading/UI/Filter/DownloadFilterTextbox.cs b/Quaver.Shared/Screens/Downloading/UI/Filter/DownloadFilterTextbox.cs index d3bccab133..4cd1e6c643 100644 --- a/Quaver.Shared/Screens/Downloading/UI/Filter/DownloadFilterTextbox.cs +++ b/Quaver.Shared/Screens/Downloading/UI/Filter/DownloadFilterTextbox.cs @@ -14,6 +14,7 @@ public DownloadFilterTextbox(WobbleFontStore font, int fontSize, string initialT : base(new ScalableVector2(122, 32), font, fontSize, initialText, placeHolderText, onSubmit, onStoppedTyping) { AllowSubmission = false; + AllowCursorMovement = false; Tint = ColorHelper.HexToColor("#2F2F2F"); AddBorder(ColorHelper.HexToColor("#5B5B5B"), 2); diff --git a/Wobble b/Wobble index 23d64b54b2..f911ead9ac 160000 --- a/Wobble +++ b/Wobble @@ -1 +1 @@ -Subproject commit 23d64b54b23ad42507b1cab36e89257df3bfdb7c +Subproject commit f911ead9ac43a1405399b6785cf1066fad5b98d4 From 8d162f0c6653b974b751ab23da2b2fe6e4f2ad3d Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Fri, 25 Aug 2023 19:26:37 +0300 Subject: [PATCH 037/249] Close stale issues and PR action --- .../workflows/close-stale-issues-and-pr.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/close-stale-issues-and-pr.yml diff --git a/.github/workflows/close-stale-issues-and-pr.yml b/.github/workflows/close-stale-issues-and-pr.yml new file mode 100644 index 0000000000..88d349728e --- /dev/null +++ b/.github/workflows/close-stale-issues-and-pr.yml @@ -0,0 +1,18 @@ +name: 'Close stale issues and PR' +on: + schedule: + - cron: '0 */6 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v8 + with: + stale-issue-message: 'This issue is stale because it has been open 2 days with no activity. Remove stale label or comment or this will be closed in 7 days.' + stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.' + close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.' + days-before-stale: 2 + days-before-close: 7 + days-before-pr-close: -1 + any-of-labels: 'needs-op-response' From 6c14755b1fee135cd1b88b2d3f4d6cb5916d8489 Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Fri, 25 Aug 2023 19:41:00 +0300 Subject: [PATCH 038/249] Fix messages --- .github/workflows/close-stale-issues-and-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/close-stale-issues-and-pr.yml b/.github/workflows/close-stale-issues-and-pr.yml index 88d349728e..257f3ad2f8 100644 --- a/.github/workflows/close-stale-issues-and-pr.yml +++ b/.github/workflows/close-stale-issues-and-pr.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/stale@v8 with: stale-issue-message: 'This issue is stale because it has been open 2 days with no activity. Remove stale label or comment or this will be closed in 7 days.' - stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.' + stale-pr-message: 'This PR is stale because it has been open 7 days with no activity.' close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.' days-before-stale: 2 days-before-close: 7 From f13059b89c63a19f0a5c8a7a85e4b21b0030acb7 Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Fri, 25 Aug 2023 19:42:34 +0300 Subject: [PATCH 039/249] Fix typo --- .github/workflows/close-stale-issues-and-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/close-stale-issues-and-pr.yml b/.github/workflows/close-stale-issues-and-pr.yml index 257f3ad2f8..5288cae5dd 100644 --- a/.github/workflows/close-stale-issues-and-pr.yml +++ b/.github/workflows/close-stale-issues-and-pr.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/stale@v8 with: stale-issue-message: 'This issue is stale because it has been open 2 days with no activity. Remove stale label or comment or this will be closed in 7 days.' - stale-pr-message: 'This PR is stale because it has been open 7 days with no activity.' + stale-pr-message: 'This PR is stale because it has been open 2 days with no activity.' close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.' days-before-stale: 2 days-before-close: 7 From caa8452acd5341db7ba5ed3cb05073b8813f8035 Mon Sep 17 00:00:00 2001 From: Swan Date: Sat, 26 Aug 2023 09:12:42 -0400 Subject: [PATCH 040/249] Update Wobble --- Wobble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wobble b/Wobble index f911ead9ac..e67c5369d9 160000 --- a/Wobble +++ b/Wobble @@ -1 +1 @@ -Subproject commit f911ead9ac43a1405399b6785cf1066fad5b98d4 +Subproject commit e67c5369d95f240aaf48cc5a5c6bfea57b6444ce From a12618f4c4ab1f45fd3ec3647a0bbf212498c7ad Mon Sep 17 00:00:00 2001 From: AiAe Date: Sat, 26 Aug 2023 22:45:32 +0300 Subject: [PATCH 041/249] Update submodules --- Quaver.Server.Client | 2 +- Quaver.Server.Common | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Quaver.Server.Client b/Quaver.Server.Client index 60aa596819..abe344a150 160000 --- a/Quaver.Server.Client +++ b/Quaver.Server.Client @@ -1 +1 @@ -Subproject commit 60aa596819e508e33f348dc26afb33f78db5c2a4 +Subproject commit abe344a150620071a7413c2bf643bf6cc434d66b diff --git a/Quaver.Server.Common b/Quaver.Server.Common index ed618c1453..89d56de33d 160000 --- a/Quaver.Server.Common +++ b/Quaver.Server.Common @@ -1 +1 @@ -Subproject commit ed618c1453d286d2ffb8b15d33940e287c40bf9b +Subproject commit 89d56de33d8711e4ba87c3a7f3e476565b115d10 From 5a6fbcaf653df5614cc8f70b812f08fc3b15a060 Mon Sep 17 00:00:00 2001 From: Warp Date: Sun, 27 Aug 2023 11:26:28 +0200 Subject: [PATCH 042/249] stop audio track later so jukebox doesnt start it --- Quaver.Shared/Screens/Downloading/DownloadingScreen.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Quaver.Shared/Screens/Downloading/DownloadingScreen.cs b/Quaver.Shared/Screens/Downloading/DownloadingScreen.cs index f87322262f..f04e8fc9de 100644 --- a/Quaver.Shared/Screens/Downloading/DownloadingScreen.cs +++ b/Quaver.Shared/Screens/Downloading/DownloadingScreen.cs @@ -218,9 +218,6 @@ public DownloadingScreen(QuaverScreenType previousScreen = QuaverScreenType.Menu /// private void Initialize() { - if (AudioEngine.Track != null) - AudioEngine.Track?.Stop(); - ModManager.RemoveSpeedMods(); CurrentSearchQuery.ValueChanged += OnSearchQueryChanged; @@ -265,6 +262,9 @@ private void Initialize() public override void OnFirstUpdate() { + if (AudioEngine.Track != null) + AudioEngine.Track?.Stop(); + if (!HasRecommendedDifficulty) { ShowRecommendedDifficultyDialog(); From ce76842a2508f3d756e697202f24bd21e7230ceb Mon Sep 17 00:00:00 2001 From: Warp Date: Sun, 27 Aug 2023 13:23:25 +0200 Subject: [PATCH 043/249] change how GetNearestSnapTimeFromTime finds tick --- Quaver.Shared/Audio/AudioEngine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Audio/AudioEngine.cs b/Quaver.Shared/Audio/AudioEngine.cs index c074881608..29e684b2fe 100644 --- a/Quaver.Shared/Audio/AudioEngine.cs +++ b/Quaver.Shared/Audio/AudioEngine.cs @@ -173,7 +173,7 @@ public static double GetNearestSnapTimeFromTime(Qua map, Direction direction, in var nearestTick = Math.Round((pointToSnap - point.StartTime) / snapTimePerBeat) * snapTimePerBeat + point.StartTime; - if (Math.Abs(nearestTick - time) - snapTimePerBeat <= snapTimePerBeat / 2) + if (Math.Abs(nearestTick - time) <= snapTimePerBeat * 1.01) return nearestTick; if (direction == Direction.Backward) From dd9d7a97c4120339a16e60944552919edcfb3d6b Mon Sep 17 00:00:00 2001 From: Warp Date: Sun, 27 Aug 2023 14:51:56 +0200 Subject: [PATCH 044/249] fix changing mp name putting cursor at start --- .../Screens/Multi/UI/Status/Name/ChangeGameNameDialog.cs | 4 +--- .../MultiplayerLobby/UI/Dialogs/JoinPasswordGameDialog.cs | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Quaver.Shared/Screens/Multi/UI/Status/Name/ChangeGameNameDialog.cs b/Quaver.Shared/Screens/Multi/UI/Status/Name/ChangeGameNameDialog.cs index 86c8de8ddc..b1d73f4e92 100644 --- a/Quaver.Shared/Screens/Multi/UI/Status/Name/ChangeGameNameDialog.cs +++ b/Quaver.Shared/Screens/Multi/UI/Status/Name/ChangeGameNameDialog.cs @@ -9,7 +9,7 @@ namespace Quaver.Shared.Screens.Multi.UI.Status.Name { public class ChangeGameNameDialog : JoinPasswordGameDialog { - public ChangeGameNameDialog(MultiplayerGame game) : base(game) + public ChangeGameNameDialog(MultiplayerGame game) : base(game, initialText: game.Name) { Textbox.OnSubmit = null; @@ -17,8 +17,6 @@ public ChangeGameNameDialog(MultiplayerGame game) : base(game) Confirmation.Text = $"Enter a new name for the multiplayer game..."; Textbox.PlaceholderText = "Enter a name..."; - Textbox.RawText = game.Name; - Textbox.InputText.Text = game.Name; Textbox.MaxCharacters = 50; HandleEnterPress = false; diff --git a/Quaver.Shared/Screens/MultiplayerLobby/UI/Dialogs/JoinPasswordGameDialog.cs b/Quaver.Shared/Screens/MultiplayerLobby/UI/Dialogs/JoinPasswordGameDialog.cs index f420162a50..dd4b47d847 100644 --- a/Quaver.Shared/Screens/MultiplayerLobby/UI/Dialogs/JoinPasswordGameDialog.cs +++ b/Quaver.Shared/Screens/MultiplayerLobby/UI/Dialogs/JoinPasswordGameDialog.cs @@ -29,7 +29,7 @@ public class JoinPasswordGameDialog : YesNoDialog /// /// /// - public JoinPasswordGameDialog(MultiplayerGame game, bool spectating = false) : base("ENTER GAME PASSWORD", + public JoinPasswordGameDialog(MultiplayerGame game, bool spectating = false, string initialText = "") : base("ENTER GAME PASSWORD", "Enter the password to join the multiplayer game...") { Game = game; @@ -40,15 +40,15 @@ public JoinPasswordGameDialog(MultiplayerGame game, bool spectating = false) : b NoButton.Visible = false; NoButton.IsClickable = false; - CreateTextbox(); + CreateTextbox(initialText); } /// /// - private void CreateTextbox() + private void CreateTextbox(string initialText) { Textbox = new Textbox(new ScalableVector2(Panel.Width * 0.90f, 50), FontManager.GetWobbleFont(Fonts.LatoBlack), - 20, "", "Enter password...", s => + 20, initialText, "Enter password...", s => { DialogManager.Show(new JoinGameDialog(Game, s, false, IsSpectating)); }) From 22b24d7b7e672916abd3784655b23492d70d0aa9 Mon Sep 17 00:00:00 2001 From: Warp Date: Sun, 27 Aug 2023 20:01:33 +0200 Subject: [PATCH 045/249] max custom fps 5000 --- Quaver.Shared/Config/ConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index e5ede99f6b..5674d505d2 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -907,7 +907,7 @@ private static void ReadConfigFile() WindowFullScreen = ReadValue(@"WindowFullScreen", false, data); FpsCounter = ReadValue(@"FpsCounter", false, data); FpsLimiterType = ReadValue(@"FpsLimiterType", FpsLimitType.Unlimited, data); - CustomFpsLimit = ReadInt(@"CustomFpsLimit", 240, 60, int.MaxValue, data); + CustomFpsLimit = ReadInt(@"CustomFpsLimit", 240, 60, 5000, data); SmoothAudioTimingGameplay = ReadValue(@"SmoothAudioTimingGameplay", false, data); ScrollSpeed4K = ReadInt(@"ScrollSpeed4K", 150, 50, 1000, data); ScrollSpeed7K = ReadInt(@"ScrollSpeed7K", 150, 50, 1000, data); From 0847b722ce25f5db80601a07a26e878460d46d98 Mon Sep 17 00:00:00 2001 From: Warp Date: Sun, 27 Aug 2023 20:24:52 +0200 Subject: [PATCH 046/249] fix exiting downloading screen too --- Quaver.Shared/Screens/Downloading/DownloadingScreen.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Quaver.Shared/Screens/Downloading/DownloadingScreen.cs b/Quaver.Shared/Screens/Downloading/DownloadingScreen.cs index f04e8fc9de..ab6a4083b8 100644 --- a/Quaver.Shared/Screens/Downloading/DownloadingScreen.cs +++ b/Quaver.Shared/Screens/Downloading/DownloadingScreen.cs @@ -245,6 +245,8 @@ private void Initialize() SelectedMapset.ValueChanged += OnSelectedMapsetChanged; SortBy.ValueChanged += OnSortByChanged; + ScreenExiting += OnScreenExiting; + SearchTask = new TaskHandler(SearchMapsets); #if !VISUAL_TESTS @@ -272,6 +274,11 @@ public override void OnFirstUpdate() } } + public void OnScreenExiting(object sender, ScreenExitingEventArgs e) + { + ShouldPreviewPlay = false; + } + /// /// /// From 7dc66cd36802465a555d5b67a8e67ab06a99de1e Mon Sep 17 00:00:00 2001 From: Warp Date: Tue, 29 Aug 2023 09:52:04 +0200 Subject: [PATCH 047/249] update wobble --- Wobble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wobble b/Wobble index e67c5369d9..c2a0cd5a90 160000 --- a/Wobble +++ b/Wobble @@ -1 +1 @@ -Subproject commit e67c5369d95f240aaf48cc5a5c6bfea57b6444ce +Subproject commit c2a0cd5a90ee11385d15e7c88f0f779dd861f5b5 From 185124373c3d61191d2e423dbfe1bf90f8ece641 Mon Sep 17 00:00:00 2001 From: Warp Date: Sun, 3 Sep 2023 13:54:28 +0200 Subject: [PATCH 048/249] dont limit characters to 11 --- Quaver.Shared/Screens/Edit/Dialogs/ColorDialog.cs | 1 - .../Edit/UI/Panels/Layers/Dialogs/DialogChangeLayerColor.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/Dialogs/ColorDialog.cs b/Quaver.Shared/Screens/Edit/Dialogs/ColorDialog.cs index 87e130236f..3fae1242a2 100644 --- a/Quaver.Shared/Screens/Edit/Dialogs/ColorDialog.cs +++ b/Quaver.Shared/Screens/Edit/Dialogs/ColorDialog.cs @@ -60,7 +60,6 @@ private void CreateTextbox() X = 24, Tint = ColorHelper.HexToColor("#2F2F2F"), AlwaysFocused = true, - MaxCharacters = 11, StoppedTypingActionCalltime = 100 }; diff --git a/Quaver.Shared/Screens/Edit/UI/Panels/Layers/Dialogs/DialogChangeLayerColor.cs b/Quaver.Shared/Screens/Edit/UI/Panels/Layers/Dialogs/DialogChangeLayerColor.cs index d51d9ac649..f077eab007 100644 --- a/Quaver.Shared/Screens/Edit/UI/Panels/Layers/Dialogs/DialogChangeLayerColor.cs +++ b/Quaver.Shared/Screens/Edit/UI/Panels/Layers/Dialogs/DialogChangeLayerColor.cs @@ -75,7 +75,6 @@ private void CreateTextbox() X = 24, Tint = ColorHelper.HexToColor("#2F2F2F"), AlwaysFocused = true, - MaxCharacters = 11, StoppedTypingActionCalltime = 100 }; From 9452cb1ae949577eea76f1d56bf0bb49b7a79e7b Mon Sep 17 00:00:00 2001 From: Warp Date: Sun, 3 Sep 2023 17:20:46 +0200 Subject: [PATCH 049/249] change edit playlist dialog to use save button --- .../UI/Playlists/Dialogs/Create/CreatePlaylistContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Selection/UI/Playlists/Dialogs/Create/CreatePlaylistContainer.cs b/Quaver.Shared/Screens/Selection/UI/Playlists/Dialogs/Create/CreatePlaylistContainer.cs index 91a048a506..cc1897f7f9 100644 --- a/Quaver.Shared/Screens/Selection/UI/Playlists/Dialogs/Create/CreatePlaylistContainer.cs +++ b/Quaver.Shared/Screens/Selection/UI/Playlists/Dialogs/Create/CreatePlaylistContainer.cs @@ -259,7 +259,7 @@ private void CreateDescriptionTextbox() /// private void CreateButtonCreate() { - CreateButton = new IconButton(Dialog.Playlist == null ? UserInterface.CreateButton : UserInterface.EditPlayButton, OnSubmit) + CreateButton = new IconButton(Dialog.Playlist == null ? UserInterface.CreateButton : UserInterface.SaveButton, OnSubmit) { Parent = this, Alignment = Alignment.BotLeft, From 1f13a282a3da8bba54b29659815e58a4b043fca3 Mon Sep 17 00:00:00 2001 From: Swan Date: Tue, 5 Sep 2023 16:30:56 -0400 Subject: [PATCH 050/249] Update submodule --- Quaver.Server.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Server.Client b/Quaver.Server.Client index abe344a150..abb3fdb0a0 160000 --- a/Quaver.Server.Client +++ b/Quaver.Server.Client @@ -1 +1 @@ -Subproject commit abe344a150620071a7413c2bf643bf6cc434d66b +Subproject commit abb3fdb0a0b8f28c99a66e225811be86ee043dce From edda89bbac6a9d82ff1be63b30413d50a494fb3e Mon Sep 17 00:00:00 2001 From: Swan Date: Tue, 5 Sep 2023 16:32:14 -0400 Subject: [PATCH 051/249] Change disconnection notification texts --- Quaver.Shared/Online/OnlineManager.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Quaver.Shared/Online/OnlineManager.cs b/Quaver.Shared/Online/OnlineManager.cs index e3c3af2dbb..3255819cf5 100644 --- a/Quaver.Shared/Online/OnlineManager.cs +++ b/Quaver.Shared/Online/OnlineManager.cs @@ -299,7 +299,7 @@ private static void SubscribeToEvents() Client.OnGameNeedDifficultyRatings += OnNeedsDifficultyRatings; Client.OnAutoHostChanged += OnAutoHostChanged; } - + /// /// Called when the connection status of the user has changed. /// @@ -313,7 +313,7 @@ private static void OnConnectionStatusChanged(object sender, ConnectionStatusCha return; ClearOnlineData(); - + var game = (QuaverGame) GameBase.Game; switch (game.CurrentScreen?.Type) @@ -386,14 +386,14 @@ private static void OnDisconnection(object sender, DisconnectedEventArgs e) { // Error ocurred while connecting. case 1006: - NotificationManager.Show(NotificationLevel.Error, "Unable to connect to the server"); + NotificationManager.Show(NotificationLevel.Error, "You have been disconnected from the server."); return; // Authentication Failed case 1002: - NotificationManager.Show(NotificationLevel.Error, "Failed to authenticate to the server"); + NotificationManager.Show(NotificationLevel.Error, "You have failed to authenticate to the server."); return; } - + ClearOnlineData(); } @@ -711,10 +711,10 @@ private static void OnMultiplayerGameInfoReceived(object sender, MultiplayerGame e.Game.Host = OnlineUsers[e.Game.HostId].OnlineUser; var game = (QuaverGame) GameBase.Game; - + if (game.CurrentScreen.Type != QuaverScreenType.Lobby || game.CurrentScreen.Exiting) return; - + Logger.Important($"Received multiplayer game info: ({MultiplayerGames.Count}) - {e.Game.Id} | {e.Game.Name} " + $"| {e.Game.HasPassword} | {e.Game.Password}", LogType.Network); } @@ -1755,13 +1755,13 @@ private static void OnNeedsDifficultyRatings(object sender, GameNeedDifficultyRa return; CurrentGame.NeedsDifficultyRatings = e.Needs; - + if (CurrentGame.NeedsDifficultyRatings) SendGameDifficultyRatings(e.Md5, e.AlternativeMd5); - + Logger.Debug($"Game Needs Difficulty Ratings: {CurrentGame.NeedsDifficultyRatings}", LogType.Runtime); } - + private static void OnAutoHostChanged(object sender, AutoHostChangedEventArgs e) { if (CurrentGame == null) @@ -1770,7 +1770,7 @@ private static void OnAutoHostChanged(object sender, AutoHostChangedEventArgs e) CurrentGame.IsAutoHost = e.Enabled; Logger.Debug($"AutoHost has been changed to: {CurrentGame.IsAutoHost}", LogType.Network); } - + /// /// Leaves the current multiplayer game if any /// @@ -1798,7 +1798,7 @@ public static void SendGameDifficultyRatings(string md5, string alternativeMd5) if (map.DifficultyProcessorVersion != DifficultyProcessorKeys.Version) return; - + Client?.SendGameDifficultyRatings(map.Md5Checksum, map.GetAlternativeMd5(), map.GetDifficultyRatings()); CurrentGame.NeedsDifficultyRatings = false; } From 1a41ab63a6c6060ade675ef282ae27f0d6509b35 Mon Sep 17 00:00:00 2001 From: Swan Date: Tue, 5 Sep 2023 16:32:36 -0400 Subject: [PATCH 052/249] Send logout packet as last step before exiting --- Quaver.Shared/QuaverGame.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/QuaverGame.cs b/Quaver.Shared/QuaverGame.cs index 0abf6f620d..705904ee39 100644 --- a/Quaver.Shared/QuaverGame.cs +++ b/Quaver.Shared/QuaverGame.cs @@ -326,10 +326,10 @@ protected override void LoadContent() protected override void UnloadContent() { ConfigManager.WriteConfigFileAsync().Wait(); - OnlineManager.Client?.Disconnect(); Transitioner.Dispose(); DiscordHelper.Shutdown(); base.UnloadContent(); + OnlineManager.Client?.Disconnect(); } /// @@ -723,7 +723,7 @@ private void HandleKeyPressAltEnter() if (!KeyboardManager.IsUniqueKeyPress(Keys.Enter)) return; - + ConfigManager.WindowFullScreen.Value = !ConfigManager.WindowFullScreen.Value; } From 8312400abc93f47c86f6f6448ccdb76ae3d41fce Mon Sep 17 00:00:00 2001 From: Warp Date: Fri, 8 Sep 2023 22:38:48 +0200 Subject: [PATCH 053/249] update wobble --- Wobble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wobble b/Wobble index c2a0cd5a90..35daea06c5 160000 --- a/Wobble +++ b/Wobble @@ -1 +1 @@ -Subproject commit c2a0cd5a90ee11385d15e7c88f0f779dd861f5b5 +Subproject commit 35daea06c59faff4a68477244381cf27f6ff7fa0 From e6f5a88d0d0bae0a70cb4511774c816e697f3ded Mon Sep 17 00:00:00 2001 From: Swan Date: Mon, 11 Sep 2023 09:44:04 -0400 Subject: [PATCH 054/249] Update Wobble --- Wobble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wobble b/Wobble index 35daea06c5..bba6e46af3 160000 --- a/Wobble +++ b/Wobble @@ -1 +1 @@ -Subproject commit 35daea06c59faff4a68477244381cf27f6ff7fa0 +Subproject commit bba6e46af3b8c25c47e06e9cbae5fa95bcb23df3 From 9c81202e3ee92853355ebfe0ffbfdd3b29ac72e8 Mon Sep 17 00:00:00 2001 From: Swan Date: Mon, 11 Sep 2023 11:05:31 -0400 Subject: [PATCH 055/249] Add database table & methods for blocking users --- .../Database/BlockedUsers/BlockedUsers.cs | 94 +++++++++++++++++++ Quaver.Shared/QuaverGame.cs | 2 + 2 files changed, 96 insertions(+) create mode 100644 Quaver.Shared/Database/BlockedUsers/BlockedUsers.cs diff --git a/Quaver.Shared/Database/BlockedUsers/BlockedUsers.cs b/Quaver.Shared/Database/BlockedUsers/BlockedUsers.cs new file mode 100644 index 0000000000..5fe059bd8d --- /dev/null +++ b/Quaver.Shared/Database/BlockedUsers/BlockedUsers.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Quaver.Shared.Graphics.Notifications; +using SQLite; +using Wobble.Logging; + +namespace Quaver.Shared.Database.BlockedUsers +{ + public static class BlockedUsers + { + /// + /// Users who are currently blocked. + /// + public static List Users { get; private set; } = new List(); + + /// + /// Creates the BlockedUsers database table + /// + public static void Load() + { + try + { + DatabaseManager.Connection.CreateTable(); + Users = DatabaseManager.Connection.Table().ToList(); + Logger.Important($"BlockedUsers table has been created", LogType.Runtime); + } + catch (Exception e) + { + Logger.Error(e, LogType.Runtime); + } + } + + /// + /// Adds a user to the block list + /// + /// + /// + public static void Block(int userId, string username) + { + try + { + var user = new BlockedUser { UserId = userId }; + DatabaseManager.Connection.Insert(user); + Users.Add(user); + + NotificationManager.Show(NotificationLevel.Info, $"You have successfully blocked {username}. All communications will be hidden from this user."); + } + catch (Exception e) + { + Logger.Error(e, LogType.Runtime); + NotificationManager.Show(NotificationLevel.Error, $"An error occurred while adding this user to your block list."); + } + } + + /// + /// Removes a user from the block list + /// + /// + /// + public static void Unblock(int userId, string username) + { + try + { + var user = Users.Find(x => x.UserId == userId); + + if (user != null) + { + DatabaseManager.Connection.Delete(user); + Users.RemoveAll(x => x.UserId == userId); + } + + NotificationManager.Show(NotificationLevel.Info, $"You have removed {username} from your block list."); + } + catch (Exception e) + { + Logger.Error(e, LogType.Runtime); + NotificationManager.Show(NotificationLevel.Error, "An error occurred while removing this user from your block list."); + } + } + + public static bool IsUserBlocked(int userId) => Users.Any(x => x.UserId == userId); + } + + public class BlockedUser + { + [PrimaryKey] + [AutoIncrement] + public int Id { get; set; } + + [Unique] + public int UserId { get; set; } + } +} \ No newline at end of file diff --git a/Quaver.Shared/QuaverGame.cs b/Quaver.Shared/QuaverGame.cs index 705904ee39..68f0d2a4cd 100644 --- a/Quaver.Shared/QuaverGame.cs +++ b/Quaver.Shared/QuaverGame.cs @@ -24,6 +24,7 @@ using Quaver.Shared.Audio; using Quaver.Shared.Config; using Quaver.Shared.Database; +using Quaver.Shared.Database.BlockedUsers; using Quaver.Shared.Database.Judgements; using Quaver.Shared.Database.Maps; using Quaver.Shared.Database.Playlists; @@ -420,6 +421,7 @@ public void PerformGameSetup() QuaverSettingsDatabaseCache.Initialize(); JudgementWindowsDatabaseCache.Load(); UserProfileDatabaseCache.Load(); + BlockedUsers.Load (); // Force garabge collection. GC.Collect(); From 7671acedbca261a747b1af5732880c4acbf00482 Mon Sep 17 00:00:00 2001 From: Swan Date: Mon, 11 Sep 2023 11:05:57 -0400 Subject: [PATCH 056/249] Hide multiplayer game invites from blocked users --- Quaver.Shared/Online/OnlineManager.cs | 59 ++++++++++++++++----------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/Quaver.Shared/Online/OnlineManager.cs b/Quaver.Shared/Online/OnlineManager.cs index 3255819cf5..1f770e190e 100644 --- a/Quaver.Shared/Online/OnlineManager.cs +++ b/Quaver.Shared/Online/OnlineManager.cs @@ -26,6 +26,7 @@ using Quaver.Server.Common.Objects.Twitch; using Quaver.Shared.Audio; using Quaver.Shared.Config; +using Quaver.Shared.Database.BlockedUsers; using Quaver.Shared.Database.Maps; using Quaver.Shared.Database.Scores; using Quaver.Shared.Discord; @@ -968,33 +969,43 @@ private static void OnGameNameChanged(object sender, GameNameChangedEventArgs e) /// /// private static void OnGameInvite(object sender, GameInviteEventArgs e) - => NotificationManager.Show(NotificationLevel.Info, $"{e.Sender} invited you to a game. Click here to join!", - (o, args) => - { - if (CurrentGame != null) - { - NotificationManager.Show(NotificationLevel.Error, "You already in a multiplayer game. Please leave it before joining another."); - return; - } + { + var user = OnlineUsers.Values.ToList().Find(x => x.OnlineUser.Username == e.Sender); - var game = (QuaverGame) GameBase.Game; - var screen = game.CurrentScreen; + // Ignore message because they're blocked. + if (user != null && BlockedUsers.IsUserBlocked(user.OnlineUser.Id)) + return; - switch (screen.Type) + NotificationManager.Show(NotificationLevel.Info, $"{e.Sender} invited you to a game. Click here to join!", + (o, args) => { - case QuaverScreenType.Menu: - case QuaverScreenType.Results: - case QuaverScreenType.Select: - case QuaverScreenType.Download: - case QuaverScreenType.Lobby: - DialogManager.Show(new JoinGameDialog(null, null, true)); - Client?.AcceptGameInvite(e.MatchId); - break; - default: - NotificationManager.Show(NotificationLevel.Error, "Finish what you're doing before accepting this game invite."); - break; - } - }); + if (CurrentGame != null) + { + NotificationManager.Show(NotificationLevel.Error, + "You already in a multiplayer game. Please leave it before joining another."); + return; + } + + var game = (QuaverGame)GameBase.Game; + var screen = game.CurrentScreen; + + switch (screen.Type) + { + case QuaverScreenType.Menu: + case QuaverScreenType.Results: + case QuaverScreenType.Select: + case QuaverScreenType.Download: + case QuaverScreenType.Lobby: + DialogManager.Show(new JoinGameDialog(null, null, true)); + Client?.AcceptGameInvite(e.MatchId); + break; + default: + NotificationManager.Show(NotificationLevel.Error, + "Finish what you're doing before accepting this game invite."); + break; + } + }); + } /// /// From 24d47060590807bcdf5e5ca4da4eacfb9df9fc8e Mon Sep 17 00:00:00 2001 From: Swan Date: Mon, 11 Sep 2023 11:06:28 -0400 Subject: [PATCH 057/249] Hide messages from blocked users --- .../Channels/Scrolling/ChatChannelScrollContainer.cs | 4 ++++ .../Messages/Scrolling/ChatMessageScrollContainer.cs | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/Quaver.Shared/Graphics/Overlays/Chatting/Channels/Scrolling/ChatChannelScrollContainer.cs b/Quaver.Shared/Graphics/Overlays/Chatting/Channels/Scrolling/ChatChannelScrollContainer.cs index f165946219..501ca952e6 100644 --- a/Quaver.Shared/Graphics/Overlays/Chatting/Channels/Scrolling/ChatChannelScrollContainer.cs +++ b/Quaver.Shared/Graphics/Overlays/Chatting/Channels/Scrolling/ChatChannelScrollContainer.cs @@ -7,6 +7,7 @@ using Quaver.Server.Client.Handlers; using Quaver.Server.Client.Structures; using Quaver.Server.Common.Enums; +using Quaver.Shared.Database.BlockedUsers; using Quaver.Shared.Graphics.Containers; using Quaver.Shared.Graphics.Form.Dropdowns.RightClick; using Quaver.Shared.Graphics.Notifications; @@ -284,6 +285,9 @@ private void OnChatMessageReceived(object sender, ChatMessageEventArgs e) if (e.Message.SenderId == OnlineManager.Self.OnlineUser.Id) return; + if (BlockedUsers.IsUserBlocked(e.Message.SenderId)) + return; + // Private message if (!e.Message.Channel.StartsWith("#")) { diff --git a/Quaver.Shared/Graphics/Overlays/Chatting/Messages/Scrolling/ChatMessageScrollContainer.cs b/Quaver.Shared/Graphics/Overlays/Chatting/Messages/Scrolling/ChatMessageScrollContainer.cs index 4830c61b64..17338ea0d8 100644 --- a/Quaver.Shared/Graphics/Overlays/Chatting/Messages/Scrolling/ChatMessageScrollContainer.cs +++ b/Quaver.Shared/Graphics/Overlays/Chatting/Messages/Scrolling/ChatMessageScrollContainer.cs @@ -8,6 +8,7 @@ using Quaver.Server.Client.Events; using Quaver.Server.Client.Handlers; using Quaver.Server.Client.Structures; +using Quaver.Shared.Database.BlockedUsers; using Quaver.Shared.Graphics.Containers; using Quaver.Shared.Graphics.Form.Dropdowns.RightClick; using Quaver.Shared.Online; @@ -182,7 +183,12 @@ private int RunRequestHistoryTask(int val, CancellationToken token) lock (MessageHistoryQueue) { foreach (var message in history.Messages) + { + if (BlockedUsers.IsUserBlocked(message.User.Id)) + continue; + MessageHistoryQueue.Add(message.Message.ToChatMessage(message.User.ToUser())); + } } } @@ -394,6 +400,9 @@ private void OnChatMessageReceived(object sender, ChatMessageEventArgs e) if (!HasRequestedMessageHistory) return; + if (BlockedUsers.IsUserBlocked(e.Message.SenderId)) + return; + // Public Chats if (Channel.Name.StartsWith("#") && Channel.Name != e.Message.Channel) return; From d9f660fa561d83d947b26b547c31a131ad4dc20e Mon Sep 17 00:00:00 2001 From: Swan Date: Mon, 11 Sep 2023 11:06:46 -0400 Subject: [PATCH 058/249] Add blocking functionality to user right click options --- .../DrawableOnlineUserRightClickOptions.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Quaver.Shared/Graphics/Overlays/Hub/OnlineUsers/Scrolling/DrawableOnlineUserRightClickOptions.cs b/Quaver.Shared/Graphics/Overlays/Hub/OnlineUsers/Scrolling/DrawableOnlineUserRightClickOptions.cs index d1a8b989be..fab98c3c4b 100644 --- a/Quaver.Shared/Graphics/Overlays/Hub/OnlineUsers/Scrolling/DrawableOnlineUserRightClickOptions.cs +++ b/Quaver.Shared/Graphics/Overlays/Hub/OnlineUsers/Scrolling/DrawableOnlineUserRightClickOptions.cs @@ -6,6 +6,7 @@ using Quaver.Server.Client.Structures; using Quaver.Server.Common.Enums; using Quaver.Server.Common.Objects.Multiplayer; +using Quaver.Shared.Database.BlockedUsers; using Quaver.Shared.Graphics.Form.Dropdowns.RightClick; using Quaver.Shared.Graphics.Notifications; using Quaver.Shared.Graphics.Overlays.Chatting; @@ -28,6 +29,10 @@ public class DrawableOnlineUserRightClickOptions : RightClickOptions private const string RemoveFriend = "Remove Friend"; + private const string BlockUser = "Block User"; + + private const string UnblockUser = "Unblock User"; + // private const string JoinListeningParty = "Join Listening Party"; private const string InviteToGame = "Invite To Game"; @@ -65,6 +70,12 @@ public class DrawableOnlineUserRightClickOptions : RightClickOptions case RemoveFriend: OnlineManager.RemoveFriend(user); break; + case BlockUser: + BlockedUsers.Block(user.OnlineUser.Id, user.OnlineUser.Username); + break; + case UnblockUser: + BlockedUsers.Unblock(user.OnlineUser.Id, user.OnlineUser.Username); + break; // case JoinListeningParty: // HandleJoinListeningParty(user); // break; @@ -126,6 +137,10 @@ private static Dictionary GetOptions(User user) else options.Add(AddFriend, ColorHelper.HexToColor("#27B06E")); + // Block User + if (!user.OnlineUser.UserGroups.HasFlag(UserGroups.Bot) && !user.OnlineUser.UserGroups.HasFlag(UserGroups.Developer)) + options.Add(BlockedUsers.IsUserBlocked(user.OnlineUser.Id) ? UnblockUser : BlockUser, ColorHelper.HexToColor($"#FF6868")); + // Invite To Multiplayer if (OnlineManager.CurrentGame != null && OnlineManager.CurrentGame.Type == MultiplayerGameType.Friendly From 08ab72e9097f9105f618994a6a1628d169a78af6 Mon Sep 17 00:00:00 2001 From: Swan Date: Mon, 11 Sep 2023 11:15:17 -0400 Subject: [PATCH 059/249] Remove space --- Quaver.Shared/QuaverGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/QuaverGame.cs b/Quaver.Shared/QuaverGame.cs index 68f0d2a4cd..870c2456cc 100644 --- a/Quaver.Shared/QuaverGame.cs +++ b/Quaver.Shared/QuaverGame.cs @@ -421,7 +421,7 @@ public void PerformGameSetup() QuaverSettingsDatabaseCache.Initialize(); JudgementWindowsDatabaseCache.Load(); UserProfileDatabaseCache.Load(); - BlockedUsers.Load (); + BlockedUsers.Load(); // Force garabge collection. GC.Collect(); From 91fd36bc4878781980624b68992df1069c90ab21 Mon Sep 17 00:00:00 2001 From: Warp Date: Tue, 19 Sep 2023 14:45:31 +0200 Subject: [PATCH 060/249] dont toggle autoplay when a dialog is open --- Quaver.Shared/Screens/Gameplay/GameplayScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs index 25d4252e61..f720cb3193 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs @@ -1500,7 +1500,7 @@ public void ExitToNewEditor(bool seekToTime = false) public void HandleAutoplayTabInput(GameTime gameTime) { // Handle play test autoplay input. - if (IsPlayTesting && KeyboardManager.IsUniqueKeyPress(Keys.Tab) && !KeyboardManager.IsShiftDown() && !OnlineChat.Instance.IsOpen) + if (IsPlayTesting && KeyboardManager.IsUniqueKeyPress(Keys.Tab) && !KeyboardManager.IsShiftDown() && !OnlineChat.Instance.IsOpen && DialogManager.Dialogs.Count == 0) { var inputManager = (KeysInputManager) Ruleset.InputManager; From 7cf0b262e3868d693022e5b4cadff586d40f8fa3 Mon Sep 17 00:00:00 2001 From: Warp Date: Tue, 17 Oct 2023 15:10:43 +0200 Subject: [PATCH 061/249] fix typo in error message --- .../Screens/Edit/Dialogs/EditorUploadingMapsetDialog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Edit/Dialogs/EditorUploadingMapsetDialog.cs b/Quaver.Shared/Screens/Edit/Dialogs/EditorUploadingMapsetDialog.cs index 2532ed3282..f52755cd4f 100644 --- a/Quaver.Shared/Screens/Edit/Dialogs/EditorUploadingMapsetDialog.cs +++ b/Quaver.Shared/Screens/Edit/Dialogs/EditorUploadingMapsetDialog.cs @@ -36,7 +36,7 @@ public class EditorUploadingMapsetDialog : LoadingDialog {MapsetSubmissionStatusCode.ErrorConflictingDifficultyNames, "One or more of your maps have the same difficulty name!"}, {MapsetSubmissionStatusCode.ErrorNoExistingMapsetFound, "You're trying to update a mapset, but this mapset isn't uploaded online!"}, {MapsetSubmissionStatusCode.ErrorAlreadyRanked, "You cannot update a mapset that is already ranked!"}, - {MapsetSubmissionStatusCode.ErrorContainsNonUploadedNotNewMapId, "One or more of youir maps contains a non-uploaded map id!"}, + {MapsetSubmissionStatusCode.ErrorContainsNonUploadedNotNewMapId, "One or more of your maps contains a non-uploaded map id!"}, {MapsetSubmissionStatusCode.SuccessUploaded, "Success! Your mapset has been uploaded!"}, {MapsetSubmissionStatusCode.SuccessUpdated, "Success! Your mapset has been updated!"}, {MapsetSubmissionStatusCode.ErrorExceededLimit, "You have exceeded the amount of maps you can upload at this time!"} From d62d92abd1573188eb32918fce7e0563ab51c872 Mon Sep 17 00:00:00 2001 From: Warp Date: Wed, 18 Oct 2023 12:53:31 +0200 Subject: [PATCH 062/249] Get the unknown avatar when normal not available --- Quaver.Shared/Graphics/Online/OnlinePlayercard.cs | 2 +- Quaver.Shared/Online/SteamManager.cs | 13 +++++++++++++ .../Screens/Gameplay/GameplayScreenView.cs | 2 +- .../Gameplay/UI/Scoreboard/ScoreboardUser.cs | 2 +- .../Components/DrawableLeaderboardScoreContainer.cs | 2 +- 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Quaver.Shared/Graphics/Online/OnlinePlayercard.cs b/Quaver.Shared/Graphics/Online/OnlinePlayercard.cs index a33199d235..67cf1232f8 100644 --- a/Quaver.Shared/Graphics/Online/OnlinePlayercard.cs +++ b/Quaver.Shared/Graphics/Online/OnlinePlayercard.cs @@ -86,7 +86,7 @@ public OnlinePlayercard() Alignment = Alignment.MidLeft, Size = new ScalableVector2(Height * 0.75f, Height * 0.75f), X = 12, - Image = SteamManager.UserAvatars[SteamUser.GetSteamID().m_SteamID] + Image = SteamManager.GetAvatarOrUnknown(SteamUser.GetSteamID().m_SteamID) }; Avatar.AddBorder(Color.White, 2); diff --git a/Quaver.Shared/Online/SteamManager.cs b/Quaver.Shared/Online/SteamManager.cs index fa6bc9d205..359e0a2968 100644 --- a/Quaver.Shared/Online/SteamManager.cs +++ b/Quaver.Shared/Online/SteamManager.cs @@ -365,6 +365,19 @@ private static Texture2D LoadAvatar(ulong steamId, bool large = false) return null; } + /// + /// Gets a user's avatar from or returns if it doesn't exist. + /// + /// + /// + public static Texture2D GetAvatarOrUnknown(ulong steamId) + { + if (UserAvatars.ContainsKey(steamId)) + return UserAvatars[steamId]; + + return UserInterface.UnknownAvatar; + } + /// /// public static void RefreshWorkshopSkins() diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs index f1359a7dc4..8d4c47fb21 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs @@ -499,7 +499,7 @@ private void CreateScoreboards() // Use the replay's name for the scoreboard if we're watching one. var scoreboardName = Screen.InReplayMode ? Screen.LoadedReplay.PlayerName : ConfigManager.Username.Value; - var selfAvatar = ConfigManager.Username.Value == scoreboardName ? SteamManager.UserAvatars[SteamUser.GetSteamID().m_SteamID] + var selfAvatar = ConfigManager.Username.Value == scoreboardName ? SteamManager.GetAvatarOrUnknown(SteamUser.GetSteamID().m_SteamID) : UserInterface.UnknownAvatar; SelfScoreboard = new ScoreboardUser(Screen, ScoreboardUserType.Self, scoreboardName, null, selfAvatar, diff --git a/Quaver.Shared/Screens/Gameplay/UI/Scoreboard/ScoreboardUser.cs b/Quaver.Shared/Screens/Gameplay/UI/Scoreboard/ScoreboardUser.cs index f91ccb4b26..e9810776db 100644 --- a/Quaver.Shared/Screens/Gameplay/UI/Scoreboard/ScoreboardUser.cs +++ b/Quaver.Shared/Screens/Gameplay/UI/Scoreboard/ScoreboardUser.cs @@ -229,7 +229,7 @@ internal ScoreboardUser(GameplayScreen screen, ScoreboardUserType type, string u else { Avatar.Image = ConfigManager.Username?.Value == UsernameRaw - ? SteamManager.UserAvatars[SteamUser.GetSteamID().m_SteamID] + ? SteamManager.GetAvatarOrUnknown(SteamUser.GetSteamID().m_SteamID) : UserInterface.UnknownAvatar; } } diff --git a/Quaver.Shared/Screens/Selection/UI/Leaderboard/Components/DrawableLeaderboardScoreContainer.cs b/Quaver.Shared/Screens/Selection/UI/Leaderboard/Components/DrawableLeaderboardScoreContainer.cs index 292272db15..1f5bf3b417 100644 --- a/Quaver.Shared/Screens/Selection/UI/Leaderboard/Components/DrawableLeaderboardScoreContainer.cs +++ b/Quaver.Shared/Screens/Selection/UI/Leaderboard/Components/DrawableLeaderboardScoreContainer.cs @@ -616,7 +616,7 @@ private void UpdateAvatar() { if (Score.IsPersonalBest && !Score.Item.IsOnline) { - Avatar.Image = SteamManager.UserAvatars[steamId]; + Avatar.Image = SteamManager.GetAvatarOrUnknown(steamId); Avatar.Alpha = 1; return; } From 2457418f9073bef79e2cdd6e4e4a6aa8a07027f5 Mon Sep 17 00:00:00 2001 From: Grim Date: Tue, 24 Oct 2023 14:08:31 +0300 Subject: [PATCH 063/249] Add an option to unlock windows key during gameplay --- Quaver.Shared/Config/ConfigManager.cs | 6 ++++++ Quaver.Shared/Screens/Gameplay/GameplayScreen.cs | 2 +- Quaver.Shared/Screens/Options/OptionsMenu.cs | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index 5674d505d2..f4e3961697 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -406,6 +406,11 @@ public static class ConfigManager /// internal static Bindable SkipResultsScreenAfterQuit { get; private set; } + /// + /// If true, the windows key is locked during gameplay + /// + internal static Bindable LockWinkeyDuringGameplay { get; private set; } + /// /// If true, it'll use hitobjects specifically for viewing layers in the editor. /// @@ -1006,6 +1011,7 @@ private static void ReadConfigFile() DisplayJudgementCounter = ReadValue(@"DisplayJudgementCounter", true, data); HitErrorFadeTime = ReadInt(@"HitErrorFadeTime", 1000, 100, 5000, data); SkipResultsScreenAfterQuit = ReadValue(@"SkipResultsScreenAfterQuit", false, data); + LockWinkeyDuringGameplay = ReadValue(@"LockWinkeyDuringGameplay", true, data); DisplayComboAlerts = ReadValue(@"DisplayComboAlerts", true, data); LaneCoverTopHeight = ReadInt(@"LaneCoverTopHeight", 25, 0, 75, data); LaneCoverBottomHeight = ReadInt(@"LaneCoverBottomHeight", 25, 0, 75, data); diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs index f720cb3193..84323be73f 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs @@ -478,7 +478,7 @@ public override void OnFirstUpdate() if (ReplayCapturer != null) ReplayCapturer.Replay.TimePlayed = TimePlayed; - if (!InReplayMode) + if (!InReplayMode && ConfigManager.LockWinkeyDuringGameplay.Value) Utils.NativeUtils.DisableWindowsKey(); base.OnFirstUpdate(); diff --git a/Quaver.Shared/Screens/Options/OptionsMenu.cs b/Quaver.Shared/Screens/Options/OptionsMenu.cs index 17730e60b8..c0054c7213 100644 --- a/Quaver.Shared/Screens/Options/OptionsMenu.cs +++ b/Quaver.Shared/Screens/Options/OptionsMenu.cs @@ -214,7 +214,8 @@ private void CreateSections() { new OptionsItemCheckbox(containerRect, "Enable Tap To Pause", ConfigManager.TapToPause), new OptionsItemCheckbox(containerRect, "Enable Tap To Restart", ConfigManager.TapToRestart), - new OptionsItemCheckbox(containerRect, "Skip Results Screen After Quitting", ConfigManager.SkipResultsScreenAfterQuit) + new OptionsItemCheckbox(containerRect, "Skip Results Screen After Quitting", ConfigManager.SkipResultsScreenAfterQuit), + new OptionsItemCheckbox(containerRect, "Lock Windows Key during gameplay", ConfigManager.LockWinkeyDuringGameplay) }), new OptionsSubcategory("User Interface", new List() { From 6c25bcc7b5063a42c1c615b35c7b44e015081e67 Mon Sep 17 00:00:00 2001 From: Warp Date: Mon, 25 Dec 2023 20:58:19 +0100 Subject: [PATCH 064/249] Improve download screen infinite scroll --- Quaver.Shared/Screens/Downloading/DownloadingScreen.cs | 9 +++++++++ .../Screens/Downloading/DownloadingScreenView.cs | 2 +- .../UI/Mapsets/DownloadableMapsetContainer.cs | 9 +++++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Quaver.Shared/Screens/Downloading/DownloadingScreen.cs b/Quaver.Shared/Screens/Downloading/DownloadingScreen.cs index ab6a4083b8..e0cd5fe4f9 100644 --- a/Quaver.Shared/Screens/Downloading/DownloadingScreen.cs +++ b/Quaver.Shared/Screens/Downloading/DownloadingScreen.cs @@ -154,6 +154,11 @@ public sealed class DownloadingScreen : QuaverScreen /// public Bindable Page { get; } = new Bindable(0) { Value = 0}; + /// + /// Determines if the user has reached the end of the mapset list + /// + public Bindable ReachedEnd { get; } = new Bindable(false) { Value = false }; + /// /// public Bindable SortBy { get; } = new Bindable(DownloadSortBy.Newest) @@ -507,7 +512,10 @@ private void StartSearchTask() PreviousPageMapsets = new List(); } else if (PreviousPageMapsets.Count < 50) + { + ReachedEnd.Value = true; return; + } if (SearchTask.IsRunning) SearchTask.Cancel(); @@ -671,6 +679,7 @@ private void OnSearchQueryChanged(object sender, BindableValueChangedEventArgs diff --git a/Quaver.Shared/Screens/Downloading/DownloadingScreenView.cs b/Quaver.Shared/Screens/Downloading/DownloadingScreenView.cs index 98d6a31bf4..300e41c791 100644 --- a/Quaver.Shared/Screens/Downloading/DownloadingScreenView.cs +++ b/Quaver.Shared/Screens/Downloading/DownloadingScreenView.cs @@ -141,7 +141,7 @@ private void CreateFilterContainer() private void CreateMapsetContainer() { MapsetContainer = new DownloadableMapsetContainer(DownloadingScreen.Mapsets, DownloadingScreen.SelectedMapset, - DownloadingScreen.Page, DownloadingScreen.SearchTask) + DownloadingScreen.Page, DownloadingScreen.ReachedEnd, DownloadingScreen.SearchTask) { Parent = Container, Alignment = Alignment.TopRight, diff --git a/Quaver.Shared/Screens/Downloading/UI/Mapsets/DownloadableMapsetContainer.cs b/Quaver.Shared/Screens/Downloading/UI/Mapsets/DownloadableMapsetContainer.cs index 85eaf94136..cc2cb9f930 100644 --- a/Quaver.Shared/Screens/Downloading/UI/Mapsets/DownloadableMapsetContainer.cs +++ b/Quaver.Shared/Screens/Downloading/UI/Mapsets/DownloadableMapsetContainer.cs @@ -36,6 +36,10 @@ public class DownloadableMapsetContainer : SongSelectContainer private Bindable Page { get; } + /// + /// + private Bindable ReachedEnd { get; } + /// /// private TaskHandler SearchTask { get; } @@ -52,12 +56,13 @@ public class DownloadableMapsetContainer : SongSelectContainer /// public DownloadableMapsetContainer(BindableList mapsets, - Bindable selectedMapset, Bindable page, TaskHandler searchTask) + Bindable selectedMapset, Bindable page, Bindable reachedEnd, TaskHandler searchTask) : base(mapsets.Value, int.MaxValue) { AvailableMapsets = mapsets; SelectedMapset = selectedMapset; Page = page; + ReachedEnd = reachedEnd; SearchTask = searchTask; CreateLoadingWheel(); @@ -77,7 +82,7 @@ public override void Update(GameTime gameTime) { // Handle infinite scrolling if (ContentContainer.Height - Math.Abs(ContentContainer.Y) - Height < 500 && !SearchTask.IsRunning - && ContentContainer.Y != 0) + && !ReachedEnd.Value) { Page.Value++; } From dff719f5a0fde55699a0f75fba6a8298d5ab4be8 Mon Sep 17 00:00:00 2001 From: Warp Date: Tue, 26 Dec 2023 20:03:49 +0100 Subject: [PATCH 065/249] Update API request URLs for Privacy Policy and Terms of Service --- Quaver.Shared/Online/API/Legal/APIRequestPrivacyPolicy.cs | 4 ++-- Quaver.Shared/Online/API/Legal/APIRequestTOS.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Quaver.Shared/Online/API/Legal/APIRequestPrivacyPolicy.cs b/Quaver.Shared/Online/API/Legal/APIRequestPrivacyPolicy.cs index acefd2dd91..9f6eaa3f41 100644 --- a/Quaver.Shared/Online/API/Legal/APIRequestPrivacyPolicy.cs +++ b/Quaver.Shared/Online/API/Legal/APIRequestPrivacyPolicy.cs @@ -4,11 +4,11 @@ namespace Quaver.Shared.Online.API.Legal { public class APIRequestPrivacyPolicy : APIRequest { - private const string BaseUrl = "https://raw.githubusercontent.com"; + private const string BaseUrl = "https://wiki.quavergame.com"; public override string ExecuteRequest() { - var request = new RestRequest($"{BaseUrl}/Quaver/Quaver.Wiki/master/Legal/Privacy/en.md", Method.GET); + var request = new RestRequest($"{BaseUrl}/md/Legal/Privacy/en.md", Method.GET); var client = new RestClient(BaseUrl); var response = client.Execute(request); diff --git a/Quaver.Shared/Online/API/Legal/APIRequestTOS.cs b/Quaver.Shared/Online/API/Legal/APIRequestTOS.cs index 7e0222968a..4e45336d7f 100644 --- a/Quaver.Shared/Online/API/Legal/APIRequestTOS.cs +++ b/Quaver.Shared/Online/API/Legal/APIRequestTOS.cs @@ -4,11 +4,11 @@ namespace Quaver.Shared.Online.API.Legal { public class APIRequestTOS : APIRequest { - private const string BaseUrl = "https://raw.githubusercontent.com"; + private const string BaseUrl = "https://wiki.quavergame.com"; public override string ExecuteRequest() { - var request = new RestRequest($"{BaseUrl}/Quaver/Quaver.Wiki/master/Legal/Terms/en.md", Method.GET); + var request = new RestRequest($"{BaseUrl}/md/Legal/Terms/en.md", Method.GET); var client = new RestClient(BaseUrl); var response = client.Execute(request); From 452dabd8c34dd431f376ea551fb44c5e1d697b31 Mon Sep 17 00:00:00 2001 From: Swan Date: Mon, 22 Jan 2024 09:55:19 -0500 Subject: [PATCH 066/249] Add jukebox back --- Quaver.Shared/Graphics/Menu/Border/MenuHeaderMain.cs | 1 + Quaver.Shared/Screens/Main/UI/Jukebox/FooterJukebox.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Graphics/Menu/Border/MenuHeaderMain.cs b/Quaver.Shared/Graphics/Menu/Border/MenuHeaderMain.cs index 9d73e2fefd..d2a255ac42 100644 --- a/Quaver.Shared/Graphics/Menu/Border/MenuHeaderMain.cs +++ b/Quaver.Shared/Graphics/Menu/Border/MenuHeaderMain.cs @@ -20,6 +20,7 @@ public class MenuHeaderMain : MenuBorder new MenuBorderLogo(), new IconTextButtonHome(), new IconTextButtonDownloadMaps(), + new IconTextButtonMusicPlayer(), new IconTextButtonSkins(), new IconTextButtonDonate() }, diff --git a/Quaver.Shared/Screens/Main/UI/Jukebox/FooterJukebox.cs b/Quaver.Shared/Screens/Main/UI/Jukebox/FooterJukebox.cs index 9c6a62d3d2..456942415c 100644 --- a/Quaver.Shared/Screens/Main/UI/Jukebox/FooterJukebox.cs +++ b/Quaver.Shared/Screens/Main/UI/Jukebox/FooterJukebox.cs @@ -113,7 +113,8 @@ public FooterJukebox() : base(UserInterface.BlankBox) CreatePreviousButton(); CreateNextButton(); CreateSongInfo(); - //CreateInfoIcon(); + CreateInfoIcon(); + CreateGoToJukeboxScreenButton(); CreateProgressBar(); Clicked += (sender, args) => HandleClickSeeking(); From 893127247ee55b68f8a5b2cbdc422e3c8a63dbcb Mon Sep 17 00:00:00 2001 From: Emik Date: Wed, 21 Feb 2024 02:32:17 +0100 Subject: [PATCH 067/249] Accept a range of values in the beat snap dialog --- .../Edit/Dialogs/CustomBeatSnapDialog.cs | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/Dialogs/CustomBeatSnapDialog.cs b/Quaver.Shared/Screens/Edit/Dialogs/CustomBeatSnapDialog.cs index 32fed7c029..efc0e61334 100644 --- a/Quaver.Shared/Screens/Edit/Dialogs/CustomBeatSnapDialog.cs +++ b/Quaver.Shared/Screens/Edit/Dialogs/CustomBeatSnapDialog.cs @@ -30,7 +30,7 @@ public class CustomBeatSnapDialog : YesNoDialog /// /// public CustomBeatSnapDialog(BindableInt beatSnap, List availableBeatSnaps) : base("CUSTOM BEAT SNAP", - "Enter a value for the custom beat snap divisor (1/?)...") + "Enter a value or range of values for the custom beat snap divisor (1/?)...") { BeatSnap = beatSnap; AvailableBeatSnaps = availableBeatSnaps; @@ -49,14 +49,14 @@ public CustomBeatSnapDialog(BindableInt beatSnap, List availableBeatSnaps) private void CreateTextbox() { Textbox = new Textbox(new ScalableVector2(Panel.Width * 0.90f, 50), FontManager.GetWobbleFont(Fonts.LatoBlack), - 20, "", "Enter a beat snap value (max 48)", s => OnSubmit(s)) + 20, "", "Enter a beat snap value or range (max 48, e.g. \"7\" or \"5-9\")", s => OnSubmit(s)) { Parent = Panel, Alignment = Alignment.BotCenter, Y = -100, Tint = ColorHelper.HexToColor("#2F2F2F"), AlwaysFocused = true, - AllowedCharacters = new Regex(@"^([1-9]|[1-3][0-9]|4[0-8])$") + AllowedCharacters = new Regex(@"^(?:[1-9]|[1-3][0-9]|4[0-8])(?:-(?:[1-9]|[1-3][0-9]|4[0-8])?)?$") }; Textbox.AddBorder(ColorHelper.HexToColor("#363636"), 2); @@ -73,13 +73,27 @@ public override void Close() private void OnSubmit(string s) { - BeatSnap.Value = int.Parse(s); + int from, to; - if (!AvailableBeatSnaps.Contains(BeatSnap.Value)) + if (s.IndexOf('-') is var range && range is -1) + from = to = int.Parse(s); + else if (range == s.Length - 1) + from = to = int.Parse(s.AsSpan()[..^1]); + else { - AvailableBeatSnaps.Add(BeatSnap.Value); - AvailableBeatSnaps.Sort(); + from = int.Parse(s.AsSpan()[..range]); + to = int.Parse(s.AsSpan()[(range + 1)..]); } + + // "15-5" should behave the same as "5-15". + if (from > to) + (from, to) = (to, from); + + BeatSnap.Value = from; + + for (var i = from; i <= to; i++) + if (AvailableBeatSnaps.BinarySearch(i) is var index && index < 0) + AvailableBeatSnaps.Insert(~index, i); } } -} \ No newline at end of file +} From 380f38d98105f163cb78bbf2e1b73a13f3bb153b Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 2 Mar 2024 13:25:45 +0800 Subject: [PATCH 068/249] Add editor action for swapping lanes --- .../Screens/Edit/Actions/EditorActionType.cs | 1 + .../HitObjects/Swap/EditorActionSwapLanes.cs | 67 +++++++++++++++++++ .../Swap/EditorLanesSwappedEventArgs.cs | 21 ++++++ 3 files changed, 89 insertions(+) create mode 100644 Quaver.Shared/Screens/Edit/Actions/HitObjects/Swap/EditorActionSwapLanes.cs create mode 100644 Quaver.Shared/Screens/Edit/Actions/HitObjects/Swap/EditorLanesSwappedEventArgs.cs diff --git a/Quaver.Shared/Screens/Edit/Actions/EditorActionType.cs b/Quaver.Shared/Screens/Edit/Actions/EditorActionType.cs index a07ed0ecd1..edf88e5445 100644 --- a/Quaver.Shared/Screens/Edit/Actions/EditorActionType.cs +++ b/Quaver.Shared/Screens/Edit/Actions/EditorActionType.cs @@ -9,6 +9,7 @@ public enum EditorActionType RemoveHitObjectBatch, PlaceHitObjectBatch, FlipHitObjects, + SwapLanes, MoveHitObjects, AddHitsound, RemoveHitsound, diff --git a/Quaver.Shared/Screens/Edit/Actions/HitObjects/Swap/EditorActionSwapLanes.cs b/Quaver.Shared/Screens/Edit/Actions/HitObjects/Swap/EditorActionSwapLanes.cs new file mode 100644 index 0000000000..66ae6af59a --- /dev/null +++ b/Quaver.Shared/Screens/Edit/Actions/HitObjects/Swap/EditorActionSwapLanes.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using Quaver.API.Maps; +using Quaver.API.Maps.Structures; + +namespace Quaver.Shared.Screens.Edit.Actions.HitObjects.Swap +{ + public class EditorActionSwapLanes : IEditorAction + { + /// + /// + /// + public EditorActionType Type { get; } = EditorActionType.SwapLanes; + + /// + /// + private EditorActionManager ActionManager { get; } + + /// + /// + private Qua WorkingMap { get; } + + /// + /// + private List HitObjects { get; } + + private int SwapLane1 { get; } + private int SwapLane2 { get; } + + /// + /// + /// + /// + /// + /// + /// + public EditorActionSwapLanes(EditorActionManager actionManager, Qua workingMap, List hitObjects, + int swapLane1, int swapLane2) + { + ActionManager = actionManager; + WorkingMap = workingMap; + HitObjects = hitObjects; + SwapLane1 = swapLane1; + SwapLane2 = swapLane2; + } + + /// + /// + /// + public void Perform() + { + foreach (var h in HitObjects) + { + if (h.Lane == SwapLane1) + h.Lane = SwapLane2; + else if (h.Lane == SwapLane2) + h.Lane = SwapLane1; + } + + ActionManager.TriggerEvent(EditorActionType.SwapLanes, new EditorLanesSwappedEventArgs(HitObjects, SwapLane1, SwapLane2)); + } + + /// + /// + /// + public void Undo() => Perform(); + } +} \ No newline at end of file diff --git a/Quaver.Shared/Screens/Edit/Actions/HitObjects/Swap/EditorLanesSwappedEventArgs.cs b/Quaver.Shared/Screens/Edit/Actions/HitObjects/Swap/EditorLanesSwappedEventArgs.cs new file mode 100644 index 0000000000..b1614ad953 --- /dev/null +++ b/Quaver.Shared/Screens/Edit/Actions/HitObjects/Swap/EditorLanesSwappedEventArgs.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using Quaver.API.Maps.Structures; + +namespace Quaver.Shared.Screens.Edit.Actions.HitObjects.Swap +{ + public class EditorLanesSwappedEventArgs : EventArgs + { + public List HitObjects { get; } + + private int SwapLane1 { get; } + private int SwapLane2 { get; } + + public EditorLanesSwappedEventArgs(List hitObjects, int swapLane1, int swapLane2) + { + HitObjects = hitObjects; + SwapLane1 = swapLane1; + SwapLane2 = swapLane2; + } + } +} \ No newline at end of file From beb14520b286669dc7ba9f9438c13013f13991c4 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 2 Mar 2024 20:13:08 +0800 Subject: [PATCH 069/249] Add options for inverting scrolling in editor --- Quaver.Shared/Config/ConfigManager.cs | 6 ++++++ Quaver.Shared/Screens/Options/OptionsMenu.cs | 3 ++- Quaver.Shared/Screens/Settings/SettingsDialog.cs | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index f4e3961697..4515557276 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -771,6 +771,11 @@ public static class ConfigManager /// internal static Bindable KeyEditorIncreaseAudioRate { get; private set; } + /// + /// Whether scrolling in editor is inverted. + /// + internal static Bindable EditorInvertScrolling { get; private set; } + /// /// internal static Bindable KeyScreenshot { get; private set; } @@ -999,6 +1004,7 @@ private static void ReadConfigFile() KeyEditorPausePlay = ReadValue(@"KeyEditorPausePlay", Keys.Space, data); KeyEditorDecreaseAudioRate = ReadValue(@"KeyEditorDecreaseAudioRate", Keys.OemMinus, data); KeyEditorIncreaseAudioRate = ReadValue(@"KeyEditorIncreaseAudioRate", Keys.OemPlus, data); + EditorInvertScrolling = ReadValue(@"EditorInvertScrolling", false, data); EditorEnableHitsounds = ReadValue(@"EditorEnableHitsounds", true, data); EditorEnableKeysounds = ReadValue(@"EditorEnableKeysounds", true, data); EditorBeatSnapColorType = ReadValue(@"EditorBeatSnapColorType", EditorBeatSnapColor.Default, data); diff --git a/Quaver.Shared/Screens/Options/OptionsMenu.cs b/Quaver.Shared/Screens/Options/OptionsMenu.cs index c0054c7213..35c1a5b49c 100644 --- a/Quaver.Shared/Screens/Options/OptionsMenu.cs +++ b/Quaver.Shared/Screens/Options/OptionsMenu.cs @@ -380,7 +380,8 @@ private void CreateSections() { new OptionsItemKeybind(containerRect, "Pause/Play Track", ConfigManager.KeyEditorPausePlay), new OptionsItemKeybind(containerRect, "Decrease Playback Rate", ConfigManager.KeyEditorDecreaseAudioRate), - new OptionsItemKeybind(containerRect, "Increase Playback Rate", ConfigManager.KeyEditorIncreaseAudioRate) + new OptionsItemKeybind(containerRect, "Increase Playback Rate", ConfigManager.KeyEditorIncreaseAudioRate), + new OptionsItemCheckbox(containerRect, "Invert Scrolling", ConfigManager.EditorInvertScrolling) }), new OptionsSubcategory("Misc", new List() { diff --git a/Quaver.Shared/Screens/Settings/SettingsDialog.cs b/Quaver.Shared/Screens/Settings/SettingsDialog.cs index 094f80e498..266820ee9d 100644 --- a/Quaver.Shared/Screens/Settings/SettingsDialog.cs +++ b/Quaver.Shared/Screens/Settings/SettingsDialog.cs @@ -446,7 +446,8 @@ private void CreateSections() new SettingsKeybind(this, "Interface - Navigate Down", ConfigManager.KeyNavigateDown), new SettingsKeybind(this, "Editor - Pause/Play Track", ConfigManager.KeyEditorPausePlay), new SettingsKeybind(this, "Editor - Decrease Audio Playback Rate", ConfigManager.KeyEditorDecreaseAudioRate), - new SettingsKeybind(this, "Editor - Increase Audio Playback Rate", ConfigManager.KeyEditorIncreaseAudioRate) + new SettingsKeybind(this, "Editor - Increase Audio Playback Rate", ConfigManager.KeyEditorIncreaseAudioRate), + new SettingsBool(this, "Editor - Invert Scrolling", ConfigManager.EditorInvertScrolling) }), // Misc new SettingsSection(this, FontAwesome.Get(FontAwesomeIcon.fa_question_sign), "Miscellaneous", new List From c745512080fb18f31fc52fc8022f3208064c152a Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 2 Mar 2024 13:27:39 +0800 Subject: [PATCH 070/249] Add triggering keybind (alt + lane number) for swapping lanes --- .../Edit/Actions/EditorActionManager.cs | 11 ++++++ Quaver.Shared/Screens/Edit/EditScreen.cs | 39 ++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Edit/Actions/EditorActionManager.cs b/Quaver.Shared/Screens/Edit/Actions/EditorActionManager.cs index bc52fd107c..06de184dec 100644 --- a/Quaver.Shared/Screens/Edit/Actions/EditorActionManager.cs +++ b/Quaver.Shared/Screens/Edit/Actions/EditorActionManager.cs @@ -20,6 +20,7 @@ using Quaver.Shared.Screens.Edit.Actions.HitObjects.Resize; using Quaver.Shared.Screens.Edit.Actions.HitObjects.Resnap; using Quaver.Shared.Screens.Edit.Actions.HitObjects.Reverse; +using Quaver.Shared.Screens.Edit.Actions.HitObjects.Swap; using Quaver.Shared.Screens.Edit.Actions.Hitsounds.Add; using Quaver.Shared.Screens.Edit.Actions.Hitsounds.Remove; using Quaver.Shared.Screens.Edit.Actions.Layers.Colors; @@ -116,6 +117,12 @@ public class EditorActionManager : IDisposable /// public event EventHandler HitObjectsFlipped; + + /// + /// Event invoked when a batch of hitobjects have been swapped between 2 lanes + /// + public event EventHandler LanesSwapped; + /// /// Event invoked when a batch of hitobjects have been reversed /// @@ -647,6 +654,9 @@ public void TriggerEvent(EditorActionType type, EventArgs args) case EditorActionType.FlipHitObjects: HitObjectsFlipped?.Invoke(this, (EditorHitObjectsFlippedEventArgs)args); break; + case EditorActionType.SwapLanes: + LanesSwapped?.Invoke(this, (EditorLanesSwappedEventArgs)args); + break; case EditorActionType.MoveHitObjects: HitObjectsMoved?.Invoke(this, (EditorHitObjectsMovedEventArgs)args); break; @@ -756,6 +766,7 @@ public void Dispose() HitObjectBatchRemoved = null; HitObjectBatchPlaced = null; HitObjectsFlipped = null; + LanesSwapped = null; HitObjectsMoved = null; HitsoundAdded = null; HitsoundRemoved = null; diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index 2f753ba9e6..f9b4fb178b 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -29,6 +29,7 @@ using Quaver.Shared.Screens.Edit.Actions.HitObjects.Flip; using Quaver.Shared.Screens.Edit.Actions.HitObjects.PlaceBatch; using Quaver.Shared.Screens.Edit.Actions.HitObjects.Resnap; +using Quaver.Shared.Screens.Edit.Actions.HitObjects.Swap; using Quaver.Shared.Screens.Edit.Dialogs; using Quaver.Shared.Screens.Edit.Dialogs.Metadata; using Quaver.Shared.Screens.Edit.Input; @@ -482,6 +483,7 @@ private void HandleInput() HandlePlaybackRateChanges(); HandleTemporaryHitObjectPlacement(); HandleCtrlInput(); + HandleAltInput(); HandleKeyPressDelete(); HandleKeyPressEscape(); HandleKeyPressF1(); @@ -718,6 +720,27 @@ private void HandlePlaybackRateChanges() ChangeAudioPlaybackRate(Direction.Forward); } + /// + /// + private void HandleAltInput() + { + if (!KeyboardManager.IsAltDown()) + return; + var swapLane1 = -1; + var swapLane2 = -1; + // Clever way of handing key input with num keys since the enum values are 1 after each other. + for (var i = 0; i < WorkingMap.GetKeyCount(); i++) + { + if (KeyboardManager.IsUniqueKeyPress(Keys.D1 + i)) + swapLane2 = i; + else if (KeyboardManager.CurrentState.IsKeyDown(Keys.D1 + i)) + swapLane1 = i; + if (swapLane1 == -1 || swapLane2 == -1) continue; + SwapSelectedObjects(swapLane1 + 1, swapLane2 + 1); // 1-based + break; + } + } + /// /// private void HandleCtrlInput() @@ -805,7 +828,7 @@ private void HandleTemporaryHitObjectPlacement() { if (!LiveMapping.Value) return; - + if (KeyboardManager.IsAltDown()) return; // Swapping lanes, not placing objects // Clever way of handing key input with num keys since the enum values are 1 after each other. for (var i = 0; i < WorkingMap.GetKeyCount(); i++) { @@ -1199,6 +1222,20 @@ public void FlipSelectedObjects() ActionManager.Perform(new EditorActionFlipHitObjects(ActionManager, WorkingMap, new List(SelectedHitObjects.Value))); } + /// + /// Swap selected objects' lanes (2 of 4/7 lanes only) + /// + public void SwapSelectedObjects(int swapLane1, int swapLane2) + { + if (SelectedHitObjects.Value.Count == 0) + return; + + ActionManager.Perform(new EditorActionSwapLanes(ActionManager, WorkingMap, + new List( + SelectedHitObjects.Value.Where(h => h.Lane == swapLane1 || h.Lane == swapLane2)), + swapLane1, swapLane2)); + } + /// /// Highlights notes and goes to a specific timestamp. /// Acceptable inputs: From f6bbcce8dc745904298dff874b116a71e870741e Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 2 Mar 2024 13:29:13 +0800 Subject: [PATCH 071/249] Add event subscription and unsubscription for relevant UI updates for swapping lanes --- .../Screens/Edit/UI/Panels/EditorPanelDetails.cs | 10 ++++++++++ .../Screens/Edit/UI/Playfield/EditorPlayfield.cs | 16 ++++++++++++++++ .../UI/Playfield/Seek/EditorDifficultySeekBar.cs | 5 +++++ .../Screens/Edit/UI/Preview/EditorMapPreview.cs | 4 ++++ 4 files changed, 35 insertions(+) diff --git a/Quaver.Shared/Screens/Edit/UI/Panels/EditorPanelDetails.cs b/Quaver.Shared/Screens/Edit/UI/Panels/EditorPanelDetails.cs index 3527cf2ef0..141a972ea9 100644 --- a/Quaver.Shared/Screens/Edit/UI/Panels/EditorPanelDetails.cs +++ b/Quaver.Shared/Screens/Edit/UI/Panels/EditorPanelDetails.cs @@ -15,6 +15,7 @@ using Quaver.Shared.Screens.Edit.Actions.HitObjects.Resize; using Quaver.Shared.Screens.Edit.Actions.HitObjects.Resnap; using Quaver.Shared.Screens.Edit.Actions.HitObjects.Reverse; +using Quaver.Shared.Screens.Edit.Actions.HitObjects.Swap; using Wobble.Audio.Tracks; using Wobble.Bindables; using Wobble.Graphics; @@ -99,6 +100,7 @@ public EditorPanelDetails(Qua workingMap, BindableInt beatSnap, IAudioTrack trac ActionManager.HitObjectBatchPlaced += OnHitObjectBatchPlaced; ActionManager.HitObjectBatchRemoved += OnHitObjectBatchRemoved; ActionManager.HitObjectsFlipped += OnHitObjectsFlipped; + ActionManager.LanesSwapped += OnLanesSwapped; ActionManager.HitObjectsReversed += OnHitObjectsReversed; ActionManager.HitObjectsMoved += OnHitObjectsMoved; ActionManager.HitObjectsResnapped += OnHitObjectsResnapped; @@ -120,6 +122,7 @@ public override void Destroy() ActionManager.HitObjectBatchPlaced -= OnHitObjectBatchPlaced; ActionManager.HitObjectBatchRemoved -= OnHitObjectBatchRemoved; ActionManager.HitObjectsFlipped -= OnHitObjectsFlipped; + ActionManager.LanesSwapped -= OnLanesSwapped; ActionManager.HitObjectsReversed -= OnHitObjectsReversed; ActionManager.HitObjectsMoved -= OnHitObjectsMoved; ActionManager.HitObjectsResnapped -= OnHitObjectsResnapped; @@ -298,6 +301,13 @@ private void OnTrackRateChanged(object sender, TrackRateChangedEventArgs e) /// private void OnHitObjectsFlipped(object sender, EditorHitObjectsFlippedEventArgs e) => UpdateObjects(); + /// + /// + /// + /// + /// + private void OnLanesSwapped(object sender, EditorLanesSwappedEventArgs e) => UpdateObjects(); + /// /// /// diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs index 8379a25d97..8b85d370af 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs @@ -25,6 +25,7 @@ using Quaver.Shared.Screens.Edit.Actions.HitObjects.Resize; using Quaver.Shared.Screens.Edit.Actions.HitObjects.Resnap; using Quaver.Shared.Screens.Edit.Actions.HitObjects.Reverse; +using Quaver.Shared.Screens.Edit.Actions.HitObjects.Swap; using Quaver.Shared.Screens.Edit.Actions.Timing.Add; using Quaver.Shared.Screens.Edit.Actions.Timing.AddBatch; using Quaver.Shared.Screens.Edit.Actions.Timing.ChangeBpm; @@ -358,6 +359,7 @@ public EditorPlayfield(Qua map, EditorActionManager manager, Bindable ActionManager.HitObjectBatchRemoved += OnHitObjectBatchRemoved; ActionManager.HitObjectBatchPlaced += OnHitObjectBatchPlaced; ActionManager.HitObjectsFlipped += OnHitObjectsFlipped; + ActionManager.LanesSwapped += OnLanesSwapped; ActionManager.HitObjectsReversed += OnHitObjectsReversed; ActionManager.HitObjectsMoved += OnHitObjectsMoved; ActionManager.HitObjectsResnapped += OnHitObjectsResnapped; @@ -465,6 +467,7 @@ public override void Destroy() ActionManager.HitObjectBatchRemoved -= OnHitObjectBatchRemoved; ActionManager.HitObjectBatchPlaced -= OnHitObjectBatchPlaced; ActionManager.HitObjectsFlipped -= OnHitObjectsFlipped; + ActionManager.LanesSwapped -= OnLanesSwapped; ActionManager.HitObjectsReversed -= OnHitObjectsReversed; ActionManager.HitObjectsMoved -= OnHitObjectsMoved; ActionManager.HitObjectsResnapped -= OnHitObjectsResnapped; @@ -990,6 +993,19 @@ private void OnHitObjectsFlipped(object sender, EditorHitObjectsFlippedEventArgs RefreshHitObjectBatch(e.HitObjects); } + /// + /// + /// + /// + /// + private void OnLanesSwapped(object sender, EditorLanesSwappedEventArgs e) + { + if (IsUneditable) + return; + + RefreshHitObjectBatch(e.HitObjects); + } + /// /// /// diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Seek/EditorDifficultySeekBar.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Seek/EditorDifficultySeekBar.cs index d270d63de6..2bb8d58126 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Seek/EditorDifficultySeekBar.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Seek/EditorDifficultySeekBar.cs @@ -12,6 +12,7 @@ using Quaver.Shared.Screens.Edit.Actions.HitObjects.RemoveBatch; using Quaver.Shared.Screens.Edit.Actions.HitObjects.Resize; using Quaver.Shared.Screens.Edit.Actions.HitObjects.Reverse; +using Quaver.Shared.Screens.Edit.Actions.HitObjects.Swap; using Wobble.Audio.Tracks; using Wobble.Graphics; @@ -30,6 +31,7 @@ public EditorDifficultySeekBar(EditorActionManager actionManager, Qua map, ModId ActionManager.HitObjectRemoved += OnHitObjectRemoved; ActionManager.HitObjectsMoved += OnHitObjectsMoved; ActionManager.HitObjectsFlipped += OnHitObjectsFlipped; + ActionManager.LanesSwapped += OnLanesSwapped; ActionManager.HitObjectsReversed += OnHitObjectsReversed; ActionManager.HitObjectBatchPlaced += OnHitObjectBatchPlaced; ActionManager.HitObjectBatchRemoved += OnHitObjectBatchRemoved; @@ -45,6 +47,7 @@ public override void Destroy() ActionManager.HitObjectRemoved -= OnHitObjectRemoved; ActionManager.HitObjectsMoved -= OnHitObjectsMoved; ActionManager.HitObjectsFlipped -= OnHitObjectsFlipped; + ActionManager.LanesSwapped -= OnLanesSwapped; ActionManager.HitObjectsReversed -= OnHitObjectsReversed; ActionManager.HitObjectBatchPlaced -= OnHitObjectBatchPlaced; ActionManager.HitObjectBatchRemoved -= OnHitObjectBatchRemoved; @@ -62,6 +65,8 @@ public override void Destroy() private void OnHitObjectsFlipped(object sender, EditorHitObjectsFlippedEventArgs e) => Refresh(); + private void OnLanesSwapped(object sender, EditorLanesSwappedEventArgs e) => Refresh(); + private void OnHitObjectsReversed(object sender, EditorHitObjectsReversedEventArgs e) => Refresh(); private void OnHitObjectBatchPlaced(object sender, EditorHitObjectBatchPlacedEventArgs e) => Refresh(); diff --git a/Quaver.Shared/Screens/Edit/UI/Preview/EditorMapPreview.cs b/Quaver.Shared/Screens/Edit/UI/Preview/EditorMapPreview.cs index bc8fc0d150..b787ebd4b6 100644 --- a/Quaver.Shared/Screens/Edit/UI/Preview/EditorMapPreview.cs +++ b/Quaver.Shared/Screens/Edit/UI/Preview/EditorMapPreview.cs @@ -9,6 +9,7 @@ using Quaver.Shared.Screens.Edit.Actions.HitObjects.Resize; using Quaver.Shared.Screens.Edit.Actions.HitObjects.Resnap; using Quaver.Shared.Screens.Edit.Actions.HitObjects.Reverse; +using Quaver.Shared.Screens.Edit.Actions.HitObjects.Swap; using Quaver.Shared.Screens.Edit.Actions.SV.Add; using Quaver.Shared.Screens.Edit.Actions.SV.AddBatch; using Quaver.Shared.Screens.Edit.Actions.SV.ChangeMultiplierBatch; @@ -58,6 +59,7 @@ public EditorMapPreview(EditorActionManager manager, Bindable isPlayTestin ActionManager.HitObjectRemoved += OnHitObjectRemoved; ActionManager.HitObjectsMoved += OnHitObjectsMoved; ActionManager.HitObjectsFlipped += OnHitObjectsFlipped; + ActionManager.LanesSwapped += OnLanesSwapped; ActionManager.HitObjectsReversed += OnHitObjectsReversed; ActionManager.HitObjectBatchPlaced += OnHitObjectBatchPlaced; ActionManager.HitObjectBatchRemoved += OnHitObjectBatchRemoved; @@ -136,6 +138,8 @@ public void Refresh() private void OnHitObjectsFlipped(object sender, EditorHitObjectsFlippedEventArgs e) => Refresh(); + private void OnLanesSwapped(object sender, EditorLanesSwappedEventArgs e) => Refresh(); + private void OnHitObjectsReversed(object sender, EditorHitObjectsReversedEventArgs e) => Refresh(); private void OnHitObjectBatchPlaced(object sender, EditorHitObjectBatchPlacedEventArgs e) => Refresh(); From 18044c620dffedb881e1c6ddcdb66623dbb1d7ca Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 2 Mar 2024 13:38:54 +0800 Subject: [PATCH 072/249] Add menu item for swapping lanes of objects --- .../Screens/Edit/UI/Menu/EditorFileMenuBar.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs index eb43fdc723..faa9977c84 100644 --- a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs +++ b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs @@ -223,6 +223,21 @@ private void CreateEditSection() if (ImGui.MenuItem("Flip Objects", "CTRL + H", false, Screen.SelectedHitObjects.Value.Count > 0)) Screen.FlipSelectedObjects(); + if (ImGui.BeginMenu("Swap Lanes of Objects", Screen.SelectedHitObjects.Value.Count > 0)) + { + for (var i = 1; i <= Screen.WorkingMap.GetKeyCount(); i++) + { + for (var j = i + 1; j <= Screen.WorkingMap.GetKeyCount(); j++) + { + if (ImGui.MenuItem($"Lane {i} and {j}", $"ALT + {i} + {j}")) + { + Screen.SwapSelectedObjects(i, j); + } + } + } + ImGui.EndMenu(); + } + if (ImGui.BeginMenu($"Move Objects To Layer", Screen.SelectedHitObjects.Value.Count > 0)) { if (ImGui.MenuItem("Default Layer", "")) From 964da51ec205da2972687abef822883731d5fc1b Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sun, 3 Mar 2024 22:56:33 +0800 Subject: [PATCH 073/249] Improve menu layout for lane swapping --- .../Screens/Edit/UI/Menu/EditorFileMenuBar.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs index faa9977c84..c95d062302 100644 --- a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs +++ b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs @@ -227,12 +227,18 @@ private void CreateEditSection() { for (var i = 1; i <= Screen.WorkingMap.GetKeyCount(); i++) { - for (var j = i + 1; j <= Screen.WorkingMap.GetKeyCount(); j++) - { - if (ImGui.MenuItem($"Lane {i} and {j}", $"ALT + {i} + {j}")) + if (ImGui.BeginMenu($"Lane {i}")) + { + for (var j = 1; j <= Screen.WorkingMap.GetKeyCount(); j++) { - Screen.SwapSelectedObjects(i, j); + if (i == j) continue; + if (ImGui.MenuItem($"Lane {j}", $"ALT + {i} + {j}")) + { + Screen.SwapSelectedObjects(i, j); + } } + + ImGui.EndMenu(); } } ImGui.EndMenu(); From d513150476e1f4be0420330a3bc77d07871870f7 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 2 Mar 2024 20:43:51 +0800 Subject: [PATCH 074/249] Add invert scrolling logic for editor --- Quaver.Shared/Screens/Edit/EditScreen.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index 2f753ba9e6..14fe05e0db 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -666,7 +666,7 @@ private void HandleSeekingBackwards() { var leftPressed = KeyboardManager.IsUniqueKeyPress(Keys.Left); - if (!leftPressed && MouseManager.CurrentState.ScrollWheelValue <= MouseManager.PreviousState.ScrollWheelValue) + if (!leftPressed && !MouseManager.IsScrollingDown(ConfigManager.EditorInvertScrolling.Value)) return; if (Track == null || Track.IsDisposed || (!CanSeek() && !leftPressed)) @@ -681,7 +681,7 @@ private void HandleSeekingForwards() { var rightPressed = KeyboardManager.IsUniqueKeyPress(Keys.Right); - if (!rightPressed && MouseManager.CurrentState.ScrollWheelValue >= MouseManager.PreviousState.ScrollWheelValue) + if (!rightPressed && !MouseManager.IsScrollingUp(ConfigManager.EditorInvertScrolling.Value)) return; if (Track == null || Track.IsDisposed || (!CanSeek() && !rightPressed)) @@ -694,8 +694,8 @@ private void HandleSeekingForwards() /// private void HandleBeatSnapChanges() { - var scrolledForward = MouseManager.CurrentState.ScrollWheelValue > MouseManager.PreviousState.ScrollWheelValue; - var scrolledBackward = MouseManager.CurrentState.ScrollWheelValue < MouseManager.PreviousState.ScrollWheelValue; + var scrolledForward = MouseManager.IsScrollingUp(ConfigManager.EditorInvertScrolling.Value); + var scrolledBackward = MouseManager.IsScrollingDown(ConfigManager.EditorInvertScrolling.Value); if (KeyboardManager.IsCtrlDown() && (scrolledForward || KeyboardManager.IsUniqueKeyPress(Keys.Down))) ChangeBeatSnap(Direction.Forward); From db9c67727b74b250d807603a0808810273c25634 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Mon, 4 Mar 2024 11:12:04 +0800 Subject: [PATCH 075/249] Use new mouse scrolling api for DifficultySeekBar --- Quaver.Shared/Graphics/Graphs/DifficultySeekBar.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Graphics/Graphs/DifficultySeekBar.cs b/Quaver.Shared/Graphics/Graphs/DifficultySeekBar.cs index d8647189ba..4419c2877e 100644 --- a/Quaver.Shared/Graphics/Graphs/DifficultySeekBar.cs +++ b/Quaver.Shared/Graphics/Graphs/DifficultySeekBar.cs @@ -10,6 +10,7 @@ using Quaver.API.Maps.Processors.Difficulty.Rulesets.Keys.Structures; using Quaver.Shared.Assets; using Quaver.Shared.Audio; +using Quaver.Shared.Config; using Quaver.Shared.Helpers; using Quaver.Shared.Screens; using Wobble; @@ -125,9 +126,9 @@ public override void Update(GameTime gameTime) (game?.CurrentScreen?.Type == QuaverScreenType.Select || game?.CurrentScreen?.Type == QuaverScreenType.Multiplayer)) { - if (MouseManager.CurrentState.ScrollWheelValue < MouseManager.PreviousState.ScrollWheelValue) + if (MouseManager.IsScrollingUp(ConfigManager.EditorInvertScrolling.Value)) SeekInDirection(Direction.Forward); - else if (MouseManager.CurrentState.ScrollWheelValue > MouseManager.PreviousState.ScrollWheelValue) + else if (MouseManager.IsScrollingDown(ConfigManager.EditorInvertScrolling.Value)) SeekInDirection(Direction.Backward); } From 37020e15f4ab22b944639959319e053877647bbb Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Mon, 4 Mar 2024 11:19:08 +0800 Subject: [PATCH 076/249] Use the new scrolling api in volume control --- Quaver.Shared/Graphics/Overlays/Volume/VolumeControl.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Quaver.Shared/Graphics/Overlays/Volume/VolumeControl.cs b/Quaver.Shared/Graphics/Overlays/Volume/VolumeControl.cs index 075b8287f2..22c6c9a61a 100644 --- a/Quaver.Shared/Graphics/Overlays/Volume/VolumeControl.cs +++ b/Quaver.Shared/Graphics/Overlays/Volume/VolumeControl.cs @@ -124,14 +124,14 @@ private void HandleInput(GameTime gameTime) // Activate the volume control box. if (KeyboardManager.IsUniqueKeyPress(Keys.Up)|| KeyboardManager.IsUniqueKeyPress(Keys.Down) || KeyboardManager.IsUniqueKeyPress(Keys.Left) || KeyboardManager.IsUniqueKeyPress(Keys.Right) - || MouseManager.CurrentState.ScrollWheelValue != MouseManager.PreviousState.ScrollWheelValue) + || MouseManager.IsScrolling) { TimeInactive = 0; } - if (MouseManager.CurrentState.ScrollWheelValue > MouseManager.PreviousState.ScrollWheelValue) + if (MouseManager.IsScrollingUp(ConfigManager.EditorInvertScrolling.Value)) UpdateVolume(5); - else if (MouseManager.CurrentState.ScrollWheelValue < MouseManager.PreviousState.ScrollWheelValue) + else if (MouseManager.IsScrollingDown(ConfigManager.EditorInvertScrolling.Value)) UpdateVolume(-5); if (KeyboardManager.CurrentState.IsKeyDown(Keys.Right)) From f85e6810b4d56abb6b10b5f87464a296b6487d23 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Mon, 4 Mar 2024 11:21:24 +0800 Subject: [PATCH 077/249] Rename ConfigManager.EditorInvertScrolling to ConfigManager.InvertScrolling --- Quaver.Shared/Config/ConfigManager.cs | 4 ++-- Quaver.Shared/Graphics/Graphs/DifficultySeekBar.cs | 4 ++-- Quaver.Shared/Graphics/Overlays/Volume/VolumeControl.cs | 4 ++-- Quaver.Shared/Screens/Edit/EditScreen.cs | 8 ++++---- Quaver.Shared/Screens/Options/OptionsMenu.cs | 2 +- Quaver.Shared/Screens/Settings/SettingsDialog.cs | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index 4515557276..367d42dc50 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -774,7 +774,7 @@ public static class ConfigManager /// /// Whether scrolling in editor is inverted. /// - internal static Bindable EditorInvertScrolling { get; private set; } + internal static Bindable InvertScrolling { get; private set; } /// /// @@ -1004,7 +1004,7 @@ private static void ReadConfigFile() KeyEditorPausePlay = ReadValue(@"KeyEditorPausePlay", Keys.Space, data); KeyEditorDecreaseAudioRate = ReadValue(@"KeyEditorDecreaseAudioRate", Keys.OemMinus, data); KeyEditorIncreaseAudioRate = ReadValue(@"KeyEditorIncreaseAudioRate", Keys.OemPlus, data); - EditorInvertScrolling = ReadValue(@"EditorInvertScrolling", false, data); + InvertScrolling = ReadValue(@"EditorInvertScrolling", false, data); EditorEnableHitsounds = ReadValue(@"EditorEnableHitsounds", true, data); EditorEnableKeysounds = ReadValue(@"EditorEnableKeysounds", true, data); EditorBeatSnapColorType = ReadValue(@"EditorBeatSnapColorType", EditorBeatSnapColor.Default, data); diff --git a/Quaver.Shared/Graphics/Graphs/DifficultySeekBar.cs b/Quaver.Shared/Graphics/Graphs/DifficultySeekBar.cs index 4419c2877e..24ec89159b 100644 --- a/Quaver.Shared/Graphics/Graphs/DifficultySeekBar.cs +++ b/Quaver.Shared/Graphics/Graphs/DifficultySeekBar.cs @@ -126,9 +126,9 @@ public override void Update(GameTime gameTime) (game?.CurrentScreen?.Type == QuaverScreenType.Select || game?.CurrentScreen?.Type == QuaverScreenType.Multiplayer)) { - if (MouseManager.IsScrollingUp(ConfigManager.EditorInvertScrolling.Value)) + if (MouseManager.IsScrollingUp(ConfigManager.InvertScrolling.Value)) SeekInDirection(Direction.Forward); - else if (MouseManager.IsScrollingDown(ConfigManager.EditorInvertScrolling.Value)) + else if (MouseManager.IsScrollingDown(ConfigManager.InvertScrolling.Value)) SeekInDirection(Direction.Backward); } diff --git a/Quaver.Shared/Graphics/Overlays/Volume/VolumeControl.cs b/Quaver.Shared/Graphics/Overlays/Volume/VolumeControl.cs index 22c6c9a61a..5d358fc4dc 100644 --- a/Quaver.Shared/Graphics/Overlays/Volume/VolumeControl.cs +++ b/Quaver.Shared/Graphics/Overlays/Volume/VolumeControl.cs @@ -129,9 +129,9 @@ private void HandleInput(GameTime gameTime) TimeInactive = 0; } - if (MouseManager.IsScrollingUp(ConfigManager.EditorInvertScrolling.Value)) + if (MouseManager.IsScrollingUp(ConfigManager.InvertScrolling.Value)) UpdateVolume(5); - else if (MouseManager.IsScrollingDown(ConfigManager.EditorInvertScrolling.Value)) + else if (MouseManager.IsScrollingDown(ConfigManager.InvertScrolling.Value)) UpdateVolume(-5); if (KeyboardManager.CurrentState.IsKeyDown(Keys.Right)) diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index 14fe05e0db..2785639e4d 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -666,7 +666,7 @@ private void HandleSeekingBackwards() { var leftPressed = KeyboardManager.IsUniqueKeyPress(Keys.Left); - if (!leftPressed && !MouseManager.IsScrollingDown(ConfigManager.EditorInvertScrolling.Value)) + if (!leftPressed && !MouseManager.IsScrollingDown(ConfigManager.InvertScrolling.Value)) return; if (Track == null || Track.IsDisposed || (!CanSeek() && !leftPressed)) @@ -681,7 +681,7 @@ private void HandleSeekingForwards() { var rightPressed = KeyboardManager.IsUniqueKeyPress(Keys.Right); - if (!rightPressed && !MouseManager.IsScrollingUp(ConfigManager.EditorInvertScrolling.Value)) + if (!rightPressed && !MouseManager.IsScrollingUp(ConfigManager.InvertScrolling.Value)) return; if (Track == null || Track.IsDisposed || (!CanSeek() && !rightPressed)) @@ -694,8 +694,8 @@ private void HandleSeekingForwards() /// private void HandleBeatSnapChanges() { - var scrolledForward = MouseManager.IsScrollingUp(ConfigManager.EditorInvertScrolling.Value); - var scrolledBackward = MouseManager.IsScrollingDown(ConfigManager.EditorInvertScrolling.Value); + var scrolledForward = MouseManager.IsScrollingUp(ConfigManager.InvertScrolling.Value); + var scrolledBackward = MouseManager.IsScrollingDown(ConfigManager.InvertScrolling.Value); if (KeyboardManager.IsCtrlDown() && (scrolledForward || KeyboardManager.IsUniqueKeyPress(Keys.Down))) ChangeBeatSnap(Direction.Forward); diff --git a/Quaver.Shared/Screens/Options/OptionsMenu.cs b/Quaver.Shared/Screens/Options/OptionsMenu.cs index 35c1a5b49c..bf7587e810 100644 --- a/Quaver.Shared/Screens/Options/OptionsMenu.cs +++ b/Quaver.Shared/Screens/Options/OptionsMenu.cs @@ -381,7 +381,7 @@ private void CreateSections() new OptionsItemKeybind(containerRect, "Pause/Play Track", ConfigManager.KeyEditorPausePlay), new OptionsItemKeybind(containerRect, "Decrease Playback Rate", ConfigManager.KeyEditorDecreaseAudioRate), new OptionsItemKeybind(containerRect, "Increase Playback Rate", ConfigManager.KeyEditorIncreaseAudioRate), - new OptionsItemCheckbox(containerRect, "Invert Scrolling", ConfigManager.EditorInvertScrolling) + new OptionsItemCheckbox(containerRect, "Invert Scrolling", ConfigManager.InvertScrolling) }), new OptionsSubcategory("Misc", new List() { diff --git a/Quaver.Shared/Screens/Settings/SettingsDialog.cs b/Quaver.Shared/Screens/Settings/SettingsDialog.cs index 266820ee9d..207072c53a 100644 --- a/Quaver.Shared/Screens/Settings/SettingsDialog.cs +++ b/Quaver.Shared/Screens/Settings/SettingsDialog.cs @@ -447,7 +447,7 @@ private void CreateSections() new SettingsKeybind(this, "Editor - Pause/Play Track", ConfigManager.KeyEditorPausePlay), new SettingsKeybind(this, "Editor - Decrease Audio Playback Rate", ConfigManager.KeyEditorDecreaseAudioRate), new SettingsKeybind(this, "Editor - Increase Audio Playback Rate", ConfigManager.KeyEditorIncreaseAudioRate), - new SettingsBool(this, "Editor - Invert Scrolling", ConfigManager.EditorInvertScrolling) + new SettingsBool(this, "Editor - Invert Scrolling", ConfigManager.InvertScrolling) }), // Misc new SettingsSection(this, FontAwesome.Get(FontAwesomeIcon.fa_question_sign), "Miscellaneous", new List From 9943f9e3cecb5bf0212fe0bfec485964bcba0284 Mon Sep 17 00:00:00 2001 From: Emik Date: Mon, 4 Mar 2024 05:27:59 +0100 Subject: [PATCH 078/249] Truncate when details in rich presence is too long --- Quaver.Shared/Discord/DiscordRpc.cs | 1 + Quaver.Shared/Helpers/RichPresenceHelper.cs | 31 ++++++++++++++++----- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/Quaver.Shared/Discord/DiscordRpc.cs b/Quaver.Shared/Discord/DiscordRpc.cs index c18ecc96ae..8ba1c34813 100644 --- a/Quaver.Shared/Discord/DiscordRpc.cs +++ b/Quaver.Shared/Discord/DiscordRpc.cs @@ -32,6 +32,7 @@ public struct EventHandlers [System.Serializable] public struct RichPresence { + public const int MaxStateLength = 128, MaxDetailsLength = 128; public string State; /* max 128 bytes */ public string Details; /* max 128 bytes */ public long StartTimestamp; diff --git a/Quaver.Shared/Helpers/RichPresenceHelper.cs b/Quaver.Shared/Helpers/RichPresenceHelper.cs index 85ecef96fb..2e63f890a6 100644 --- a/Quaver.Shared/Helpers/RichPresenceHelper.cs +++ b/Quaver.Shared/Helpers/RichPresenceHelper.cs @@ -2,7 +2,9 @@ using Quaver.Shared.Online; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Text; +using Steamworks; namespace Quaver.Shared.Helpers { @@ -14,15 +16,30 @@ public static class RichPresenceHelper /// note: it might be a good idea to set your images and other properties in DiscordHelper.Presence first before calling this function, instead of /// calling rpc twice. /// - /// state of the game - /// details of the game. - public static void UpdateRichPresence(string state, string details) { - // might be a good idea to check k_cchMaxRichPresenceValueLength and the return value in the future. - SteamManager.SetRichPresence("State", state); - SteamManager.SetRichPresence("Details", details); + /// State of the game. It is assumed to be small enough to fit in the rich presence. + /// Details of the game. Strings too long gets automatically truncated. + public static void UpdateRichPresence(string state, string details) + { + static string Truncate(string s, int length) => s.Length >= length ? $"{s[..(length - 1)]}…" : s; + + // It is assumed that state is already sufficiently small enough to fit in both the rich presences. + // The largest case is multiplayer lobby names, which is 50 characters long. Combine that with the + // interpolation, and it ends up at most 61 characters long. + Debug.Assert( + state.Length <= DiscordRpc.RichPresence.MaxStateLength && + state.Length <= Constants.k_cchMaxRichPresenceKeyLength, + $"State is too long for rich presence: {state.Length} chars" + ); - DiscordHelper.Presence.Details = details; + SteamManager.SetRichPresence("State", state); DiscordHelper.Presence.State = state; + + // This would be potentially problematic as the source specifies 'bytes', and .NET using UTF-16 could have + // made things complicated. Fortunately however, through direct testing it appears that they consider + // UTF-16 codepoints to be 1 byte long, even if the underlying memory layout would suggest otherwise. + SteamManager.SetRichPresence("Details", Truncate(details, Constants.k_cchMaxRichPresenceValueLength)); + DiscordHelper.Presence.Details = Truncate(details, DiscordRpc.RichPresence.MaxDetailsLength); + DiscordRpc.UpdatePresence(ref DiscordHelper.Presence); } } From 5af1ea7e373f3a0c3182c342ae59b6e748f45bf9 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 5 Mar 2024 10:50:19 +0800 Subject: [PATCH 079/249] Make dropdowns scrollable --- .../Graphics/Form/Dropdowns/Dropdown.cs | 20 +++++++++++++++---- Quaver.Shared/Screens/QuaverScreen.cs | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Quaver.Shared/Graphics/Form/Dropdowns/Dropdown.cs b/Quaver.Shared/Graphics/Form/Dropdowns/Dropdown.cs index 51972344a9..22d4882a4c 100644 --- a/Quaver.Shared/Graphics/Form/Dropdowns/Dropdown.cs +++ b/Quaver.Shared/Graphics/Form/Dropdowns/Dropdown.cs @@ -100,6 +100,21 @@ public class Dropdown : ImageButton /// /// private int MaxHeight { get; } + + /// + /// The height of the dropdown when opened + /// + public int OpenHeight + { + get + { + var height = (int) Height * Options.Count; + + if (MaxHeight != 0 && height >= MaxHeight) + height = MaxHeight; + return height; + } + } /// /// @@ -270,10 +285,7 @@ public void Open(int time = 500) ItemContainer.ClearAnimations(); - var height = (int) Height * Options.Count; - - if (MaxHeight != 0 && height >= MaxHeight) - height = MaxHeight; + var height = OpenHeight; ItemContainer.ChangeHeightTo(height, Easing.OutQuint, time); diff --git a/Quaver.Shared/Screens/QuaverScreen.cs b/Quaver.Shared/Screens/QuaverScreen.cs index e83173898a..0d57002fdf 100644 --- a/Quaver.Shared/Screens/QuaverScreen.cs +++ b/Quaver.Shared/Screens/QuaverScreen.cs @@ -140,7 +140,7 @@ public void ActivateRightClickOptions(RightClickOptions rco) WindowManager.Width - ActiveRightClickOptions.Width); var y = MathHelper.Clamp(MouseManager.CurrentState.Y, 0, - WindowManager.Height - ActiveRightClickOptions.Items.Count * ActiveRightClickOptions.Items.First().Height - 60); + WindowManager.Height - ActiveRightClickOptions.OpenHeight - 60); ActiveRightClickOptions.Position = new ScalableVector2(x, y); From a8f6075da22a32119d800500f25f033afb07c819 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 5 Mar 2024 10:51:04 +0800 Subject: [PATCH 080/249] Allow RightClickOptions to override maxWidth and maxHeight --- .../Graphics/Form/Dropdowns/RightClick/RightClickOptions.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Graphics/Form/Dropdowns/RightClick/RightClickOptions.cs b/Quaver.Shared/Graphics/Form/Dropdowns/RightClick/RightClickOptions.cs index f40ca52433..c02f5a3875 100644 --- a/Quaver.Shared/Graphics/Form/Dropdowns/RightClick/RightClickOptions.cs +++ b/Quaver.Shared/Graphics/Form/Dropdowns/RightClick/RightClickOptions.cs @@ -19,7 +19,11 @@ public class RightClickOptions : Dropdown /// /// /// - public RightClickOptions(Dictionary options, ScalableVector2 size, int fontSize) : base(options.Keys.ToList(), size, fontSize, Color.White) + /// + /// + public RightClickOptions(Dictionary options, ScalableVector2 size, int fontSize, + int maxWidth = 0, int maxHeight = 0) + : base(options.Keys.ToList(), size, fontSize, Color.White, 0, maxWidth, maxHeight) { Options = options; From 29998b9480aa761a946e0c2e10be39e5c18bbf7f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Mar 2024 22:14:19 +0000 Subject: [PATCH 081/249] Bump SixLabors.ImageSharp from 2.1.3 to 2.1.7 in /Quaver.Shared Bumps [SixLabors.ImageSharp](https://github.com/SixLabors/ImageSharp) from 2.1.3 to 2.1.7. - [Release notes](https://github.com/SixLabors/ImageSharp/releases) - [Commits](https://github.com/SixLabors/ImageSharp/compare/v2.1.3...v2.1.7) --- updated-dependencies: - dependency-name: SixLabors.ImageSharp dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Quaver.Shared/Quaver.Shared.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Quaver.Shared.csproj b/Quaver.Shared/Quaver.Shared.csproj index afb2529d9d..a826856820 100644 --- a/Quaver.Shared/Quaver.Shared.csproj +++ b/Quaver.Shared/Quaver.Shared.csproj @@ -130,7 +130,7 @@ - + From 3b4ad29509592e62396456a24b7999351670900b Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 9 Mar 2024 10:50:28 +0800 Subject: [PATCH 082/249] Make displaying pausing warning toggleable --- Quaver.Shared/Config/ConfigManager.cs | 5 +++++ Quaver.Shared/Screens/Gameplay/GameplayScreen.cs | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index f4e3961697..df7c9813b8 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -609,6 +609,10 @@ public static class ConfigManager /// /// internal static Bindable DisplayNotificationsInGameplay { get; private set; } + + /// + /// + internal static Bindable DisplayPauseWarning { get; private set; } /// /// @@ -1071,6 +1075,7 @@ private static void ReadConfigFile() DisplayGameplayOverlay = ReadValue(@"DisplayGameplayOverlay", true, data); EnableHighProcessPriority = ReadValue(@"EnableHighProcessPriority", false, data); DisplayNotificationsInGameplay = ReadValue(@"DisplayNotificationsInGameplay", false, data); + DisplayPauseWarning = ReadValue(@"DisplayPauseWarning", true, data); TournamentPlayer2Skin = ReadValue(@"TournamentPlayer2Skin", "", data); ResultGraph = ReadValue(@"ResultGraph", ResultGraphs.Deviance, data); AudioOutputDevice = ReadValue(@"AudioOutputDevice", "Default", data); diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs index 84323be73f..8384ccdaf1 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs @@ -782,8 +782,9 @@ public void Pause(GameTime gameTime = null, bool removeMods = true) // Add the pause mod to their score. if (!ModManager.IsActivated(ModIdentifier.Paused) && Ruleset.ScoreProcessor.TotalJudgementCount > 0) { - NotificationManager.Show(NotificationLevel.Warning, "WARNING! Your score will not be submitted due to pausing " + - "during gameplay!", null, true); + if (ConfigManager.DisplayPauseWarning.Value) + NotificationManager.Show(NotificationLevel.Warning, "WARNING! Your score will not be submitted due to pausing " + + "during gameplay!", null, true); ModManager.AddMod(ModIdentifier.Paused); ReplayCapturer.Replay.Mods |= ModIdentifier.Paused; From d38140f49154738de77480d2f4cbcb037abce9fd Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 16 Mar 2024 22:52:19 +0800 Subject: [PATCH 083/249] Rename Bindable name of InvertScrolling --- Quaver.Shared/Config/ConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index 367d42dc50..e2c376550b 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -1004,7 +1004,7 @@ private static void ReadConfigFile() KeyEditorPausePlay = ReadValue(@"KeyEditorPausePlay", Keys.Space, data); KeyEditorDecreaseAudioRate = ReadValue(@"KeyEditorDecreaseAudioRate", Keys.OemMinus, data); KeyEditorIncreaseAudioRate = ReadValue(@"KeyEditorIncreaseAudioRate", Keys.OemPlus, data); - InvertScrolling = ReadValue(@"EditorInvertScrolling", false, data); + InvertScrolling = ReadValue(@"InvertScrolling", false, data); EditorEnableHitsounds = ReadValue(@"EditorEnableHitsounds", true, data); EditorEnableKeysounds = ReadValue(@"EditorEnableKeysounds", true, data); EditorBeatSnapColorType = ReadValue(@"EditorBeatSnapColorType", EditorBeatSnapColor.Default, data); From 4d28c255d024a70e1263ad10ba4f430a72089760 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 16 Mar 2024 22:52:41 +0800 Subject: [PATCH 084/249] Bind ConfigManager.InvertScrolling to ScrollContainer.GlobalInvertedScrolling --- Quaver.Shared/Config/ConfigManager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index e2c376550b..fa22921fdf 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -28,6 +28,7 @@ using Quaver.Shared.Screens.Selection.UI.Leaderboard; using Wobble; using Wobble.Bindables; +using Wobble.Graphics.Sprites; using Wobble.Input; using Wobble.Logging; @@ -1084,6 +1085,9 @@ private static void ReadConfigFile() PrioritizedMapDifficulty7K = ReadInt(@"PrioritizedMapDifficulty7K", 0, 0, 1000, data); PrioritizedGameMode = ReadValue(@"PrioritizedGameMode", (GameMode)0, data); + // Bind global inverted scrolling so ScrollContainers get InvertScrolling setting too + ScrollContainer.GlobalInvertedScrolling = InvertScrolling; + // Have to do this manually. if (string.IsNullOrEmpty(Username.Value)) Username.Value = "Player"; From b86c1bc9e546d48041017c39a09c2792897f27e7 Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Fri, 22 Mar 2024 23:14:53 +0200 Subject: [PATCH 085/249] New command to switch servers --- Quaver.Shared/Online/Chat/QuaverBot.cs | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Quaver.Shared/Online/Chat/QuaverBot.cs b/Quaver.Shared/Online/Chat/QuaverBot.cs index addd76c3e7..9220a406dd 100644 --- a/Quaver.Shared/Online/Chat/QuaverBot.cs +++ b/Quaver.Shared/Online/Chat/QuaverBot.cs @@ -8,6 +8,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; +using Quaver.Server.Client; using Quaver.Server.Client.Structures; using Quaver.Server.Common.Helpers; using Quaver.Shared.Graphics.Notifications; @@ -42,6 +44,36 @@ public static void HandleClientSideCommands(ChatMessage message) case "help": ExecuteHelpCommand(); break; + case "server": + ExecuteServerCommand(args); + break; + } + } + + /// + /// Executes the `/server {server}` command. + /// + /// Switches the server endpoint + /// + public async static void ExecuteServerCommand(string[] args) + { + if (args.Length > 1) + { + var server = args[1]; + + NotificationManager.Show(NotificationLevel.Info, $"Switching to {server}"); + + OnlineClient.SERVER_ENDPOINT = server; + + OnlineManager.Client?.Disconnect(); + + await Task.Delay(1000); + + OnlineManager.Login(); + } + else + { + SendMessage(OnlineChat.Instance.ActiveChannel.Value, $"No server provided!"); } } From 184ce26b2c1145328034c3f31d6fe7c208d2b54e Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Sat, 23 Mar 2024 17:30:07 +0200 Subject: [PATCH 086/249] Load menu border bg footer --- Quaver.Shared/Skinning/Menus/SkinMenuBorder.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Skinning/Menus/SkinMenuBorder.cs b/Quaver.Shared/Skinning/Menus/SkinMenuBorder.cs index db25184e63..62ac73d829 100644 --- a/Quaver.Shared/Skinning/Menus/SkinMenuBorder.cs +++ b/Quaver.Shared/Skinning/Menus/SkinMenuBorder.cs @@ -18,6 +18,8 @@ public class SkinMenuBorder : SkinMenu public Texture2D Background { get; private set; } + public Texture2D BackgroundFooter { get; private set; } + public SkinMenuBorder(SkinStore store, IniData config) : base(store, config) { } @@ -42,6 +44,7 @@ protected override void ReadConfig() protected override void LoadElements() { Background = LoadSkinElement("MenuBorder", "menu-border-background.png"); + BackgroundFooter = LoadSkinElement("MenuBorder", "menu-border-background-footer.png"); } } -} \ No newline at end of file +} From c38219ec28f2934bdd9c04e624e87b4d0bf4178b Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Sat, 23 Mar 2024 17:30:52 +0200 Subject: [PATCH 087/249] Use bg footer if available --- Quaver.Shared/Graphics/Menu/Border/MenuBorder.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Graphics/Menu/Border/MenuBorder.cs b/Quaver.Shared/Graphics/Menu/Border/MenuBorder.cs index d0fbc3c553..87e1d81fd6 100644 --- a/Quaver.Shared/Graphics/Menu/Border/MenuBorder.cs +++ b/Quaver.Shared/Graphics/Menu/Border/MenuBorder.cs @@ -52,7 +52,15 @@ public MenuBorder(MenuBorderType type, List leftAligned = null, List Date: Sat, 23 Mar 2024 17:43:37 +0200 Subject: [PATCH 088/249] Load status-various --- Quaver.Shared/Skinning/Menus/SkinMenuSongSelect.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Skinning/Menus/SkinMenuSongSelect.cs b/Quaver.Shared/Skinning/Menus/SkinMenuSongSelect.cs index f2fc99032b..21f8176e97 100644 --- a/Quaver.Shared/Skinning/Menus/SkinMenuSongSelect.cs +++ b/Quaver.Shared/Skinning/Menus/SkinMenuSongSelect.cs @@ -86,6 +86,8 @@ public class SkinMenuSongSelect : SkinMenu public Texture2D StatusStepmania { get; private set; } + public Texture2D StatusVarious { get; private set; } + #endregion #region LEADERBOARD @@ -189,8 +191,9 @@ protected override void LoadElements() StatusRanked = LoadSkinElement(folder, "status-ranked.png"); StatusOsu = LoadSkinElement(folder, "status-osu.png"); StatusStepmania = LoadSkinElement(folder, "status-sm.png"); + StatusVarious = LoadSkinElement(folder, "status-various.png"); LeaderboardPanel = LoadSkinElement(folder, "leaderboard-panel.png"); PersonalBestPanel = LoadSkinElement(folder, "personalbest-panel.png"); } } -} \ No newline at end of file +} From 4bc38676886ea7ae519f049293d3adfdacf0351a Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Sat, 23 Mar 2024 17:44:42 +0200 Subject: [PATCH 089/249] Load StatusVarious if available --- .../Selection/UI/Playlists/DrawablePlaylistContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Screens/Selection/UI/Playlists/DrawablePlaylistContainer.cs b/Quaver.Shared/Screens/Selection/UI/Playlists/DrawablePlaylistContainer.cs index 8587a70ab9..819c4b9fd9 100644 --- a/Quaver.Shared/Screens/Selection/UI/Playlists/DrawablePlaylistContainer.cs +++ b/Quaver.Shared/Screens/Selection/UI/Playlists/DrawablePlaylistContainer.cs @@ -377,7 +377,7 @@ private Texture2D GetRankedStatusImage() } if (Playlist.Item.Maps.Any(o => o.RankedStatus != Playlist.Item.Maps.First().RankedStatus)) - return UserInterface.StatusVarious; + return SkinManager.Skin?.SongSelect?.StatusVarious ?? UserInterface.StatusVarious; switch (Playlist.Item.Maps.Max(x => x.RankedStatus)) { @@ -454,4 +454,4 @@ private void AnimateSprites(float fade, int time) ClearAnimations(); } } -} \ No newline at end of file +} From 3d4dde1933d61d71ef0e5acfa714e3d392d5bfd8 Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Sat, 23 Mar 2024 19:09:25 +0200 Subject: [PATCH 090/249] Add select filter panel --- Quaver.Shared/Skinning/Menus/SkinMenuSongSelect.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Skinning/Menus/SkinMenuSongSelect.cs b/Quaver.Shared/Skinning/Menus/SkinMenuSongSelect.cs index f2fc99032b..a8f9bae91d 100644 --- a/Quaver.Shared/Skinning/Menus/SkinMenuSongSelect.cs +++ b/Quaver.Shared/Skinning/Menus/SkinMenuSongSelect.cs @@ -54,6 +54,10 @@ public class SkinMenuSongSelect : SkinMenu public Color? PersonalBestRankColor { get; private set; } + public Texture2D SelectFilterPanelRight { get; private set; } + + public Texture2D SelectFilterPanelLeft { get; private set; } + #region MAPSET public Texture2D MapsetSelected { get; private set; } @@ -191,6 +195,8 @@ protected override void LoadElements() StatusStepmania = LoadSkinElement(folder, "status-sm.png"); LeaderboardPanel = LoadSkinElement(folder, "leaderboard-panel.png"); PersonalBestPanel = LoadSkinElement(folder, "personalbest-panel.png"); + SelectFilterPanelRight = LoadSkinElement(folder, "select-filter-panel-right.png"); + SelectFilterPanelLeft = LoadSkinElement(folder, "select-filter-panel-left.png"); } } -} \ No newline at end of file +} From 5206c821e12d8b850a88eddaa97d381928c4bd65 Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Sat, 23 Mar 2024 19:10:32 +0200 Subject: [PATCH 091/249] Make SelectFilterPanel skinnable --- .../Selection/UI/FilterPanel/SelectFilterPanel.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Selection/UI/FilterPanel/SelectFilterPanel.cs b/Quaver.Shared/Screens/Selection/UI/FilterPanel/SelectFilterPanel.cs index adbd56003a..ba873e4a7e 100644 --- a/Quaver.Shared/Screens/Selection/UI/FilterPanel/SelectFilterPanel.cs +++ b/Quaver.Shared/Screens/Selection/UI/FilterPanel/SelectFilterPanel.cs @@ -16,6 +16,7 @@ using Quaver.Shared.Screens.Selection.UI.FilterPanel.Dropdowns; using Quaver.Shared.Screens.Selection.UI.FilterPanel.MapInformation; using Quaver.Shared.Screens.Selection.UI.FilterPanel.Search; +using Quaver.Shared.Skinning; using Wobble.Bindables; using Wobble.Graphics; using Wobble.Graphics.Sprites; @@ -107,7 +108,13 @@ public SelectFilterPanel(Bindable> availableMapsets, Bindable Date: Sat, 23 Mar 2024 19:11:49 +0200 Subject: [PATCH 092/249] Make Gradient sprite skinnable --- .../Screens/Selection/UI/FilterPanel/FilterPanelBanner.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Screens/Selection/UI/FilterPanel/FilterPanelBanner.cs b/Quaver.Shared/Screens/Selection/UI/FilterPanel/FilterPanelBanner.cs index ea49da9245..599366f310 100644 --- a/Quaver.Shared/Screens/Selection/UI/FilterPanel/FilterPanelBanner.cs +++ b/Quaver.Shared/Screens/Selection/UI/FilterPanel/FilterPanelBanner.cs @@ -4,6 +4,7 @@ using Quaver.Shared.Database.Maps; using Quaver.Shared.Graphics.Backgrounds; using Quaver.Shared.Helpers; +using Quaver.Shared.Skinning; using Wobble.Bindables; using Wobble.Graphics; using Wobble.Graphics.Animations; @@ -82,7 +83,7 @@ private void CreateGradientSprite() { Parent = this, Size = Size, - Image = UserInterface.FilterPanelGradient + Image = SkinManager.Skin?.SongSelect?.SelectFilterPanelLeft ?? UserInterface.FilterPanelGradient }; } @@ -135,4 +136,4 @@ private void OnBackgroundLoaded(object sender, BackgroundLoadedEventArgs e) FadeIn(); } } -} \ No newline at end of file +} From 2bc6539f44af2423becf4caebb23e59548594370 Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Sat, 23 Mar 2024 19:16:55 +0200 Subject: [PATCH 093/249] Load logo-background --- Quaver.Shared/Skinning/Menus/SkinMenuMain.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Skinning/Menus/SkinMenuMain.cs b/Quaver.Shared/Skinning/Menus/SkinMenuMain.cs index 998bc8e428..3b02f0346f 100644 --- a/Quaver.Shared/Skinning/Menus/SkinMenuMain.cs +++ b/Quaver.Shared/Skinning/Menus/SkinMenuMain.cs @@ -47,6 +47,8 @@ public class SkinMenuMain : SkinMenu public Color? JukeboxProgressBarColor { get; private set; } + public Texture2D LogoBackground { get; private set; } + public SkinMenuMain(SkinStore store, IniData config) : base(store, config) { } @@ -104,6 +106,7 @@ protected override void LoadElements() NewsPanel = LoadSkinElement(folder, "news-panel.png"); JukeboxOverlay = LoadSkinElement(folder, "jukebox-overlay.png"); NoteVisualizer = LoadSkinElement(folder, "note-visualizer.png"); + LogoBackground = LoadSkinElement(folder, "logo-background.png"); } } -} \ No newline at end of file +} From ac51cd21e1552f8f622c90a12f2926090a5a1e47 Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Sat, 23 Mar 2024 19:17:38 +0200 Subject: [PATCH 094/249] Make menu logo background skinnable --- Quaver.Shared/Screens/Main/MainMenuScreenView.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Screens/Main/MainMenuScreenView.cs b/Quaver.Shared/Screens/Main/MainMenuScreenView.cs index f7e2b5ca27..6077428d7b 100644 --- a/Quaver.Shared/Screens/Main/MainMenuScreenView.cs +++ b/Quaver.Shared/Screens/Main/MainMenuScreenView.cs @@ -122,7 +122,7 @@ private void CreateBackground() /// private void CreateMenuLogoBackground() { - var tex = UserInterface.MenuLogoBackground; + var tex = SkinManager.Skin?.MainMenu?.LogoBackground ?? UserInterface.MenuLogoBackground; MenuLogoBackground = new Sprite() { @@ -288,4 +288,4 @@ private void OnScreenExiting(object sender, ScreenExitingEventArgs e) News.MoveToX(News.Width + 50, Easing.OutQuint, animTime); } } -} \ No newline at end of file +} From af8694bc1bf0d76653632e0825114ede2e77c4c5 Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Sat, 23 Mar 2024 20:31:02 +0200 Subject: [PATCH 095/249] Parse alpha if found --- Quaver.Shared/Config/ConfigHelper.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Quaver.Shared/Config/ConfigHelper.cs b/Quaver.Shared/Config/ConfigHelper.cs index ad244523b8..8c23e91b8d 100644 --- a/Quaver.Shared/Config/ConfigHelper.cs +++ b/Quaver.Shared/Config/ConfigHelper.cs @@ -215,6 +215,12 @@ internal static Color ReadColor(Color defaultColor, string newVal) try { var colorSplit = newVal.Split(','); + + if (colorSplit.Length > 3) + { + return new Color(byte.Parse(colorSplit[0]), byte.Parse(colorSplit[1]), byte.Parse(colorSplit[2]), byte.Parse(colorSplit[3])); + } + return new Color(byte.Parse(colorSplit[0]), byte.Parse(colorSplit[1]), byte.Parse(colorSplit[2])); } catch (Exception) From 04124da4583cd083834bd25b14c60cc5caa6d419 Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Sat, 23 Mar 2024 22:10:47 +0200 Subject: [PATCH 096/249] Rewrite command to use onDisconnection event --- Quaver.Shared/Online/Chat/QuaverBot.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Quaver.Shared/Online/Chat/QuaverBot.cs b/Quaver.Shared/Online/Chat/QuaverBot.cs index 9220a406dd..486699d191 100644 --- a/Quaver.Shared/Online/Chat/QuaverBot.cs +++ b/Quaver.Shared/Online/Chat/QuaverBot.cs @@ -55,9 +55,9 @@ public static void HandleClientSideCommands(ChatMessage message) /// /// Switches the server endpoint /// - public async static void ExecuteServerCommand(string[] args) + public static void ExecuteServerCommand(string[] args) { - if (args.Length > 1) + if (args.Length > 1 && OnlineManager.Client != null) { var server = args[1]; @@ -65,11 +65,13 @@ public async static void ExecuteServerCommand(string[] args) OnlineClient.SERVER_ENDPOINT = server; - OnlineManager.Client?.Disconnect(); + OnlineManager.Client.Disconnect(); - await Task.Delay(1000); - - OnlineManager.Login(); + OnlineManager.Client.OnDisconnection += async (sender, eventArgs) => + { + await Task.Delay(500); + OnlineManager.Login(); + }; } else { From f21ba54be9106903cce49025aa02b4caee84141f Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Sat, 23 Mar 2024 22:21:31 +0200 Subject: [PATCH 097/249] Unsub from event --- Quaver.Shared/Online/Chat/QuaverBot.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Quaver.Shared/Online/Chat/QuaverBot.cs b/Quaver.Shared/Online/Chat/QuaverBot.cs index 486699d191..3199618c01 100644 --- a/Quaver.Shared/Online/Chat/QuaverBot.cs +++ b/Quaver.Shared/Online/Chat/QuaverBot.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Threading.Tasks; using Quaver.Server.Client; +using Quaver.Server.Client.Events.Disconnnection; using Quaver.Server.Client.Structures; using Quaver.Server.Common.Helpers; using Quaver.Shared.Graphics.Notifications; @@ -67,11 +68,7 @@ public static void ExecuteServerCommand(string[] args) OnlineManager.Client.Disconnect(); - OnlineManager.Client.OnDisconnection += async (sender, eventArgs) => - { - await Task.Delay(500); - OnlineManager.Login(); - }; + OnlineManager.Client.OnDisconnection += OnDisconnection; } else { @@ -128,5 +125,12 @@ public static void SendMutedMessage() $"{OnlineManager.Self.GetMuteTimeLeftString()}.\n" + "You won't be able to speak 'till then. Check your profile for more details."); } + + private static async void OnDisconnection(object sender, DisconnectedEventArgs e) + { + await Task.Delay(500); + OnlineManager.Login(); + OnlineManager.Client.OnDisconnection -= OnDisconnection; + } } } From f96259f787e19c0589d03f8f5badaeb1c47bed2e Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Sat, 23 Mar 2024 22:23:34 +0200 Subject: [PATCH 098/249] Move OnDisconnection event above Disconnect --- Quaver.Shared/Online/Chat/QuaverBot.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Online/Chat/QuaverBot.cs b/Quaver.Shared/Online/Chat/QuaverBot.cs index 3199618c01..1fa4dd3614 100644 --- a/Quaver.Shared/Online/Chat/QuaverBot.cs +++ b/Quaver.Shared/Online/Chat/QuaverBot.cs @@ -66,9 +66,9 @@ public static void ExecuteServerCommand(string[] args) OnlineClient.SERVER_ENDPOINT = server; - OnlineManager.Client.Disconnect(); - OnlineManager.Client.OnDisconnection += OnDisconnection; + + OnlineManager.Client.Disconnect(); } else { From 531a1d8f6222b20b230ab30ad814dba09f010a1f Mon Sep 17 00:00:00 2001 From: AiAe Date: Sun, 24 Mar 2024 12:35:38 +0200 Subject: [PATCH 099/249] Update Client dll --- Quaver.Shared/Quaver.Server.Client.dll | Bin 90112 -> 87552 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Quaver.Shared/Quaver.Server.Client.dll b/Quaver.Shared/Quaver.Server.Client.dll index f17016395fccf7e68ceee44a9d3cbc98b56ad8b1..0bdb642865271a2a6f471b58e153a16f74702964 100644 GIT binary patch literal 87552 zcmeFad0bUh-!{J1I&hc|h|Hjz1B#%6;DED%nu_y?rfHyA*&rrXX14}qmX)PtWoimG zX?OEH2hBE`O*X@w4HivScQ*g7@3q%Ca^QKM55M<$-}j&Q@xHtE_qx98yRNnO+S56E z1C!=%R4%1d2>$!~Z>1i_QvQ_?|7)m0+LQUHN8KOss_$Vv@m1f zC|#)3WUZ9jQ43B->_uFIrBZQ>jWcc$Wc>WsfIM{h=Ta3HNmKd%(WRv#;otkGX*ERx z^!uf3h_?Ugw2FrI?5SGKY@7S9xL2w0R{3!7ajkg4ip48efcKw>@gm1{wd3|TgIrOt zyn6W}2pk*6$Q7N6WvhQBn6855i%+jYLm8{Wn7IvRwre-CRT)E%Y*+k~u^APr4xXe` zlS`}MN;c^bq^-e(h4f5Y6Xq%y3AZ&s+7)+Q7W?M+~u>OzBZA!>M7E?BQfcUZqqdj1!6FzQepb_Mt)1p7f0 zKT+Wa*50pe9sN2`hbe$xVz3`X@e>t(VD0_d*3qv6bvRz(mlW&=QT#-OA6R?8wsrLD zKwT*Pl7syqil3<3Z&M1>z%d%w1I^y@$! zPFPsKj9@>A;wLKnz}ow@t)pKD>TsHaUzcD%h~g(I{J`4#wXLIH2kLMZgkx z;Rn{t(VD0_d*3qv6b-0GZFE7{+qWFmlKd|a=+}WdT*KklJJ=7R_=yTXu=aj!>*&{kI$Xoymmlm0 zQT#-OA6R?8wsrLDKpn2(@GA)RgD8HY!Vj#yU)wtRb)XK{aQO8J_Jb&XqQVcXy# zj(#1e!!;a!1A_e^il3i9{UC~;sPF@8@7K1DejTX8H5`5=!F~|MPgMAUwfAdVN52l#;TjIV z(qKP`;wLKnz}ow@t)pKD>TnH*-^gG;h~g(I{J`4#wXLIH2kMIGH!9ca=+}WdT*KivHrNlM_=yTXu=aj!>*&{kI$XoyH!j!@qWFml zKd|p&f@;qV(D><3Z&M1>z%d%w1I^y@$! zuHo>T5bOt0{6vKxSbM*=b@b~%9j@Wp&f@;qaRr><3Z&M1>z%d%w1I^y@$!uHo>T66^<2{6vKxSbM*=b@b~% z9j@Wp&f@;qaRt><3Z& zM1>z%d%w1I^y@$!uHo>T5$p$1{6vKxSbM*=b@b~%9j@Wp&f@;qWUD_Jb&XqQVcXy4%cw_%?b8{D1M^C53Idk+dBGnpbpn?_#GGQ2T}Y)g&$aZzqWPs z>p&f@;qaRq><3Z&M1>z%d%w1I^y@$!uHo>T7wiX7{6vKxSbM*=b@b~%9j@Wa z=+}WdT*KjaLa-l1@e>t(VD0_d*3qv6b-0GZuOiqFqWFmlKd|p&f@;qY4+><3Z&M1>z%d%w1I^y@&~H2N(H_Jb&XqQVcXyk;eSx_Gsm+N>SgPX_?QJ~}G zou~+vl&K<7PD7_pYeOe_M@X{)?gKi-tG^O)eot>VjH8AB`y#uhSjhhqdqyT<&1HQ< z)dq|g_X(X``R?je**3&TS7z^|sxZx28Hl2KO~R(=f+#vX^B+z&CgO@EDFx=^ ze(s2*t@5BCav#nqTE4=Mj7Z*^>&5bBQL252sTT?G%3~jF37o%doDERVG=`}gsZxXB=T$mQyNI5O9EqI1-4Bfake=0 zvI%UYK;i>Q^tOrgR*CZ@k#7@Nvp_|&BnoT-+cuC`EeWi6WY3eF*DvgFL#qFhOrgzS zL;FiYs_}ZHyQ;``mVGo+-9r+6Z2}wJUsCOpL_eFr#t$TVNus|b7C3LSIri24MaRc_ zZWGKwpx}2IRk2NA>jx5_NMfK(VEYFW=SpIbOJ1uuWk52NEAiVu(#(`v(%) zGOD3Af$bkibeF_1NpvoZR9JKVbDkgiH)yCQ#1raqd%`^7)pv-)aN7~4CD2i*bcByL zTyIQEAmNh4F_OTB!mQXt^_$XDiPbSDe*H4habF*a73*B+R=DN1W5WFSdktArt%W4# ztih|0A%D4#flD=-Ms}}}OfJqKy>fBb_Nv|?{f)Lhm`T6S3ej=@$#G#K{d$e)I2Ce5 zFp+-C>gOea8#*R1k%7cBlE9r86PU=APHOIWo&6Ck_7r1=ve)z?b~v%nHej6YL-r6e#jOyKiP zAW<&~d_rOZ6BbBZC5dutf*JECLaO&lX0|QCocS}=uSo(|4~`3S=1+uF-z1siYzZdK zpQ*k_5_4?{Ce5D+seV^7IQz0QOxw}SE0Q_hc7|#5XR7mLr=M?2Fm3)sNcH1Vg8M?Q zCFaeasopG!6Kn$07D()qM1@UY+5(9LF~KbnyTYsm5=oL+XcL&WKq5*KxXEAzrY(?& zk;I9TSnhl_Z*}Zf$4hdtt-{O&st(D>PLc!;ZOoiaRDUJ<$)e-P4L+}68vW%VtSlG?4g05~tb(CNz-vR1&Az1ST|)_(c+@+XUt_koZ{=XV?U$ zGmyxXHD4+TS&tar`^d@2`Fzo88~M+Vo(NB*C#pJ3>Z+s;2RJ6%-(g5~s$`Z~E6lb( zQ+=0=;Y^#rYzGn%QnB17Fx!E|ds0y?iBp~X*T8tHACc4wTO&7>HdTFrB+ilq?sHyS zem&k9uW=*4`~``^B>F|7aEbCYq6mqWNEC@kUMR<&;M`a;fPg)~5g0(g)H?zL2$)!w zVji6Y10*nij=*5L$(@gz)8#kAuIg-zGU+dk>yq={IQB2xr(<4Qzc-G3Sniue#eFlH zw7+j|Th|HwuINA=_Eh*`cWLd16Poym3O_Vy@7K1De*a~iaei(>_A6)o_xRVpw|jXb z;+|Ijtd%aYU;n$6F0s{n?r^r3z40^ExKz5U&cXVdgw?Xiu#h#8`#o9ucM`mf@2*-Y zJIQ!ceJ+-(W&WHG5V8Z{ND1jV#N@7$o?NR*A>%k{Et5jVVc<+L0|?mr9f1J^?DdYo z00Q=QM_>Q}d$}VpfPj755g0(gKJ5q$%I&8eSMDmTiwVj72Npf=4X9^3>PlIJI&C~o zBjX9pT_cTgyoV~B3lvU9_^h)U+NrtcVT~h~CmOXZeM1cHxw|LDtK5G;Yfa` z`Y25?x#ys6SNBwtdlmxQD$V4cCvxr@G?fjCUELWx0|=NSM_|zP=%&Z4I0*)5iRp0! z2CZ6Ra-0MMw8YpQfdK@J+YuN*z{nke0R)WC5g0%?-!CwLP~#UEaGOoSHj{tV%W$sg zUX7NK-A(RBtZ-G06JINFtrK4*@dZx2UE&L!c(cT75m%jpZm^N~@l`e!3Y|eQfPiX8 zU;qJKI06F*=*JNlU~DqCqp*~@byb}#C&i24Wy5s{P3|s?z~tUSya||lG2}>0@FiHD zUcDAlcUKjDkSJ($=*F>P0B>~W2n<@S2fA|-4A2t&IsyX->-_=)2$%W=1`sau3k)D! z?iUz9*x(l!K)Av$Fo1BSUtj=XqhDYEVUu5A0O2aXzyQM4et`jm&3=Iaglqf)0|?jp z1qKkV^9u|hZ1D>WAmD1{Y#;^@w)zDI5VrXR1`xLU1qKjq@CyuDO~(#NputYNth38{ zyWC}$H_EcF$^8TqUxkU`0eq8)hyACQ)zX&zrX;(eSU!fg!}yh z0|*cJ1qKiv^a~6iJmeP`KzP_MFo5ugUtj>?QNO?d!ef4c0ffi>0s{z7_yqZ1`JTq=oc73c-k*8fbfi8U;yD+zrX;(bAEvVgy;PN0|+np z1qT1YxL))(V1SCfet`jmm;3?)2rv5u1`uBH3k)E<>K7P5c+D>`Xth~h_a_*j1qO1ycBzHR`B#l|U1WFH z{;JDmX>te3*?WT%50>}}Cmt&Cl}6`o(W%R;=W403zmN=DJr55?xE1npZ<;+j4-+EnGjysz!F{I0|vXswKy_&2S7ex>@w9m z5w4cCF-eID$;;2dV3SU*t`k*;WX~TmjxaSE3WOQ}_(=bfH= z3-suC6UNnzS$-~tpXR?mb>-fQY~cRXmAm^WO^J8q?m0?RL2nCC=k9c}$z6gmyQ=<) zgsTddCLSNFoaK3c$;=)7C_st{{o6mG3?u-;wAtGZLDocDIkmmTbA~LScRFQStR$;-dL`EyBXcaEmz-p`}Z8d_3;4~?! zB7}lmw1t}>@3SbC#yxqhNFsXf^C&(mq~V+Y?mo^nQE75 zc9||qIo{0Cr+ua-VxC0*h!OL~;MEa_41vZP13%aR`DE{i>4L>b_%R)-XF zmtdI`a@Sy)6mn*;ObXd6GL}BO%#!8uz^*503|O)lFq3GUZ5wx!<nY1X=c`4=pr|6- z%eLwr+)8A$qKa1d8maXVNFf&l%cPL|1k0pH54FHviHi;gSR~7%0~8t7RMA=A;Lb=P z_Y0OuA@>iKNg)pimPsKO2g{_82L{WekOu|Jq>u*(%cPKp1k0pHH^O;CC5>jo>~gqW zjwQiM<-fj445jiWwzD$;8r4|6;-sFAlbmqzM2&B z#9)~e@+6Vv92BBX<`rMQpW{`e8Xw4T3tEMbVUgWKs_v3SXw}`a@Pt(1lNOetRrr8~ zrMn8BtFR2K!pA8r!>jNq3d@M92f2)_!e=J%sH%stTuoD$PYxavDdZ`^GAZP#!7?f2 zX(D4e-7aU?syf9cM zg}g*$EKjw|(`0${av&7jL|S39`x~7ujgD?^(pHE58IneyOJ#X<`-+Uds3KcsTP>62 za+A1Pgur~0gsf%~jn9-uM;%EEsz>0lL(OuhALrw9q^sx18RK!xYaR{Jz_a@o6c+R= z>{m2cq)>G_u-jUtveziJ5zABXp&)z4iseg|o?I<0j(mvUiDRJ6nxPKhvy6Nc$R0bZ zOzx?_1V4fg64@h9muFY}ho*XVmj@#vB0+!C{ZtYJgfsD975=LR!INKwM~D13>fdhQ-zvJBr-up$USgE5Ux~Gg9SD{JYq#&QtGdx9ZyO zjv|j*KWWAo4?Z;s9yeje7`M6-?c>!?MVSL5RZQvM#gXc5qHna+7LB>1C`tV>zG_UG zx}f`*2}$bf@pH@E>KIg|sh9ODW0KU`?4JjvsnE#_5U(%#c~Fv?JW-U7azxpXBT8Jq z#NP4hmI;{y)6{+51?cV3@eAO79Y>pbOz1?nx~Al(K4aC{m{qsB5u;5~Kju`ShdTy} zV;$BaNpqx&Ol`vG4XS>U2 z*OZT$kf!<$F2`2=8e1$$<;=Rbz^yLe82;H?w%g`B!K))ede2ao=gCMe>Ltn*gF{gD z2)2e>z2oaV(5=Q}JZb8NyfG8pDzt~_-}nSevILtE$E%i^M=)!*_W!=GTP@2J$IuBf zo<(q!xliOSAp5pP^uqX0ClyJI|HY{zlRlj^0R9fsMJEp%pcbq7Xc(t@l$<>540VP& zlk91-rPwbeceYPjR;jbdUd|PEwpvGa!a!jw)n#N)k*!i2$u3}BEq?iw^s@vzl*YEN6LTET5`{%-zqHzgqS! z*YV-|Ll#jgOzBl*JJ?d|e~`UALUKm0Av?@E zm#!hZ+_GA-dqzvk5Pc!pH*|OFb!7LEh3oZXbz~9xGO`>xMCi-OMv_J9E6A=Ti_#m( zt{Wu|(fVq#Fpe`uUqkj0-DCAuvYR=wPI@QVm+YmJt|NPi?(upT*?r6<=$px6O2i>U z-%0itb6xb^Wb?_o>U*7*WSRPYvZZwQ>Icb2u$OH8DA^|Fy6Y#%emGVfdg!OgJ|xT4 z&yt4wt#&W z>bJ?>W6Qq!0NG-0&wl!SvY}&Soc;BOWLexp2I!B;o?^>l-9+{%`y8nMMfMuUIY=KQ z^D;MBe@@ngeGbuIl8t0;sQ!xVARUJ4ubmtnM(Ll((#b~af0IS9mt*x2vQxNzWAtxi z>)6X!{fE<%xibAX*=uAIv~kIi`T*HP9YS_F$1+K~$%@&_WF79b@H;X%ATl*(}|e>`OY7>qN3lve`PB>_l$)<8&I?d1P~S23b8F=IO3v7qRYm?Ir8V zk~!6ixid<;^u;+@_b0pCvH@fVMoO+q4CNly;%>BE3cUsT>`u=UofBXF2qG@PwZ&mLE`hkBsExz5*jx;POH2wV}Yv6 z1mJ0LlYw=eMGr|4?3ytRxFKZ@aAlXd9<8FfS0Mho#}eSx{gwlFkCSg{W<{?B_K4m9 zJg4wlU}@nF;QW|dfxj2t1$?&fVc_PNM&K(kF9D|vdlUFV^oPLO@Xvr1Io|@;dwu~v z?opkzn&gQ9Mr6kW-4W@)KU~?suOoT`cV`U%-kCiDm>4z|SX4X}xTg3xU~Tk5;M=a# zfQ92%0JphT1K)_e2>3_&6~N)MuLHh2vkv%m@gCs6L+%Ckbw38I40{&1eC(^h4WaJ< z_l*4nxGv&L;K@By&0;2M2&}15HlUw>g2h=Z{rt0*%mh( zZ60F})yc9ieH$-0Jz*iTKc_4PzMClhT$v#)L(`T(iAg;Z_-*`Iz#R5)THLvae_=nL zc9kA3XK$Hl|A2l1M&)nmxV)cI3&rtrdVVftU7*dE?DKdW1zNqzxF|v5`{OS~n_Dxk zl$LY}qvLdrd3&0)akj&|)Spa}F+Y*K8SUpK2~J8DWh?c!x=8#8F(qEc^He9nxlnvc z++(1-id&=Yy8Gwa8HuyapG#>#`>>2>f#HcS0?*8N z1?a4iGgB?xs`>15SfZ>&Ch=*GxrpoHZ1eAU9&^_29acI0Eat5FNBb6faQ1|<^aDX& z{%!u(z_+l4$Hoe-ixhlglHj90!BKq$yJrdBlp|O?RIn43`?SQf2MgXyoXYG$#;Y?$ z-x(ozk`cVqB{(li@JzV)x01nLfu~ieP1qm8GCqU;T*hI~ zorR+k`%?P3i05I;_|zZRNAWKR%;VkJ zmu6*zCiv7vY2vj%*&zO!D0`Gook?8Fk;wM+spnAZ!_g!y9rt_FJcuvjsPBOGCAm$ZQLdneff9y<0Sz7{Y2 zJKO54luWdAw#J1VN$Z}Scw}Zbv?)o+13K&Ltm(D{>A9B2>CIfP5jbP`RM&R>G$t-a zOF5SP<2;46?<5qU&$=#B)!H|vYrnv#TI)`HWyT<6AEWQkv|)%_w`t-?I^k^Q!&y{( zo$M{CqtWt_^a*X+pG`e1V9--W&L+&sa1BLx3RjDX_fvFV?? zIf+%Mos(G29%N?a-0M>{IAi$KF{u(CNINH?7?<+%67Y#$)g(;y%BR?C!}IiI*aK%< zb~@Pt%jS_SwycKibcd#dNXF`a2$maT*n-W6RQabr2B^n(Q2b*AN2SlAx6Du*%2@gzDkl_)`hR^ zcQ_$d4YKTVusF5MvMa{@na~M$QclZyuz1yE*}&-V#Lg-O-*rhZlfe?yRLi2HJ0~Wp zODyw(C8-9>W)*f#Oy+lFQnw5&MHO~)ddVuxO-xm%ST+PKP1RX8ET&&#x;kK4Iar2@ z>+aNjTsS^QLAYNKVRd9F#!S1($20a$_Juc5}Mw|nXm`=~+qZb{bR z0kA@~%(8Bte#Le9`#kHx#C|G8zHCID$$l`gznW^n z4VE>#UQQgSj#&0H*dSHd%jxCyh<6ePt5YmH2sT93S$26=bK+2Sz_NO)ICZyW3F8Zr%G8&Z<$;Y? zUcCMx?yFsclP0J+mTd-`s5V-5Z{(J-c3+!rOyRdtr-gl)tR?;WtL1nhWx3+vR)9J?)Pz8YuQ zX2>1v~8+uc7VouOW|>`}0#N)2@C%G`e?RjEOiEe2brmRYtfJRShi)*>B$$WEtWk9wpQ)4Z0MkK zk}p!>!<@R~z}Be}%l?SCAo*go(z5u-3z9EUcUyK~;)dk)>PyR7z%EtZ;Z85t4Be7^ znVMtSJz$rsjh1~j`^MxA>P5@coEwv`P-=uzS2X9&GkAm)FqY`gI%i{EW2XX`^nd-BbMC(wnY_| zIK6z7|5@_&>J-a#!Dq=^Rh?x+3cgR?rVdy(7i_zVD|PC^V}DP+L5;I42W*F`vFr_x zJ7uSO*s`y|>eTm^eHzm#rC!Nz4w3mYo=z#dRE1@~M`onlsJ2)ZAC-}EliFw5!0a9= zH>>bbPTef9TU3cdpy_+LQ;?o0j$Mvmxan^$uCBx}$7M$|LH4W#^Q|sYliOmc24{ zN6KUBL(9$_cXP@U>SN2^n09;0Q>uwYvfpND^}OnD*@t*B;{{c0*?B{sOL2+2&zuQx2#_mR(nlJ7KlhvdhU%w(R*V$t|&LUzR)dJ$0I8kAQuk&aljv?MeMe z$?mPsBb}XIZAlC#HU^&atf0n%I6C@1w( zb(v-HcxUFI+F;r1n9a}Bm6lZ(<)?nGHd!`o;(*jI)zy}j;T@4f>Ke;l?7K7JD|MZp zG^K;y}Hq|rtCPd zn=Sh;Gfw@WZnf;5yz!}r)gH@MV|)Il{%P5!Vqte!c3h?gyUVggV85$-EbEHbDE?6Q zS@vF*R)4AoEW2^08_&`nvTVfgIjMiCM=VPkuqajQ$1MAL*3N`T{e)%Dl&wgO(hZjF zn0P^ItbW?EpZjl3?W~`*?2d_bsfqdp%LdQ7B{fOEWZAe`_ok-mS1o&V;&q;`dY@&Z zil0i&)NfgKZtr(ev-IU-WM8UP=jAq~X6vnF7pi5`KTqwZU$(3cFS&Hr2Q9mJ%IB#$ z`cKQMr#+R@L*u3s|1MPJ)8bUF#)E6JH>b3u=IL>kJ&0USU1{0XV7>Gz%MMRDoZ4G& zwye|CBdG=YPRkycx;CXyzhGI>RFzhwn=G3&H7c#I{?)P@rs7$@?ljJEcTM-C_R~Er zJ3KiK&!|ROHfM%b1N8BhU5H$MJZeZK_s-^sSa%1U6VVSoY-Pu4zN` z_m*W%$2aFXqReq02sTP*S@!r8w;HX7ShjSsu$h)!GQA*ewBBXebmYe9M=hI-x-ojc zWy>+@G5TxE)`N}JA>$ptgVTTUjMH5#n=#$3%Jcxswoe|ER;H&|R^Z0>;rbNI=7)_) zo2Y9odo0YGI!SM{Y<=jc)G7LY%l3yIPMxY>v+RNhx0{R`NW&4Jnly;gvXj#FO6=_xa zPs^UitSr+hlbk+3ojN{snI30ZIF7NEy3(>;*n7^^t1Rn}nO>!@_P3m-)p`1M%ZkAM zp`WuXb;jD1)w;xGuR?%kQN zLCY@`l)9|p8`G}P*OIMMV-6VNF8qmXSL}8A zTg#q@!*%)>%O2|Iiru3Bw(Ny|acYYWpCW$i!lTFbh`nCNS(Y{S56|^F)v_;!>`vRN zeU^=Km3y}8Jjp7P7j=CppyWVKo zK(HJ1iE4t5# zy;WCQc3tTWH+Fj403wqE6)J^Dh+9_h6+VUNDtVOXO_ zQf||`0_?4{+x0%O=hfA{U9tD*LDQXHt{mUuxkr~+wspK#_vkXqz8LaR+P!+3Wj_u1 z9Bi(`xUKKg3vKR_f#0Xyr%$zP)4-p>sx2Gk`#tS`Ex&eAMm@!+((l)69mZZB(3jiX zVcp_+KyS9}S3C-TK;K|lUlSMmpuX9%5#|@qgZfU(-o~svq#v~GUznAL^i!7A&UVE< ztY5Hf!|XWqu-<1`Uwr%eh(2IhNl~17L^oTehq}`r)rTyL8yXFE*s^cO^oV^-|7O{r zWB%|wrbA|M{nW#lvB&j%hjINL*NYt%F6;NWKEpCuzsL32mdW1pgkEEr>^)EDi!Hmm zcv$R{`bx{5Ebg5Cq~2ng>{(CgI?H6wdP?8!Ftx&$mfoO$cNoXgs9iIU&U>SdvP|Z^ zQ72gTS$|*p(>lYlZ~Eteb$1whc}C~koUG9^dVpoJM$hOGmbKtGdRC9|=WrZ7tEX6Y zhjPU}r)OLC0KV6MPFGmA4|UJ$Q!JDBTb|cvS~jh_EA|C_u4Rk6$Eg?e1(xmd_D+9M zUuxN1-o9X0TXysK!RdSTHp?Cye+<}7md&2!ihW7nZrQ?Faq1=gfMw&dT(K|fCoG$u z6^FMEpSLW@>xz9vzh;@w8>e2;?^^ciFjwrW`V-6E85XBr)n8gB=j7M)_m;^y`8EBk zW!tbPzpnKxXO8Z~p8UFwwCsa|7SBE%Z`tPsTJ6*6mfc@8CjAYaZQ0{Ry2Y|ZeWlM|EnAf}4=r^$=LqhVU?UvHBfMFUu}qGK zWT@lVv)e&^fn{=b`%GVI znVj7|(^p&eJ+{Q>dYfgx;~4u~-(=a{#c{D;=-VxOviKLz7y1FqHsUz?Qa@qY^*D~c z)X!Vi(0fkoA^nfx5HoY3O=ULR}O#rV;P@AYKMwq&+=e$eHX-Ga}eKj;%In*zVX`ee)I z!|$+OX4ymIT0B4Mm6kn+&*4AnTFc(RbG)DQdduF&bG)DQRb;OvTyp|1-8brH^)7YuFX2jJ{tEZ*p-%b>(P*I%ypI( z^mq=e&a#Q)Uru+K-ImQC{}$Li4&xeym`7|*u9P9B(K0zRgqW8slXF(6*>9O#4MWWb z4r4EF^Qp~wFh_3lwPjw+k=y)a*(&71%paC*KrYOL9_P%_bv@!@!%d83xAgeM6K?Pj z2mjWE4?-@&bhT_8auKG7Wsl+5j5K{LdmYDSq#0yc3(f^mro^&{{H|bSmdW)Y+Dx-d zt`E`Xc!#NhWgn-PPOdAF^AKW%yP>< z8}mDu{IX<)?{ejyJ=t97+AWV78e*|Sp2O_s@?m16F27<)-I z57?ZnQL1^uGFhWk^SouTt`<+4dCf9;3pUNX>oE3`Za(qX4GhglH(yw`XJ8cA_m;gm zE9!X zE~8R=n^=d1Ka%r6Y`#ejjlVA(_X{<^>n47A*t(8r9ix%2YNJ%wg+ zt6ZU(Z*%b&XOUTKSyvOMip)~W`VVx)_BCf)cFe#y{3OWv4#PMfN$F>nCWZTXZ@va zh-JIH4}gudO!kmrX1rx{hA+q%W@b7JYt%Jun7PU3KEwAtqs{Gs+|Gp2<}sTag1ew& z%`<_VR>zwCHkUP|#WTjd-zqo89I`q225YSOA&`s1&)}#EXKm#SHqL}wCTly+#98*& z;BwD6lj1Opb7R^#Gr;C#{wA2=mdW*Df;l$Oa#Gp^Gp|+4NoJ8{GSidH>6XcHG})}M zYz&^@Og5_>2EW6pQ_R&iCw|k+wm=T=SDV{f<))i^Efa_7=CM{SXPB2-wVY}8TQ;xg zq>P#7{Xon7)S2e{RxQiT5z8(e)8Z*Ns#4+KI*e>mTDieX6T-p^C!U@$$8-(k2Bgk0 z`8Fq`KF$mXoo4H<##rW=YRhgUJ1@|3 zQrbMT$>tvSmV4%#>jSx+3G>Zvo0D_o0&{mD7pE4Or)(~~|MH9z%nN~Ba@q;z9h;N0 zRE7CCkc(3l<{O*qjcZP&`MFiD(u6E@wvud#g(liz*n%2A!Ij=Bx5#9-$}KW|TjfqP zLt5odG-WpT!R(b8i_Nq^ZbjN+QxRZpb&@&7whZmJCgUWt%rZH`PckbllVkcMQ)Ahm z-Q$qE#IjfMIrSv7(XwNs%F%L*WoO`fjgw5BWyj;2jFZf6hhhE3r=Dbv*qp5UQiC55 zl>LS4zSP7zEL?I`Cb?Cv%H-HweXrNkmYIS;?(@`TX1LACr{puuv4NZ?^-MF<=A_T% z=6K8Mutv+xiGh}fQ_7fn_5myJAY^HYa_aZFUE8TAghk zYLz?3Jk=_9j(N@I(q>(nvC=UvtU1(-F4BI76U1%2CocOIZrv`Fb ztu^Pi%3Wk?Tjee?8(ZbpnJt!GIeuftI#Xv^X3m`0b!N9^%Z6Tq+}#d?`$=i*%-c36 z?w6X60y(WNH7%`jmzkeh#m*Z2g+1hQ6J=QjzD>K_baogVE={}KJ2Ez!69T!lDI3jFo9j95mW)m2>_ASXZ88_x zob-8>*$~KSb(PuPDtEQHsa5W3bDz!a8@wZ9vw19#Tbr`kykv84j=MYK8nZu;n~`>n zX|}nGhdrHftvM9PMWtP9j@aCl<6q6V&Zv|8$HRWGScmcR!xoclnOvQ=m`uwa>v14s zi|G-l`v`1wAop3uHZw7h`wDDcfVtInv&goTJMr!2G|S}s-R-72&~i=6cC+5*MtEC1 zJIp4_x(;^5?l9N4YPrKaXmf9rexFfio($xE278svd74yj-V5a1_!;wNvI|wZ`aMH_ z;vCn7*rRx9FNyyL|7Q#Q&ldQ9a|?{eIl-to-IQ{vCH>@VaBg=|M0Zhl&KO3$Q^0ct z-dOM}nL{Ps%5%u`TH^5orPfjYv-l0R{8xWzxyvV2pBD+n5b@jb5j$8sLE3zZ@wikA zy+rY!!D86Y13g4}9kEeQ;56^TyL3l;%g+)0=02j|fwc=!wS6V~;ozgVuUlN4Q88Xo zf@4uixxP0Jk$!UWMUhymo*d`(?Zzx4H_Fi$<1Gd}8(^D!tS`Q8!8hf2hi}-?_%inC zoRe2$O|cy~lI|ESMuqiq;f_E?9*1=aRqvw>-Xn=p`1um~(`@1D)GVoLz1>>J(t}aW z+%kFGhK?eA;%T@w8>3_vu$_zI;zHC2 zpSa7uVR&V6sgdE*=X-giFuPFY8z-o@Iv)Lzblth-U7yQqgy4^{W}_V?iLUwU@g zp8wTLvOBnKf5cwr-vUnT>`@N3-p3rpaeSOxI}-a3_IA8i;omFz zm3W1zi+LQa$dbLi5p5#XY1lKPcr0kO0JC6}^n>R_k%G=X@Ir*dt&gv8Iy$9aFn@JDP})JWrWCnl85t{n_DJ~`_KQ$?el+st^YfYaxM5jxix+q zDA#4@JTr-BKj-Z2jM+KHTOSe5S>Iit3A`Tu(@aLy_Vc)oSc@b*psXAl3i+c~$$S{vn@Z(EPY(VaUJ=c?pn zo!H49y;m{HIfpyP zXN?^E@1NU`ZlBgyN?T>i{x*NTnIO(sLGShcfnCC51Jvhtrq3JuhAD%(G@&UUiNw>1mlHP=w-cj@HxX|q-cNj-_$=`i;#c6{gE ztOu1B1Hb7$3|KpOB=Cj_Q(}MB=M9+?+sV8-`}o+OxQ;(y9nXmUgL_Vh>$ZYRF~XAa z-N3;ZXES2yr(npPs2z^Cn|{@6@g(k7eGi^D{;K84TfLU&eLtC-if)h5>{FXQczaSC zc^ak7g<1F^7;`*hc`HF1d0$K$c^gt2dGAphc^gri30N0x#$X-8@mcR>V6@r?j8ks| z6V&^_Ek^Y1M)W!(`b|dk-R2YE?dD)?yjtRV8F(fzTx}$7c1im!E@{8rCGG26(*7pb zLEvuJA>i$^+56o7t1AD3D%mUR<6#<8;5x`Mu0&oKL8Nd?uP(iFAHWHhN%|xZe zMH9Wm5@IQ_f>=qcA=VP>i1oweVnoOy zb$zCnv5%Ms<;Khs#-+qED0gR8Fs>vnf%0%>4dYtkdMM9l)-kRp?t$`FW&`6!;$A4t znN5tFiHD&4kf}mB9%4l3BBi}v#y(;ml)E!a7?%>upmg$9Fs>vnfzs7m!?>2X9?FfG zb&TtYd!U@3*}%AwxED&kw~28x@eq`|GnJd;Ax5|tsba5}v5%MsGUMSakn;16}4?)@GRbd?7tudBj`7xRh81GUMPQgn;16} z4?#)vsc?>m7!kfm75Ti3eZ)K{6MQ9%ONnJr=J_fZR}z;%In7tYxR$sc%K5%J#`VNK zP_FegFm5C^5u1rBf@2_hi9TWpv6NUrtR&VDYl(HldSU~yk+?TPX1|GXGw~3VJw6pl zFJeTb?D<~CK4Kn}=QB$fmlDgMyp>tOxRSU8%7eZd#WRvg0dh>MRPpFi0DOXX_l9E@l&iAp7}pc`K-rPiz_^jP7s}mPO^lm~hoC&4rD8Z9Vnoa$ z^>LP$v5%Ms<=3nd#-+qEC>RgpO5zeIIG-@CC9a2p>9?k#Pv`nW!EvT zC+>mrov(p$BXKX3McGY^n~8^@oRzI&IUZsJUS7B?+soKT%!7iXm~knw49Y*VD;QT2 zmq2;dSHrlLxE{)r*>#NTiF=^jo!P*+k+>Jii`h+#n~8^@oS&)UI38j|oa_%?#y(;m zlwxlQ<5FT7ln=8j7*`UPK)E}!hH))%J(Od;b&TtYd!XPdz_^jP7s}_^O^lm~suM>- z^b&o<5@IQ_f>=qcA=VP>i1owe8f^>C9#%RPi!PM6Ll)v z6HAGe#9CrKv60wJ)M;!_EG1SFYl-#5Mq)Ejr?Wk=lvqiuCDs!giOocv!S=*bC(ab5 zlvqiuCDs!giOodqWi7FkSV^oU))O0v%|z{EdtxcEl2}WuCpHqBi8_nziKWC!VlATI?rmJ%z8wZwX2Be9vNyRkj7lvqiuCDs!giOodao$ZOG#7bfMcEJqL=6+ zmJmyc6~r219kGGfMD*r!48#&*DY1fBNvt8(66=YL#Ac!{kRH55AF+a1M{FQA5mg_y zB$f~>h&9AIVgs>>s0!JhSVC+dHW5`3v&0Hw4Y7{cKvaF%hFC(ZAl4A;hz-OhqUy)? z#1di!v4&VjRQ=h8SVF8I))4E64a6p5$pC3zL98Lx5u1prn7t88h!w;dVjZ!8*hEwV z*`8QJtRU79>xd1+CZZa|_QVom1+j)$M{FQA5!GO}CzcQ^h&9AIVgs>>sD`jTv4mJb ztRdDB8;DIrHI(g%CBzD14Y7gPL{!7rl2}5lAl4C^h-x_75KD*^#2R89v4Pk`R3q4) zSVF8I))4E64a6p*I)?3uCBzD14Y7{cKx`ta61FFn5G#l^#5!UFv5BZk*`8QJtRU79 z>xd0R??`D`LaZRx5bKBy#3rH|#XgB8#0p{!v5we4Y$B@B(!PRNL#!h<5SxhVSoTRQ zAyyD;h;_sUViQq~;b@5!#2R89v5Bb0vQJ_Ov4U7btRprMn}}*0+Y?KO6~r216H%41 z4Y7n+L98J*5Sxf>s3x!_v4mJhY#=rf)kJAiLaZRx5bKCdL^X;1 z5G#l^PCQxkI${%1O<`}u8e#*{J5}@wVhyo@*hKVBlWYyKfvBd7;w4rP>xd1+CZd`_ zFJc9;hFC{bGnpk;5bKBy#3rJeMHgZTv4U7bY#^#~_Cu^8HV~VLYBsaP3Su3xf!IV; zbJ&JhL#!h<5!G?b5-W&x#3rJ5u4LKveTZuOQYC>xd1+ zCZbv(ZM?)1Vhyo@s7_$d#2R89v5BZEm?hQ_8;GisIKx`tag>)oV5bKCdM74;u z#2R7)QJqMgSVL?es>RfaHN*yD6H%SSEU}K*KvXA-?j=?b?>FC>POg5gS*~+j*Sj8a zz32MHl^QZMWPZr`A@w28gnSkf7Md42A@q#UD?{%HeLeJt(9Z4w?%D2h-P_zxxIc00 zu*|U1uti}Ph20vqH|*=MnD9Q~)55F6uL*xB{Jrqs!@EQr6Hys)VZ_Z5FGL)Qh>FaQ zoDz9v@s=FKbcMrULgTFQ50q!0b51bX91nd!= z20W**E3mW>FY(}SP2fcq{H=+ez-J2!fSY6R!V~`1L@{v6FubONzcn!eSQ|bPSdlXZ zxZX1!__${>aFR!SBeL<@0shv+T;LzB1;DQ(76Nx?odmoy`&3|J*iv9o@p9mr;R%Gw3&9pUA&}VUzF(34d#X?@7&x zL_BSd2RJ`39=Iej2{?RG8u0#EU4h{RS-{GI9AJKIPvBt>e!T_$)6<~?}(CK2!6B;gIMWIUnjinp`8csDBx&ua4UtSVo{ zs6NW0#^8z7WIPRDsN&VBc!F~#o?TU=auq69qw;)}qApZv>LQhnzd_YSU4fpiRGDfc zdfTLY>MA_xxmtBon^kxGUE>_JMdhmN;jmTpRNL_UXS?dHZh+fPxYgs=`0m1U_M22+ zbvvF4-J$y9w^0sIcd24^w;HJKQ-jn4YOs1p4Z+`b8>$|`Ux0p04ab|QBh-`lt7lKC zk*Wbtke*hf)idf?ysJ7!J*URv=Yq%KH#?T87x8z&_Tt&nOKOsO8Ef^XnxfvqFH?G3 zO;hjSSa=tI9qv8+CAkmOEcKx(S0Aa__&tbo@Oul7Q%!0v-qoCk-!OQ*`drOdU*N|! z52+K>SE@pNtt#dAXmo$r)P=JThb$aHdQ8xg}EY^*q7QqGN$uedB@kW(x2u)`k{I?N*;)Zq5wg zsDZPAPxO+iJ`<$nks-$eza6+3xYc(mu%}D(k){f`th>~1nk_h*y(J8-Mx4*_I4vEQ zr;E>p?sz#p?=C(ciqqy#jw(V+KmTI9ggxKqT?^&W@s|Q?W=VEwmSA_U;QPY_oh@)L z*XXMP8CyfqM&S5-!B6^#@>0Iw^)m%uo+0>cw2bG1D8Wq=MRDBMly64dckotVT!`q& zdD4%wea_{KJe*wz#Toh29Buop@wbw(JzZP{Jj}I7$e)22e{BSKTMy~^{c^b0FJzfG&?sf*xIc-_k5p;;(j z+ekV;(0`Jnd}p1~4f0Q%PV}vPV}O6K{v&{= zocD3S=)Lgl(ocf&?9%I)OBR-K?EPJYzx{M7{|~xvI5v&BQDg302&my}r7*`H1&^y8 zb7d3Lnn8IRX5e}1}MkiYR4R!2BiseavF2#EKq1}){vMDU1`8H=GHl&<1rlQ zGcj8SkZ1)RkDNeX0;nN37xX!RY0SUv`1cN6c=G`>I?lrZrqzj<+3}TBKn=Nhptl32 zF-Ok__D(9W(pU zkiQo-4)De2w}HMA^~QGv05!gyc?{?u1WaSzKNj@WfEqL7aiH%8Ov4Z80(~tYUf>QE zgFXzHhF|b@(ANRt)!g8CP)|K&b>LgR%j2ZHK3^9F&RR6lmKAn133r zesvg8ot!Ipzj9M z>NCN4pnn!H4d1FC^kG1auX1hy{RY4^e66jZ=K%3eRIm;7&jF_4du<1O51__3%g+b> zM!+RS7m|SL1*hzs&l6(7y_phR3`M^fI7Uw*?;n{dT}Kyyy>t zUIoFjmZq?Uk6O9{jfnhejj!i^t*yA=ywCA)g)}t4u3oc z`Zt0Q^gjknt4m>nc6Aw`#&;e^K)(kt4X=F^^bZ5#ehl)U|0!S^p8Un2*8sKpX0R9Z z`vBAU7IOjgj{@QyuwV@IKLbp|>o0P3z)|0=C?urJRsi43a$YCNx(E#J6D4K`+yo>=er8@zXwcX1@v*yZvxcnhruU6 ze;P22RnZ~PzX+)DtG-u*@+H(BUsMIu>c_z+LAe?A#1`#_j4BK%8+8 zJ_X9HsB63WLqLr$>U|oNucDUiSZRF*lxKs_f^s|R*N!#U^`JZ#+yKhgP_uTdzJ3ps z=Y!9IawqE4uD%Ya@k_!tqMR=Prm;HvebDa))as|fAAtTZfN8AQZUX&}0X4p6_XW^j z1WaQU_eId}0o3?f-IqZB86fV*;4tVH18RJY?q<+m2E_dsd>QltpvKqcz5@C$05!fK zcPsE$0X4q-_Eq5j4ye_yg4=<=2B`6cwyy#I4?wN{Gq@A@>wp?xOS=pBe*tRsM(_>b zN`C`*p#KE82E@Fq?*)!8lmTzn-vr(Qh`CsQ3wRn3^RNCk@L7PEbM*tj=Kx}!)qerJ z4G?pveh7FcAm&Z|SHR~3YJA!1JHX!psPRRr?*d;4sPSE?M}Z#=sMXu_W55>yYITf$ z9Qd(-S{txnK?2mBp?TAiq$0=^VbtCRE(fxi<_JV_!)p&ovHs7 z_)!xBA5i1>rr!WQ2&mOg{Mt0W=?$pWg_?dS_98&M380&S zzYh@phMocZgMjcc^i1Hp0X4p5G8=dn5T1i>1s(!w{4O<)m#R@ft@649@M1j=aIanf zdI3&=%mF~HKBN}|UZ#%+{IEU&a7v#D_%VGF;6eQ^ zz~9!`8&Fs1Qvk2hD*+GbQvrWRuK~P9p9XE$0&4XseLCQ$^_if222iWd>URUb9uRY) zeh=`^0b(B1-N0`I#N4Mh0De*T0)9zv0=!xG0e)HI7lzeW^cKKd^ftg-_4$B*s0RSQ zs&@k3rY{8iBmG{$JM{Yjzpg(3c(=wLhWcZj0sIr41-wUxfPbn-0Kcj8fZx)4QHDPQ z#C)O0fZq?O;jxzhAJp`F=??*7wAU5DhxG*DU+evV-_?_VkLpVSAJe}D__+QE;1Bc^ z;NR+x0Y0fO2mE_|1>n>AD!_lxp8$MDUk&(=`jddq>1zT1Nq-9P1^sEjf7PD_d{N&3 z_>%q{;Lr4pfIruN0Qj>00^lq9OMt)7Hv_(^zXJI0`c}Z#^j87@L*EYgy8asAZ}gpj zZ|J)KRq7jnI`t=jO{seUTTHb4;3=t}0fZoQPyG_`%+#-f1z44w zfIYf3@RWPuhYiBF!%0qfFqf*2sZXd+VXpfUW|Oxa00gX_5F#i|=u7T4vt4&!q1TW)SE!HUrLJq+7OOjPJ=O;Eu3w(5 zmda-h4-KtaF|d&u)z@2`7z@iLwjom}3}>=?!RXFb^Q9sXPP;i%%oV~iqW$?wmC_Zfr>;^PCyLqK ztJL7cIFzljX;!J8Jg3f-CqW$vt3z9IYgEOAmrP_RS}0|5VU92o=q^;laxqiQ?+b~P zsgJUX%(&Ac=$Hv04+E(6TS~c!LU@)se^*0oZ?Jc0=Z=20VcXWN zy&DFH1~2Rz+_0HR1Jy7yw!c)~TUoxfcTftmy>F;@YtQy=eOrYv(7WS;-W?6J4cm6~ z4tUzWOkpA%8d96WDy!_CsE!WivwOp8Po|ntz2$PL+*gEDKDPx05389R(NS0hi=YNs zH85GJhGWY&lnRBA4Om&eDJ+KNe3pyVmtz|uAbU^?6KVo7Zl;L$WuqVxF;ptR&WaN- z=!8&+xuG0p(5H<8A0r;}Lhmsw7eU@aC6Vog%wz$5(+EYP+r#pnQh7`|^tR&QC^8Zg z_EmNkIYM32MY?l2wPm7E&5uK6SSC9UWXoY#?B^bOVuf$R$F{K1a9G`nX=Bc%4R3(s5n*5$T z1`k`Js5N6upD!BGj5;=9-QW>ti{3cBRO~73FVc9vy<8e8V|cO(B{K~BMVW&J(0~(; z>$00pz?XFgCSwe&RX+^BRTLkikR6(!`$g~fbvWP7Guo!l7Gozkf92r^vFb28{Cb~@dH`2jaO z;xeQ9xMb)j&4Io7@$qQ6<1DgNUu9r4Q-)mLBBF@8iueJB@2nN$P=XDGQpIOxVxoV0 z20jSsH;9|8;hlj_Lh#?h{!FF1b6jXtw?>*2OYY5cGSzA!+)2a*5|&h zctj~_uOo_3nau46wURUWQ=aCmQBU@wa38XAher!W(sdk3Kqkc~P_C;)lt={{%zL3~ z1Q!(p*-|+im>3?*SL?Ji(5{m&lcbgZuOF>HSz6Pdu%J zEpVM9+gIFEitA|bcI$*h4fTvVa$OIrOsg=t451|X>b67^^)||C>Xy_cQgE2I2%!dUK>({4o zJ_e)2bdkkpJP}r?Zv7bNHY_?51nO6yae=;EoJRL;65T8q;|a`4G0s4hlN8ydUpxVK zqJ$#3wO%}ljPXg?&B{5|RO5(_z66Z9`9&rIT zl*d_-fi})0#cp(rvp19`iq%b}QbHpPV3C*&lak~Ts5`>Sc&S(kH^8fory*ZEPUV?7 zkyF0{ji)pNb(}Gn$3z<^(@-%DiwY4>K2$#<4|#(!DI+fC z=6tm@d@)uiwBZvkjBH-S>C|j-D)t&T6-vXILNXgws+5YE!a3wMOs9ZLRp!N5JcIfo zZ^d3=rjQ^d_j{bty$}1i<(=h(G_;)(PjJq70&z>em>-)+Vq^{^Gi+BZ?G20ZOrm=$ zajx8;ae7aP?Zf;yE#l(L9bpERj?+b1Ww5kkB7uv&hYh8%;Zi&uCiggzMuti?GnT*- z&mx|L2DLbKTd{zZaWan`zjzWJ6tEvcyR~?_5_1AcSzoRaXKcmBYXaH$oN@Vq5Ua1G z6qq>^^w4~oM3%))0%vW zk|4!PbAIB~T~5++A)Z9UbKDW;=uS&!o`c6S({stej4Pyu-c&A4B=JxY+D|-^h}*zR z)|`-`-=K>p;o)gZzLJoPR#6G$zGAk7T?cII(Br2B0gTTHRR6p%UWo4OXg=I0PZQ%@ zSEygk z$CKWcDesFXW7f94naMb7M>vks$LZbK>_j<}P2d=ZWO@P4t(=h3%#?mKiD!%3iRR2B ziSU+ZEM<2tCxf=y!H@GygqBq_F`bt$n#ZDBOT7nRTij%_MBRv$mFyOBxoM5Xd!Rm6 z#GP2hle|nhr=*kdh&x;&!j7)8|HiJjjpZ=ie45#e#{5V8@>x>8Cllc^#n!_s*h z(MASKk!?YG%5N5chQArTp;Vr<(ql#)T%U1lVD}!#@?%$drfzQ7!BVL|Q$|^EEC7Rv z@<%aLAI7xtQiUE9D0o#>^gUV}ZRTSblsUy{tzanZ(D>5@8!`DyR?=wLVSHbHoT+<= zh2g1ELdb1FYiKqBFPWJpt2?IKrL?ND9Hk%|yEcvNKg**7=GJWwM(E}_M94z@A=WoYEOGEXN^_b6o=)hLx6jZD+9ZK5V@`DDUlU)tP7ra@_%V2ar~VLb9=01y56 z@qw~)3Jb^$M=JP!15(AbxtEm*6rA$D8?Hl-IBU?R1ipd+xSsT)uH zE{ko&3+MmVn%4*&oM&qtHW`g zg$#l8;VvxmfkI@J>#oA(9LDoZ)e{a+jEs=4;c4BKN;o!Lm}H;8lLyIPsq(N#My!Sx!R(iEJX5o4U8`B5}RYjldZ! z4G01en(k<-B1gzjW#pn=3=*cg#kQikhH)y%PPco_Nv35|OUNEJXXsrJL!@~jMx;dc z2qG=-)IhZ~ZahL;^vDJ{9Za#C;pA5Hp(&)oLuenNDsWTQ?Th;sttZP3?;4C3sx%!N zux)%9+Y&e`!Q*ER}mR+0i3+ zPVvpKkgbk#OUd&AwvRzEik7RD3-d;R=4@s&LguY|NWiRmiaZoWXL)9kKxHH_LgXw zZS=^K2}x>jX=hm+VScr9tnWB+mL$Q`j6A;_keDiw%j>wPxM-4dY=b4rDav(D%ya^# zk7DphOh%(hl#w-6#>Q-8H!I*!NOgG*4C*89K@D5V$2NskKE+1Qa9EJGizB{iB)Y9S z8kWh23Cm24_^KSQQc5iN0jXQ`;W)n;a9J^){4kwD92Xp8qS?<9TsE$FD?m}j6pf<; zn77oqgS6quQqdUVpIjfY^Zd)G-Z|M)F04z2V095l~v1x_b}6H=@a#^D%P+ z<1*}9%08Lmv2#D6c@FQfM&!oZ%PAalFw|~^TKL0a{AZKS#l@k zKg3f%tCvSl{8Ebd=?pQ$uTa>u*eNnERU~&3V<5AaY|g%7boT=}j3PbL$jI?ne@E4m zvEu^=j4T;Cj5=ZD2^-rZ8pl%}`}D1`kUX0!3^P^Qy4h1*PVR|m7UdIuipxzlUiN=tSTAK#43O(562K6ytZr7#A<*K(* zJ?p}7Sf1-p;YqSGZf|q!Z(P!No51-6k;))XkhuU3rJXE=$+Rzrxq=VnzuD$4+&p?E z3du+(by?QP2vKAg!OBC2U+TcB4X`Kb|35-8jp6xe=5I5jkdT!Z=)n53;VZ$!Yw z#JK}bZH^kKJ#r3K>MW-WRCg|C4jf>WhP}iC>{A#fE#WL}OTJLZR}908){&{Ge1a-Q z#XVGEb22hUG2^q^wWciL!!q^RJPuiKDIpC^_8`bG97Sr&mIoRg>H;Cvk0}9Y@0|bK02Zb^cEF&0y;Cot;mQJnZ_{c=Dsmx-5x3S^de%> zFQ%CKo(0c97G&aEiY-b*iS)|22N%DbGNJnb8iUL;yf z|5Bvqvk{P?xId9BBfIf2p5IxIvEaT9V2`2h4MU%s)R-1Kv4m0AKE|X{!iR{_VZ_GQ z^L%E}H|IPR1vGY#X|rApM*8SWZ9Xk-{-pQXyg>b%X`W8yrwYchv@4ewLtYH?8r-cf zOK;DppY5ZsVR;$f*UXz!o@FeHTV8xQ&!HmWh_bxzZG$i|HmNJ(~24$WuK}Br)?e%@Z3`>@bfsyZ{Y6^=IE2*0eOFn&%Wg z6;)5R_dTt?Gf``uJkV2RKhfXno0#zNa3iT;L`$d;pF9J z&lgStgN#UhTRu_ZCQpA9(d)3H6A@WArc#&kqV+d{jHpMFsOQn-hO?yHPrOd4Vw_Gl zjq!cFFqPG~w6LG0Q#r=42s=)qUS)guLy zw(;(mQ*NI+$(jJ0EV7g!gz_MR%86X2N211Ot3Q*=LdLadkae9D}H{C z`AmsE*>vHtMKsZBw_+^EUD3Qf?W&&CmB3$B4%R0n-I*820V7DNHdx zHLk?n)`cg@BEZLG`$fbXmMgMWalNs=tDA?Y#tPxrH70~Hy*6v3^rbnW72@@gtMN@?6j6&TjLciF38j3UOj-2l1tm+7fJ&Z?= zipt~bQmlfkIw9pZD77d(Gs8}h=hYGX3~eOjn8XHN+*%SExQ^h4{5mRkt>}4ju14Jn zbF&)d()MH~U0c)0Bdc*N++v4k$mx`K9fzu%l)Ee+OP4J8l}g$qqGz2r+@Rp6(BZAB zI#>D)j+B=1$Pe!qyEr|GuuqFwDfLm)BWb}~$;vW?bC`~2HdnTWQZBhUIfX&pHYsOe z7B-Pv-clli4RsSsF3a~j+(9)t6VJM26TuK@uc3}$7h%%5>vqUZBx%zbYRpLDT9~F~ zKa|vy8@5bXRBnO#na@xDUemM^rSYLYtY7lkJerXvDQZl7vp}|v=#fr{MSVX$=>XCJ z0_Yrg0YHrn;fV`l)Dfd6qx@R}+%(g$%+{1x3^#YRan9vi!kk2tj<4M9Jl&zR$W1Ig zSMCqN08Ch;dcy4`6tIudbqP|>|PPw?jNET;CU48TUp*Whl zUdFA&k_&_zCYrW!?PU^C7ie64C6-8BZ@2z3GD4+VHFCskehp?42)S-`MxF$_uY9@} zGj*a(sywfm6Q9@af%$y7VsEYU%Cb!9dy9(iW4VdzUUDxwqPBB ziewxq73m##@J0R1xF1x?f<);cvy1ZF&F{>~TrDV3NmaMucoe;nv?RG5zGwN$)%Wy> z#30U#Rq!HH!`)21&$w%Lli5V;HL={5(mp&bIS}ztx8Nl{t_K^GV^o!`rTy#y?5t)9 z#bVDm1ZZG-xz8uzm>6^%7IOzM#92nYn98TW`b)#ZlY9n6Ft((5_fKqTBg8Q$HtoBA zEYL_3qf*n5XN=7dZ^tOH!DM|nn?bVC)b_q?RN&|_9geeUng{jXV&#avfO9`bNVPQQ zy1XYO)W%m6RbPd^$GffEJ2qaGlzimV)Ip}B`UdwkN#qi-A%W?ln0gu*Zh=SxZvzkC z8QwXHU#zI&S1u~}#g$X=FN_ew*gV-XzCZ+Ne$5ByH+= z{AAnlu-gQFy`+j9hXC>304VDCD199K41P(4+AJb-)O0dx!SS%#IOMa?n88&-O4a>- zZqGB&rqrrua7)?P2UHj0t3dC~1i$tWCdPEnjNzyoUW2lnb7;0Vwo$4-}4B|OXM`h>&m1J{`ajP-FIpE{m=Yxt!nBDf^@1&1SO!#>dUSq<8CcNH+JtkZz*&L8) z&9dhk_LU|)WcUx7_@D{5n@}0P$+mX4s2w+Cqo($Pi9ct;*Cm{4GV$w7_&pOIG@+?Q z?Isg{#)QU}wI@vc4ig?W;e94FR;j&c;;)+UeiJ?-;Z)khUp8@Do~I4{HWR*P_yrS= zn{c-Yb0!=$;W87hFyU$wE-~R!6Ly(!y$RQuux7%GOjwn$_OOZXGT|IUf6|19q#5Q* zu&`O_wzfqrDh0Uctus=lgVqPLmF}EsmH%_vgVeFA{aF0m7=8kER$wGu(mRA;11E8Z zlekADY~UpBaS~^Xgbkd;*-qjzk+6Z2xXei$5D6PNi39UZC95o8VA@oq>e9DMdK^Is(CZQ#;7BiCvy;Q}jBtGP0479BRQ5>uORy)7~2HI9TVAI#GXSNG$`kF7pe96HER$^+t)iHIYNZ7zhTU?pm<mX8{AP#m8L^Vy2o6TzY4%+6$6{4V=UaPRDa1VFM@eoRfH6By8X$Ubhlc zO)TL8D>2n%C2H4+4jWjB+I3Fidm>>2C-FTeaZn^|;3N(@iEBi{22R4=Nwu3q!Uj&_ zCa2>Wk+6Z2c*aTGBN8@n5^fBxJs}b{a1u{A9e0R?4V=UsPU5gg*uY5~b`tlAgbkd; zeNN&Qk+6Z2aHCW0MUk+9lX%hTcvU29;3Qsk68DRQ4V=XNPT~=fuz{0!#7a!1S;7TY zVk&JVYA=fp8(4|j%T{9QI?-VRD>3E!*wdoJ23Df>wAE3&O(bmKByMvOuZe^WoWyHZ zVyYnJuz{7BDmaO8k+6Z274CK5Jq63d*# z3X!mZlUU&-R*QrUoWyD;u|yoa}pPcgbkd;MNXnB5;kxWRVz_@m?d0bC29{_iK$(p!vq?0%#5;kxWhv4l_EeJYxHh1Y@5v^#>M`W&oyLx^wPcd_z zg3x>QG0ih)Kxm$t7tEZY7j*Upoqf7RAz8adI*QT?ZXSMJI0zQam=P@K!0)LwGwhhr z#1az=I%@X@ZEeT2Ns5Vdmp-N~)iJfWy*)sly}_}R=Sc`svx2r}qO{HIQk-m_YLVnk zUHE-qrdc8S z3KL(M(={VV^IykQU%Cr=ej|EXp+~pPMg_lvWH)!#9-rOS5_C*$Nw+n(%}S&G=LaC7 zby3pVynvMyjeM zv>SqxSY$Z0nibh>k$#IDw#Y3O`MgE0vB*J-TyK#ci!3yG9x#NO6Pa&CuC&M@EAybG z3|eHnMbP)SQZ-wO+TA9Ham%s#YA;yIa~65sAX812a-BuKXOV*zvDK^HWGT;B#9F=f zgr(eJk;4|b&mz`vwHGbrRg2tjkw*+Nm9~_ZEyb1aY0J9JBClDQfSY)Y1x-7EZBI_(tv&cmjsT!pAu%+y>$Q;Xh(jteb-Dse0YQdmD5gKYt zW6n3>LfjJVxEAS_Rs;njwgmW6T+4LJEK2?ofmc9SO~}mzZq+Swnp!$*za;Q$0+-`1 zTTiT~0H$s-s?J6#y83Z#$2Dl^7eP8_@ZFUBp*bj`8-yS^DK}{|sk}kAG*iWTF|u4G z^j4x@P2gGrkCKib5OyPh2N0bKkjEt^j|oY|?N1}wL$m>i>@dOsHe+a;X$YN@$h-&F zW?UiZxo;M+WNyHC)kZ^a8WT)scQb~=&TbljHHN#{gv_3SsR#va!zjzM6%ZzL_RTbu z&ORjW*g}arw#bOeqmM`L@aR1rJ=>#~dGvs&rIE-J+Z{0?xybQrHHS`Z_GrIH4}0_$ zkAB{x*Ld`xN3Zv2k4G1}(j0KaT10fdqfTAv(L)ZcJ?PM>L62_tsB&n{m$7!Y%Xi%K zeGau3JoPz`zV6VeCWqFp^XT_HdeEcZAhnx3^%;-)maIMDsdsqvut)Fns5gJ@MNfUz zqxXCC5rd|E$UE$Hy9$n(mr5^3_ z=z5Q?^JvYZ7kRYm(AvWeo!aHmIS#Eo>Cr=cQ_}D#U4*cfu2e9)O_M7rt}GGSX2Y$5 zzvymf*j0Ca0K*1ualZXC>_2vf667TqV-}oU4NsQw_BqhjaY-wW!xK!J1ngXqW++1I zAjHmaXKkvj4USZ2S7-Z-t|lmHW4{%{ERivQT`ra35s|W))heFi)#BdPu9oPv5`0>M z8(O>47(#DsOV4VBi#dxb*hVF4ZSR_iAq!3)7qB(m*4ow7S^EM;dyMsM>9s1&t{YcZcGrsG4jtvogziYS^(`unwv>WYpWfIIoixHo$hpcP%xPQP-CnM zYzvWc%qVSLv!R?TgC=MtjT$XLcGIY2XHUAVV?E})HcSkG9qAXjac9267Se>#P5@rJ z>MW$Yn$fGE(k5CWqL`ePkib$l#8N_+F$xHO{w1lj30^_0b5Uo3q*k*OAf#>?4kiXu}tab)TyTiml*(Mm2OhB z8&V-H>7Q&x(}2TWs`j|ZJ#HprG&-zu1K-+w$IP~U1pb7PLEA#+YKGS`d=wB}fbkm{ zK0uVIfC_qvE#ibppElZ2h7MCY?_A8Xl+H;?Om|Gs!9GOK40I;yMpTK-Y^2SEhKyoV zK^MQOIhBUnu@oLhYAF`2OVw;#Z8Me{PeVeu6-(h(q$x(@SuTNd(@rk>55!41f~d8VYH#uKcSc~>*Mmf@p-)Ocis8yP-8 z6f_Yu^X`4Cmal|{w)yT$emJK}r+Mgg_YO+Z zWz%#)^r%41PkISJsX2j~u>;>f%is%)99g-11^rX%)Icrg$h%J4vo>?u%FJoGY;N_6 zwJT1~t~zz?+U$zqwd=yu_N*CR2Q0gC?W$q?;CP@~D)O#?YDH!7>OK9M8=lqA3{=wu zPW4G9hOX2M)D%D2#lrmU+94YwVg8UTm&zW?QF8+|E5pCnF*JdXJ#l>8{aAv;Dd`A* z{9kg*$}v#**)tu#NzKa5=OdW0AW-c#?#oA7M_~3L zywU?Bb_S{y$HDN{#E>-wnZ+D6Q?mlqEMH<4YvBc)EK-qu@XNfgRC}OiN53Pa<_Bsn z-p4Z^wHp$nkjeu8gshZz7$2lkZwYXhc*!z8vsA=sowo(*Eo{J{QTwwlLq-0P6s4$V zH1Y~KAZIL#9yP>liSNP5M;o6Ps5Ux$H{?FyV+uet`kw93g!#EeHc5$p&10y<-{qHf zArrD)sT0EfLt|ugvV>Ujt%2$s_rG^DWb0!jjIFBlfz~0saDh+8azDys<&*WcT@Rqd z`1umkoOF(HXpBzQN%L}?auLgmE%53&-UEpUjL_%cBk>ZQvx~&X=zSt`L!FRs7FO9^mYvn1EJz%k4Ezz9jFD4UT4BxG7*seSvQ5g9hDwQT{}y#5hr^5X^F%}5_{Rn6XiH6M zMY>OH)rW@hWg)SjWKEq*#)Hgw-gJ3xc6yzVZxI^o3OKyU9f$|Z^e4Y`rwX=gr=jz6 zxEAgW)WR*afhd23&BJvfuDigyE5aiQ+8>Pm?HK48n19}#>Q@gu-1CQ1;fr^f5qMYR zCk8NTC(17$&kd`A&E2cktW~CTPyCB19W1bN>$Q(v@%>-?^48|hJpK>5;A_8#@UCe6 zJl+HOBW<$Y0gcUwFNhd4m z?4bPJ(exse1}GDqS)`MP_||Dm)59!7=OpFdYBfwYQ0J-zNK2<4@wG_KV-huzQ?#W2 ze4Jynr)!rZ4?3|aXKj}wRRQ^g$Pq&x{&uh~n|BX5bas@=G!6-0=KmX=E8u7bu09uV z{&?5Z)qf?4oQ%jJvwFkxMZ1VryrqMD;J8g&V5=u6~=U{!U{^vT8z4|E5 zG6$+&SEFUUUEifnLvLJ*(w~M?uLN_*J*QT~?rU+Kj@T-7D)6D zb%vHj>a~y@rXqk!?b?kVLT6#eKrO(cUAPmd)qD&4a!@Cn1mo2noFj2_PYK=~6TSEC>=M;6)TYv4AKjDi*AX z*M`?#u3}BBU;{f=65AE5*K*aX;y32l^Q4^I=lSLRp5Oa^e|#_ZI?fzpuDQnAd#zp0 zIXlVJ1>20r7!!v7{`}LJNAN2DN{RnH1dxu;el*@Z81;((5j*7-|LhZ&*Z59ZU46pp zs*`<7t5&V5UgKMGoNx8oRlen`eB-Cj@||40?6{)1xagkR^^7BpnPNTW^1g$<2)Z?y z9AAtrHfFvx#_OtgpN80nIDl7U5*a)0xJ8ih^Irq<(B+@URGu$Q<^Qd(N=3rIMd)|B z1n76W?1<2Rb=Jf}o4?kY*`c}rj{A&>Y?F@!pV)>MtvPPp8t@0_V!X(4TXo#QW{@k3 zR@bav3V~z87`dSf@Y?2IDWHh!(NM-}FlO$9V>+}O+op`+HrcQECu4I=xLLH^ znBQZpX-Fvt^tyHb35&=)wi7=RwKGe)p=J zAaW1qj2J1%aXcFsK){B%0s{zfL4g4T^ys!=00Diu0s{zG2d=;XLYJVx07BQGzyMp^ zt=MA<@QRUlan9(5bhI}n!jq34qSrvojCIZwH8LU}tWUHzV&H+o|E3Q8cB&3b0e(sC z{UC~;sPF^p=oeZ?zfRO)3gDOA-VdVqi3&fkj((wa^y@?&j#v1lwD*H3exkw;tfOCO z9sN2{7f!#__I?n>PgMAUb@U6ZqhBZLa1>zs(%Sn$6hBen2iDOqw2pqAsKem~zx4Kg z5XDba_|y&pvJ6BT}79sNS<=+}uloaW%yy}chq@e>t(U>*HJ>*&{sI-Et}*Q323 zMDY_9eqbH_LhI<)i8`F5;g{Xs52E;q3O}%pexY^r>qH%{0r2y+_k$>YqQVcXqhDwp z{W?*HOAP${?foE%pQ!Ky>*yC+N54+g;rau=oc4YY#ZOfDfpzo?t)pKj>TrRCUv7Ip zh~g(I{J=W;h1Sup6Ln|@zn<;=Ac~);@B{1U7g`4%y`~d&xQ4?oue~2c@e>t(U>*HJ z>*&{sI$Xoy*Q>oBMDY_9eqbH_LhI<)i8@@v;n%ypA4KsJ6@FkH{X*;L*NHk@!{L|T z-VdVqi3&fkj((wa^y@?&uHo=2XzvG6{6vKxSVzCmI{I~@4%cw_6}I<-D1M^C53HkK zXdV4JQHN_d{Q9)_gD8HY!Vj#YUuYfuI#GvfIQ;sy_k$>YqQVcXqhDwp{W?*HYdHM+ zwfBQ4exkw;tfOCO9sN2{hif?eirV`@6hBen2iDOqw2pqAsKYfJe*N3~K@>kx;Rn{y zFSL$+ov6b#9Dc>^{UC~;sPF^p=oeZ?zfRQQ8VPgMAUb@U6ZqhBZLa1Dpw zfcAb6#ZOfDfpzo?t)pKj>TnH*-@x{M5XDba_PgMAUb@U6ZqhBZLa1DpwkoJBM#ZOfDfpzo?t)pKj>TnH* z-_Z7c5XDba_PgMAU zb@U6ZqhBZLa1Dpwi1vOE#ZOfDfpzo?t)pKj>TnH*-^liU5XDba_*yC+N54+g;TjIV(e3>pil3

|eMDY_9eqbH_LhHK9`@c@q;Tn$ZJF>kWMDY_9eqbH_LhI<) zi8@@v;WweZA4KsJ6@FkH{X*;L*NHk@!{Jxn-VdVqi3&fkj((wa^y@?&uHo>T*xnDK z_=yTXu#SGAb@b~*9j@W2T}Y)g&$Z)ztB4R zb)pW}aQIDb?*~!*M1>z%N59ZI`gNiX*KqhvY3~P7{6vKxSVzCmI{I~@4%cw_O>OT7 zQT#-OA6Q4f&^r2cq7K(^_)Tl?2T}Y)g&$Z)ztB4Rb)pW}aQIDc?*~!*M1>z%N59ZI z`gNiX*KqjFXzvG6{6vKxSVzCmI{I~@4%cw_9o60sqWFmlKd_E|p>_1@L>;c-@SEA* z52E;q3O}%pexY^r>qH%{;qaT)-VdVqi3&fkj((wa^y@?&uHo>T-QEwP_=yTXu#SGA zb@b~*9j@W_1@L>;c-@SEG-52E;q3O}%pexY^r>qH%{;qaT+ z-VdVqi3&fkj((wa^y@?&uHo>T-`)?R_=yTXu#SGAb@b~*9j@Wt(U>*HJ>*&{sI$Xoyx469@MDY_9eqbH_LhI<)i8@@v;de}XKZxQdD*V7Y z`i0ifuM>5+hQsgJ_I?n>PgMAUb@U6ZqhBZLa1DoFWqUt};wLKnz&iSc*3qvMb-0GZ zud2NtMDY_9eqbH_LhI<)i8@@v;kTr{A4KsJ6@FkH{X*;L*NHk@!{N8Iy&pvJ6BT}7 z9sNS<==WdM;Q^dw=!?(&Fu2J{*?@{LN4|WE#uG};VQ;)O-kKy(`NAqX|515FtT5UX z4oTUH^N3BUNr5ba{y!-gv?!(!<1Thgkuhc_w#%@4Z*)qGx2u(}iK12URHq5~C?=wB zgjc>_i8ddg)+WlcQqd+Y$HbruME}tO&kk7=fjsUHx|pQsKE_-4pEQ)qb4C{Z7_TjR zW^uTA9Iv=$bupf#!!|auZ%j=Fc5>XXTcIsm_F$kNN8pV@GOjM!G z!eGs4GkvRxFF|LH8DQ4lW)egl;dtbm*l76%6l?c&<30$L*U!_dPn_ea`PDJr>U`t#r-@VFU=^}>d>rkW+yKbXR#w5B#ls+g%&?vp1Re7xfct2rz=>?ZVx945sr zV-638daDNnyTo*~cG+7}1A{5dO>4?8sX@UMrm8j7M^b~CI^KPxj{}KyY6eMWNU#Xg z*;@3QjBsc$h1qLO9gx(pU<&irnmS!l!-FZzTx;q=NsS1mFmtV`4<$7+n8M7prgCM3 zqk<{STx%*%Qb#a_HP)>-+F%3t&;4GN8<~#19UdPRA0F?GkBE<~xl61@2m8aEw)Ph; z{f!BxFsH34kEBYO!XCt&x+(0zH>5wjy}?8xgBfkfTqGI15kaO~vDb`m85^e7Z8Q{P zJFCVDOYL>UHAnsKPg3rHp(r_w(8I7jxWxk38FjkxQCMbP53MlutE8LWYp zOh7WYxkU|*A1nbkBgfqHlEU@FO<@hRrk;@$ZW-Ma)eSiLD3+=rnE zD;i6{&2U9uAUWJKxgBB+w08J_q;T?aQ&tbsN?o-aAvHMnJ11g< zC4~#TTZXA_Q`RUsT#S*!Y+7}Tc8IBN?eHN zZY{e+Qn-q_DNJc=s$NpKc(^I7jMmg;lESsaHN$kbG20_KTtnP4On7VAtCGSc#4W>& zw<)_>atnfGnDW-L`y_=^xm$)QZ&UV;~8iB!yc_ce63+t*In2!`+{o!lbvRQY5uBn8KX5reY+u zESSQix2EDGbsSUc-A_Mw7U|-iMQXZA`uJca=DoG@pp5barm(ay?`{f9>yTtlWCrH| zIZxqi>i%;-5crQLsvo3kdC&?gqSfj%Nu3l-VL`N}{voLq!4wumYw8n8tqi8H9$HgB zOX}oc3hSXY^^>Gl1yfiKt*LC;8P!a2yW&jWmd(Q2a2sZ~KmW!@#Ye}-)I><>DJ;dw z0;|Ps%#%gBD2b{hibf<) z`eS*ycl-<>U_rS80|;0~uD}2SRu42B(Mrxfx%2Ce=*)HtiB$0)qlajar;-u zxpANA$IRkA4VFjSXZo@J?z?Hjpz7g$W6GR=J$%;8=;a<#CSK6#q^Ihc!! zLQYsrpC~sM)5k;<-ijq8`-%r`oa5Bsy6vrAC#%Fs+8`Oc$i~SPS6;QDrv^Vg;jKQM z%W0ZZvmU_)nOA+QCo2PoU|8X3C;trg=Gj0F9miigog6w2K6myRK)@m83Jf6NIC2FB z5LO2T1`u$}xGfk!z>(q#3?Sg(a0Ld(x4bp=lYq?K2FNGnpNSn1y*kb@r(|x8!<&h`l&&U!14RBP%}sf! zpl7wxG$;QrsJkgI-O0y~EqS$72KJ=L`DddkCl6bLYbOi{Lt!C$R@NxwP5H1V~3?N(- z6c|9*5)>Ff*cucVK)5(4Fo1ALP+$OITToyC;nJYM0K#QKfdPceg8~Bx+k*lF2v-CJ z1`w_c3Jf4z6%-gixH>2>fPib9yFVB}xF#qtfN*V4U;yE|puhma^+7>{*7>+WGH7w5 zzSik$y}sV0uYZ--0Z#q_Onx;cfi<0s{z-1_cHX9t#Q#AUqxv7(jR; zC@_HVWKduL;i;g&pof!>LnFG~38W#|f&oezg8~BxPX`4C5S|GN3?Muk6c|8wE+{a7 z@O)5U0O5t8z~Da(Y%_XPzFTARKW%rHRH*MkBB2yX-h1`ys13Jf5;6%-gicsnRCfbdRGU?4vadXjPf z)oj76=6PPP-ilYbvJ92e_{B=xcN+`A&jnDyfquFSs;dTduGg4-Rp`c!O3M(}>tlYcw1E%&pY{GF{dt*a;hj#iou z`nML!yV-8uKBkMea4sf!Ifn14#-$7QQn+5Z2hSM{FqemAE-{4^@YChw(Ekk1F@v!! z4f5BaJ0n+QtciA&dhKY{XaCtpO3k=3830%#oBk$FA z5uxoQfp*liMsHkrKbK%zn#kfSgT8<5rt52lzGliR&J&KAz@t;nM!`+pAe}n#ksG8_ zcuAkQTazy3ZcVzByEW-j?$)GBxm#nG7*ZB^+f5>e-aSMohu$MZCx@OLqLV}SiH=vl zzUIj5`ZmLt1{k*FF>I#MJXf3dl-IUu7x)qpzjihT_A6AX`bx6dASL6Mh1m5Zg-5{bG@83j;tp2+jL{;Pc znkdoLc%LRgO!Y1aHgE(O$+VCWl0%;!qLV|P5u%erKT34G&eYdg`Z`-)uL&+UTx_Kw z_MEiC!jNXNFxU*`bEJ8j9Z3#-Zir3}eV*ueoiDF#CsGoyp^p5~A@$_Y7li2K&=-d2 zSAvTh5bJveU0&YFZXh2@bG3p{O5QE}10;(;Z@MG7}7fxqCB zf!wo|Fp#Qc5O^UpeF=j6QtHysdAoxPv;QLmC!;8<`q=)mq9?vuJIVDqt z*Wd784und)O1)QKr5|G^mxv~6uRiaM@S1hR;|gCJo?ya8ivG94ReinY*gnS$^qL6b zvxO2j6DLsF&bZrv10`N_aI)Y^BRHv0`ndAYrZXPBYWwjqA3r1$U?6PO~(hrLAE+DnwN1Izn% zHTx$YH8jIK;M<1YzM8ZR?pJZ-lgA95;x#Xn_9~ukZpRFJ&0834ius{eJ$g7WR2=KD z6)EP(LDJiq6FHUW%DN=hGsuIfrBTu&?aH?FE9DM-Az_&}=D?kv!Z-l!oC$Q1vDD zhS$99KN5Rp62_BZ9xPZl*=xdki~hA=urx=o8F5$BV@}~9uX%S+4rcI_Y;hclJ&C;v zM_C8STn@6%Euvp1B~L4n82?K&h0~I!4TitV2A1p?HP{?y7NcRJIl6SmsI}%~b1K=7 zWUH`7C3l)%T2`C2WH03lJI!n)yL70qb>4JB71_pG@HeajAgA)#`1|-!rX&w`KvPDFzMwt za{_aZ95H(0T65T}A-jL9%hr@8$txP4jn zZn9$5b+`ADUBZ?SJFd+sd)@v#*fla>k16Ub1tzU;5kE$PUoG*uFuwg1KV*CfPRj zS!~}Ti(xMV?0&MVxIYKl_sCA>$OhRD$Y!(UVEYl-KiP7KZ6f=MeGauBlNq`Xv!9a9 zWNx_qoNPM#9AUp8Tf^K)dx$KBeU7wWx;e6O_D8a*WaI6>$VRf4BkeC_*KqqL*ng8f z!f}?{-`$qXRoFkt4Ci68bv$yU{)4$GHjL~^j%BL#lAXw2rrAihCHMSv8%;KnEoazR zvL)>EC>u{UglvxOMwUS~*Cvx4MZbAAmF!CHrK4>I*U+wWG3tFU00a99aac>@^g)IldJ5 zczgwLYWz%KRPI8cH)1gwqy8}Kd9 zgTN(|o&a9!c^>#$^sB(4dG7$L=QRPN<{SieANoD;Utzxi2YAD}T2mDf2fVpF1-Lal z8~A>CK5%1H32mBalq%orvR^rm;*fEJqEbadjjyy$Wws#hn)`W8hH*dZ}vsN z6|=Vk7gt;lJP>m$uV0z3^!1cKcfRn?P0*4*3 zQsSt!z}3lT0jF~%%;!otBXeUnzX{K{2zrk$vRYOVN5@0t|d%g%TX`mq>Qu%+wrUfPRLTraoh=hF7JwE2R4F2Ye@%`1#ck|chx>lnh`Us*B(P zD1IaEezUDd6ZX~I^v{69x`=*5@`s3J|M<=DZil)BkLv^dzwI_Ruy2ImGxG#X3IxYf zDKGgJJ-hqrq5oo!UCQl!FHQF34cyZB6q$wn=|2ERCCg}U?;*X((yN6He`U`4jW&8pBwtILI&Jx@kq1zsuYj-5>K7TGP3hg7Z;((FK zU4W-%B?H|pa%bu*?$v(mb5ycyMK;N2dp#clXuWwB=~&?u~OflD0j&@!0HDXj7V219Z37-O_85q~}^5r?+ssM&pd( zH$6J^)0liWTFS8;9OsFweLHC#`mF0NRc(FadTeMJRa@PSFUdLw*~jQRGGim+wr!d? zk}f!#`EeE%UpISe`o(CuEAxtw_NP&g$hragii~@Jmt<{D46gs*V}HRJ=JW!=`=bQ^ zMvQ{u=dl@_yZOnFqIQ1rlk7oeR?fYC6Tlh6Z^oocyg%dVq#>q@eLg7#cZn}0&G5;) z*efFo?Aus@^OUV5J4V?;vg4Em$X2?{1WMjR-C1Opnzv@Xm*ki$$TmifopJ!rWM5BK z8##5#r(m~|og4Y*DQ}%xE0m*S@x3XWr z;_G0Dm10%bn16w{z=Q}OcTRP&3no50dcF+M$$vCJ;6NlrH>Dq9JbVd|8PiaRSg z)9hC^7c9#p=DBr=B^M-jHxrfh0_$M{%3dqJJUQDuqU>8RpZQK%P23I1ej~sAOWe1D z<={TkWp~8wOwKh|D|-&Cr`f0Mm{AWV=b6afZp-yxy-caHzr{Y4+}o^E)&Q1o?p3xZ z^2Ou=^F^=@tkC%6u?V=A_j)V2kD0IRXt2I!o3a)0P09VtZe{0y6&e02bktoJ|7CK2 zGYp?CNiPq96`NC(^^E^la*4TJ*#xiwrb*f7IW}dWNyBHOQs?B_ltE^OvV)PaDTB=g z%Kii!Vj7e+ds0${nqQRt1UAeR_i=mK8|6_Pgv!&#IJivrgHoU=z)~$|g)YJ*C2Y zp==@8B;)Jv)@|^dmonMRSGFB&irJ>@{^*NSrkdT#UId$FOtD*+Ja0$JbTdp@Z?GBW z6lJsK-JEiixn0?bU^7jVvV*hlNttER__-hEZ_Yg_v&{@;hlf0xGRIt?EM@4UDRWJO zve(0&OPObWQT8p^d{aEY9m`MQ`%;cJCn`(y?n_x<>Xh}0cppDnxnEf+*dmiS(5>58 z{&~t`Gf~;~V8@t%vf1JPOgYv(qU>a_O7oqvhs%FYsWS50m1G{?1Y2S%l`W2nNL^~K zR<;Uknc1f-A-rqqaVB!GTbBoRyeU=YDeRtlf?2053G763udPZ}m{XKp8#z1m zRCBws2f79bAhs$$dgh}GY!fjX0J{Ci}^)a z53qHnnBQV-jGR0>kb1f~QQ0!E^`=hQkcx{^&oKLyO#nO7B#v<1FN?V{b%U9x>>;qT zOhDONgX>ezHjgO#4lH24Q}(;>&eU2XzYR#{uZ#cA)N@RwvKNOvoO-UgTGiH&elv{TZ*hW*T?DwcwQa71(%DP6sl6rx;S6TGb{i&PH7s`5oU1)qqxV^kE z@{`ny%zR~^fNe3`l%>x9CUvXXt!x0;#qtBe9QD%qzouSdhABG}Y@0bn*^q*;v`fwH z%I1PyW}1|hMJJ?PZqmlMy(|ISZe}Q}n3|Dxg}Ff4iC|Zn24xL%^3tv{zbHEZcC{%k zb$dxDDo)#BPE>5+2Y-Q2Nv}?_NWm~|mGl^wxU1Y+9wCl}8Wxc>|Fac$+#UGV+ zqj^Nxmtb|~J7u55ElR65^4n`<{+#$lX*ZclWxqwAkoH$|wX&`;C#2nM_9+{hyE^R_ z6FJVUn*(;MDOHw_=VWd(>y(WLyWQNYtjiG>r0q0cC@TTG!}!L#y`)85p7u90Us(~@ zon~t=mw!XrUFHg9L-TI~yTN5?58&IWyUhzOGe1whFYRvgB3Z4u&OVZMpV_auxV)#q z-dA?}+&yUznvayNt=OCPchjtFS^xcM51UWPYR!R)Pttaoe<<5ok!T(@hm;+jaWL&M z^R=?SPW&$I3GZ8UxHjZ~xgMOi=3;j~6G{zz`SS?CW-f7+BQGbM@U zSu;slLveKab7rctzW5sZc_V+R2Qy~k@hHa&W|p$MMkc23HglClPwt-nl3Aqe(?LDc zUpA+-u)_3L%(=?0n_8Uys<}Yfz`;Y(_nJCo*UTHA{2H|7D+?REH|-s> zOWE_I_NMJOk1N|V7k9$uDP>QRJsoVBBf004?aP^!{;t`rY!}%3<|SqR+=b~Mnpc%o z4ff*4{a;gdCfS?H9vdLJca#mBv?To_^Iovd<247&hsqY<8@dCgN!h4*!ah+JAp2Zd zJ-%x@U=AufkL)XDKbD`Ie!zUItlPxF3C-qvWy1>-&Bx{^WmlHeq<><5Q8pek_Nn)9d}fHSKFW^HwqQldmV*6eN|epOgBHJ=LCW6EvE~mm zRM}gzz4$I|gtAkPxF!9tIYQaQ!S|+HTdJ)4+(SvxcD%BmDjL&c>^x=rrtD2munUy+ z9{i8=Zg#P<15>_EPqvlHR?PV!J;kn3wtmi^>FIWpvahDR65qpKsBGfr?V3I?qnG_g*)2y!r}wr| z6Q!4P%>_p#nta<+S@?|M83lHXvM-P;vHF88&i?>wZp@*UYpz%0`DLf*qkOzc4I)mOWb8hvAD8=GZmL zZjZ2LuDwLr``$xI^XwhU&hxHMpKqU6wlDIW^aZwA+1+7RWGu9QC@YM_Hv~3qsymip zv#-rK#tu>z0IRe!lpS4BpRvTQQ1*VzwHZt8CS~zBqa0^%QdZ!%=6L&rvM4-Ic!J%p z>`t6*PP9KL>l2M{CTu+32FaQqjAzSEvIWYVQ4eOUuoIP4PJcS1+AdZ03ufgM8&H;f z)K%%H*awu2z%jPYzN+j^te(^DL1o8drq|oB>8{_1qpUf@W-D6?cBUPnZ1SwVX&dZ( zWrJqz$vDeyQtQZwwW`; zeWNLlI+S#&9j5HH?9VbTvy;g#P1`m6`;4pX*={cKiQMvptL!Fa&%@y=yG_}_f#nHT z+pCrRIxx{(ZR?edF0W45VRtHv&!+fm?9<9-cn&39V_#Bs zS@fp(Ywa7#Zi+sXbglhB*~bN&;;*wmD(f5bXU285?@`>o$f01@+hxj<C-{eF)Yc`?0c9@mb!@_Df}> zipvvju|FuASe$5XvA-)D=PysV)rQS-w|zRkfxXqnDZ4nYF5xzttn8}1C15?29Y3^t z=IyqZvQvi+%(&h5SN1`lP4PSJP-S2CIh3^1mbwgkA}sw5yP$>TWZr4Fkv(so>sy|1 zpZ!DY8YdNkh0k_J_VOfa?z8dA(nt2myx-y@1N_?<*=yt=uxyucUq4{wSG!2=(V?R= zAF##Bo*6n0Y`C&<{))^8ZJDy^{%K&7T*h7=va>Yzy)8_5$Szd&E4~VU$SzYhz?q%! zce_&AXlF3kT4irxRvxw+lzog@dDw1L_Q1UIgh%Yf%AT5+XdbawDO*-jp0LZ_sBCpf zqS<9{S2l3u?94~)y~@UpTmZI9nGZ+KW42LQe;hfF*_T~r4q?U~w|(Ytzi|5=w}V_3 zDckqB9i>dR?{RyiGFd%O*s027^*mwcD*JfIc?nP2W0ZY2q$=}CdxA1qSx;H{MJ+N9 zva+7C>s)5m_)pAiu)AHxu{7G(w57~@qkUJI%zLB#SXt`e>ddF@A!XTv*MR-tGWPO} z{atgiMbFrQP^<^_9{vYUMCGk4pgmEGeDfGtt>&ZJG5d+bTdzL>NX ztVY@9Ipqm2+B1}GpOa`_wC5?Cm{XqclHH|KHID; zt-m$<>>*{Jmu$~`&3>=!o06-+ep9xz-}cPct!JJ)N9+1s4Hm2HNA&rIO;Q$DZ1H4B zcV*4}3KQP6dCGq2hi4INk+NO03lrY5LzKOMcN1^fG0MK2RhaO$Em!s{uBmU^8Orv@ zo|o{Bov-XrY*pqvwo2JaG3O=hx675C6;qYD-^!mGm34jhl){8}?RsTT;d%FW?YS=F zihR#*(VVQT_w06M*U!B^^F4c=vdZCq1-n^UY*-;$-l?ox!FJ>xR3_)-_w3`!uE4i6 z@7X=d*5~X*%hy~6_j|w+=ey&Sd~Q!x_8X3|&+Tc-J{~eV z;UD&_V9Ozc!8R$|hU4f9dx^3gIF7!s@}#rO!}opb5)RrrWs&`sfbCTF%!r1}L-syp zFOPU0>``S`^)5{K(mt*1R{Y@4m-Z!PZ_KSr_{zSatZD8Nun&|yHK;J*Yx}9Pmj_w% zwf$Pzw7G=|-`Jm&EyBCxZ|q@Z|L#+m@U8V8?T$LKFW$r21Z8(jE=>5&rYhTopQ-rH z`jlOrU6}Aso3HFvychkaEm2krzwhmEWtYJ3dplOy!HIg4oCTHlM>_%mMeXnHx%U-N(uxw!M{ohqZgy7Auo$z@K)QGPwr)Y0H$Q4*n(6I2FpW2mb*! zQ(1V0C(AkulqFO|f-P0nvv+)!rMD*N0fAw=%gt#5%<;Gs`RbX2m(Tx{TWw@7$v;3+HT# zk9QtcHge9Pq_wErmP6tp6I-*Y$&!p(fQbAbm-!IsX3X4F3u0i zWFERWzblKznYODFw#eP0Oq^-EI&m&zFWnq`oyDz{_1Vqop-k3iH>bC%03QkFxnoza?;d;OlygqB>Q>FLbX+`mU`&dPI+X~}KL$a7Y;F#HUU^A~OTSk$KYUe4Jq zxkE|4oJ%x!Qu*wJ-p*CZmf}e5?cCVX^5pd1&cm8}V_scCf%BBIgJdtXwA`3c;2hAL zamo|=IR9wL;d4dj=N9HQeH~Nj?xj_|F3sxeM7j+7Wm|e*Crxv^dL2&a=lEK3i@}OD zw|~H<_#$VRvV-{iy2vSOX?ZBAzcW*FclFy8U+gSslPh*k(p*=Jv&31gtcR0mO8AS# z<#;%LXnDc_=UmO5Iy4bK39`jy7-v}eK<8G?$xIJ+?p7uf}C5O*A9Z!|Pzm1WaHWQ;B<&0A%XPZ&ZWM!#?rEZq8n|z1C7Alh!GRj%1 z?7}1N${OXYbQ#=NW{h&4)ZAzIyl1@gd`s?7(s<`>&8@^;(2>qZEjeqBbiUEt>=A_t z6P$mw$xU#)OWbXjPq50Jc$dL15kG^|LvwNlo9OgXCR;nvDN5agNED z?(EW>_#Ndmw&bii%6Y9#Zl?3DGI5yceB7qxEa!(dEoVEwDcf4|P}Xe6vy^)Ye(Td` zJDDzv6u-GnuCm7`2h=cjmOoEp-;R$t`tGZj)Q)tZkE9=A5s&xcM(+ z9p`Lq$vvHMoO68(^P1zG+qC7-fv;p8@7$wIj_~81hn2}OeZ2FOvOn@>gT0{a6}(S9 z-r1|{$e2y>$2;#RyAhvj9Pb=Zwg{hO9PfPLGVGVD(vNpW9Ov#c+4fb=SeGG(r}Lee zZF1Glf;PEo=OoR&(WiUnDbDJa+`x=eoO3iM?~+e-E^NsyOh47RMswn~+PO(t9kyt- zb4N?dF&V3!r!}{#*Be=DoEMdyGOavejq`d-%Of+^IEOSReV*og-;%TDG{-rJHXziLk0FLD0XlEX9S&SRP@9r1P6Hs_g^+}^Zp&KsIrH1VgbOP%*ya#J%db-vV` z^m&={Lrcz@%N);%t@C%e6XP<@-{nrG<{bEKcXC^Dd(*Z%12q?3@n_Z*&d8Qrea02e zWX%%dnUBq}|{YYR)sZw0oU1pe1)C*jO_6+oXDDT1(E0pEsXJ zcCN`Zle^bD@>AtUC$!?@eI)+B_<#4n|J?)s|J(zUa87W{{GP^m%(VmMY;by>D586) zyJrl?ME2*o0#8;1m7_*V{4&oW&s&M*4|+SQ{8#a7Y?(AzTHfTBs+s9rFZEpB_9*CyLu!zh0uhgOPj80c>5Ed4OK= zBczRs`+6oOIwsC1O8Z!p8gB1fXpgM&**zhhkeDm;5(O%ffC9VGD- ze3OGuW4SGE`~>@S&&eCGrTE!Lj$|H2i&0^_Jh&r}ktbqX!p(bVgXc(M41T`kZhUta zX->+KsKOCcUc<_uPh!jHd6ZhuAsec%}9<@w$U+B*c%@6+i+T*Y;KoO?SO>&G$jhew0! z;rNLXzsEV+hvNc2#fW_s?B9(SayHvuNjtw+WiEofwa%2IE3H+r=EhiQc`DWq_77Li zV_bXPIFExc&fuDqt#JEvbbY#ONmePg4o4;a#ueMRYcH8cx_Gr;t8Klm<$lP=N^{K9 zeWV9>zkNPI;*Axpj<55~;I8DhwNSDdyh*^N8jgPae8=i56t?*VM7 zhv&&Ko`=JY%*g-Sv2|u7?y=dKmjCD5xbrCA?0ZZNWW8zo^x<{XTy?&Kvt+wYh_sDh6 zgf68tBJa2s@J`9S(>{A{l#x&Nx&XZ<9T<%t4wcW^ zONhgXWyA{NQN*K(rvtraGjNB~Y^BQY?4Ep=Blv(*;Ed_@x>MrZ;r|Hn3w>U9mb3lM z&I7}@#@*?hUvN#_gNW~st8fa3JsbD9v)J=G;=fPbANMkJFM7LjYXA77on>=|#lPv? zGJID22hOr-%i{6p?+TX3pX&VSUlm_vJOzJ={}nx-9se?WsC5SS+8_6UbL8Aj@td9B z@MP#N^UTzX;(MCC{!8M|BlaZ@B90=CCr%;GCN3f_Bd#QFA#NvLM~o%jOuUo$An|eH zv&5H)ZxTNseoFj`_#^RmSLdEN+WBsBU3{38(Z&*!h*`v*#Jl#5Ke-iRTfw5dBVW|5xI-Gro@a71z%}uD5sXh7q}VPHgvZ zd0OmFJOd=_vk1>M%NpBZ<-746wpZUopgd!>!^*RpJFGlYwZqCMdpoQ=ZS}6baOz~> zl_O^3iI{pk{dK9+G>tUNn$pOvR1?z8ed#C=wt zeAr=A@ZIwcj1w--zwuqy4l7TP?yz^_lkb1xsl>B@U+0|%d|>#6z`c{N!ZR3mji|%; z|D1Po!jIfWi|j)QzjNh;c|It59V1*l_n*KO80S9`OFxw(enxFIo^gtD{*LeCqMT3g ztz(oU-@n~wpSM812zs8T09pNr^tQ5L;b@d;afw6q#=3S74?|%Pumx#t-ac z<^uRUn-xVdRvfLOC_cue#4<(ED;ZZQ ziV|R4t0+nx<9bC=8W=Y!iqgcmSy2?@a14r~_!#>YMJZ)mrYK4!<0?f_0*q@FMX6(4 zuP90b<3>eMniw}Liefw*kD@3(#(qUnN*R|aic-nAN>P*m<61>g>KNB6iqgQiQBjmV zo)zYX>?SJBilQHcen++mTLX z(BH`pP^ncEeKYjWvg@eSD~i4o`VZL+R2mgU-vizFny54@ihd9}7C0XL!g^B_Jt};K z@%emI{EDI%K;N5PN~KIu^a|+3zDg=pilQ%veuOVTrB+e&&Cs9DuA@?~DEdz5H)J6T$H-iXIiQ z!o2MBQSmE^UI2Z+uarueqUaUSKlW8psZtbuIrJZV0V=hMqHl&C>93 zpnsNKN~KIu^a|)dWLHwDQWSkT^tb#0Dz%EDZ-#z^uZ~K+qUbxJf9!9d(x@o<9_U~B zo2WD^ihdA!SdNL|_!UKuidkXeb9_|%ilP@l&(0~OQl=<+1@wM7l~k$}MPCkmR8D|O zt)l3gp-;)Fqf)OZ`cCMJavG>KDvG`b`pG#>RGJk?6-AGVU17H7_^9|5 zMK6GUeNHKrGDXoVpx=;PNu^3r^yScR%L!1aRTOMbQhOJ>%b2_1I@R2mgU z-vd3oXA_lXMbS+cj$Ba`A7j6wD5Z?c6h*0IT%{;VfN`y&D0PhM6-8-a+^8r@6XRw@ zQA}5kM^O|XW51#(rHsoIMX6+5r6@{(ajl{#b&Ts3MQLE%s3=Mk<7P!sOgD~4Q4}9z zzoICmjLQ^7sbpNGC`y2Ft)eJ(jO!IeX<*!_C`uFKW<^m<633$`ijT2hQIt}~Ws0Iy zGOkh-CBV2=QItBy4a6p*Nfs|3v6NUz3=nG-SY2A#o$LJaMn^3 z#mCsMC`u{gGDT4;8CNNa5@1}bC`ujUdPPwh7&j`4(!{t~Q52KP@hFPoW9(NHrIc}* zq9|32tMFU5uE?%sT+6tgaXsTk#*K`d88?4JT0dhy<1)r&jH?(|F|K7?%ebC# zJ>y2kjf|TaH#4^B>_46TGxjqsV_e3#ig6X=TE?}E>lxQGZe-lZxS4S?W1GSLGuS_4 zKjSjSWsIvBS23<-T+6tgaXsTk#*K`d88^}{&U$sV?X0E#$}AF7*{c_Wn9a+o^d_nM#hbdn;ADVwmsQ@PxjB)&$x_n z8RII(Rg7yH*D|hWT+g_XaUpDRuTimI${H{ ziD<^KJ+YKnNemF{hz-OhqA6v2Vkxnb7$DXW8;DIrQ^xkhQeq`BK&&G+5Pf5%Wht?e z7$DXW8;DIrGmd={ONo`l0I`nPKx`tK@zTDM7$DXW8;DIrb0qsDmJ%z80b(7of!IVe z6F6F8B{4v(BQ_CDIr}7*5-W)TVjZ!8*hDlF*`8QRtRx1AO+-_{HpEh5B{4v3AT|-r zB(@}05(C6KVgs>>XeP5Iv6NUxY#=rf%@k=Ok_`|Wh-RiJK4K-Yj@UqKBAQwBB32Ru#5$sx%`CB! zSVwFiHWAGnx)4i=mBawCfoSHkA7X&mKx`tKdCU?kiFL#VViVEKXB%RGSVwFknxmN| zRub!oO+?=U$<`4Yh-M+%69dEsqFF?p7$7zf&0^6ji2-6Av4Pk`G{;CAAF-4eAT|)q zvFw=`Al4C^h^CTRVu08{G*#4z0b&EOiD;J4kyuHrBQ_DuQq~d!#0H{SMx7WSHW1Bm z)QJIN1F?x{j%SuwM{FRP6GZnBD~XRfKRKzM;hu$_4W1i4Pk0V^{_tdnm4z)0J3s8U zusva4hQ)>V4?ilrCj5%<2g2V9|2aI}JJP$@d$zaU`;7N9Z$w01M0v#Wh>Ifbir5?R z&xmf3gCplfo*sE^;v1?PoYOXD7m`!Md~xbNeBj{74H zf37!vcl_(|-^Ir#WF?d(oRF|K;ll(Eo)HW)GYy`n&GQ2P?2Sig@fV}wf$vT13Y-(0 z0_+`|0o+;K16WgxM{)5NqwqK?{$f-i@L$D6!0mB(6dHdqY6!4)6dtz4UyK?papYKF zWv>aq&GC3(AAd1Qei0!4ViYhc7Y`5OFGei@{_c@q2JmIn65#DQ#{=)qJqef`u?o0! z$ZFudLrw$M#;ymxe*|1H`(xnZiqC-uVh#bj4*3Sy&;LCzZuq}|_YV6vuz&Pn;P9z< z1O$IEN}hHeK0g|G?tJ_SFZ{)*uE6EdDZuKf8Nkoy^Z<@1$^l+p)C<@zp%D0ed=W4z zegH5%W(aV7?g-%Iu+hL_M~sy?Y65U{41VDR{tnbM>&|>Uo}o>~6SOIK&N>xOnDoFi z);>I8jW3Y!)LN|RXW~qM6K^KqiIZt~rfrGoYEHrv7^mXdlNwa6N96`oo^8_1xhBJ$ zZ~mY5zCBK^qRPK+%_E)143i;|SAf9;2pIB!00{)hB$H$&OrA3#;pL=fx--+Hr+et` zNis^bN29_L6a?fUCT)$hU7-$FpHoMu ztJO?(4Pv+!zqR^#oZq-k%~IDRwl5;K8*v%`mvCC{W_7H(6Q?@vQuA@A^zrJe>IAh% zov7|n3)FpTq58TyN!_nb##yvQ>YM5md|z^^`Zj*M^g*>mJ*1YZ@2WQSuv&&MPL`|h zsTH`XxgGa2cc@44>zW3D)l&W^+UBrJ)_pDXVp6O@8}CZQeC(axm*2MZBQ@b zH)4OHHsUtpO}KsdO!bmF3twEEjeCO6QNK{f8Nm#xP5*e@U>Mt08gH|6Yz1CdG|Py*|g%5fJdLa8?d{E z^n7^&!CxFj@Wgpn0$zLAwSYJ3F9Hs8*c0bb*lQ0XIP-{`h<@U&fWP|yg-V`Il1HC> zCt%Bh`v9*!?4OBJL-Zy3LBOHIDeS`=32tL;%TIn7c#ouqB_)TPT=RXj8}lC7XBKrsg~&b9YuPie7?dp za{u%D-+TsBW)b`r zm;4=v5`H<;|JqJ?(@JUyQhFD+yaW8Ri}WG*HRBBv0WapfNAo4}k`gJ;7fxt`e7R#P z;8%}45b*MqhXP8Dq#Vv*Z4WcNwVGO)>#&x}mTY}{Ag%c?3JuM_7v48rMEbw1(ulxkp=I}|RO?t7E zx#*dH5~V@nmGTr^@*;Q`{>!`)Gz;bB<)rg7ogZ?P%O_C@JjV3r1-6iWDXZ@JWk^bC z%;nM!YZm;on?4Ddwa4uSl>ACggCj`u(}z3FQoL3Tk@Mj z5||q`-3A4yVQbZ3jy(VtR}<#STBg;3au8hcumy1p^4g?k zbB?DXuK^?ufczZfxCt}+fsp?Oavb1y8x8_}9`cRbfdDmbz&;rC4+17I?;isCd_awv z5eG8WX21mOfELhC0MsfO90qzXU;=i*`$1m-sPT)g(?MB?+&95Sm;p*EI2@8`zyvG? z%Kaihjr*~W1ic?H0SjU#=%)hW3xnV&P)o77@JjbC*A04S}Z8Fz(93`c*fJ-9z7|lc(comz zKL(h9ZL+lCpZQ4PXQ)i>zoRDH=tG*2B(335nuwg&tlNe0Mz(J@mA1x114Y# zEdhNKpjMXzOF{oMU;?&L8|Y^NYW(K-GSDvrOu$wm+wmMgtv(y90R3{n1Z<~v&_4*M z)fGVp=vM+JU`wq8eKVj|p9@Y0{VKo&Y^zStdjU1>)?NkrHGm1&TB|`%0cv$^um<$c z114a5tp$AxpvEtctONaezyxfu^`H*`YTR$#1^SJE3D{@dpq~eb@9=^Rpx*?TP+L(3 zO|aU|0R84*Bj~pPCSb#D0zC()amVzTpx+9Zz;Cmk1^N)6#_y}24f!LBA*H z1N~mWgc?B^G{GKEfqq|*2K}D^6Y9e#gC=zWpvG;@{h;3um{2=W22JWCfas4w2K0Xh zOu&*q5A-siR^JS^g8l$t0>7w`1^r`y_>wyq1pV8930VC((02i9+=!e9{UN{vRsuty ze-cpR#^dur|1MwxtAhgQ7XoVBVO#|L5x@k#c`1SZ4}cnX6c2;`FMtVkG0FgUxdUq4 zLA)LGM*$P+5|lv`))_lMe=HaQ{ri9ktU5jf`lW#Qb~gAh=uZG9unM^V^v?on+!4GJ z^#22xz^ddUpkD!~@tyidL4OJ`p{_(3G-0h$2L0(^6!d=sOsK0+2DsfFP~#5YkAwaU zU;?X|U7%kJh;Mv@Pk{dKfC;Q_J_-7DfcVlq_!Q{R0Vc58xe)X(0BYQ^dlBd_04A^k z`UlW&0MzQo!Ns7z2$;aCXgBCz0@Uh1f=fWT3Ax9;=zv=NH24fCw;g7Wj=^Pt>~JT1YQq_xmbS-cmfdfuYM5t1VGHW`a8fU z0b-uj4+C!m#2l)>2fP^&^QQh+;8OrK?x%bV`1=4g?v;ET`2K(zw?jS&{6Ijh4$?mW zJ`GT-gZ0zE4*}HbQ2j&TEr420*Uti<0jSmC`bWTz0MzP8{XFoQfLa};e+>L+K#jWv ze*%0KpjOA|p8}r^sMQ?(GVr;88h7{o4ES+?TFuu#2Yx)DRwwFT0$%{Aal_uPfS&}Y z)yeudz!w2(b&7r+_^E(eou+>Wd@-O_t@`)CmjG(DRR0lp8=%IGbbkWg4yaX!{tNJx zfLfid-v-_ZsMTuySKw;^wOXs+1-=eYt1kT>@NPh@Hs~P0FLMBDwGlsjk6(rc#P@u< z4)|Gsus8HL;2#8pjiJW_-wdd6BiTgYeSokWbOZ1-pjQ2QAK(LkT4nTP!1MIJfLrxc z(6fMA4eI>>bNT?lygmqUSRV|yT^|aW9e`SWNFN4x0efz0Y~+bfFIXK0q)Wt z0Q{sr2JlmQ4&X(49^h_$9N?$*@qm}=6QS)gK&>v<3juNe5-6Vo)aojI3h=7|F(>NN zfPWqk^Pp}8emx-OKD`w1OL`gLO?n03ExH5n%ldS{TlFfy+w>a1+x0rYJ9HP|SM&zJ zJM~7uf7E9J?$KuheodbXc(3jO{3o3R{JQP~ykDmQ|5^6~ep6=vzooY#4c`XDe4z({ zKLm(740Imw5q&-=-vh*GuZw_>>S4g|>+OJ#>k+^w^@jm}pmzd3tv?F*tR4mYcl~j| z=kzB4U(lZdd{JKn_#gUWz?bwTfd8pK1Ne%*4Dc8Fa==&hm4Ls|R{_4JuK|2re;)9+ z`g*|M=`RAlp>G8Iy}k+X5Be6sH}$Q6f7Z7HzNNnc_+R=iz<2al0pHbk1HPy40aP{j z0qUBs1J>4j1F*j4n}CU$ZvjrIc@S_?&36DBYaRyNr{;TrO*Q`tIJxFAz#(4Lu!5m*i!R6;QMQS3^=3aCxA!P{1k9z&C7sC*Zd4{ zR?W`=XV?4^a8Avy0O!{H2JqOL*8z{O`5oX1HNOX3Q1eH?g*ATyJh|pC!BnhD4#ytd z@vxMa!4B(&ZPy3;s0bV5D0E>w+_bWRMBY4@QE~;G*EN;F{pZ;P&9|;2Xhrg2#fVgXe>ngI@)| zr|${%cs*Gkpbx`$uk-Xm-KsnEI(?>2>Wn^LkLXc-k-kiSQGZ!~qT%v}I~pEu_)Wvi z##N2yHh!*g>OQyc^PPRZzfWzMzK9S1OFAjrMS{ql=Z^bhNwas-b2&YGS%tho^|=N<8=Cc^1!`c&64)S0~~* z6VHe7T#x5LJTK#^shh42$Fl-Y2G7NK?!fanp4aj0Q$JnJ!_$Rl7|&IBzJce*c;3Y` zZQOKq3Z5Q3qj+w@^F2Jj#4|oIU3~!0Dm-~Sm*Kew&$D>`g6Dwo)78m%&c*XlJU8Nb z7|+k~jGHiB9fjv~JcD>XgXeBMKg9DVJo`Jn)=}=fe#PRY&7R>YI%V)pr|_Zaj7PVjF*pX7`oyg;RTb zdXAmjGgmD-bzx7>{JHZ9U7jhV*W^|t2h%HuQ~l||bgoofwhfS^MP~JIHk<6trZ>-1 z9m{gVgXx0tElp;#y~)0A#^QUf5x))RkWpR)-UVw zv~9`kaJr{Qtw@(xW$SQhpgYsIHC<{;mXc~&p^z_h(z$dY)5po`NO2j$q4J;_Ce%1oxXDuAfDy9zprKI+PL|<2 zoN{k8B3UjpVVvZp`CKWP$rV@Ua~p=F&Q|A3tJB*>8g*boccpWwbipVHc?^{ju%YM# z3_2_nVlFMDlW5aMf%g#)aiR4Xmh&KPp_0hjY;q)vwrPYy-nHq%mV9AQ8uXf6_W&Z2 zBJ3z`$gzi-rt`F>Qfk$3wv-uy%5;IsxvQ^`PUkwg#j0c~)w3qon$1!i=|a(v3+ci9 zwsah)Yq+m3T`bz-GYUxWG89fqxsaYDage_)Q|v|Os}}QQ$~@ACHq$kfhFCF#i<<8I zFuE2E1n42@r31-ae^^~S+ozy2pX*1Jr%{f5(2?wqs-T39E#^n0v1KH>w#=3-nLd>L z$a+d-B&lQYeOgx|A(2p&x-i41CrlM3%TduNrS6fTsGb;}sET3uB(0EzlAd=Nst|*LU`D99C?#5^kQA){-vPEl@?p3zui>3AX5(f2r zE>>xrfT-n?i3#qNV-$$h8KR3YI%8;>UANhl&W}=${2>%Mr|clRhS{-n0fPIs*AarA<CsF-|#xMAsPh9tC_DoV(fOXo7V{~y*c z^8-3N{4%3@zeMOK&91GPp`mcPqbw?^j$+q9vH-b^MMM!b6|n;h-<4K`Ljjg%^F<$- z@rnMmN!TEy-ym+XhIIxy3Bi6#cP5La4MResyj9aAS<*L`Buk}idVPK*nN2UtW(G5` zYkll%a(k7M`r4}qrO9+ZsFj$>p7JzT8kJ-(3iF{)dU!ZtBwfLg092$H1qv0J2ootm z-5D=b3gM!nt1n+jcMbOrW=a)Ws_3?~y`(K`Pk|}oJrtm$xN4Y`QcSBbkikG%H=G{! z8LK2pf-sbn41ImNFOA_m)Z^lq5{zPk(MFk#28&}#Fr*p7eIL(+(QvU`3D~_o1DoED zwb8(1O4tNf7_uF?E%~U9Dr>hwNYqfvs32FgunLY3ATviaI#O1}lR#wHtWTdmoQB;H zsgyXTk861$gY`#|MyqI0iZD)q2e1vfBvxwqLgqtg0+|$+k*VWP-P+T$1nXc7jmtCX zEQpos(#QkmWc< zcIg)lz>O#-Np7td4I*QFTr@O7%6cyv$}Z)ioX|ok$~4n*lz|b(PHb^ZnS$atGN(j2 zRkKG_zzyY5R%oD&GD)!;9i!}}`Qcn?MLr)>30+tu_NC*3q++P+)5W2Du9#j5t2!En zZ0#tOXXaQ=ExpX$3$w9|p z@jgjB&#!Oe`P74Qv1yfQU~InWV`B?Yvx*jhnd_nqDdM;wWGTsBOT5a~;Z!Cc&!RWDRyili0G7y|BGW`ZhS0E*Rx$ z-k^+&h?Ci#Ddl_5!wQ8qe4>ew&5J0Vsx3;zUgL^vzBieTXCq6+d@h+?LRQ0A3OH2- zUW`Q}s7&%|>=h=nF;ddsqm0&V*vBnwD8z)J?UZPMB||a9Rhe97a5#>U+!4>PO)MJe;X3iKrG@r(iWw8^(SzE|#!zMz1T&U#*Odv5~IP-CI-v5aU!TVa#%ylRG zW7J@MUp=zKNb%B~-#E1v;(eRfY4OY@cr7zFmki9PLaOK$h5T?F4;i8T z#J!2Q4ZL{GF%kL=x@Zs{o>pawF~MjR6+`aG_2seafNdRm{S+gB@i~U--xo#`(b_kV zNpF+4iBYbs=Av{n4|5BOa+eMl3b3ok3e5RlflahhG$@t6St+Jde#;^%*hM>XF$&BE zWHf*b8*8>~!OlaJ8#V!G+=UH*w-r%2*zed`i($~_9%n2Xh75+WSfM#C8qgF(l+ju! zVLcqBlkqkdPg+~Duq_&lS!=c?N209t=^>;(N^kA!8!jaKVmQVi8C!s3E5w8}Go>F* zqS2x|(VV$A5#I8QgltWvWYAXY`E{Q0(6WjqrqeSy^ICLue%TI`EjpPjQI}(7CA)>3 zZdzmU9;o-_&=X5|lb0-{lr%CPaeMPbSf4Io17jOrG-97jNJWpzzIW9w&DvzKh}YIB zHDnRm43lLXM{Q7|(lX5?2MAHD@yeV~a*7p8at zlc^#j!_s*h(R#Y`p>08W%WoEdhTo3XkS~l_=|Q6orq2*Iu$S%VDycN= zFrJkeV(J!RVR$O$;c{Kj8k&oMm&{C))g9CAQd*T*jzUlwyE+Z+Kg+8F=2mPEhUn%! zM4E*<(>SNW7)daKEM(Bt^6ZhCme5s=-&c4%o0q%k>Oi8_fa zKH6V*n-!{9f*Iwd+)jmP4SVTShZ8hIwe$ON*TqpqO1T&P5{Qa}5=sS?vt{EvQg2HK z@_38mh^}y}=%l7{H-uAPMJn;ex>Lr5M5ac*;Am7dRof%(p>&|b;;r&FRn9Mrmm76RoTe0WD8r?RF zD#-YuHWtveIZGzwJLVW|-tj2`E5_WY z;sou5Jx1Pm;&)N3$(=z**kBNaZDBs==49lglT1BTL^5(|+uo6p9y2;sPNYttU<-@a zhB#VC6C*x=N)`ayk~s`Mgi+Qb`)-QHRJ#dhik4mx`Ip{g#H8iLu5bKfi||6EQDRO{kE}(Gib4}jG0|~$I*PQ8CzbxR z2H6wYG)U5SsV~Z$=~Ew~dGp5z!Q4&{@#JI(qz|35z~>ARRjRcFOSBiSIZZyu1gWNl z^5$8s#bSD}H#@>sgeP~Cg;U~zk`7sVhLxwW;S7vG*m8<8>CGax<7huBnH^IK=FFB% zKNcD!=aop_%uCm7atC8Y&$lVSj!$a1uM}mFf@E&QGmI(5^irlblg*Su+?5!-p=&6O z!xTOYHB2m7s6>~H0Ps0c)r(yTv&K*~8Om^lO<_~>b|0Agtlt)9E>E|#lgmu+qnON8 zygZxi_uRq=`_1uEIRJ^3i#;eSLo1G*4i4sXv)Ay(ZE?11mG)Sw$^2|n4Iu@1k2#xq z`)s#&R-8?P35l#ABb{2l>^pJ3(89p!&UXm{5t<-rawF5pP-U#6eGd|*yhS#zIfqdy z$&R((%|XUwQccLNH%I7=7DJ?&BtoP>_Ru0N2vt`pKV)o3oAl6@IUQxO+vjA6^D!%g z!fR{qp)7D!R_vtv8m%PDSl}v*m$o#4o5CaG!VeF77jsJ!I-tV=$5P&&EdDLURz!NS zNlj(^tb*2DOAE>3Kxe*REzcL0CHn^U-Z;gY!_v1jz%?ar6u5j0iUGDzDsIde0h;BR z%?K55#cKs-RZ`?NDpKP@prsYVZyGpIEYb8r7ZRGoBx*)gqynW6TLd&lDtX<(8AHRt zNv}*Y%f8AcA#W-;6|oF=oO}@PYOxzd3v;7K-epKogG)WjatbrDot1sViLxXJ-hX8H zbAs4Vv0PsHg~dgaoP6ufQ%qs5GkeAoFl`irM{F<}RlRyVUOj**mRr@){# z;$H1=NqOIjbcxTs(OVxDqz&efH5>A-DGj6xWc8#AObuDL>@QJBY!Lubv*^GF0A|2t z#duf5baHXPaFB^+2TX9;>f+4(3tG{4CYH8|Twgc7_P2L=jnHw0FVc$}=$rO*ho*dS$p;91o#iWLVg_v@Aw2$BnDlrQO5t2_ zcumOlOM|iJQm*W4L00TM>(l)x0Gcl_AIg>}3=8&CAWSGUuNgic6m$Mko-+q=qjE^u zBhEyVHOYot(H=r1qO_)}oDic8`}ghGor+;04KdU))=s1gkK0uxf86Sb=+acExTwWD z8-ZAE0F32&D?bvjE6v8fdDD^|snF^pl<{eGJ`z`$VM|kvJB7-iGVv0GN~t>G6&$e% zDvbdXXb=IZwrLa{%n5KP?R_asrX4BF6?}~UZ*2#|%%hK}5R7zElVy#J5JmPK96jX4<_}4)7-N;p zOge#HS&&Q~rhv=nSh^j$`4hWr7w@Df3KgnEYFSc2%I<#}uM{_xFb-^cyVh`B(;Q2{ z?3#`d+$zWTuy^J00#0c;Q5rYI7BZbDkHg5!F^%7T$`M{C`@zfws$iRj9~*RwXs>Gh6;>TjSCUGD zlJRL!NtT^|Q=v`KqEA*lJ2p-%=&`*56T?Jk4wPVqcQZI`hoKpx&MZRpCEF$viVvHp zEPZ#yjet&>a4j+-Nv1K3nz^qGS+|FhJ+_FHXnb(0LJApGOv#Kp+JY~bqvu@|BxTq% zAC0mJNhe(tXC^lwKs9O#d0B=W`(-LNR|rQHJ*KBRszsA7R0K z>%tyG#W#sQHpww9b|L}8rhSA-se}y?p`#G1Kj`z3g}=4)R3y-pdqkVHYB17=KZEmO z(fQ-Pg!2NG-?Di+rJphwiPElIA`JPI%xiF6UzXmUQ90X(Kiu*%zO9*Wtvt(=EV{hd za-Ks;!l7pQLduh+@Z!H~@=`Ppn$illV**n@Qp$Qu-ZQCeY2D)~BvZX;^IYk6B*RNH&mfX8Wy$0@lw8|tMIy{Yn#W?Y zsS}covn)J2cJ}vdPIBBt>_sGT)J||20Zcgg9NP1R6T_fFq_!=Wnz+f+A71o2tmtq^ z){Uvuq`YWlCy)_!ZxWR}n%r>ul-r5dDOrrtsnZzS#|u+hjY&>v@r=rpNwH9q@e&db z_HR-n)kL)RDmwQQG9^Q@(r+sx@|uWC6)K&S>3F9HQ=PUh`QjA2^+fN1XvtHwgf3oc z+BZwWqJfJ}>arCVI=RdK@L^rGouuS#P}dM%wXNaZF{j+Vb&@p!Hd$mTK?ub`1uAEF znI7^Qo2~K`FAEvdqDs~^LW#h(RkLAJ-Gse~9N~zS8sGALurc_dkwp|!o%p0h*rLV@ z47zm38tLtF!;ddJSyzhR+fSiQ?htX%*;o9=9rKwIeX{AoV~c2_)o#(095+Sty|vGN z1(jzx`e==&BrbmLW9%ay4+KmXtZLY|)21+`_^EQm@3t;HNfrU#FWWC7UbS42wTf$v zl}+8eL^Y*g&S7)2tU8;`X|JY9*;P}!(#b;K04!qIZKg1BjGGnEd2ZJ3M?t2Cymko_ zBKbvAi0BH1npcQ^p1VwnBWewu2j{3N2(c}UM~-$8E^y zl&>RuN*t7%EFWZ-DEK2x+9aZPojBZ}V5iV=uaa6R?FNTX3wY&+FOr>~-bC29#jKRt zsA-Y3;4SvaGKFIpi)S`hR;MLh(m6SVLESbfM`0E=p)PMJp}~fli6s~0#t(P2O-|9X zF4;sd1lnt;AlOBiH13KWa^s2HbcPx;lDHbCY1t1YmE@`|6Bd;&P&xDY$=_=lQzAdq z(}DF%rZ0nPq)Cb@6So}5))BqZNn=spiAx|r+Ccz~1D_12!5+MEVT>AL_$-zGu7Im% z43^oNk`hDbE>+LD+$GG}HEHudqWAo66xEOv2Gq z|1=|+S>c*M#PEH&l1Cj4BOL9Mhjqaguge&n99aAv8=rux;O{xeo z%&>hDPW_k*UlVw5OJAFa%Hs>7J#1gC8A|8EamKD@jk2nt+bu^8n<~XGHbX=)Cqv84 z5YMfTY4sUSNZ6JO(USIW4RSbT!eVZvdn*Ip(NHG%5YS8R&J z^mgknBO_GGRW(P7&9A{s03lbb&d8Er^Oes7W856Vx=SW%Y9n06JRx|WH{ikxP(N!? zf+{#N1y+!}4oQ<;u2yQP;)$LrqAvv5ge5aaIkQpHPjejw^SSZ94fMh8R%yF9r^>;7 z>NrFSXA5`?tV~@Mj}AIuc*ThmDZcO#EiSfpetbp) zs{A&*E!h#WQCH!!Kh6giDElZYtMl900@zUMBNU50V-TQ%>2p9Ignd$=LntwK07Hyr zMH1bz~~xk7ank~v^388sDmdn4y*3M7Zl3rMeTp$JBy5*oiYAzTnUeXgfF-3w_%GThvlu!@#h{L;7%)d7re5 z7Su7NxQO2_qR`Y5TDZoBA!(@++aPkDgBHrWaFXz+I2@&+1yssOo~cE?Zo7G5Fn;`h ze{AumFF$B_@y4ljsQxQk zwEf1_m}Xk(OjNpgbgKNH)D+YlqVSuE^l$uxz(~5FdxT&eC$Yy#+%FQ=aT51CiNzve z9VfBaNn9Wj)^QRSIEfu1VI3#2V~WXSi3JQyn2eNM_$CQ&9T&dIhL^YvwBddH(t7@cBuj9w@b)^QRSI*HvPVI3#2+e(xlk#tzcN|Yb5 z5~JOs!#Y-CwA)Fn6$$G&iM38biG+2Wgt8K4sdDF7iL$Ht^1YH$>o|#fZHW)DfPuEe zhg=L&Ol9l1@UFDVuSyKoaT2dO9j}Olb)3X2PU0Pru#S^>$4ZRWvV?Q2#AvORC|@Bu ztYan0S2&5MM8Y~w;wdMwOC+r0Bz8H8OGUyuPQvx1@(m(k9Vc;v)A5o>SjS1ci3dc&I!@vNCvlrdSjS1Y(W(5J zNLa^7yykSgDH7Ik5^p+*heX0UPU0aa@q|cN$4NY4B}Nl0;T$V5ny?b(H$;bZtVH<@ zD=~V7=&+8J81-%JMbTj$D^Y&Y>L}kS64r4NcRGo;M8Y~w;w>vNnw4}|$4ZQ5oy3qx zSjR~WIf>08VI3#2*-4~C!a7bOZwCCZPogmbJ!`B5t|dXDI@j+Gca$4N{Q3F|nCNmio#oWx=s zD^Y&VN$eI0>o|$suz5!h4kn*l*P?@Iw5~Z5o_!R|)iZ;A6*Ko$5K8KU>&B0R(7tNl zVEi~ewRu(0yiV6E1Z&a=M_w|V_rNbYDd=LPc#0etH-QN;D=EmX!-9kDfA4>1n_kDKm-5qsLeZ>Xmd z*jFJn<)7=i$>qxqY|=;$#enEIB4)v!59%kdMBRh{a+H-eEM9e^oywyEGWjh7KJbgQvCMh(YiYP%zgQ7CcZtX1r@=6lSkJjS^}^| zR|9nE#)-)H(+GA~bNTs+E%jh;NHo?pPDr3w&O~KCkM@D;FF!v4@i7gJWZbw36cNWI zX=~&tUq|9WZ9q!v5>I@1-}jz8<|peCbMIR7i?4n8A78Jfor2ncfF@8wfQYpStZM5C zj3baBFrL5!0`xm;wUY?YudvlN5}+UXs%;`LnLsmveF;nfpz!>s>l3K`gsyL_sc-Jk z^{9f0_1xqd$C+lGfXkZ_7TIHw`z^BAA{SU>2Zctck|i`5f|HnLIOVbxX}3tHMQ*mp zZ5FxCB9~fZmqo6&NSj6WH*xMTgt8NvVnr^r$Zjk1h^2H}WUWQe3^`L}n~L(iCWax) zvHHrdTFNUHdB-55wU%;)MV_+AE{oXgm2a?=mn>pSz5I-&?6Jtr7J0xTw&2RIS<0Ih zdB`G97-TeIDQ{ScOW})_b*Dw%vNBnV3|VBeMN$?Su*fWn%(cjTi_EadOpCNwWRXP{ zSfp%`O%^E`r2MF*oMVwmmi3%Pc2m94SYKO@QGq<@!P87-PBHHO=n_qMrs?_yI9Ve$ z1NcllvvmCg3jQYoZ-X$Okh=)nt?MV%);E{m1sDwoy9B*#5wTtd7`?%$T8vQibmCcy zr`ymsK{{pd%@q8}Nl2m_g@`gLy|j*W-l*&AC}*oMv|J|i2BKd{;2HwYQjnhzb_;>W z;2j;0NJdN~J0%o102<4lq^(56hcOPYjs`bFK+H&J8=iJNgQVyI=Ef}6A76(4wDp8+K7Rc=P@8mXkItoP@2~v@Z=2?c=84rRC)aI=pK*W z@6p8`y}+Y8L@kX)p4jAwA<1cuUoJazwB4hf9=+M4w|VqBk6!B0T^_yKqir7D-=$`U zBbGy=Qyg{lLXYltX!#L`j&^%=tw)tZ%RY_edtJOkp6_EQzv`*4c=R2Ij@CM~e1%7! z^5`y)`T{B6;HfWp)YoMB8Bg8g(VIQ`fJc4tmtXVLH$D20N1t%$Xu_f8Hyk<|%EgP0 zTE5exZ#i@{>(L>PZuV%(qXQnD<D&7G14NNFO3)wk3H6B{)dnPLwUp>bj(+0HO9P3J|q>gF`Y4Rmqt{u#C` zn+L@?Uz}lP!D801a~W@%1Z|T?8h9k$NhTgf8gO7nGZd)>4J|cdOgEP=YHWm9b4zp6 zxRzR|X=IxgLoJaJ`Vz?w4~i7gga)ExR20*V$qDMPq~buqb;e4$eHSQjo7K!)aXV6DTVf&XGY~QlVsXBZ^PF-tvO3l^H~ZIQg?~eUF^AAJa=P~nnaV-Bv3JX6CB0sIpCKx zVUd7!#JeCQCN+S-0biDYFOz5V2KJ2JV3WJpC3i8!zL;ZYw3E?JqON6hEur0vcH7{a zT<}e(A56hyTp*qliA_#fvCU3wGpF=PN(I_M1NBp9GB9hj0d)fwH>&dUBKW+SkWuR> zmK(WybDx=L8wrdGBZJC?%#{qUVfZW{EDOeOVfYwPM#oc1M{F8*n(zsu9ch?slIJaq ziI#$POQEUL1e4j2Xd8#-L=B0Gshx|dozOu>F|?53Q&*FK?J*N(N6k#EX=kd5cpAse zH0Fl5uq|f7wn&hV2DLi)uq-spX%xJPz%+zS;AtRF*79eNZzh3R;7#Dbe`5GH5$7{{ z7sI)}1DN!f2%GGbspFpQTXs zA#{SW(MF)1z+fUUt4{p9cc8lU`!*JmL-<8tcV!Lkl*46*0p!Nf3Rs;Gr~~NdlyO*# zZ@ivmt^qn_+p)9fK||vdcOxLqyV9v3z6bVzKuwAJc0j2KfvV##{*{`9NaoF+OaFw< z9;}NwW<)+QQ1wOmfIu}M<@g?-eiRO`^2Z0Nb{I$fM2Vp*O0&iN>RO6^RhTKXG#{$qlekz--6?i*1vC{8tFt{ zkGt2$B!FmiEpE@S`PD`)l05$?NKc-x_m_I1BII(Vc7^iq8Kgp|l8}WNiV%+z_Q6(Vv*rIaWg$?#`H+{SY0?LGbcb3Z6)N(Lp4 zh+HOx5ksZGwtb5_lEaBc`jw*|U-*Xx>Of14t3~Q3HtRjTxNb}V3@zzUMe{%fw5B_@Fr?*pxIUCS$i*r(2=JaPub7a&s z&g?Gp-6ekm5>L$co;QaF)H$IaCcxkvD?NKC)vLPNTaP{d1ZC24NWDo5@;mjO_uqZ~ zEWPRIQ+7PRZ5Q%n|Gq$Yi}h8ymxkn&!fls@wrqA4Zb?#u+>O$F3m=7f?{Mg-E|yAV z|LgnzC;@aFgx+7Na-|ZQ>lK}IjL=E09nYq6pcc?ZxA|L8GauL)ste}^&rr+MdSD%D zjam(V2jFtx^!KgW-~G4r1d4(F#63+ssunKwpYUHBJOQ-QiO%IX!+0wc0p)?!-c`89V30Z(kr(AuG{@%D}|L704 zJ9KtJAD!{da%m0WTqfnLALlVC_bqtN;<(osI@JV8+wveEoe%!|%QTz@si(Ejokz-s z`An+M)!(lZ(WAWZ8cS8`sxDbyu1YQ7I%q|0(izf0w45w*--1?4CG2a%hoL(8ANU)> U^oeNqobW#o_y0!uKVAa=7fuEi`v3p{ From bcb1a290388c4db7779d8c583859fb7ea04d690c Mon Sep 17 00:00:00 2001 From: AiAe Date: Sun, 24 Mar 2024 12:59:56 +0200 Subject: [PATCH 100/249] Update Client dll --- Quaver.Shared/Quaver.Server.Client.dll | Bin 87552 -> 90112 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Quaver.Shared/Quaver.Server.Client.dll b/Quaver.Shared/Quaver.Server.Client.dll index 0bdb642865271a2a6f471b58e153a16f74702964..e8e2f52af0eeba4c0f9c40752169969cea33ccbb 100644 GIT binary patch literal 90112 zcmeFacYIY<+BUq_I>|}T36P#Z&Pgaq=)GftNC;iJU`YTAibjbjI--dMWWCF{F<60*Hj-H zTv2`8aWy9fmmeNnv-Y^)s^fxF=FATsTeI@;K`AMTy|wGP2N*Nc`plhg$83qXHJid< zk{x2q5^Id#Q$IfmaS(AGw#K9}cHX!}kn!_hBl6JYpU+gCBTeQ1ZCj-x;ooKGca8+; zcZ(c|=zn2rlA&!~Yt4e_+<(V|#>BVF$AeF6#|NEw__`Cpzg~p#BFBBzaYvd#t{Swa zcFhV192>^S9bJlTyMJYvu0d-KUtNQSGFF2z^B5f3somsuWej)9am7Cwn=`RyGhjh}_HlDM<W+d^-uK24S&D%zo|pNU8=*9 zfM0q?KZxQdD*V7Y`$gB$uM2fJ1>l#_(GQ~di3&fk&VJE#^y@+$rW}5m9sMARpQ!Ky z>+Ba@N53xA#nLaUqaQ@^6BT}7o&BQg=+}ihoDJB&?2djA#ZOfDfpzwauA^TU>TtTj zFQ=m)MDY_9eqf#bqU-3_g*u$q@XPJ!2T}Y)g&$aFzvw#pb)hbSet8}JAc~);@B{1Y z7hOlcF4Wz%XTRt=`gNfWmpSm_!V^YgD8HY!Vj#oUvwS)x=@FE0Q`a-{UC~;sPF^p z>=#`}zb@3_76ZRfM?Z+-Co25FI{QV}(XR`2xc|Vfu%jPD@e>t(V4eM<>*&{oI@}=P zSJcrDqWFmlKd{bz(RK9eLLJ({uXjg3h~g(I{J=Wt( zV4eM<>*&{oI^4tI*SDh|MDY_9eqf#bqU-3_g*x2B;n%ODA4KsJ6@Fly{i5sW*M&OV z!{Jxl(GQ~di3&fk&VJE#^y@+$?&0t&>F5Vh{6vKxSZBZJI{I~?4)<{Qm3H)lD1M^C z53I9abRGS=P=|Xs{Q7tFgD8HY!Vj#oUvwS)x=@FEIQ#~5^n)mVqQVcXvtM)_{kl+x zdpP_CcJzZNexkw;tg~Nq9sRmchkH2u26gmkx;Rn{)FS@R~y#MP$9q!@SzXLk@K@>kx;Rn{) zFS?F?U8uu79DY+f`au*wQQ-&H*)O_|eqE@;Jsf@&9sMARpQ!Ky>+Ba@N53xA;T{gZ zX&wC_il3{+z7wT{ihu`#$eh|e^RQQ2) z_KU8gUl;0d4~O53j(!luPgMAUb@q#{qhA;5a1V#y%#MB##ZOfDfpzwauA^TU>TnN- z->i;)5XDba_{+z7wT{ihu`dueh|e^RQQ2)_KU8gUl;0d4~O5Jj(!luPgMAU zb@q#{qhA;5a1V#y+>U+_#ZOfDfpzwauA^TU>TnN--+>+dAc~);@B{1Y7hOlcF4W;3 z4!?OF{UC~;sPF^p>=#`}zb@3_9uB|x9sMARpQ!Ky>+Ba@N53xA;T{gZ1s(k$il3

=#`}zb@3_ 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 literal 87552 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 Date: Sun, 24 Mar 2024 13:49:57 +0200 Subject: [PATCH 101/249] Update submodule --- Quaver.Server.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Server.Client b/Quaver.Server.Client index abb3fdb0a0..fc07fde9ce 160000 --- a/Quaver.Server.Client +++ b/Quaver.Server.Client @@ -1 +1 @@ -Subproject commit abb3fdb0a0b8f28c99a66e225811be86ee043dce +Subproject commit fc07fde9ce4f89e7a3873c006cab97fa3549f3d4 From 8c96ed4d61ac9647f2e50f587609bb0184157b85 Mon Sep 17 00:00:00 2001 From: AiAe Date: Mon, 25 Mar 2024 20:17:39 +0200 Subject: [PATCH 102/249] Truncate difficulty name --- .../Tournament/Overlay/Components/TournamentDifficultyName.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Quaver.Shared/Screens/Tournament/Overlay/Components/TournamentDifficultyName.cs b/Quaver.Shared/Screens/Tournament/Overlay/Components/TournamentDifficultyName.cs index 6ac0059820..e8dac38350 100644 --- a/Quaver.Shared/Screens/Tournament/Overlay/Components/TournamentDifficultyName.cs +++ b/Quaver.Shared/Screens/Tournament/Overlay/Components/TournamentDifficultyName.cs @@ -29,6 +29,7 @@ public override void UpdateState() Tint = ColorHelper.DifficultyToColor(difficulty.OverallDifficulty); Text = Qua.DifficultyName; + TruncateWithEllipsis(Settings.MaxWidth.Value); } } } \ No newline at end of file From 46279c5bb25c7bed9006b5b071e8029d1a43a192 Mon Sep 17 00:00:00 2001 From: AiAe Date: Mon, 25 Mar 2024 20:42:36 +0200 Subject: [PATCH 103/249] Use player1 difficulty rating for overlay instead qua --- .../Overlay/Components/TournamentDifficultyName.cs | 12 ++++++++---- .../Overlay/Components/TournamentDifficultyRating.cs | 12 ++++++++---- .../Screens/Tournament/Overlay/TournamentOverlay.cs | 4 ++-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Quaver.Shared/Screens/Tournament/Overlay/Components/TournamentDifficultyName.cs b/Quaver.Shared/Screens/Tournament/Overlay/Components/TournamentDifficultyName.cs index e8dac38350..40836bc8e6 100644 --- a/Quaver.Shared/Screens/Tournament/Overlay/Components/TournamentDifficultyName.cs +++ b/Quaver.Shared/Screens/Tournament/Overlay/Components/TournamentDifficultyName.cs @@ -1,8 +1,11 @@ +using System.Collections.Generic; +using System.Linq; using Quaver.API.Maps; using Quaver.Shared.Assets; using Quaver.Shared.Helpers; using Wobble.Graphics.Sprites; using Wobble.Graphics.Sprites.Text; +using Wobble.Logging; using Wobble.Managers; namespace Quaver.Shared.Screens.Tournament.Overlay.Components @@ -11,9 +14,12 @@ public sealed class TournamentDifficultyName : TournamentOverlaySpriteText { private Qua Qua { get; } - public TournamentDifficultyName(Qua qua, TournamentSettingsDifficultyRating settings) : base(settings) + private TournamentPlayer Player { get; } + + public TournamentDifficultyName(Qua qua, TournamentSettingsDifficultyRating settings, List players) : base(settings) { Qua = qua; + Player = players.First(); SetText(); } @@ -21,12 +27,10 @@ public override void UpdateState() { base.UpdateState(); - var difficulty = Qua.SolveDifficulty(); - var settings = (TournamentSettingsDifficultyRating) Settings; if (settings.UseDefaultColor.Value) - Tint = ColorHelper.DifficultyToColor(difficulty.OverallDifficulty); + Tint = ColorHelper.DifficultyToColor((float) Player.Rating.DifficultyRating); Text = Qua.DifficultyName; TruncateWithEllipsis(Settings.MaxWidth.Value); diff --git a/Quaver.Shared/Screens/Tournament/Overlay/Components/TournamentDifficultyRating.cs b/Quaver.Shared/Screens/Tournament/Overlay/Components/TournamentDifficultyRating.cs index 920fdf1898..c210113eaa 100644 --- a/Quaver.Shared/Screens/Tournament/Overlay/Components/TournamentDifficultyRating.cs +++ b/Quaver.Shared/Screens/Tournament/Overlay/Components/TournamentDifficultyRating.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using IniFileParser.Model; using Quaver.API.Enums; using Quaver.API.Maps; @@ -5,6 +6,7 @@ using Quaver.Shared.Config; using Quaver.Shared.Helpers; using Wobble.Bindables; +using System.Linq; namespace Quaver.Shared.Screens.Tournament.Overlay.Components { @@ -12,9 +14,12 @@ public sealed class TournamentDifficultyRating : TournamentOverlaySpriteText { private Qua Qua { get; } - public TournamentDifficultyRating(Qua qua, TournamentSettingsDifficultyRating settings) : base(settings) + private TournamentPlayer Player { get; } + + public TournamentDifficultyRating(Qua qua, TournamentSettingsDifficultyRating settings, List players) : base(settings) { Qua = qua; + Player = players.First(); SetText(); } @@ -22,13 +27,12 @@ public override void UpdateState() { base.UpdateState(); - var difficulty = Qua.SolveDifficulty(); - Text = StringHelper.RatingToString(difficulty.OverallDifficulty); + Text = StringHelper.RatingToString(Player.Rating.DifficultyRating); var settings = (TournamentSettingsDifficultyRating) Settings; if (settings.UseDefaultColor.Value) - Tint = ColorHelper.DifficultyToColor(difficulty.OverallDifficulty); + Tint = ColorHelper.DifficultyToColor((float) Player.Rating.DifficultyRating); } } diff --git a/Quaver.Shared/Screens/Tournament/Overlay/TournamentOverlay.cs b/Quaver.Shared/Screens/Tournament/Overlay/TournamentOverlay.cs index 672917e819..9d82c1b105 100644 --- a/Quaver.Shared/Screens/Tournament/Overlay/TournamentOverlay.cs +++ b/Quaver.Shared/Screens/Tournament/Overlay/TournamentOverlay.cs @@ -410,11 +410,11 @@ private void CreateWinnerDisplays() } // ReSharper disable twice ObjectCreationAsStatement - private void CreateDifficultyNameSettings() => new TournamentDifficultyName(Qua, DifficultyNameSettings) {Parent = this}; + private void CreateDifficultyNameSettings() => new TournamentDifficultyName(Qua, DifficultyNameSettings, Players) {Parent = this}; private void CreateSongArtistAndTitle() => SongTitle = new TournamentSongArtistAndTitle(Qua, SongTitleSettings) { Parent = this }; private void CreateSongLength() => new TournamentSongLength(Qua, SongLengthSettings) {Parent = this}; private void CreateSongBpm() => new TournamentBpm(Qua, SongBpmSettings) {Parent = this}; - private void CreateDifficultyRating() => new TournamentDifficultyRating(Qua, DifficultyRatingSettings) {Parent = this}; + private void CreateDifficultyRating() => new TournamentDifficultyRating(Qua, DifficultyRatingSettings, Players) {Parent = this}; private void CreateMapCreator() => new TournamentMapCreator(Qua, MapCreatorSettings) {Parent = this}; private void CreateMatchRound() => new TournamentCustomText(MatchRoundSettings) {Parent = this}; private void CreateBestOf() => new TournamentCustomText(BestOfSettings) {Parent = this}; From 7a3688901b2b20f6e39cae4e46df14e508927b0b Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 26 Mar 2024 08:57:49 +0800 Subject: [PATCH 104/249] Add pausing warning toggle to Options->Misc->Notifications --- Quaver.Shared/Screens/Options/OptionsMenu.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Options/OptionsMenu.cs b/Quaver.Shared/Screens/Options/OptionsMenu.cs index c0054c7213..43415a2010 100644 --- a/Quaver.Shared/Screens/Options/OptionsMenu.cs +++ b/Quaver.Shared/Screens/Options/OptionsMenu.cs @@ -414,7 +414,8 @@ private void CreateSections() { new OptionsItemCheckbox(containerRect, "Display Notifications From Bottom-To-Top", ConfigManager.DisplayNotificationsBottomToTop), new OptionsItemCheckbox(containerRect, "Display Online Friend Notifications", ConfigManager.DisplayFriendOnlineNotifications), - new OptionsItemCheckbox(containerRect, "Display Song Request Notifications", ConfigManager.DisplaySongRequestNotifications) + new OptionsItemCheckbox(containerRect, "Display Song Request Notifications", ConfigManager.DisplaySongRequestNotifications), + new OptionsItemCheckbox(containerRect, "Display Warning For Pausing", ConfigManager.DisplayPauseWarning) }), new OptionsSubcategory("Effects", new List() { From b5daf5f9723c2635bf1808240e651091c80e9893 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 30 Mar 2024 11:45:17 +0800 Subject: [PATCH 105/249] Add batch action for bookmarks --- .../AddBatch/EditorActionAddBookmarkBatch.cs | 41 +++++++++++++++++++ ...EditorActionBookmarkBatchAddedEventArgs.cs | 13 ++++++ ...itorActionBookmarkBatchRemovedEventArgs.cs | 13 ++++++ .../EditorActionRemoveBookmarkBatch.cs | 41 +++++++++++++++++++ .../Screens/Edit/Actions/EditorActionType.cs | 2 + 5 files changed, 110 insertions(+) create mode 100644 Quaver.Shared/Screens/Edit/Actions/Bookmarks/AddBatch/EditorActionAddBookmarkBatch.cs create mode 100644 Quaver.Shared/Screens/Edit/Actions/Bookmarks/AddBatch/EditorActionBookmarkBatchAddedEventArgs.cs create mode 100644 Quaver.Shared/Screens/Edit/Actions/Bookmarks/RemoveBatch/EditorActionBookmarkBatchRemovedEventArgs.cs create mode 100644 Quaver.Shared/Screens/Edit/Actions/Bookmarks/RemoveBatch/EditorActionRemoveBookmarkBatch.cs diff --git a/Quaver.Shared/Screens/Edit/Actions/Bookmarks/AddBatch/EditorActionAddBookmarkBatch.cs b/Quaver.Shared/Screens/Edit/Actions/Bookmarks/AddBatch/EditorActionAddBookmarkBatch.cs new file mode 100644 index 0000000000..e9a9955dbe --- /dev/null +++ b/Quaver.Shared/Screens/Edit/Actions/Bookmarks/AddBatch/EditorActionAddBookmarkBatch.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using Quaver.API.Maps; +using Quaver.API.Maps.Structures; +using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Interop; +using Quaver.Shared.Screens.Edit.Actions.Bookmarks.RemoveBatch; + +namespace Quaver.Shared.Screens.Edit.Actions.Bookmarks.AddBatch +{ + [MoonSharpUserData] + public class EditorActionAddBookmarkBatch : IEditorAction + { + public EditorActionType Type { get; } = EditorActionType.AddBookmarkBatch; + + private EditorActionManager ActionManager { get; } + + private Qua WorkingMap { get; } + + private List Bookmarks { get; } + + [MoonSharpVisible(false)] + public EditorActionAddBookmarkBatch(EditorActionManager manager, Qua workingMap, List bookmarks) + { + ActionManager = manager; + WorkingMap = workingMap; + Bookmarks = bookmarks; + } + + [MoonSharpVisible(false)] + public void Perform() + { + Bookmarks.ForEach(x => WorkingMap.Bookmarks.Add(x)); + WorkingMap.Sort(); + + ActionManager.TriggerEvent(Type, new EditorActionBookmarkBatchAddedEventArgs(Bookmarks)); + } + + [MoonSharpVisible(false)] + public void Undo() => new EditorActionRemoveBookmarkBatch(ActionManager, WorkingMap, Bookmarks).Perform(); + } +} diff --git a/Quaver.Shared/Screens/Edit/Actions/Bookmarks/AddBatch/EditorActionBookmarkBatchAddedEventArgs.cs b/Quaver.Shared/Screens/Edit/Actions/Bookmarks/AddBatch/EditorActionBookmarkBatchAddedEventArgs.cs new file mode 100644 index 0000000000..68008a47ff --- /dev/null +++ b/Quaver.Shared/Screens/Edit/Actions/Bookmarks/AddBatch/EditorActionBookmarkBatchAddedEventArgs.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using Quaver.API.Maps.Structures; + +namespace Quaver.Shared.Screens.Edit.Actions.Bookmarks.AddBatch +{ + public class EditorActionBookmarkBatchAddedEventArgs : EventArgs + { + public List Bookmarks { get; } + + public EditorActionBookmarkBatchAddedEventArgs(List bookmarks) => Bookmarks = bookmarks; + } +} \ No newline at end of file diff --git a/Quaver.Shared/Screens/Edit/Actions/Bookmarks/RemoveBatch/EditorActionBookmarkBatchRemovedEventArgs.cs b/Quaver.Shared/Screens/Edit/Actions/Bookmarks/RemoveBatch/EditorActionBookmarkBatchRemovedEventArgs.cs new file mode 100644 index 0000000000..4dd057dc28 --- /dev/null +++ b/Quaver.Shared/Screens/Edit/Actions/Bookmarks/RemoveBatch/EditorActionBookmarkBatchRemovedEventArgs.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Quaver.API.Maps.Structures; +using Quaver.Shared.Screens.Edit.Actions.Bookmarks.AddBatch; + +namespace Quaver.Shared.Screens.Edit.Actions.Bookmarks.RemoveBatch +{ + public class EditorActionBookmarkBatchRemovedEventArgs : EditorActionBookmarkBatchAddedEventArgs + { + public EditorActionBookmarkBatchRemovedEventArgs(List bookmarks) : base(bookmarks) + { + } + } +} \ No newline at end of file diff --git a/Quaver.Shared/Screens/Edit/Actions/Bookmarks/RemoveBatch/EditorActionRemoveBookmarkBatch.cs b/Quaver.Shared/Screens/Edit/Actions/Bookmarks/RemoveBatch/EditorActionRemoveBookmarkBatch.cs new file mode 100644 index 0000000000..e0360014b5 --- /dev/null +++ b/Quaver.Shared/Screens/Edit/Actions/Bookmarks/RemoveBatch/EditorActionRemoveBookmarkBatch.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using Quaver.API.Maps; +using Quaver.API.Maps.Structures; +using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Interop; +using Quaver.Shared.Screens.Edit.Actions.Bookmarks.AddBatch; + +namespace Quaver.Shared.Screens.Edit.Actions.Bookmarks.RemoveBatch +{ + [MoonSharpUserData] + public class EditorActionRemoveBookmarkBatch : IEditorAction + { + public EditorActionType Type { get; } = EditorActionType.RemoveBookmarkBatch; + + private EditorActionManager ActionManager { get; } + + private Qua WorkingMap { get; } + + private List Bookmarks { get; } + + [MoonSharpVisible(false)] + public EditorActionRemoveBookmarkBatch(EditorActionManager manager, Qua workingMap, List bookmarks) + { + ActionManager = manager; + WorkingMap = workingMap; + Bookmarks = bookmarks; + } + + [MoonSharpVisible(false)] + public void Perform() + { + foreach (var sv in Bookmarks) + WorkingMap.Bookmarks.Remove(sv); + + ActionManager.TriggerEvent(Type, new EditorActionBookmarkBatchRemovedEventArgs(Bookmarks)); + } + + [MoonSharpVisible(false)] + public void Undo() => new EditorActionAddBookmarkBatch(ActionManager, WorkingMap, Bookmarks).Perform(); + } +} diff --git a/Quaver.Shared/Screens/Edit/Actions/EditorActionType.cs b/Quaver.Shared/Screens/Edit/Actions/EditorActionType.cs index a07ed0ecd1..6a8bac2c7f 100644 --- a/Quaver.Shared/Screens/Edit/Actions/EditorActionType.cs +++ b/Quaver.Shared/Screens/Edit/Actions/EditorActionType.cs @@ -42,8 +42,10 @@ public enum EditorActionType Batch, ReverseHitObjects, AddBookmark, + AddBookmarkBatch, EditBookmark, RemoveBookmark, + RemoveBookmarkBatch, ChangeBookmarkOffsetBatch } } \ No newline at end of file From ac57765f28854636535bee80c70fd1d9a81cd202 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 30 Mar 2024 11:46:44 +0800 Subject: [PATCH 106/249] Allow editor plugin to access bookmarks --- Quaver.Shared/Screens/Edit/Plugins/EditorPluginMap.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Quaver.Shared/Screens/Edit/Plugins/EditorPluginMap.cs b/Quaver.Shared/Screens/Edit/Plugins/EditorPluginMap.cs index e333f5b6dd..c8460648e6 100644 --- a/Quaver.Shared/Screens/Edit/Plugins/EditorPluginMap.cs +++ b/Quaver.Shared/Screens/Edit/Plugins/EditorPluginMap.cs @@ -50,6 +50,11 @@ public class EditorPluginMap /// The non-default editor layers that are currently in the map /// public List EditorLayers { get; [MoonSharpVisible(false)] set; } + + ///

+ /// The bookmarks that are currently in the map + /// + public List Bookmarks { get; [MoonSharpVisible(false)] set; } /// /// The default editor layer @@ -69,6 +74,7 @@ public void SetFrameState() ScrollVelocities = Map.SliderVelocities; // Original name was SliderVelocities but that name doesn't really make sense HitObjects = Map.HitObjects; EditorLayers = Map.EditorLayers; + Bookmarks = Map.Bookmarks; TrackLength = Track.Length; Normalized = Map.BPMDoesNotAffectScrollVelocity; } From 3d6badf9151aa88a030b17cbd06e0538dc9c98db Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 30 Mar 2024 11:47:51 +0800 Subject: [PATCH 107/249] Allow bookmark creation through utils.createBookmark for editor scripts --- .../Screens/Edit/Plugins/EditorPluginUtils.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Quaver.Shared/Screens/Edit/Plugins/EditorPluginUtils.cs b/Quaver.Shared/Screens/Edit/Plugins/EditorPluginUtils.cs index 3a84480ad9..0a9eebb4fc 100644 --- a/Quaver.Shared/Screens/Edit/Plugins/EditorPluginUtils.cs +++ b/Quaver.Shared/Screens/Edit/Plugins/EditorPluginUtils.cs @@ -10,6 +10,12 @@ using Quaver.Shared.Screens.Edit; using Quaver.Shared.Screens.Edit.Actions; using Quaver.Shared.Screens.Edit.Actions.Batch; +using Quaver.Shared.Screens.Edit.Actions.Bookmarks.Add; +using Quaver.Shared.Screens.Edit.Actions.Bookmarks.AddBatch; +using Quaver.Shared.Screens.Edit.Actions.Bookmarks.Edit; +using Quaver.Shared.Screens.Edit.Actions.Bookmarks.Offset; +using Quaver.Shared.Screens.Edit.Actions.Bookmarks.Remove; +using Quaver.Shared.Screens.Edit.Actions.Bookmarks.RemoveBatch; using Quaver.Shared.Screens.Edit.Actions.HitObjects; using Quaver.Shared.Screens.Edit.Actions.HitObjects.Place; using Quaver.Shared.Screens.Edit.Actions.HitObjects.PlaceBatch; @@ -122,6 +128,22 @@ public static EditorLayerInfo CreateEditorLayer(string name, bool hidden = false return layer; } + + /// + /// + /// + /// + /// + public static BookmarkInfo CreateBookmark(int startTime, string note) + { + var layer = new BookmarkInfo + { + StartTime = startTime, + Note = note + }; + + return layer; + } /// /// From 0e980d347c7cf9c172459b4ca6edd32afdb06cc8 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 30 Mar 2024 11:48:42 +0800 Subject: [PATCH 108/249] Create corresponding editor actions for bookmarks in utils.CreateEditorAction --- .../Screens/Edit/Plugins/EditorPluginUtils.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Quaver.Shared/Screens/Edit/Plugins/EditorPluginUtils.cs b/Quaver.Shared/Screens/Edit/Plugins/EditorPluginUtils.cs index 0a9eebb4fc..f4fbce0d16 100644 --- a/Quaver.Shared/Screens/Edit/Plugins/EditorPluginUtils.cs +++ b/Quaver.Shared/Screens/Edit/Plugins/EditorPluginUtils.cs @@ -206,6 +206,18 @@ public static IEditorAction CreateEditorAction(EditorActionType type, params Dyn return new EditorActionChangeScrollVelocityOffsetBatch(EditScreen.ActionManager, EditScreen.WorkingMap, args[0].ToObject>(), args[1].ToObject()); case EditorActionType.ChangeScrollVelocityMultiplierBatch: return new EditorActionChangeScrollVelocityMultiplierBatch(EditScreen.ActionManager, EditScreen.WorkingMap, args[0].ToObject>(), args[1].ToObject()); + case EditorActionType.AddBookmark: + return new EditorActionAddBookmark(EditScreen.ActionManager, EditScreen.WorkingMap, args[0].ToObject()); + case EditorActionType.RemoveBookmark: + return new EditorActionRemoveBookmark(EditScreen.ActionManager, EditScreen.WorkingMap, args[0].ToObject()); + case EditorActionType.AddBookmarkBatch: + return new EditorActionAddBookmarkBatch(EditScreen.ActionManager, EditScreen.WorkingMap, args[0].ToObject>()); + case EditorActionType.RemoveBookmarkBatch: + return new EditorActionRemoveBookmarkBatch(EditScreen.ActionManager, EditScreen.WorkingMap, args[0].ToObject>()); + case EditorActionType.EditBookmark: + return new EditorActionEditBookmark(EditScreen.ActionManager, EditScreen.WorkingMap, args[0].ToObject(), args[1].ToObject()); + case EditorActionType.ChangeBookmarkOffsetBatch: + return new EditorActionChangeBookmarkOffsetBatch(EditScreen.ActionManager, EditScreen.WorkingMap, args[0].ToObject>(), args[1].ToObject()); case EditorActionType.ResnapHitObjects: return new EditorActionResnapHitObjects(EditScreen.ActionManager, EditScreen.WorkingMap, args[0].ToObject>(), args[1].ToObject>(), args[2].ToObject()); case EditorActionType.Batch: From e51f1f32b8992b7ed5aaa3e7b681b0f692d10395 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 30 Mar 2024 11:58:11 +0800 Subject: [PATCH 109/249] Add batch action in EditorActionManager --- .../Edit/Actions/EditorActionManager.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Quaver.Shared/Screens/Edit/Actions/EditorActionManager.cs b/Quaver.Shared/Screens/Edit/Actions/EditorActionManager.cs index bc52fd107c..56a585f154 100644 --- a/Quaver.Shared/Screens/Edit/Actions/EditorActionManager.cs +++ b/Quaver.Shared/Screens/Edit/Actions/EditorActionManager.cs @@ -8,9 +8,11 @@ using Quaver.Shared.Screens.Edit.Actions.Batch; using Quaver.Shared.Screens.Edit.Actions.Bookmarks; using Quaver.Shared.Screens.Edit.Actions.Bookmarks.Add; +using Quaver.Shared.Screens.Edit.Actions.Bookmarks.AddBatch; using Quaver.Shared.Screens.Edit.Actions.Bookmarks.Edit; using Quaver.Shared.Screens.Edit.Actions.Bookmarks.Offset; using Quaver.Shared.Screens.Edit.Actions.Bookmarks.Remove; +using Quaver.Shared.Screens.Edit.Actions.Bookmarks.RemoveBatch; using Quaver.Shared.Screens.Edit.Actions.HitObjects.Flip; using Quaver.Shared.Screens.Edit.Actions.HitObjects.Move; using Quaver.Shared.Screens.Edit.Actions.HitObjects.Place; @@ -260,6 +262,16 @@ public class EditorActionManager : IDisposable /// Event invoked when a bookmark has been removed. /// public event EventHandler BookmarkRemoved; + + /// + /// Event invoked when a bookmark has been added. + /// + public event EventHandler BookmarkBatchAdded; + + /// + /// Event invoked when a bookmark has been removed. + /// + public event EventHandler BookmarkBatchRemoved; /// /// Event invoked whe na bookmark has been edited. @@ -600,12 +612,23 @@ public void SetHitObjectSelection(List hitObjects) /// public void AddBookmark(int time, string note) => Perform(new EditorActionAddBookmark(this, WorkingMap, new BookmarkInfo { StartTime = time, Note = note })); + /// + /// Adds a batch of bookmarks to the map + /// + /// + public void AddBookmarkBatch(List bookmarks) => Perform(new EditorActionAddBookmarkBatch(this, WorkingMap, bookmarks)); /// /// Removes a bookmark from the map. /// /// public void RemoveBookmark(BookmarkInfo bookmark) => Perform(new EditorActionRemoveBookmark(this, WorkingMap, bookmark)); + /// + /// Removes a batch of bookmarks from the map. + /// + /// + public void RemoveBookmarkBatch(List bookmark) => Perform(new EditorActionRemoveBookmarkBatch(this, WorkingMap, bookmark)); + /// /// Edits the note of an existing bookmark /// @@ -734,6 +757,12 @@ public void TriggerEvent(EditorActionType type, EventArgs args) case EditorActionType.RemoveBookmark: BookmarkRemoved?.Invoke(this, (EditorActionBookmarkRemovedEventArgs) args); break; + case EditorActionType.AddBookmarkBatch: + BookmarkBatchAdded?.Invoke(this, (EditorActionBookmarkBatchAddedEventArgs) args); + break; + case EditorActionType.RemoveBookmarkBatch: + BookmarkBatchRemoved?.Invoke(this, (EditorActionBookmarkBatchRemovedEventArgs) args); + break; case EditorActionType.EditBookmark: BookmarkEdited?.Invoke(this, (EditorActionBookmarkEditedEventArgs) args); break; @@ -786,6 +815,8 @@ public void Dispose() BookmarkAdded = null; BookmarkRemoved = null; BookmarkEdited = null; + BookmarkBatchAdded = null; + BookmarkBatchRemoved = null; BookmarkBatchOffsetChanged = null; } } From 15b281c70b22761a4d57393ff540e5d49ab422ad Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 30 Mar 2024 11:58:40 +0800 Subject: [PATCH 110/249] Add event listeners for batch updating bookmarks --- .../EditorFooterBookmarkContainer.cs | 19 +++++++++++- .../Lines/EditorPlayfieldLineContainer.cs | 31 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Footer/Bookmarks/EditorFooterBookmarkContainer.cs b/Quaver.Shared/Screens/Edit/UI/Footer/Bookmarks/EditorFooterBookmarkContainer.cs index 010b8b50c9..5be6b57dda 100644 --- a/Quaver.Shared/Screens/Edit/UI/Footer/Bookmarks/EditorFooterBookmarkContainer.cs +++ b/Quaver.Shared/Screens/Edit/UI/Footer/Bookmarks/EditorFooterBookmarkContainer.cs @@ -1,9 +1,11 @@ using System.Collections.Generic; using Quaver.API.Maps.Structures; using Quaver.Shared.Screens.Edit.Actions.Bookmarks.Add; +using Quaver.Shared.Screens.Edit.Actions.Bookmarks.AddBatch; using Quaver.Shared.Screens.Edit.Actions.Bookmarks.Edit; using Quaver.Shared.Screens.Edit.Actions.Bookmarks.Offset; using Quaver.Shared.Screens.Edit.Actions.Bookmarks.Remove; +using Quaver.Shared.Screens.Edit.Actions.Bookmarks.RemoveBatch; using Wobble.Graphics; using Wobble.Window; @@ -25,7 +27,9 @@ public EditorFooterBookmarkContainer(EditScreen screen) CreateBookmarks(); Screen.ActionManager.BookmarkAdded += OnBookmarkAdded; + Screen.ActionManager.BookmarkBatchAdded += OnBookmarkBatchAdded; Screen.ActionManager.BookmarkRemoved += OnBookmarkRemoved; + Screen.ActionManager.BookmarkBatchRemoved += OnBookmarkBatchRemoved; Screen.ActionManager.BookmarkEdited += OnBookmarkEdited; Screen.ActionManager.BookmarkBatchOffsetChanged += OnBookmarkBatchOffsetChanged; } @@ -33,7 +37,9 @@ public EditorFooterBookmarkContainer(EditScreen screen) public override void Destroy() { Screen.ActionManager.BookmarkAdded -= OnBookmarkAdded; + Screen.ActionManager.BookmarkBatchAdded -= OnBookmarkBatchAdded; Screen.ActionManager.BookmarkRemoved -= OnBookmarkRemoved; + Screen.ActionManager.BookmarkBatchRemoved -= OnBookmarkBatchRemoved; Screen.ActionManager.BookmarkEdited -= OnBookmarkEdited; Screen.ActionManager.BookmarkBatchOffsetChanged -= OnBookmarkBatchOffsetChanged; base.Destroy(); @@ -69,8 +75,19 @@ private void RemoveBookmark(BookmarkInfo bookmark) } private void OnBookmarkAdded(object sender, EditorActionBookmarkAddedEventArgs e) => AddBookmark(e.Bookmark); + private void OnBookmarkBatchAdded(object sender, EditorActionBookmarkBatchAddedEventArgs e) + { + foreach (var bookmark in e.Bookmarks) + AddBookmark(bookmark); + } + private void OnBookmarkRemoved(object sender, EditorActionBookmarkRemovedEventArgs e) => RemoveBookmark(e.Bookmark); - + private void OnBookmarkBatchRemoved(object sender, EditorActionBookmarkBatchRemovedEventArgs e) + { + foreach (var bookmark in e.Bookmarks) + RemoveBookmark(bookmark); + } + private void OnBookmarkEdited(object sender, EditorActionBookmarkEditedEventArgs e) { RemoveBookmark(e.Bookmark); diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Lines/EditorPlayfieldLineContainer.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Lines/EditorPlayfieldLineContainer.cs index f69abd6c80..8118085f10 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Lines/EditorPlayfieldLineContainer.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Lines/EditorPlayfieldLineContainer.cs @@ -7,8 +7,10 @@ using Quaver.Shared.Screens.Edit.Actions; using Quaver.Shared.Screens.Edit.Actions.Bookmarks; using Quaver.Shared.Screens.Edit.Actions.Bookmarks.Add; +using Quaver.Shared.Screens.Edit.Actions.Bookmarks.AddBatch; using Quaver.Shared.Screens.Edit.Actions.Bookmarks.Offset; using Quaver.Shared.Screens.Edit.Actions.Bookmarks.Remove; +using Quaver.Shared.Screens.Edit.Actions.Bookmarks.RemoveBatch; using Quaver.Shared.Screens.Edit.Actions.Preview; using Quaver.Shared.Screens.Edit.Actions.SV.Add; using Quaver.Shared.Screens.Edit.Actions.SV.AddBatch; @@ -83,7 +85,9 @@ public EditorPlayfieldLineContainer(EditorPlayfield playfield, Qua map, IAudioTr ActionManager.TimingPointOffsetChanged += OnTimingPointOffsetChanged; ActionManager.TimingPointOffsetBatchChanged += OnTimingPointOffsetBatchChanged; ActionManager.BookmarkAdded += OnBookmarkAdded; + ActionManager.BookmarkBatchAdded += OnBookmarkBatchAdded; ActionManager.BookmarkRemoved += OnBookmarkRemoved; + ActionManager.BookmarkBatchRemoved += OnBookmarkBatchRemoved; ActionManager.BookmarkBatchOffsetChanged += OnBookmarkBatchOffsetChanged; } @@ -149,7 +153,9 @@ public override void Destroy() ActionManager.TimingPointOffsetChanged -= OnTimingPointOffsetChanged; ActionManager.TimingPointOffsetBatchChanged -= OnTimingPointOffsetBatchChanged; ActionManager.BookmarkAdded -= OnBookmarkAdded; + ActionManager.BookmarkBatchAdded -= OnBookmarkBatchAdded; ActionManager.BookmarkRemoved -= OnBookmarkRemoved; + ActionManager.BookmarkBatchRemoved -= OnBookmarkBatchRemoved; ActionManager.BookmarkBatchOffsetChanged -= OnBookmarkBatchOffsetChanged; base.Destroy(); @@ -427,6 +433,14 @@ private void OnBookmarkAdded(object sender, EditorActionBookmarkAddedEventArgs e InitializeLinePool(); } + private void OnBookmarkBatchAdded(object sender, EditorActionBookmarkBatchAddedEventArgs e) + { + foreach (var bookmark in e.Bookmarks) + Lines.Add(new DrawableEditorLineBookmark(Playfield, bookmark)); + Lines = Lines.OrderBy(x => x.GetTime()).ToList(); + InitializeLinePool(); + } + private void OnBookmarkRemoved(object sender, EditorActionBookmarkRemovedEventArgs e) { var line = Lines.Find(x => x is DrawableEditorLineBookmark line && line.Bookmark == e.Bookmark); @@ -435,6 +449,23 @@ private void OnBookmarkRemoved(object sender, EditorActionBookmarkRemovedEventAr InitializeLinePool(); } + private void OnBookmarkBatchRemoved(object sender, EditorActionBookmarkBatchRemovedEventArgs e) + { + foreach (var bookmark in e.Bookmarks) + { + Lines.RemoveAll(x => + { + var found = x is DrawableEditorLineBookmark line && line.Bookmark == bookmark; + + if (found) + x.Destroy(); + + return found; + }); + } + InitializeLinePool(); + } + private void OnBookmarkBatchOffsetChanged(object sender, EditorActionChangeBookmarkOffsetBatchEventArgs e) { Lines = Lines.OrderBy(x => x.GetTime()).ToList(); From b4db9f2da60a34124744b7540d5405d6eb7d39c4 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 30 Mar 2024 11:58:56 +0800 Subject: [PATCH 111/249] Add AddBookmarkBatch and RemoveBookmarkBatch to actions --- Quaver.Shared/Screens/Edit/Actions/EditorPluginActionManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Quaver.Shared/Screens/Edit/Actions/EditorPluginActionManager.cs b/Quaver.Shared/Screens/Edit/Actions/EditorPluginActionManager.cs index 41e8f0a8c3..a92c4436c8 100644 --- a/Quaver.Shared/Screens/Edit/Actions/EditorPluginActionManager.cs +++ b/Quaver.Shared/Screens/Edit/Actions/EditorPluginActionManager.cs @@ -209,8 +209,10 @@ public void ResnapNotes(List snaps, List hitObjectsToResnap) ActionManager.ResnapNotes(snaps, hitObjectsToResnap); public void AddBookmark(int time, string note) => ActionManager.AddBookmark(time, note); + public void AddBookmarkBatch(List bookmarks) => ActionManager.AddBookmarkBatch(bookmarks); public void RemoveBookmark(BookmarkInfo bookmark) => ActionManager.RemoveBookmark(bookmark); + public void RemoveBookmarkBatch(List bookmarks) => ActionManager.RemoveBookmarkBatch(bookmarks); public void EditBookmark(BookmarkInfo bookmark, string note) => ActionManager.EditBookmark(bookmark, note); From 00cf3bd306a32d996534f645841fe5cde0bb64a7 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 13 Feb 2024 22:11:06 +0800 Subject: [PATCH 112/249] Add keybind config for toggling mirror mod --- Quaver.Shared/Config/ConfigManager.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index f4e3961697..e30b9a6cc4 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -719,6 +719,11 @@ public static class ConfigManager /// internal static Bindable KeyToggleOverlay { get; private set; } + /// + /// The key to toggle the mirror mod while in song select + /// + internal static Bindable KeyToggleMirror { get; private set; } + /// /// The key to decrease the gameplay rate while in song select /// @@ -982,6 +987,7 @@ private static void ReadConfigFile() KeySkipIntro = ReadGenericKey(@"KeySkipIntro", new GenericKey { KeyboardKey = Keys.Space }, data); KeyPause = ReadGenericKey(@"KeyPause", new GenericKey { KeyboardKey = Keys.Escape }, data); KeyToggleOverlay = ReadValue(@"KeyToggleOverlay", Keys.F8, data); + KeyToggleMirror = ReadValue(@"KeyToggleMirror", Keys.H, data); KeyDecreaseGameplayAudioRate = ReadValue(@"KeyDecreaseGameplayAudioRate", Keys.OemMinus, data); KeyIncreaseGameplayAudioRate = ReadValue(@"KeyIncreaseGameplayAudioRate", Keys.OemPlus, data); KeyRestartMap = ReadValue(@"KeyRestartMap", Keys.OemTilde, data); From 3caed92df2b7e4b4c70aa723cc7ad541b550db13 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 13 Feb 2024 22:11:29 +0800 Subject: [PATCH 113/249] Add keybind config item to options menu --- Quaver.Shared/Screens/Options/OptionsMenu.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Quaver.Shared/Screens/Options/OptionsMenu.cs b/Quaver.Shared/Screens/Options/OptionsMenu.cs index c0054c7213..057d44f722 100644 --- a/Quaver.Shared/Screens/Options/OptionsMenu.cs +++ b/Quaver.Shared/Screens/Options/OptionsMenu.cs @@ -375,6 +375,7 @@ private void CreateSections() { new OptionsItemKeybind(containerRect, "Decrease Gameplay Rate", ConfigManager.KeyDecreaseGameplayAudioRate), new OptionsItemKeybind(containerRect, "Increase Gameplay Rate", ConfigManager.KeyIncreaseGameplayAudioRate), + new OptionsItemKeybind(containerRect, "Toggle Mirror Mod", ConfigManager.KeyToggleMirror), }), new OptionsSubcategory("Editor", new List() { From 954466191126bdb7ca68caf9ac2be59858d78685 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Thu, 4 Apr 2024 09:03:20 +0800 Subject: [PATCH 114/249] Add logic to toggle mirror mod --- Quaver.Shared/Screens/Multi/MultiplayerGameScreen.cs | 11 +++++++++++ Quaver.Shared/Screens/Selection/SelectionScreen.cs | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/Quaver.Shared/Screens/Multi/MultiplayerGameScreen.cs b/Quaver.Shared/Screens/Multi/MultiplayerGameScreen.cs index 379e3c1949..1706ffff5e 100644 --- a/Quaver.Shared/Screens/Multi/MultiplayerGameScreen.cs +++ b/Quaver.Shared/Screens/Multi/MultiplayerGameScreen.cs @@ -274,6 +274,17 @@ private void HandleKeyPressControlInput() if (KeyboardManager.IsUniqueKeyPress(ConfigManager.KeyDecreaseGameplayAudioRate.Value)) ModManager.AddSpeedMods(SelectionScreen.GetNextRate(false, KeyboardManager.IsShiftDown())); } + + if (Game.Value.HostId == OnlineManager.Self?.OnlineUser?.Id || Game.Value.FreeModType.HasFlag(MultiplayerFreeModType.Regular)) + { + if (KeyboardManager.IsUniqueKeyPress(ConfigManager.KeyToggleMirror.Value)) + { + if (ModManager.IsActivated(ModIdentifier.Mirror)) + ModManager.RemoveMod(ModIdentifier.Mirror); + else + ModManager.AddMod(ModIdentifier.Mirror); + } + } } /// diff --git a/Quaver.Shared/Screens/Selection/SelectionScreen.cs b/Quaver.Shared/Screens/Selection/SelectionScreen.cs index 05886ab13b..db5e87f543 100644 --- a/Quaver.Shared/Screens/Selection/SelectionScreen.cs +++ b/Quaver.Shared/Screens/Selection/SelectionScreen.cs @@ -439,6 +439,16 @@ private void HandleKeyPressControlInput() if (KeyboardManager.IsUniqueKeyPress(Keys.D0)) ConfigManager.Pitched.Value = !ConfigManager.Pitched.Value; + // Toggle Mirror + if (KeyboardManager.IsUniqueKeyPress(ConfigManager.KeyToggleMirror.Value)) + { + if (ModManager.IsActivated(ModIdentifier.Mirror)) + ModManager.RemoveMod(ModIdentifier.Mirror); + else + ModManager.AddMod(ModIdentifier.Mirror); + } + + ChangeScrollSpeed(); } From b93ca788b16343fc8c2d9768c673b0db3706a3fa Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Thu, 4 Apr 2024 10:08:19 +0800 Subject: [PATCH 115/249] Add logic to switch maps within the mapset --- .../Dialogs/UnsavedChangesSwitchMapDialog.cs | 24 ++++++++++++ Quaver.Shared/Screens/Edit/EditScreen.cs | 37 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 Quaver.Shared/Screens/Edit/Dialogs/UnsavedChangesSwitchMapDialog.cs diff --git a/Quaver.Shared/Screens/Edit/Dialogs/UnsavedChangesSwitchMapDialog.cs b/Quaver.Shared/Screens/Edit/Dialogs/UnsavedChangesSwitchMapDialog.cs new file mode 100644 index 0000000000..7c9e53b1ae --- /dev/null +++ b/Quaver.Shared/Screens/Edit/Dialogs/UnsavedChangesSwitchMapDialog.cs @@ -0,0 +1,24 @@ +using Quaver.Shared.Database.Maps; +using Quaver.Shared.Graphics; +using Quaver.Shared.Graphics.Notifications; + +namespace Quaver.Shared.Screens.Edit.Dialogs +{ + public class UnsavedChangesSwitchMapDialog : YesNoDialog + { + public UnsavedChangesSwitchMapDialog(EditScreen screen, Map map) : base("SAVE CHANGES", + "You have unsaved changes. Would you like to save\n" + + "before switching to another difficulty?") + { + YesAction += () => + { + screen.Save(true); + NotificationManager.Show(NotificationLevel.Success, "Your map has been successfully saved!"); + + screen.SwitchToMap(map); + }; + + NoAction += () => screen.SwitchToMap(map, true); + } + } +} \ No newline at end of file diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index 2f753ba9e6..fbc945c28c 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -1512,6 +1512,43 @@ public static void CreateNewMapset(string audioFile) } } + /// + /// Switch to another difficulty of the mapset + /// + /// + /// + public void SwitchToMap(Map map, bool force = false) + { + if (Map.Game != MapGame.Quaver) + { + NotificationManager.Show(NotificationLevel.Warning, + "You cannot create new difficulties for maps from other games. Create a new set!"); + + return; + } + + if (ActionManager.HasUnsavedChanges && !force) + { + DialogManager.Show(new UnsavedChangesSwitchMapDialog(this, map)); + return; + } + + ThreadScheduler.Run(() => + { + try + { + var track = AudioEngine.LoadMapAudioTrack(map); + + Exit(() => new EditScreen(map, track)); + } + catch (Exception e) + { + Logger.Error(e, LogType.Runtime); + NotificationManager.Show(NotificationLevel.Error, "There was an issue while switching difficulty."); + } + }); + } + /// /// Creates a brand new map and reloads the editor /// From 267acf19858097eb4d32bb0767921768f187b75a Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Thu, 4 Apr 2024 10:08:47 +0800 Subject: [PATCH 116/249] Add menu item to switch to the maps in the mapset --- .../Screens/Edit/UI/Menu/EditorFileMenuBar.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs index eb43fdc723..54b82f08a1 100644 --- a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs +++ b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs @@ -105,6 +105,16 @@ private void CreateFileSection() if (ImGui.MenuItem("New Song", "CTRL + N")) DialogManager.Show(new EditorNewSongDialog()); + if (ImGui.BeginMenu("Switch Difficulty")) + { + foreach (var map in Screen.Map.Mapset.Maps) + { + if (ImGui.MenuItem(map.DifficultyName, map != Screen.Map)) + Screen.SwitchToMap(map); + } + ImGui.EndMenu(); + } + if (ImGui.BeginMenu("Create New Difficulty", Screen.Map.Game == MapGame.Quaver)) { if (ImGui.MenuItem("New Map")) From aa1d0efa3492d63e529eafa20b36902d28630c71 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Thu, 4 Apr 2024 10:23:24 +0800 Subject: [PATCH 117/249] Fix order of parameters of Vector4 in CLR <-> Lua script conversion --- Quaver.Shared/Scripting/LuaImGui.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Quaver.Shared/Scripting/LuaImGui.cs b/Quaver.Shared/Scripting/LuaImGui.cs index 8b4614b1c2..7acece8d1b 100644 --- a/Quaver.Shared/Scripting/LuaImGui.cs +++ b/Quaver.Shared/Scripting/LuaImGui.cs @@ -256,21 +256,21 @@ private void RegisterAllVectors() Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Table, typeof(Vector4), dynVal => { var table = dynVal.Table; - var w = (float)((double)table[1]); - var x = (float)((double)table[2]); - var y = (float)((double)table[3]); - var z = (float)((double)table[4]); - return new Vector4(w, x, y, z); + var x = (float)((double)table[1]); + var y = (float)((double)table[2]); + var z = (float)((double)table[3]); + var w = (float)((double)table[4]); + return new Vector4(x, y, z, w); } ); Script.GlobalOptions.CustomConverters.SetClrToScriptCustomConversion( (script, vector) => { - var w = DynValue.NewNumber(vector.W); var x = DynValue.NewNumber(vector.X); var y = DynValue.NewNumber(vector.Y); var z = DynValue.NewNumber(vector.Z); - var dynVal = DynValue.NewTable(script, w, x, y, z); + var w = DynValue.NewNumber(vector.W); + var dynVal = DynValue.NewTable(script, x, y, z, w); return dynVal; } ); From 5004c4f15cb437cd7275abad112e586eb9579915 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Thu, 4 Apr 2024 10:52:16 +0800 Subject: [PATCH 118/249] Add option to play hitsounds at long note release --- Quaver.Shared/Config/ConfigManager.cs | 6 ++++++ Quaver.Shared/Screens/Options/OptionsMenu.cs | 1 + 2 files changed, 7 insertions(+) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index f4e3961697..318368a93f 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -286,6 +286,11 @@ public static class ConfigManager /// If true, hitsounds in gameplay will be played. /// internal static Bindable EnableHitsounds { get; private set; } + + /// + /// If true, a hitsound will be played when releasing a long note + /// + internal static Bindable EnableLongNoteReleaseHitsounds { get; private set; } /// /// If true, keysounds in gameplay will be played. @@ -933,6 +938,7 @@ private static void ReadConfigFile() DisplayTimingLines = ReadValue(@"DisplayTimingLines", true, data); DisplayMenuAudioVisualizer = ReadValue(@"DisplayMenuAudioVisualizer", true, data); EnableHitsounds = ReadValue(@"EnableHitsounds", true, data); + EnableLongNoteReleaseHitsounds = ReadValue(@"EnableLongNoteReleaseHitsounds", false, data); EnableKeysounds = ReadValue(@"EnableKeysounds", true, data); KeyNavigateLeft = ReadValue(@"KeyNavigateLeft", Keys.Left, data); KeyNavigateRight = ReadValue(@"KeyNavigateRight", Keys.Right, data); diff --git a/Quaver.Shared/Screens/Options/OptionsMenu.cs b/Quaver.Shared/Screens/Options/OptionsMenu.cs index c0054c7213..94b0527cbb 100644 --- a/Quaver.Shared/Screens/Options/OptionsMenu.cs +++ b/Quaver.Shared/Screens/Options/OptionsMenu.cs @@ -208,6 +208,7 @@ private void CreateSections() new OptionsSubcategory("Sound", new List() { new OptionsItemCheckbox(containerRect, "Enable Hitsounds", ConfigManager.EnableHitsounds), + new OptionsItemCheckbox(containerRect, "Enable Long Note Release Hitsounds", ConfigManager.EnableLongNoteReleaseHitsounds), new OptionsItemCheckbox(containerRect, "Enable Keysounds", ConfigManager.EnableKeysounds) }), new OptionsSubcategory("Input", new List() From 368cfeff1f2641b90ac2c76c3586667bc3cacdd4 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Thu, 4 Apr 2024 10:53:38 +0800 Subject: [PATCH 119/249] Play long note release hitsound when the respective option is set. --- .../Screens/Gameplay/Rulesets/Input/InputManagerKeys.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Quaver.Shared/Screens/Gameplay/Rulesets/Input/InputManagerKeys.cs b/Quaver.Shared/Screens/Gameplay/Rulesets/Input/InputManagerKeys.cs index b74b5d0f95..a84767ce8f 100644 --- a/Quaver.Shared/Screens/Gameplay/Rulesets/Input/InputManagerKeys.cs +++ b/Quaver.Shared/Screens/Gameplay/Rulesets/Input/InputManagerKeys.cs @@ -281,6 +281,13 @@ private void HandleKeyRelease(HitObjectManagerKeys manager, GameplayHitObjectKey // If LN has been released during a window if (judgement != Judgement.Ghost) { + var game = GameBase.Game as QuaverGame; + if (game?.CurrentScreen?.Type != QuaverScreenType.Editor) + { + if (ConfigManager.EnableHitsounds.Value && ConfigManager.EnableLongNoteReleaseHitsounds.Value) + HitObjectManager.PlayObjectHitSounds(info.HitObjectInfo); + } + // Update stats Ruleset.ScoreProcessor.Stats.Add( new HitStat( From 09e591a5b996c308c6e27898f603bb77a8f7868b Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Fri, 5 Apr 2024 09:50:31 +0800 Subject: [PATCH 120/249] Don't refuse to switch difficulties when the map is not from Quaver --- Quaver.Shared/Screens/Edit/EditScreen.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index fbc945c28c..d14d07ea61 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -1519,14 +1519,6 @@ public static void CreateNewMapset(string audioFile) /// public void SwitchToMap(Map map, bool force = false) { - if (Map.Game != MapGame.Quaver) - { - NotificationManager.Show(NotificationLevel.Warning, - "You cannot create new difficulties for maps from other games. Create a new set!"); - - return; - } - if (ActionManager.HasUnsavedChanges && !force) { DialogManager.Show(new UnsavedChangesSwitchMapDialog(this, map)); From 107eb61c6e3a8aa60e921301d7b0ba0ec94b8c2b Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 6 Apr 2024 16:20:23 +0800 Subject: [PATCH 121/249] Add keybind config for toggling playtest autoplay --- Quaver.Shared/Config/ConfigManager.cs | 6 ++++++ Quaver.Shared/Screens/Gameplay/GameplayScreen.cs | 2 +- Quaver.Shared/Screens/Options/OptionsMenu.cs | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index f4e3961697..4a0c85058e 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -745,6 +745,11 @@ public static class ConfigManager /// internal static Bindable KeyIncreaseMapOffset { get; private set; } internal static Bindable KeyDecreaseMapOffset { get; private set; } + + /// + /// The keys to toggle autoplay during playtesting + /// + internal static Bindable KeyTogglePlaytestAutoplay { get; private set; } /// /// The key to hide the scoreboard in-game. @@ -989,6 +994,7 @@ private static void ReadConfigFile() KeyIncreaseScrollSpeed = ReadValue(@"KeyIncreaseScrollSpeed", Keys.F4, data); KeyDecreaseMapOffset = ReadValue(@"KeyDecreaseMapOffset", Keys.OemMinus, data); KeyIncreaseMapOffset = ReadValue(@"KeyIncreaseMapOffset", Keys.OemPlus, data); + KeyTogglePlaytestAutoplay = ReadValue(@"KeyTogglePlaytestAutoplay", Keys.Tab, data); KeyScoreboardVisible = ReadValue(@"KeyScoreboardVisible", Keys.Tab, data); KeyQuickExit = ReadValue(@"KeyQuickExit", Keys.F1, data); KeyScreenshot = ReadValue(@"KeyScreenshot", Keys.F12, data); diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs index 84323be73f..94c31c6bca 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs @@ -1500,7 +1500,7 @@ public void ExitToNewEditor(bool seekToTime = false) public void HandleAutoplayTabInput(GameTime gameTime) { // Handle play test autoplay input. - if (IsPlayTesting && KeyboardManager.IsUniqueKeyPress(Keys.Tab) && !KeyboardManager.IsShiftDown() && !OnlineChat.Instance.IsOpen && DialogManager.Dialogs.Count == 0) + if (IsPlayTesting && KeyboardManager.IsUniqueKeyPress(ConfigManager.KeyTogglePlaytestAutoplay.Value) && !KeyboardManager.IsShiftDown() && !OnlineChat.Instance.IsOpen && DialogManager.Dialogs.Count == 0) { var inputManager = (KeysInputManager) Ruleset.InputManager; diff --git a/Quaver.Shared/Screens/Options/OptionsMenu.cs b/Quaver.Shared/Screens/Options/OptionsMenu.cs index c0054c7213..8f8c65379c 100644 --- a/Quaver.Shared/Screens/Options/OptionsMenu.cs +++ b/Quaver.Shared/Screens/Options/OptionsMenu.cs @@ -362,6 +362,7 @@ private void CreateSections() new OptionsItemKeybind(containerRect, "Increase Scroll Speed", ConfigManager.KeyIncreaseScrollSpeed), new OptionsItemKeybind(containerRect, "Decrease Map Offset", ConfigManager.KeyDecreaseMapOffset), new OptionsItemKeybind(containerRect, "Increase Map Offset", ConfigManager.KeyIncreaseMapOffset), + new OptionsItemKeybind(containerRect, "Toggle Playtest Autoplay", ConfigManager.KeyTogglePlaytestAutoplay), }), new OptionsSubcategory("Gameplay User Interface", new List() { From 7fe9e30e097b3e0175eb338b7c703f674ea41585 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 6 Apr 2024 21:53:02 +0800 Subject: [PATCH 122/249] Move playtest autoplay keybind option to Song Selection --- Quaver.Shared/Screens/Options/OptionsMenu.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Options/OptionsMenu.cs b/Quaver.Shared/Screens/Options/OptionsMenu.cs index 8f8c65379c..4601d0d711 100644 --- a/Quaver.Shared/Screens/Options/OptionsMenu.cs +++ b/Quaver.Shared/Screens/Options/OptionsMenu.cs @@ -362,7 +362,6 @@ private void CreateSections() new OptionsItemKeybind(containerRect, "Increase Scroll Speed", ConfigManager.KeyIncreaseScrollSpeed), new OptionsItemKeybind(containerRect, "Decrease Map Offset", ConfigManager.KeyDecreaseMapOffset), new OptionsItemKeybind(containerRect, "Increase Map Offset", ConfigManager.KeyIncreaseMapOffset), - new OptionsItemKeybind(containerRect, "Toggle Playtest Autoplay", ConfigManager.KeyTogglePlaytestAutoplay), }), new OptionsSubcategory("Gameplay User Interface", new List() { @@ -376,6 +375,7 @@ private void CreateSections() { new OptionsItemKeybind(containerRect, "Decrease Gameplay Rate", ConfigManager.KeyDecreaseGameplayAudioRate), new OptionsItemKeybind(containerRect, "Increase Gameplay Rate", ConfigManager.KeyIncreaseGameplayAudioRate), + new OptionsItemKeybind(containerRect, "Toggle Playtest Autoplay", ConfigManager.KeyTogglePlaytestAutoplay), }), new OptionsSubcategory("Editor", new List() { From f195bff1bf84d6e19602cb21dfff4cec4e8bedbb Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Mon, 8 Apr 2024 18:06:59 +0800 Subject: [PATCH 123/249] Add spectrogram --- .../Spectrogram/EditorPlayfieldSpectrogram.cs | 187 ++++++++++++ .../EditorPlayfieldSpectrogramSlice.cs | 150 +++++++++ .../Spectrogram/SpectrogramColormap.cs | 284 ++++++++++++++++++ 3 files changed, 621 insertions(+) create mode 100644 Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs create mode 100644 Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs create mode 100644 Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/SpectrogramColormap.cs diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs new file mode 100644 index 0000000000..44b6e2f539 --- /dev/null +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using Microsoft.Xna.Framework; +using Wobble.Audio.Tracks; +using Wobble.Graphics; +using ManagedBass; +using Quaver.Shared.Database.Maps; +using Quaver.Shared.Scheduling; +using Wobble.Logging; +using ManagedBass.Fx; +using Quaver.Shared.Config; +using Quaver.Shared.Screens.Edit.UI.Playfield.Waveform; + +namespace Quaver.Shared.Screens.Edit.UI.Playfield.Spectrogram +{ + public class EditorPlayfieldSpectrogram : Container + { + private List Slices { get; set; } + + private List VisibleSlices { get; } + + private EditorPlayfield Playfield { get; } + + private float[,] TrackData { get; set; } + + private long TrackByteLength { get; set; } + + private double TrackLengthMilliSeconds { get; set; } + + private int FftPerSlice { get; set; } + + private int Stream { get; set; } + + public const int FftCount = 256; + public int FftResultCount { get; set; } + + private int FftRoundsTaken { get; set; } + + private int BytesReadPerFft { get; set; } + + private CancellationToken Token { get; } + + public EditorPlayfieldSpectrogram(EditorPlayfield playfield, CancellationToken token) + { + Playfield = playfield; + Token = token; + + Slices = new List(); + VisibleSlices = new List(); + + GenerateWaveform(); + CheckCancellationToken(); + } + + /// + /// + /// + public override void Update(GameTime gameTime) + { + if (Slices.Count > 0) + { + foreach (var slice in Slices) + slice.Update(gameTime); + } + + base.Update(gameTime); + } + + /// + /// + /// + public override void Draw(GameTime gameTime) + { + var index = (int)(Audio.AudioEngine.Track.Time / TrackLengthMilliSeconds * Slices.Count); + + var amount = Math.Max(6, (int)(2.5f / Playfield.TrackSpeed + 0.5f)); + + for (var i = 0; i < amount; i++) + TryDrawSlice(index + (i - amount / 2), gameTime); + } + + /// + /// + public void GenerateWaveform() + { + FftPerSlice = (int)Playfield.Height; + GenerateTrackData(); + + var tempSlices = new List(); + var millisecondPerFft = Bass.ChannelBytes2Seconds(Stream, BytesReadPerFft) * 1000; + var millisecondPerSlice = FftPerSlice * millisecondPerFft; + var sampleRate = Bass.ChannelGetInfo(Stream).Frequency; + + for (var fftRound = 0; fftRound < FftRoundsTaken; fftRound += FftPerSlice) + { + var t = (int)(fftRound * millisecondPerFft); + var trackSliceData = new float[FftPerSlice, FftResultCount]; + + for (var y = 0; y < FftPerSlice; y++) + { + var currentFftIndex = fftRound + y; + if (currentFftIndex >= TrackData.GetLength(0)) + break; + for (var x = 0; x < FftResultCount; x++) + { + trackSliceData[y, x] = TrackData[currentFftIndex, x]; + } + } + + var slice = new EditorPlayfieldSpectrogramSlice(Playfield, (float)millisecondPerSlice, FftPerSlice, + trackSliceData, t, sampleRate); + tempSlices.Add(slice); + } + + Slices = tempSlices; + Bass.StreamFree(Stream); + } + + /// + /// + private void GenerateTrackData() + { + const BassFlags flags = BassFlags.Decode | BassFlags.Float; + + Stream = Bass.CreateStream(((AudioTrack)Audio.AudioEngine.Track).OriginalFilePath, 0, 0, flags); + + TrackByteLength = Bass.ChannelGetLength(Stream); + FftResultCount = FftCount; + BytesReadPerFft = sizeof(float) * FftResultCount * Bass.ChannelGetInfo(Stream).Channels * 2; + TrackData = new float[TrackByteLength / BytesReadPerFft + 1, FftResultCount]; + + var trackDataFft = new float[FftResultCount]; + FftRoundsTaken = 0; + + while (Bass.ChannelGetData(Stream, trackDataFft, (int)DataFlags.FFT512) > 0) + { + for (var i = 0; i < FftResultCount; i++) + { + TrackData[FftRoundsTaken, i] = trackDataFft[i]; + } + FftRoundsTaken++; + } + + TrackByteLength = Bass.ChannelGetLength(Stream); + TrackLengthMilliSeconds = Bass.ChannelBytes2Seconds(Stream, TrackByteLength) * 1000.0; + } + + /// + /// + /// + /// + private void TryDrawSlice(int index, GameTime gameTime) + { + if (index >= 0 && index < Slices.Count) + Slices[index]?.Draw(gameTime); + } + + private void CheckCancellationToken() + { + if (!Token.IsCancellationRequested) + return; + + Destroy(); + } + + /// + /// + public override void Destroy() => DisposeWaveform(); + + /// + /// + public void DisposeWaveform() + { + DisposeSlices(); + base.Destroy(); + } + + private void DisposeSlices() + { + foreach (var slice in Slices) + slice.Destroy(); + + Slices.Clear(); + } + } +} \ No newline at end of file diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs new file mode 100644 index 0000000000..9e1ef984d1 --- /dev/null +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs @@ -0,0 +1,150 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Wobble.Graphics.Sprites; +using Wobble; +using Wobble.Graphics.Animations; +using Quaver.Shared.Config; +using Quaver.Shared.Screens.Edit.UI.Playfield.Waveform; +using Wobble.Logging; + +namespace Quaver.Shared.Screens.Edit.UI.Playfield.Spectrogram +{ + public class EditorPlayfieldSpectrogramSlice : Sprite + { + private EditorPlayfield Playfield { get; set; } + + private Sprite SliceSprite { get; set; } + + private int SliceSize { get; } + + private double SliceTimeMilliSeconds { get; } + + private Texture2D SliceTexture { get; set; } + + private float LengthMs { get; set; } + + private int SampleRate { get; set; } + + private int ReferenceWidth { get; } = 1024; + + public EditorPlayfieldSpectrogramSlice(EditorPlayfield playfield, float lengthMs, int sliceSize, + float[,] sliceData, + double sliceTime, int sampleRate) + { + Playfield = playfield; + SliceSize = sliceSize; + SliceTimeMilliSeconds = sliceTime; + LengthMs = lengthMs; + SampleRate = sampleRate; + + CreateSlice(sliceData); + } + + + public override void Update(GameTime gameTime) + { + SliceSprite.PerformTransformations(gameTime); + base.Update(gameTime); + } + + public override void Draw(GameTime gameTime) + { + SliceSprite.X = Playfield.ScreenRectangle.X; + SliceSprite.Y = Playfield.HitPositionY - (float)(SliceTimeMilliSeconds + LengthMs) * Playfield.TrackSpeed - + Height; + SliceSprite.Height = LengthMs * Playfield.TrackSpeed; + SliceSprite.Tint = new Color( + 255, + 255, + 255, + 255); + SliceSprite.Draw(gameTime); + } + + /// + /// + public override void Destroy() + { + SliceSprite?.Destroy(); + SliceSprite = null; + SliceTexture?.Dispose(); + + base.Destroy(); + } + + public void UpdatePlayfield(EditorPlayfield playfield) => Playfield = playfield; + + private void CreateSlice(float[,] sliceData) + { + SliceSprite = new Sprite { Alpha = 0 }; + + var textureHeight = SliceSize; + + SliceTexture = new Texture2D(GameBase.Game.GraphicsDevice, ReferenceWidth, textureHeight); + + var dataColors = new Color[ReferenceWidth * textureHeight]; + Logger.Debug($"Slice {ReferenceWidth} x {textureHeight}", LogType.Runtime); + + for (var y = 0; y < textureHeight; y++) + { + for (var x = 0; x < EditorPlayfieldSpectrogram.FftCount; x++) + { + var textureX = CalculateTextureXLinear(x); + if (textureX == -1) continue; + var intensity = + MathF.Sqrt(GetAverageData(sliceData, y, x)) * 3f; // scale it (sqrt to make low values more visible) + intensity = Math.Clamp(intensity, 0, 1); + var index = DataColorIndex(textureHeight, y, textureX); + var nextTextureX = CalculateTextureXLinear(x + 1); + if (nextTextureX == -1) nextTextureX = ReferenceWidth - 1; + for (var i = index; i < DataColorIndex(textureHeight, y, nextTextureX) && i < dataColors.Length; i++) + { + dataColors[i] = SpectrogramColormap.GetColor(intensity); + } + + // if (index + (int)Playfield.Width < dataColors.Length) + // dataColors[index + (int)Playfield.Width] = new Color(intensity, 0, 0, 1); + } + } + + SliceTexture.SetData(dataColors); + SliceSprite.Image = SliceTexture; + SliceSprite.Width = (int)Playfield.Width; + SliceSprite.Height = LengthMs; + SliceSprite.FadeTo(1, Easing.Linear, 250); + } + + private int CalculateTextureX(int x) + { + var minFrequency = (float)SampleRate / EditorPlayfieldSpectrogram.FftCount; + const float maxFrequency = 20000; + var a = 1 / MathF.Log(maxFrequency / minFrequency); + var b = -a * MathF.Log(minFrequency); + var frequency = (float)x * SampleRate / EditorPlayfieldSpectrogram.FftCount; + if (frequency < minFrequency || frequency > maxFrequency) return -1; + var processedProgress = a * MathF.Log(frequency) + b; + return (int)(processedProgress * ReferenceWidth); + } + private int CalculateTextureXLinear(int x) + { + var minFrequency = (float)SampleRate / EditorPlayfieldSpectrogram.FftCount; + const float maxFrequency = 20000; + var frequency = (float)x * SampleRate / EditorPlayfieldSpectrogram.FftCount; + if (frequency < minFrequency || frequency > maxFrequency) return -1; + + return (int)((frequency - minFrequency) / (maxFrequency - minFrequency) * ReferenceWidth); + } + + + private float GetAverageData(float[,] data, int y, int x) + { + return data[y, x]; + } + + private int DataColorIndex(int textureHeight, int y, int x) + { + return (textureHeight - y - 1) * ReferenceWidth + x; + } + } +} \ No newline at end of file diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/SpectrogramColormap.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/SpectrogramColormap.cs new file mode 100644 index 0000000000..c163f38fcd --- /dev/null +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/SpectrogramColormap.cs @@ -0,0 +1,284 @@ +using Microsoft.Xna.Framework; + +namespace Quaver.Shared.Screens.Edit.UI.Playfield.Spectrogram +{ + /// + /// Linear colormap that evenly interpolates between a series of colors + /// + public static class SpectrogramColormap + { + // Adapted from https://github.com/BIDS/colormap/blob/master/option_c.py + private static float[,] Data = + { + { 5.03832136e-02f, 2.98028976e-02f, 5.27974883e-01f }, + { 6.35363639e-02f, 2.84259729e-02f, 5.33123681e-01f }, + { 7.53531234e-02f, 2.72063728e-02f, 5.38007001e-01f }, + { 8.62217979e-02f, 2.61253206e-02f, 5.42657691e-01f }, + { 9.63786097e-02f, 2.51650976e-02f, 5.47103487e-01f }, + { 1.05979704e-01f, 2.43092436e-02f, 5.51367851e-01f }, + { 1.15123641e-01f, 2.35562500e-02f, 5.55467728e-01f }, + { 1.23902903e-01f, 2.28781011e-02f, 5.59423480e-01f }, + { 1.32380720e-01f, 2.22583774e-02f, 5.63250116e-01f }, + { 1.40603076e-01f, 2.16866674e-02f, 5.66959485e-01f }, + { 1.48606527e-01f, 2.11535876e-02f, 5.70561711e-01f }, + { 1.56420649e-01f, 2.06507174e-02f, 5.74065446e-01f }, + { 1.64069722e-01f, 2.01705326e-02f, 5.77478074e-01f }, + { 1.71573925e-01f, 1.97063415e-02f, 5.80805890e-01f }, + { 1.78950212e-01f, 1.92522243e-02f, 5.84054243e-01f }, + { 1.86212958e-01f, 1.88029767e-02f, 5.87227661e-01f }, + { 1.93374449e-01f, 1.83540593e-02f, 5.90329954e-01f }, + { 2.00445260e-01f, 1.79015512e-02f, 5.93364304e-01f }, + { 2.07434551e-01f, 1.74421086e-02f, 5.96333341e-01f }, + { 2.14350298e-01f, 1.69729276e-02f, 5.99239207e-01f }, + { 2.21196750e-01f, 1.64970484e-02f, 6.02083323e-01f }, + { 2.27982971e-01f, 1.60071509e-02f, 6.04867403e-01f }, + { 2.34714537e-01f, 1.55015065e-02f, 6.07592438e-01f }, + { 2.41396253e-01f, 1.49791041e-02f, 6.10259089e-01f }, + { 2.48032377e-01f, 1.44393586e-02f, 6.12867743e-01f }, + { 2.54626690e-01f, 1.38820918e-02f, 6.15418537e-01f }, + { 2.61182562e-01f, 1.33075156e-02f, 6.17911385e-01f }, + { 2.67702993e-01f, 1.27162163e-02f, 6.20345997e-01f }, + { 2.74190665e-01f, 1.21091423e-02f, 6.22721903e-01f }, + { 2.80647969e-01f, 1.14875915e-02f, 6.25038468e-01f }, + { 2.87076059e-01f, 1.08554862e-02f, 6.27294975e-01f }, + { 2.93477695e-01f, 1.02128849e-02f, 6.29490490e-01f }, + { 2.99855122e-01f, 9.56079551e-03f, 6.31623923e-01f }, + { 3.06209825e-01f, 8.90185346e-03f, 6.33694102e-01f }, + { 3.12543124e-01f, 8.23900704e-03f, 6.35699759e-01f }, + { 3.18856183e-01f, 7.57551051e-03f, 6.37639537e-01f }, + { 3.25150025e-01f, 6.91491734e-03f, 6.39512001e-01f }, + { 3.31425547e-01f, 6.26107379e-03f, 6.41315649e-01f }, + { 3.37683446e-01f, 5.61830889e-03f, 6.43048936e-01f }, + { 3.43924591e-01f, 4.99053080e-03f, 6.44710195e-01f }, + { 3.50149699e-01f, 4.38202557e-03f, 6.46297711e-01f }, + { 3.56359209e-01f, 3.79781761e-03f, 6.47809772e-01f }, + { 3.62553473e-01f, 3.24319591e-03f, 6.49244641e-01f }, + { 3.68732762e-01f, 2.72370721e-03f, 6.50600561e-01f }, + { 3.74897270e-01f, 2.24514897e-03f, 6.51875762e-01f }, + { 3.81047116e-01f, 1.81356205e-03f, 6.53068467e-01f }, + { 3.87182639e-01f, 1.43446923e-03f, 6.54176761e-01f }, + { 3.93304010e-01f, 1.11388259e-03f, 6.55198755e-01f }, + { 3.99410821e-01f, 8.59420809e-04f, 6.56132835e-01f }, + { 4.05502914e-01f, 6.78091517e-04f, 6.56977276e-01f }, + { 4.11580082e-01f, 5.77101735e-04f, 6.57730380e-01f }, + { 4.17642063e-01f, 5.63847476e-04f, 6.58390492e-01f }, + { 4.23688549e-01f, 6.45902780e-04f, 6.58956004e-01f }, + { 4.29719186e-01f, 8.31008207e-04f, 6.59425363e-01f }, + { 4.35733575e-01f, 1.12705875e-03f, 6.59797077e-01f }, + { 4.41732123e-01f, 1.53984779e-03f, 6.60069009e-01f }, + { 4.47713600e-01f, 2.07954744e-03f, 6.60240367e-01f }, + { 4.53677394e-01f, 2.75470302e-03f, 6.60309966e-01f }, + { 4.59622938e-01f, 3.57374415e-03f, 6.60276655e-01f }, + { 4.65549631e-01f, 4.54518084e-03f, 6.60139383e-01f }, + { 4.71456847e-01f, 5.67758762e-03f, 6.59897210e-01f }, + { 4.77343929e-01f, 6.97958743e-03f, 6.59549311e-01f }, + { 4.83210198e-01f, 8.45983494e-03f, 6.59094989e-01f }, + { 4.89054951e-01f, 1.01269996e-02f, 6.58533677e-01f }, + { 4.94877466e-01f, 1.19897486e-02f, 6.57864946e-01f }, + { 5.00677687e-01f, 1.40550640e-02f, 6.57087561e-01f }, + { 5.06454143e-01f, 1.63333443e-02f, 6.56202294e-01f }, + { 5.12206035e-01f, 1.88332232e-02f, 6.55209222e-01f }, + { 5.17932580e-01f, 2.15631918e-02f, 6.54108545e-01f }, + { 5.23632990e-01f, 2.45316468e-02f, 6.52900629e-01f }, + { 5.29306474e-01f, 2.77468735e-02f, 6.51586010e-01f }, + { 5.34952244e-01f, 3.12170300e-02f, 6.50165396e-01f }, + { 5.40569510e-01f, 3.49501310e-02f, 6.48639668e-01f }, + { 5.46157494e-01f, 3.89540334e-02f, 6.47009884e-01f }, + { 5.51715423e-01f, 4.31364795e-02f, 6.45277275e-01f }, + { 5.57242538e-01f, 4.73307585e-02f, 6.43443250e-01f }, + { 5.62738096e-01f, 5.15448092e-02f, 6.41509389e-01f }, + { 5.68201372e-01f, 5.57776706e-02f, 6.39477440e-01f }, + { 5.73631859e-01f, 6.00281369e-02f, 6.37348841e-01f }, + { 5.79028682e-01f, 6.42955547e-02f, 6.35126108e-01f }, + { 5.84391137e-01f, 6.85790261e-02f, 6.32811608e-01f }, + { 5.89718606e-01f, 7.28775875e-02f, 6.30407727e-01f }, + { 5.95010505e-01f, 7.71902878e-02f, 6.27916992e-01f }, + { 6.00266283e-01f, 8.15161895e-02f, 6.25342058e-01f }, + { 6.05485428e-01f, 8.58543713e-02f, 6.22685703e-01f }, + { 6.10667469e-01f, 9.02039303e-02f, 6.19950811e-01f }, + { 6.15811974e-01f, 9.45639838e-02f, 6.17140367e-01f }, + { 6.20918555e-01f, 9.89336721e-02f, 6.14257440e-01f }, + { 6.25986869e-01f, 1.03312160e-01f, 6.11305174e-01f }, + { 6.31016615e-01f, 1.07698641e-01f, 6.08286774e-01f }, + { 6.36007543e-01f, 1.12092335e-01f, 6.05205491e-01f }, + { 6.40959444e-01f, 1.16492495e-01f, 6.02064611e-01f }, + { 6.45872158e-01f, 1.20898405e-01f, 5.98867442e-01f }, + { 6.50745571e-01f, 1.25309384e-01f, 5.95617300e-01f }, + { 6.55579615e-01f, 1.29724785e-01f, 5.92317494e-01f }, + { 6.60374266e-01f, 1.34143997e-01f, 5.88971318e-01f }, + { 6.65129493e-01f, 1.38566428e-01f, 5.85582301e-01f }, + { 6.69845385e-01f, 1.42991540e-01f, 5.82153572e-01f }, + { 6.74522060e-01f, 1.47418835e-01f, 5.78688247e-01f }, + { 6.79159664e-01f, 1.51847851e-01f, 5.75189431e-01f }, + { 6.83758384e-01f, 1.56278163e-01f, 5.71660158e-01f }, + { 6.88318440e-01f, 1.60709387e-01f, 5.68103380e-01f }, + { 6.92840088e-01f, 1.65141174e-01f, 5.64521958e-01f }, + { 6.97323615e-01f, 1.69573215e-01f, 5.60918659e-01f }, + { 7.01769334e-01f, 1.74005236e-01f, 5.57296144e-01f }, + { 7.06177590e-01f, 1.78437000e-01f, 5.53656970e-01f }, + { 7.10548747e-01f, 1.82868306e-01f, 5.50003579e-01f }, + { 7.14883195e-01f, 1.87298986e-01f, 5.46338299e-01f }, + { 7.19181339e-01f, 1.91728906e-01f, 5.42663338e-01f }, + { 7.23443604e-01f, 1.96157962e-01f, 5.38980786e-01f }, + { 7.27670428e-01f, 2.00586086e-01f, 5.35292612e-01f }, + { 7.31862231e-01f, 2.05013174e-01f, 5.31600995e-01f }, + { 7.36019424e-01f, 2.09439071e-01f, 5.27908434e-01f }, + { 7.40142557e-01f, 2.13863965e-01f, 5.24215533e-01f }, + { 7.44232102e-01f, 2.18287899e-01f, 5.20523766e-01f }, + { 7.48288533e-01f, 2.22710942e-01f, 5.16834495e-01f }, + { 7.52312321e-01f, 2.27133187e-01f, 5.13148963e-01f }, + { 7.56303937e-01f, 2.31554749e-01f, 5.09468305e-01f }, + { 7.60263849e-01f, 2.35975765e-01f, 5.05793543e-01f }, + { 7.64192516e-01f, 2.40396394e-01f, 5.02125599e-01f }, + { 7.68090391e-01f, 2.44816813e-01f, 4.98465290e-01f }, + { 7.71957916e-01f, 2.49237220e-01f, 4.94813338e-01f }, + { 7.75795522e-01f, 2.53657797e-01f, 4.91170517e-01f }, + { 7.79603614e-01f, 2.58078397e-01f, 4.87539124e-01f }, + { 7.83382636e-01f, 2.62499662e-01f, 4.83917732e-01f }, + { 7.87132978e-01f, 2.66921859e-01f, 4.80306702e-01f }, + { 7.90855015e-01f, 2.71345267e-01f, 4.76706319e-01f }, + { 7.94549101e-01f, 2.75770179e-01f, 4.73116798e-01f }, + { 7.98215577e-01f, 2.80196901e-01f, 4.69538286e-01f }, + { 8.01854758e-01f, 2.84625750e-01f, 4.65970871e-01f }, + { 8.05466945e-01f, 2.89057057e-01f, 4.62414580e-01f }, + { 8.09052419e-01f, 2.93491117e-01f, 4.58869577e-01f }, + { 8.12611506e-01f, 2.97927865e-01f, 4.55337565e-01f }, + { 8.16144382e-01f, 3.02368130e-01f, 4.51816385e-01f }, + { 8.19651255e-01f, 3.06812282e-01f, 4.48305861e-01f }, + { 8.23132309e-01f, 3.11260703e-01f, 4.44805781e-01f }, + { 8.26587706e-01f, 3.15713782e-01f, 4.41315901e-01f }, + { 8.30017584e-01f, 3.20171913e-01f, 4.37835947e-01f }, + { 8.33422053e-01f, 3.24635499e-01f, 4.34365616e-01f }, + { 8.36801237e-01f, 3.29104836e-01f, 4.30905052e-01f }, + { 8.40155276e-01f, 3.33580106e-01f, 4.27454836e-01f }, + { 8.43484103e-01f, 3.38062109e-01f, 4.24013059e-01f }, + { 8.46787726e-01f, 3.42551272e-01f, 4.20579333e-01f }, + { 8.50066132e-01f, 3.47048028e-01f, 4.17153264e-01f }, + { 8.53319279e-01f, 3.51552815e-01f, 4.13734445e-01f }, + { 8.56547103e-01f, 3.56066072e-01f, 4.10322469e-01f }, + { 8.59749520e-01f, 3.60588229e-01f, 4.06916975e-01f }, + { 8.62926559e-01f, 3.65119408e-01f, 4.03518809e-01f }, + { 8.66077920e-01f, 3.69660446e-01f, 4.00126027e-01f }, + { 8.69203436e-01f, 3.74211795e-01f, 3.96738211e-01f }, + { 8.72302917e-01f, 3.78773910e-01f, 3.93354947e-01f }, + { 8.75376149e-01f, 3.83347243e-01f, 3.89975832e-01f }, + { 8.78422895e-01f, 3.87932249e-01f, 3.86600468e-01f }, + { 8.81442916e-01f, 3.92529339e-01f, 3.83228622e-01f }, + { 8.84435982e-01f, 3.97138877e-01f, 3.79860246e-01f }, + { 8.87401682e-01f, 4.01761511e-01f, 3.76494232e-01f }, + { 8.90339687e-01f, 4.06397694e-01f, 3.73130228e-01f }, + { 8.93249647e-01f, 4.11047871e-01f, 3.69767893e-01f }, + { 8.96131191e-01f, 4.15712489e-01f, 3.66406907e-01f }, + { 8.98983931e-01f, 4.20391986e-01f, 3.63046965e-01f }, + { 9.01807455e-01f, 4.25086807e-01f, 3.59687758e-01f }, + { 9.04601295e-01f, 4.29797442e-01f, 3.56328796e-01f }, + { 9.07364995e-01f, 4.34524335e-01f, 3.52969777e-01f }, + { 9.10098088e-01f, 4.39267908e-01f, 3.49610469e-01f }, + { 9.12800095e-01f, 4.44028574e-01f, 3.46250656e-01f }, + { 9.15470518e-01f, 4.48806744e-01f, 3.42890148e-01f }, + { 9.18108848e-01f, 4.53602818e-01f, 3.39528771e-01f }, + { 9.20714383e-01f, 4.58417420e-01f, 3.36165582e-01f }, + { 9.23286660e-01f, 4.63250828e-01f, 3.32800827e-01f }, + { 9.25825146e-01f, 4.68103387e-01f, 3.29434512e-01f }, + { 9.28329275e-01f, 4.72975465e-01f, 3.26066550e-01f }, + { 9.30798469e-01f, 4.77867420e-01f, 3.22696876e-01f }, + { 9.33232140e-01f, 4.82779603e-01f, 3.19325444e-01f }, + { 9.35629684e-01f, 4.87712357e-01f, 3.15952211e-01f }, + { 9.37990034e-01f, 4.92666544e-01f, 3.12575440e-01f }, + { 9.40312939e-01f, 4.97642038e-01f, 3.09196628e-01f }, + { 9.42597771e-01f, 5.02639147e-01f, 3.05815824e-01f }, + { 9.44843893e-01f, 5.07658169e-01f, 3.02433101e-01f }, + { 9.47050662e-01f, 5.12699390e-01f, 2.99048555e-01f }, + { 9.49217427e-01f, 5.17763087e-01f, 2.95662308e-01f }, + { 9.51343530e-01f, 5.22849522e-01f, 2.92274506e-01f }, + { 9.53427725e-01f, 5.27959550e-01f, 2.88883445e-01f }, + { 9.55469640e-01f, 5.33093083e-01f, 2.85490391e-01f }, + { 9.57468770e-01f, 5.38250172e-01f, 2.82096149e-01f }, + { 9.59424430e-01f, 5.43431038e-01f, 2.78700990e-01f }, + { 9.61335930e-01f, 5.48635890e-01f, 2.75305214e-01f }, + { 9.63202573e-01f, 5.53864931e-01f, 2.71909159e-01f }, + { 9.65023656e-01f, 5.59118349e-01f, 2.68513200e-01f }, + { 9.66798470e-01f, 5.64396327e-01f, 2.65117752e-01f }, + { 9.68525639e-01f, 5.69699633e-01f, 2.61721488e-01f }, + { 9.70204593e-01f, 5.75028270e-01f, 2.58325424e-01f }, + { 9.71835007e-01f, 5.80382015e-01f, 2.54931256e-01f }, + { 9.73416145e-01f, 5.85761012e-01f, 2.51539615e-01f }, + { 9.74947262e-01f, 5.91165394e-01f, 2.48151200e-01f }, + { 9.76427606e-01f, 5.96595287e-01f, 2.44766775e-01f }, + { 9.77856416e-01f, 6.02050811e-01f, 2.41387186e-01f }, + { 9.79232922e-01f, 6.07532077e-01f, 2.38013359e-01f }, + { 9.80556344e-01f, 6.13039190e-01f, 2.34646316e-01f }, + { 9.81825890e-01f, 6.18572250e-01f, 2.31287178e-01f }, + { 9.83040742e-01f, 6.24131362e-01f, 2.27937141e-01f }, + { 9.84198924e-01f, 6.29717516e-01f, 2.24595006e-01f }, + { 9.85300760e-01f, 6.35329876e-01f, 2.21264889e-01f }, + { 9.86345421e-01f, 6.40968508e-01f, 2.17948456e-01f }, + { 9.87332067e-01f, 6.46633475e-01f, 2.14647532e-01f }, + { 9.88259846e-01f, 6.52324832e-01f, 2.11364122e-01f }, + { 9.89127893e-01f, 6.58042630e-01f, 2.08100426e-01f }, + { 9.89935328e-01f, 6.63786914e-01f, 2.04858855e-01f }, + { 9.90681261e-01f, 6.69557720e-01f, 2.01642049e-01f }, + { 9.91364787e-01f, 6.75355082e-01f, 1.98452900e-01f }, + { 9.91984990e-01f, 6.81179025e-01f, 1.95294567e-01f }, + { 9.92540939e-01f, 6.87029567e-01f, 1.92170500e-01f }, + { 9.93031693e-01f, 6.92906719e-01f, 1.89084459e-01f }, + { 9.93456302e-01f, 6.98810484e-01f, 1.86040537e-01f }, + { 9.93813802e-01f, 7.04740854e-01f, 1.83043180e-01f }, + { 9.94103226e-01f, 7.10697814e-01f, 1.80097207e-01f }, + { 9.94323596e-01f, 7.16681336e-01f, 1.77207826e-01f }, + { 9.94473934e-01f, 7.22691379e-01f, 1.74380656e-01f }, + { 9.94553260e-01f, 7.28727890e-01f, 1.71621733e-01f }, + { 9.94560594e-01f, 7.34790799e-01f, 1.68937522e-01f }, + { 9.94494964e-01f, 7.40880020e-01f, 1.66334918e-01f }, + { 9.94355411e-01f, 7.46995448e-01f, 1.63821243e-01f }, + { 9.94140989e-01f, 7.53136955e-01f, 1.61404226e-01f }, + { 9.93850778e-01f, 7.59304390e-01f, 1.59091984e-01f }, + { 9.93482190e-01f, 7.65498551e-01f, 1.56890625e-01f }, + { 9.93033251e-01f, 7.71719833e-01f, 1.54807583e-01f }, + { 9.92505214e-01f, 7.77966775e-01f, 1.52854862e-01f }, + { 9.91897270e-01f, 7.84239120e-01f, 1.51041581e-01f }, + { 9.91208680e-01f, 7.90536569e-01f, 1.49376885e-01f }, + { 9.90438793e-01f, 7.96858775e-01f, 1.47869810e-01f }, + { 9.89587065e-01f, 8.03205337e-01f, 1.46529128e-01f }, + { 9.88647741e-01f, 8.09578605e-01f, 1.45357284e-01f }, + { 9.87620557e-01f, 8.15977942e-01f, 1.44362644e-01f }, + { 9.86509366e-01f, 8.22400620e-01f, 1.43556679e-01f }, + { 9.85314198e-01f, 8.28845980e-01f, 1.42945116e-01f }, + { 9.84031139e-01f, 8.35315360e-01f, 1.42528388e-01f }, + { 9.82652820e-01f, 8.41811730e-01f, 1.42302653e-01f }, + { 9.81190389e-01f, 8.48328902e-01f, 1.42278607e-01f }, + { 9.79643637e-01f, 8.54866468e-01f, 1.42453425e-01f }, + { 9.77994918e-01f, 8.61432314e-01f, 1.42808191e-01f }, + { 9.76264977e-01f, 8.68015998e-01f, 1.43350944e-01f }, + { 9.74443038e-01f, 8.74622194e-01f, 1.44061156e-01f }, + { 9.72530009e-01f, 8.81250063e-01f, 1.44922913e-01f }, + { 9.70532932e-01f, 8.87896125e-01f, 1.45918663e-01f }, + { 9.68443477e-01f, 8.94563989e-01f, 1.47014438e-01f }, + { 9.66271225e-01f, 9.01249365e-01f, 1.48179639e-01f }, + { 9.64021057e-01f, 9.07950379e-01f, 1.49370428e-01f }, + { 9.61681481e-01f, 9.14672479e-01f, 1.50520343e-01f }, + { 9.59275646e-01f, 9.21406537e-01f, 1.51566019e-01f }, + { 9.56808068e-01f, 9.28152065e-01f, 1.52409489e-01f }, + { 9.54286813e-01f, 9.34907730e-01f, 1.52921158e-01f }, + { 9.51726083e-01f, 9.41670605e-01f, 1.52925363e-01f }, + { 9.49150533e-01f, 9.48434900e-01f, 1.52177604e-01f }, + { 9.46602270e-01f, 9.55189860e-01f, 1.50327944e-01f }, + { 9.44151742e-01f, 9.61916487e-01f, 1.46860789e-01f }, + { 9.41896120e-01f, 9.68589814e-01f, 1.40955606e-01f }, + { 9.40015097e-01f, 9.75158357e-01f, 1.31325517e-01f }, + }; + + public static Color GetColor(float progress) + { + if (progress < 0 || progress > 1) return Color.Black; + var dataCount = Data.GetLength(0); + if (progress == 1) return new Color(Data[dataCount - 1, 0], Data[dataCount - 1, 1], Data[dataCount - 1, 2]); + var i = (int)(progress * (dataCount - 1)); + // var lerpWidth = 1f / Data.GetLength(0); + var lerpProgress = progress * (dataCount - 1) - i; + return Color.Lerp(new Color(Data[i, 0], Data[i, 1], Data[i, 2]), + new Color(Data[i + 1, 0], Data[i + 1, 1], Data[i + 1, 2]), + lerpProgress); + } + } +} \ No newline at end of file From b40337f965444127072719573d331ba5ba15dd25 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Mon, 8 Apr 2024 18:09:08 +0800 Subject: [PATCH 124/249] Make spectrograms show --- .../Edit/UI/Playfield/EditorPlayfield.cs | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs index 8379a25d97..2c81d284cb 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs @@ -35,6 +35,7 @@ using Quaver.Shared.Screens.Edit.UI.Footer; using Quaver.Shared.Screens.Edit.UI.Playfield.Lines; using Quaver.Shared.Screens.Edit.UI.Playfield.Seek; +using Quaver.Shared.Screens.Edit.UI.Playfield.Spectrogram; using Quaver.Shared.Screens.Edit.UI.Playfield.Timeline; using Quaver.Shared.Screens.Edit.UI.Playfield.Waveform; using Quaver.Shared.Screens.Edit.UI.Playfield.Zoom; @@ -211,10 +212,18 @@ public int ColumnSize /// /// private TaskHandler WaveformLoadTask { get; set; } + + /// + /// + private TaskHandler SpectrogramLoadTask { get; set; } /// /// public EditorPlayfieldWaveform Waveform { get; set; } + + /// + /// + public EditorPlayfieldSpectrogram Spectrogram { get; set; } /// /// @@ -341,6 +350,7 @@ public EditorPlayfield(Qua map, EditorActionManager manager, Bindable CreateHitPositionLine(); CreateTimeline(); CreateWaveform(); + CreateSpectrogram(); CreateLineContainer(); CreateHitObjects(); CreateButton(); @@ -393,6 +403,7 @@ public override void Update(GameTime gameTime) UpdateHitObjectPool(); Waveform?.Update(gameTime); + Spectrogram?.Update(gameTime); Timeline.Update(gameTime); LineContainer.Update(gameTime); HandleInput(); @@ -425,6 +436,9 @@ public override void Draw(GameTime gameTime) if (ShowWaveform.Value) Waveform?.Draw(gameTime); + + // TODO + Spectrogram?.Draw(gameTime); LineContainer.Draw(gameTime); DrawHitObjects(gameTime); @@ -446,6 +460,7 @@ public override void Destroy() WaveformLoadTask?.Dispose(); Waveform?.Destroy(); + Spectrogram?.Destroy(); ThreadScheduler.Run(() => { @@ -589,6 +604,44 @@ private int CreateWaveform(int arg1, CancellationToken token) return 0; } + /// + /// + public void CreateSpectrogram() + { + if (IsUneditable) + return; + + // TODO + LoadingWaveform = new LoadingWheelText(20, "Loading Spectrogram...") + { + Alignment = Alignment.TopCenter, + Y = 200, + }; + + SpectrogramLoadTask = new TaskHandler(CreateSpectrogram); + + SpectrogramLoadTask.OnCompleted += (sender, args) => LoadingWaveform.FadeOut(); + SpectrogramLoadTask.OnCancelled += (sender, args) => + { + Spectrogram?.Destroy(); + LoadingWaveform.Destroy(); + }; + + SpectrogramLoadTask.Run(0); + } + + /// + /// + /// + /// + /// + private int CreateSpectrogram(int arg1, CancellationToken token) + { + Spectrogram = new EditorPlayfieldSpectrogram(this, token); + return 0; + } + + /// /// /// @@ -1518,5 +1571,12 @@ private void ReloadWaveform() LoadingWaveform.FadeIn(); WaveformLoadTask.Run(0); } + + private void ReloadSpectrogram() + { + Spectrogram?.Destroy(); + LoadingWaveform.FadeIn(); + SpectrogramLoadTask.Run(0); + } } } \ No newline at end of file From f6a0e557fa3a6053e4093587cf12a871105a6193 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Mon, 8 Apr 2024 18:36:49 +0800 Subject: [PATCH 125/249] Lerp between two intensities --- .../EditorPlayfieldSpectrogramSlice.cs | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs index 9e1ef984d1..d75c559820 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs @@ -92,15 +92,19 @@ private void CreateSlice(float[,] sliceData) { var textureX = CalculateTextureXLinear(x); if (textureX == -1) continue; - var intensity = - MathF.Sqrt(GetAverageData(sliceData, y, x)) * 3f; // scale it (sqrt to make low values more visible) - intensity = Math.Clamp(intensity, 0, 1); + var intensity = GetIntensity(sliceData, y, x); var index = DataColorIndex(textureHeight, y, textureX); var nextTextureX = CalculateTextureXLinear(x + 1); if (nextTextureX == -1) nextTextureX = ReferenceWidth - 1; - for (var i = index; i < DataColorIndex(textureHeight, y, nextTextureX) && i < dataColors.Length; i++) + var nextIntensity = x == EditorPlayfieldSpectrogram.FftCount - 1 + ? intensity + : GetIntensity(sliceData, y, x + 1); + var curColor = SpectrogramColormap.GetColor(intensity); + var nextColor = SpectrogramColormap.GetColor(nextIntensity); + var nextDataColorIndex = DataColorIndex(textureHeight, y, nextTextureX); + for (var i = index; i < nextDataColorIndex && i < dataColors.Length; i++) { - dataColors[i] = SpectrogramColormap.GetColor(intensity); + dataColors[i] = Color.Lerp(curColor, nextColor, nextDataColorIndex == index ? 0 : (float)(i - index) / (nextDataColorIndex - index)); } // if (index + (int)Playfield.Width < dataColors.Length) @@ -115,6 +119,13 @@ private void CreateSlice(float[,] sliceData) SliceSprite.FadeTo(1, Easing.Linear, 250); } + private float GetIntensity(float[,] sliceData, int y, int x) + { + var intensity = MathF.Sqrt(GetAverageData(sliceData, y, x)) * 3; // scale it (sqrt to make low values more visible) + intensity = Math.Clamp(intensity, 0, 1); + return intensity; + } + private int CalculateTextureX(int x) { var minFrequency = (float)SampleRate / EditorPlayfieldSpectrogram.FftCount; From 7b5a66a9b047259544a91b6f81d7fe2bc889650d Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Mon, 8 Apr 2024 19:22:34 +0800 Subject: [PATCH 126/249] Use mel scale and logarithmic amplitude --- .../Spectrogram/EditorPlayfieldSpectrogram.cs | 5 +-- .../EditorPlayfieldSpectrogramSlice.cs | 33 ++++++++++++++++--- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs index 44b6e2f539..f87856a5f5 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs @@ -32,7 +32,8 @@ public class EditorPlayfieldSpectrogram : Container private int Stream { get; set; } - public const int FftCount = 256; + public static int FftCount = 256; + public static int FftFlag = (int)DataFlags.FFT512; public int FftResultCount { get; set; } private int FftRoundsTaken { get; set; } @@ -133,7 +134,7 @@ private void GenerateTrackData() var trackDataFft = new float[FftResultCount]; FftRoundsTaken = 0; - while (Bass.ChannelGetData(Stream, trackDataFft, (int)DataFlags.FFT512) > 0) + while (Bass.ChannelGetData(Stream, trackDataFft, FftFlag) > 0) { for (var i = 0; i < FftResultCount; i++) { diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs index d75c559820..05e3301d53 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs @@ -90,11 +90,11 @@ private void CreateSlice(float[,] sliceData) { for (var x = 0; x < EditorPlayfieldSpectrogram.FftCount; x++) { - var textureX = CalculateTextureXLinear(x); + var textureX = CalculateTextureXMel(x); if (textureX == -1) continue; var intensity = GetIntensity(sliceData, y, x); var index = DataColorIndex(textureHeight, y, textureX); - var nextTextureX = CalculateTextureXLinear(x + 1); + var nextTextureX = CalculateTextureXMel(x + 1); if (nextTextureX == -1) nextTextureX = ReferenceWidth - 1; var nextIntensity = x == EditorPlayfieldSpectrogram.FftCount - 1 ? intensity @@ -104,6 +104,7 @@ private void CreateSlice(float[,] sliceData) var nextDataColorIndex = DataColorIndex(textureHeight, y, nextTextureX); for (var i = index; i < nextDataColorIndex && i < dataColors.Length; i++) { + // dataColors[i] = curColor; dataColors[i] = Color.Lerp(curColor, nextColor, nextDataColorIndex == index ? 0 : (float)(i - index) / (nextDataColorIndex - index)); } @@ -121,12 +122,13 @@ private void CreateSlice(float[,] sliceData) private float GetIntensity(float[,] sliceData, int y, int x) { - var intensity = MathF.Sqrt(GetAverageData(sliceData, y, x)) * 3; // scale it (sqrt to make low values more visible) - intensity = Math.Clamp(intensity, 0, 1); + // var intensity = MathF.Sqrt(GetAverageData(sliceData, y, x)) * 3; // scale it (sqrt to make low values more visible) + var intensity = Math.Clamp(1 + 40 * MathF.Log10(GetAverageData(sliceData, y, x)) / 100, 0f, 1f); // scale it (sqrt to make low values more visible) + // intensity = Sigmoid(Math.Clamp(intensity, 0, 1)); return intensity; } - private int CalculateTextureX(int x) + private int CalculateTextureXLog(int x) { var minFrequency = (float)SampleRate / EditorPlayfieldSpectrogram.FftCount; const float maxFrequency = 20000; @@ -137,6 +139,17 @@ private int CalculateTextureX(int x) var processedProgress = a * MathF.Log(frequency) + b; return (int)(processedProgress * ReferenceWidth); } + + private int CalculateTextureXMel(int x) + { + var maxMel = Mel(20000); + var frequency = (float)x * SampleRate / EditorPlayfieldSpectrogram.FftCount; + var mel = Mel(frequency); + if (mel < 0 || mel > maxMel) return -1; + var processedProgress = mel / maxMel; + return (int)(processedProgress * ReferenceWidth); + } + private int CalculateTextureXLinear(int x) { var minFrequency = (float)SampleRate / EditorPlayfieldSpectrogram.FftCount; @@ -147,6 +160,16 @@ private int CalculateTextureXLinear(int x) return (int)((frequency - minFrequency) / (maxFrequency - minFrequency) * ReferenceWidth); } + private float Mel(float frequency) + { + return 2595 * MathF.Log10(1 + frequency / 700); + } + + private float Sigmoid(float x) + { + return x < 0.2f ? 0 : (MathF.Tanh(x * 2 - 1) + 1) / 2; + } + private float GetAverageData(float[,] data, int y, int x) { From 771087abe7e218bbb62aff584c5a9094bcaeba92 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Mon, 8 Apr 2024 21:11:05 +0800 Subject: [PATCH 127/249] Set up spectrogram visibility toggle and brightness option --- Quaver.Shared/Config/ConfigManager.cs | 10 +++++ Quaver.Shared/Screens/Edit/EditScreen.cs | 11 ++++++ Quaver.Shared/Screens/Edit/EditScreenView.cs | 4 +- .../Screens/Edit/UI/Menu/EditorFileMenuBar.cs | 27 ++++++++++++++ .../Edit/UI/Playfield/EditorPlayfield.cs | 37 ++++++++++++++++--- .../EditorPlayfieldSpectrogramSlice.cs | 8 ++-- 6 files changed, 85 insertions(+), 12 deletions(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index d55383418d..39e35c78f0 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -535,6 +535,10 @@ public static class ConfigManager /// internal static Bindable EditorShowWaveform { get; private set; } + /// + /// + internal static Bindable EditorShowSpectrogram { get; private set; } + /// /// internal static Bindable EditorAudioDirection { get; private set; } @@ -555,6 +559,10 @@ public static class ConfigManager /// internal static BindableInt EditorWaveformBrightness { get; private set; } + /// + /// + internal static BindableInt EditorSpectrogramBrightness { get; private set; } + /// /// internal static Bindable EditorPlaceObjectsOnNearestTick { get; private set; } @@ -1072,11 +1080,13 @@ private static void ReadConfigFile() EditorLiveMapping = ReadValue(@"EditorLiveMapping", true, data); EditorAudioFilter = ReadValue(@"EditorAudioFilter", EditorPlayfieldWaveformFilter.None, data); EditorShowWaveform = ReadValue(@"EditorShowWaveform", true, data); + EditorShowSpectrogram = ReadValue(@"EditorShowSpectrogram", true, data); EditorAudioDirection = ReadValue(@"EditorAudioDirection", EditorPlayfieldWaveformAudioDirection.Both, data); EditorWaveformColorR = ReadInt(@"EditorWaveformColorR", 0, 0, 255, data); EditorWaveformColorG = ReadInt(@"EditorWaveformColorG", 200, 0, 255, data); EditorWaveformColorB = ReadInt(@"EditorWaveformColorB", 255, 0, 255, data); EditorWaveformBrightness = ReadInt(@"EditorWaveformBrightness", 50, 0, 100, data); + EditorSpectrogramBrightness = ReadInt(@"EditorSpectrogramBrightness", 50, 0, 100, data); VisualOffset = ReadInt(@"VisualOffset", 0, -500, 500, data); TintHitLightingBasedOnJudgementColor = ReadValue(@"TintHitLightingBasedOnJudgementColor", false, data); Display1v1TournamentOverlay = ReadValue(@"Display1v1TournamentOverlay", true, data); diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index 2f753ba9e6..78d999d624 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -149,11 +149,19 @@ public sealed class EditScreen : QuaverScreen, IHasLeftPanel /// /// public Bindable ShowWaveform { get; } = ConfigManager.EditorShowWaveform ?? new Bindable(true) { Value = true }; + + /// + /// + public Bindable ShowSpectrogram { get; } = ConfigManager.EditorShowSpectrogram ?? new Bindable(true) { Value = true }; /// /// public BindableInt WaveformBrightness { get; } = ConfigManager.EditorWaveformBrightness ?? new BindableInt(50, 1, 100); + /// + /// + public BindableInt SpectrogramBrightness { get; } = ConfigManager.EditorSpectrogramBrightness ?? new BindableInt(50, 1, 100); + /// /// public Bindable AudioDirection { get; } = ConfigManager.EditorAudioDirection ?? new Bindable(EditorPlayfieldWaveformAudioDirection.Both); @@ -392,6 +400,9 @@ public override void Destroy() if (ShowWaveform != ConfigManager.EditorShowWaveform) ShowWaveform.Dispose(); + if (ShowSpectrogram != ConfigManager.EditorShowSpectrogram) + ShowSpectrogram.Dispose(); + if (ConfigManager.Pitched != null) ConfigManager.Pitched.ValueChanged -= OnPitchedChanged; diff --git a/Quaver.Shared/Screens/Edit/EditScreenView.cs b/Quaver.Shared/Screens/Edit/EditScreenView.cs index 3d6e96c32a..4ce7291d07 100644 --- a/Quaver.Shared/Screens/Edit/EditScreenView.cs +++ b/Quaver.Shared/Screens/Edit/EditScreenView.cs @@ -188,7 +188,7 @@ private void CreateBackground() EditScreen.Track, EditScreen.BeatSnap, EditScreen.PlayfieldScrollSpeed, EditScreen.AnchorHitObjectsAtMidpoint, EditScreen.ScaleScrollSpeedWithRate, EditScreen.BeatSnapColor, EditScreen.ViewLayers, EditScreen.CompositionTool, EditScreen.LongNoteOpacity, EditScreen.SelectedHitObjects, EditScreen.SelectedLayer, EditScreen.DefaultLayer, - EditScreen.PlaceObjectsOnNearestTick, EditScreen.ShowWaveform, EditScreen.AudioDirection, EditScreen.WaveformFilter) { Parent = Container}; + EditScreen.PlaceObjectsOnNearestTick, EditScreen.ShowWaveform, EditScreen.ShowSpectrogram, EditScreen.AudioDirection, EditScreen.WaveformFilter) { Parent = Container}; /// /// @@ -258,7 +258,7 @@ private void CreateOtherDifficultyPlayfield() EditScreen.AnchorHitObjectsAtMidpoint, EditScreen.ScaleScrollSpeedWithRate, EditScreen.BeatSnapColor, EditScreen.ViewLayers, EditScreen.CompositionTool, EditScreen.LongNoteOpacity, EditScreen.SelectedHitObjects, EditScreen.SelectedLayer, EditScreen.DefaultLayer, EditScreen.PlaceObjectsOnNearestTick, - EditScreen.ShowWaveform, EditScreen.AudioDirection, EditScreen.WaveformFilter, true) + EditScreen.ShowWaveform, EditScreen.ShowSpectrogram, EditScreen.AudioDirection, EditScreen.WaveformFilter, true) { Parent = Container, Alignment = Alignment.TopCenter diff --git a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs index eb43fdc723..9c42da98e7 100644 --- a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs +++ b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs @@ -430,7 +430,10 @@ private void CreateViewSection() if (ImGui.BeginMenu("Waveform")) { if (ImGui.MenuItem("Visible", "", Screen.ShowWaveform.Value)) + { + if (!Screen.ShowWaveform.Value) Screen.ShowSpectrogram.Value = false; Screen.ShowWaveform.Value = !Screen.ShowWaveform.Value; + } if (ImGui.BeginMenu("Brightness")) { @@ -472,6 +475,30 @@ private void CreateViewSection() ImGui.EndMenu(); } + + if (ImGui.BeginMenu("Spectrogram")) + { + if (ImGui.MenuItem("Visible", "", Screen.ShowSpectrogram.Value)) + { + if (!Screen.ShowSpectrogram.Value) Screen.ShowWaveform.Value = false; + Screen.ShowSpectrogram.Value = !Screen.ShowSpectrogram.Value; + } + + if (ImGui.BeginMenu("Brightness")) + { + for (var i = 0; i < 11; i++) + { + var value = i * 10; + + if (ImGui.MenuItem($"{value}%", "", Screen.SpectrogramBrightness.Value == value)) + Screen.SpectrogramBrightness.Value = value; + } + + ImGui.EndMenu(); + } + + ImGui.EndMenu(); + } ImGui.Separator(); diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs index 2c81d284cb..fbc5cdf96d 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs @@ -111,6 +111,10 @@ public class EditorPlayfield : Sprite /// /// private Bindable ShowWaveform { get; } + + /// + /// + private Bindable ShowSpectrogram { get; } /// /// @@ -228,6 +232,10 @@ public int ColumnSize /// /// private LoadingWheelText LoadingWaveform { get; set; } + + /// + /// + private LoadingWheelText LoadingSpectrogram { get; set; } /// /// @@ -311,12 +319,18 @@ public int ColumnSize /// /// /// + /// + /// + /// + /// + /// /// public EditorPlayfield(Qua map, EditorActionManager manager, Bindable skin, IAudioTrack track, BindableInt beatSnap, BindableInt scrollSpeed, Bindable anchorHitObjectsAtMidpoint, Bindable scaleScrollSpeedWithRate, Bindable beatSnapColor, Bindable viewLayers, Bindable tool, BindableInt longNoteOpacity, BindableList selectedHitObjects, Bindable selectedLayer, EditorLayerInfo defaultLayer, Bindable placeObjectsOnNearestTick, Bindable showWaveform, + Bindable showSpectrogram, Bindable waveFormAudioDirection, Bindable waveformFilter, bool isUneditable = false) { @@ -338,6 +352,7 @@ public EditorPlayfield(Qua map, EditorActionManager manager, Bindable DefaultLayer = defaultLayer; PlaceObjectsOnNearestTick = placeObjectsOnNearestTick; ShowWaveform = showWaveform; + ShowSpectrogram = showSpectrogram; WaveFormAudioDirection = waveFormAudioDirection; WaveformFilter = waveformFilter; @@ -400,6 +415,13 @@ public override void Update(GameTime gameTime) LoadingWaveform.Position = new ScalableVector2(X + BorderLeft.Width / 2f, 200); LoadingWaveform.Update(gameTime); } + + if (LoadingSpectrogram != null) + { + LoadingSpectrogram.Alignment = Alignment; + LoadingSpectrogram.Position = new ScalableVector2(X + BorderLeft.Width / 2f, 200); + LoadingSpectrogram.Update(gameTime); + } UpdateHitObjectPool(); Waveform?.Update(gameTime); @@ -437,8 +459,8 @@ public override void Draw(GameTime gameTime) if (ShowWaveform.Value) Waveform?.Draw(gameTime); - // TODO - Spectrogram?.Draw(gameTime); + if (ShowSpectrogram.Value) + Spectrogram?.Draw(gameTime); LineContainer.Draw(gameTime); DrawHitObjects(gameTime); @@ -449,6 +471,9 @@ public override void Draw(GameTime gameTime) if (ShowWaveform.Value) LoadingWaveform?.Draw(gameTime); + + if (ShowSpectrogram.Value) + LoadingSpectrogram?.Draw(gameTime); } /// @@ -461,6 +486,7 @@ public override void Destroy() WaveformLoadTask?.Dispose(); Waveform?.Destroy(); Spectrogram?.Destroy(); + SpectrogramLoadTask?.Dispose(); ThreadScheduler.Run(() => { @@ -611,8 +637,7 @@ public void CreateSpectrogram() if (IsUneditable) return; - // TODO - LoadingWaveform = new LoadingWheelText(20, "Loading Spectrogram...") + LoadingSpectrogram = new LoadingWheelText(20, "Loading Spectrogram...") { Alignment = Alignment.TopCenter, Y = 200, @@ -620,11 +645,11 @@ public void CreateSpectrogram() SpectrogramLoadTask = new TaskHandler(CreateSpectrogram); - SpectrogramLoadTask.OnCompleted += (sender, args) => LoadingWaveform.FadeOut(); + SpectrogramLoadTask.OnCompleted += (sender, args) => LoadingSpectrogram.FadeOut(); SpectrogramLoadTask.OnCancelled += (sender, args) => { Spectrogram?.Destroy(); - LoadingWaveform.Destroy(); + LoadingSpectrogram.Destroy(); }; SpectrogramLoadTask.Run(0); diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs index 05e3301d53..479f4ca153 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs @@ -55,10 +55,10 @@ public override void Draw(GameTime gameTime) Height; SliceSprite.Height = LengthMs * Playfield.TrackSpeed; SliceSprite.Tint = new Color( - 255, - 255, - 255, - 255); + 1.0f, + 1.0f, + 1.0f, + (float)ConfigManager.EditorSpectrogramBrightness.Value / 100); SliceSprite.Draw(gameTime); } From 65e4807494e603f6f59f5936401ee0493619e551 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Mon, 8 Apr 2024 21:58:54 +0800 Subject: [PATCH 128/249] Allow setting FFT size in editor --- Quaver.Shared/Config/ConfigManager.cs | 5 ++- Quaver.Shared/Screens/Edit/EditScreen.cs | 3 ++ Quaver.Shared/Screens/Edit/EditScreenView.cs | 4 +- .../Screens/Edit/UI/Menu/EditorFileMenuBar.cs | 10 +++++ .../Edit/UI/Playfield/EditorPlayfield.cs | 25 +++++++++-- .../Spectrogram/EditorPlayfieldSpectrogram.cs | 25 +++++++++-- .../EditorPlayfieldSpectrogramSlice.cs | 41 +++++++++++-------- 7 files changed, 85 insertions(+), 28 deletions(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index 39e35c78f0..fda20b0c89 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -538,6 +538,8 @@ public static class ConfigManager /// /// internal static Bindable EditorShowSpectrogram { get; private set; } + + internal static BindableInt EditorSpectrogramFftSize { get; private set; } /// /// @@ -1080,7 +1082,8 @@ private static void ReadConfigFile() EditorLiveMapping = ReadValue(@"EditorLiveMapping", true, data); EditorAudioFilter = ReadValue(@"EditorAudioFilter", EditorPlayfieldWaveformFilter.None, data); EditorShowWaveform = ReadValue(@"EditorShowWaveform", true, data); - EditorShowSpectrogram = ReadValue(@"EditorShowSpectrogram", true, data); + EditorShowSpectrogram = ReadValue(@"EditorShowSpectrogram", false, data); + EditorSpectrogramFftSize = ReadInt(@"EditorSpectrumFftSize", 256, 256, 16384, data); EditorAudioDirection = ReadValue(@"EditorAudioDirection", EditorPlayfieldWaveformAudioDirection.Both, data); EditorWaveformColorR = ReadInt(@"EditorWaveformColorR", 0, 0, 255, data); EditorWaveformColorG = ReadInt(@"EditorWaveformColorG", 200, 0, 255, data); diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index 78d999d624..7b7f3df6ff 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -162,6 +162,9 @@ public sealed class EditScreen : QuaverScreen, IHasLeftPanel /// public BindableInt SpectrogramBrightness { get; } = ConfigManager.EditorSpectrogramBrightness ?? new BindableInt(50, 1, 100); + public BindableInt SpectrogramFftSize { get; } = + ConfigManager.EditorSpectrogramFftSize ?? new BindableInt(256, 256, 16384); + /// /// public Bindable AudioDirection { get; } = ConfigManager.EditorAudioDirection ?? new Bindable(EditorPlayfieldWaveformAudioDirection.Both); diff --git a/Quaver.Shared/Screens/Edit/EditScreenView.cs b/Quaver.Shared/Screens/Edit/EditScreenView.cs index 4ce7291d07..6564e20f28 100644 --- a/Quaver.Shared/Screens/Edit/EditScreenView.cs +++ b/Quaver.Shared/Screens/Edit/EditScreenView.cs @@ -188,7 +188,7 @@ private void CreateBackground() EditScreen.Track, EditScreen.BeatSnap, EditScreen.PlayfieldScrollSpeed, EditScreen.AnchorHitObjectsAtMidpoint, EditScreen.ScaleScrollSpeedWithRate, EditScreen.BeatSnapColor, EditScreen.ViewLayers, EditScreen.CompositionTool, EditScreen.LongNoteOpacity, EditScreen.SelectedHitObjects, EditScreen.SelectedLayer, EditScreen.DefaultLayer, - EditScreen.PlaceObjectsOnNearestTick, EditScreen.ShowWaveform, EditScreen.ShowSpectrogram, EditScreen.AudioDirection, EditScreen.WaveformFilter) { Parent = Container}; + EditScreen.PlaceObjectsOnNearestTick, EditScreen.ShowWaveform, EditScreen.ShowSpectrogram, EditScreen.AudioDirection, EditScreen.WaveformFilter, EditScreen.SpectrogramFftSize) { Parent = Container}; /// /// @@ -258,7 +258,7 @@ private void CreateOtherDifficultyPlayfield() EditScreen.AnchorHitObjectsAtMidpoint, EditScreen.ScaleScrollSpeedWithRate, EditScreen.BeatSnapColor, EditScreen.ViewLayers, EditScreen.CompositionTool, EditScreen.LongNoteOpacity, EditScreen.SelectedHitObjects, EditScreen.SelectedLayer, EditScreen.DefaultLayer, EditScreen.PlaceObjectsOnNearestTick, - EditScreen.ShowWaveform, EditScreen.ShowSpectrogram, EditScreen.AudioDirection, EditScreen.WaveformFilter, true) + EditScreen.ShowWaveform, EditScreen.ShowSpectrogram, EditScreen.AudioDirection, EditScreen.WaveformFilter, EditScreen.SpectrogramFftSize, true) { Parent = Container, Alignment = Alignment.TopCenter diff --git a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs index 9c42da98e7..388c7499e5 100644 --- a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs +++ b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs @@ -497,6 +497,16 @@ private void CreateViewSection() ImGui.EndMenu(); } + if (ImGui.BeginMenu("FFT Size")) + { + for (var size = 256; size <= 16384; size *= 2) + { + if (ImGui.MenuItem($"{size}", "", Screen.SpectrogramFftSize.Value == size)) + Screen.SpectrogramFftSize.Value = size; + } + ImGui.EndMenu(); + } + ImGui.EndMenu(); } diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs index fbc5cdf96d..6733f6d183 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs @@ -123,6 +123,10 @@ public class EditorPlayfield : Sprite /// /// private Bindable WaveformFilter { get; } + + /// + /// + private Bindable SpectrogramFftSize { get; } /// /// If true, this playfield is unable to be edited/interacted with. This is purely for viewing @@ -324,14 +328,20 @@ public int ColumnSize /// /// /// + /// /// - public EditorPlayfield(Qua map, EditorActionManager manager, Bindable skin, IAudioTrack track, BindableInt beatSnap, + public EditorPlayfield(Qua map, EditorActionManager manager, Bindable skin, IAudioTrack track, + BindableInt beatSnap, BindableInt scrollSpeed, Bindable anchorHitObjectsAtMidpoint, Bindable scaleScrollSpeedWithRate, - Bindable beatSnapColor, Bindable viewLayers, Bindable tool, - BindableInt longNoteOpacity, BindableList selectedHitObjects, Bindable selectedLayer, + Bindable beatSnapColor, Bindable viewLayers, + Bindable tool, + BindableInt longNoteOpacity, BindableList selectedHitObjects, + Bindable selectedLayer, EditorLayerInfo defaultLayer, Bindable placeObjectsOnNearestTick, Bindable showWaveform, Bindable showSpectrogram, - Bindable waveFormAudioDirection, Bindable waveformFilter, + Bindable waveFormAudioDirection, + Bindable waveformFilter, + BindableInt spectrogramFftSize, bool isUneditable = false) { Map = map; @@ -355,6 +365,7 @@ public EditorPlayfield(Qua map, EditorActionManager manager, Bindable ShowSpectrogram = showSpectrogram; WaveFormAudioDirection = waveFormAudioDirection; WaveformFilter = waveformFilter; + SpectrogramFftSize = spectrogramFftSize; Alignment = Alignment.TopCenter; Tint = new Color(24,24,24); @@ -397,6 +408,7 @@ public EditorPlayfield(Qua map, EditorActionManager manager, Bindable Skin.ValueChanged += OnSkinChanged; WaveFormAudioDirection.ValueChanged += OnWaveFormAudioDirectionChanged; WaveformFilter.ValueChanged += OnWaveformFilterChanged; + SpectrogramFftSize.ValueChanged += OnSpectrogramFftSizeChanged; } /// @@ -521,6 +533,8 @@ public override void Destroy() WaveFormAudioDirection.ValueChanged -= OnWaveFormAudioDirectionChanged; WaveformFilter.ValueChanged -= OnWaveformFilterChanged; + SpectrogramFftSize.ValueChanged -= OnSpectrogramFftSizeChanged; + base.Destroy(); } @@ -1590,6 +1604,9 @@ private void OnWaveformFilterChanged(object sender, BindableValueChangedEventArg private void OnWaveFormAudioDirectionChanged(object sender, BindableValueChangedEventArgs e) => ReloadWaveform(); + private void OnSpectrogramFftSizeChanged(object sender, BindableValueChangedEventArgs e) + => ReloadSpectrogram(); + private void ReloadWaveform() { Waveform?.Destroy(); diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs index f87856a5f5..35fe01fdaa 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs @@ -32,8 +32,9 @@ public class EditorPlayfieldSpectrogram : Container private int Stream { get; set; } - public static int FftCount = 256; - public static int FftFlag = (int)DataFlags.FFT512; + public int FftCount { get; } + + public int FftFlag { get; } public int FftResultCount { get; set; } private int FftRoundsTaken { get; set; } @@ -46,6 +47,8 @@ public EditorPlayfieldSpectrogram(EditorPlayfield playfield, CancellationToken t { Playfield = playfield; Token = token; + FftCount = ConfigManager.EditorSpectrogramFftSize.Value; + FftFlag = (int)GetFftDataFlag(FftCount); Slices = new List(); VisibleSlices = new List(); @@ -109,7 +112,7 @@ public void GenerateWaveform() } } - var slice = new EditorPlayfieldSpectrogramSlice(Playfield, (float)millisecondPerSlice, FftPerSlice, + var slice = new EditorPlayfieldSpectrogramSlice(this, Playfield, (float)millisecondPerSlice, FftPerSlice, trackSliceData, t, sampleRate); tempSlices.Add(slice); } @@ -147,6 +150,22 @@ private void GenerateTrackData() TrackLengthMilliSeconds = Bass.ChannelBytes2Seconds(Stream, TrackByteLength) * 1000.0; } + public static DataFlags GetFftDataFlag(int fftSize) + { + return fftSize switch + { + 256 => DataFlags.FFT512, + 512 => DataFlags.FFT1024, + 1024 => DataFlags.FFT2048, + 2048 => DataFlags.FFT4096, + 4096 => DataFlags.FFT8192, + 8192 => DataFlags.FFT16384, + 16384 => DataFlags.FFT32768, + _ => throw new InvalidOperationException( + $"Expected FFT sample size to be between 256 and 16384 and power of 2, found {fftSize}") + }; + } + /// /// /// diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs index 479f4ca153..8d3f468050 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs @@ -13,6 +13,7 @@ namespace Quaver.Shared.Screens.Edit.UI.Playfield.Spectrogram public class EditorPlayfieldSpectrogramSlice : Sprite { private EditorPlayfield Playfield { get; set; } + private EditorPlayfieldSpectrogram Spectrogram { get; set; } private Sprite SliceSprite { get; set; } @@ -21,17 +22,18 @@ public class EditorPlayfieldSpectrogramSlice : Sprite private double SliceTimeMilliSeconds { get; } private Texture2D SliceTexture { get; set; } - + private float LengthMs { get; set; } - + private int SampleRate { get; set; } private int ReferenceWidth { get; } = 1024; - public EditorPlayfieldSpectrogramSlice(EditorPlayfield playfield, float lengthMs, int sliceSize, + public EditorPlayfieldSpectrogramSlice(EditorPlayfieldSpectrogram spectrogram, EditorPlayfield playfield, float lengthMs, int sliceSize, float[,] sliceData, double sliceTime, int sampleRate) { + Spectrogram = spectrogram; Playfield = playfield; SliceSize = sliceSize; SliceTimeMilliSeconds = sliceTime; @@ -88,15 +90,15 @@ private void CreateSlice(float[,] sliceData) for (var y = 0; y < textureHeight; y++) { - for (var x = 0; x < EditorPlayfieldSpectrogram.FftCount; x++) + for (var x = 0; x < Spectrogram.FftCount; x++) { - var textureX = CalculateTextureXMel(x); + var textureX = CalculateTextureXLinear(x); if (textureX == -1) continue; var intensity = GetIntensity(sliceData, y, x); var index = DataColorIndex(textureHeight, y, textureX); - var nextTextureX = CalculateTextureXMel(x + 1); + var nextTextureX = CalculateTextureXLinear(x + 1); if (nextTextureX == -1) nextTextureX = ReferenceWidth - 1; - var nextIntensity = x == EditorPlayfieldSpectrogram.FftCount - 1 + var nextIntensity = x == Spectrogram.FftCount - 1 ? intensity : GetIntensity(sliceData, y, x + 1); var curColor = SpectrogramColormap.GetColor(intensity); @@ -105,9 +107,10 @@ private void CreateSlice(float[,] sliceData) for (var i = index; i < nextDataColorIndex && i < dataColors.Length; i++) { // dataColors[i] = curColor; - dataColors[i] = Color.Lerp(curColor, nextColor, nextDataColorIndex == index ? 0 : (float)(i - index) / (nextDataColorIndex - index)); + dataColors[i] = Color.Lerp(curColor, nextColor, + nextDataColorIndex == index ? 0 : (float)(i - index) / (nextDataColorIndex - index)); } - + // if (index + (int)Playfield.Width < dataColors.Length) // dataColors[index + (int)Playfield.Width] = new Color(intensity, 0, 0, 1); } @@ -123,40 +126,42 @@ private void CreateSlice(float[,] sliceData) private float GetIntensity(float[,] sliceData, int y, int x) { // var intensity = MathF.Sqrt(GetAverageData(sliceData, y, x)) * 3; // scale it (sqrt to make low values more visible) - var intensity = Math.Clamp(1 + 40 * MathF.Log10(GetAverageData(sliceData, y, x)) / 100, 0f, 1f); // scale it (sqrt to make low values more visible) + var rawIntensity = GetAverageData(sliceData, y, x); + var intensity = MathF.Abs(rawIntensity) < 1e-4f ? 0 : + Math.Clamp(1 + 20 * MathF.Log10(rawIntensity) / 100, 0f, 1f); // intensity = Sigmoid(Math.Clamp(intensity, 0, 1)); return intensity; } private int CalculateTextureXLog(int x) { - var minFrequency = (float)SampleRate / EditorPlayfieldSpectrogram.FftCount; + var minFrequency = (float)SampleRate / Spectrogram.FftCount; const float maxFrequency = 20000; var a = 1 / MathF.Log(maxFrequency / minFrequency); var b = -a * MathF.Log(minFrequency); - var frequency = (float)x * SampleRate / EditorPlayfieldSpectrogram.FftCount; + var frequency = (float)x * SampleRate / Spectrogram.FftCount; if (frequency < minFrequency || frequency > maxFrequency) return -1; var processedProgress = a * MathF.Log(frequency) + b; return (int)(processedProgress * ReferenceWidth); } - + private int CalculateTextureXMel(int x) { var maxMel = Mel(20000); - var frequency = (float)x * SampleRate / EditorPlayfieldSpectrogram.FftCount; + var frequency = (float)x * SampleRate / Spectrogram.FftCount; var mel = Mel(frequency); if (mel < 0 || mel > maxMel) return -1; var processedProgress = mel / maxMel; return (int)(processedProgress * ReferenceWidth); } - + private int CalculateTextureXLinear(int x) { - var minFrequency = (float)SampleRate / EditorPlayfieldSpectrogram.FftCount; + var minFrequency = (float)SampleRate / Spectrogram.FftCount; const float maxFrequency = 20000; - var frequency = (float)x * SampleRate / EditorPlayfieldSpectrogram.FftCount; + var frequency = (float)x * SampleRate / Spectrogram.FftCount; if (frequency < minFrequency || frequency > maxFrequency) return -1; - + return (int)((frequency - minFrequency) / (maxFrequency - minFrequency) * ReferenceWidth); } From 15c1dcacdb6ad7b36246d1befb21c52114d465ef Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Mon, 8 Apr 2024 21:59:13 +0800 Subject: [PATCH 129/249] Use GetColorAt for SpectrogramColormap to reduce redundancy --- .../UI/Playfield/Spectrogram/SpectrogramColormap.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/SpectrogramColormap.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/SpectrogramColormap.cs index c163f38fcd..01abb0c55b 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/SpectrogramColormap.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/SpectrogramColormap.cs @@ -268,17 +268,20 @@ public static class SpectrogramColormap { 9.40015097e-01f, 9.75158357e-01f, 1.31325517e-01f }, }; + private static Color GetColorAt(int index) + { + return new Color(Data[index, 0], Data[index, 1], Data[index, 2]); + } + public static Color GetColor(float progress) { - if (progress < 0 || progress > 1) return Color.Black; + if (progress <= 0) return GetColorAt(0); var dataCount = Data.GetLength(0); - if (progress == 1) return new Color(Data[dataCount - 1, 0], Data[dataCount - 1, 1], Data[dataCount - 1, 2]); + if (progress >= 1) return GetColorAt(dataCount - 1); var i = (int)(progress * (dataCount - 1)); // var lerpWidth = 1f / Data.GetLength(0); var lerpProgress = progress * (dataCount - 1) - i; - return Color.Lerp(new Color(Data[i, 0], Data[i, 1], Data[i, 2]), - new Color(Data[i + 1, 0], Data[i + 1, 1], Data[i + 1, 2]), - lerpProgress); + return Color.Lerp(GetColorAt(i), GetColorAt(i + 1), lerpProgress); } } } \ No newline at end of file From 220c57e1f66c04234ab43547a5189e0d1392540a Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Mon, 8 Apr 2024 23:40:03 +0800 Subject: [PATCH 130/249] Remove DC --- .../Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs index 35fe01fdaa..abbf33d5a6 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs @@ -137,7 +137,7 @@ private void GenerateTrackData() var trackDataFft = new float[FftResultCount]; FftRoundsTaken = 0; - while (Bass.ChannelGetData(Stream, trackDataFft, FftFlag) > 0) + while (Bass.ChannelGetData(Stream, trackDataFft, FftFlag | (int)DataFlags.FFTRemoveDC) > 0) { for (var i = 0; i < FftResultCount; i++) { From c1de0bae3a92bcecaa40127b91cd3bf9401b72a5 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Mon, 8 Apr 2024 23:40:59 +0800 Subject: [PATCH 131/249] Improve intensity processing --- .../Spectrogram/EditorPlayfieldSpectrogramSlice.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs index 8d3f468050..8c9c4369b4 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs @@ -127,9 +127,15 @@ private float GetIntensity(float[,] sliceData, int y, int x) { // var intensity = MathF.Sqrt(GetAverageData(sliceData, y, x)) * 3; // scale it (sqrt to make low values more visible) var rawIntensity = GetAverageData(sliceData, y, x); - var intensity = MathF.Abs(rawIntensity) < 1e-4f ? 0 : - Math.Clamp(1 + 20 * MathF.Log10(rawIntensity) / 100, 0f, 1f); - // intensity = Sigmoid(Math.Clamp(intensity, 0, 1)); + var db = MathF.Abs(rawIntensity) < 1e-4f ? -100 : 20 * MathF.Log10(rawIntensity); + var intensity = Math.Clamp(1 + db / 100, 0f, 1f); + + var cutoffFactor = 0.3f; + intensity = MathF.Max(intensity, cutoffFactor); + intensity = (intensity - cutoffFactor) * (1 - cutoffFactor); + + intensity *= intensity * 7.5f; + intensity = Sigmoid(Math.Clamp(intensity, 0, 1)); return intensity; } From 6b93c7a0a337c07409b943ece5ea35393b24a2e6 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Mon, 8 Apr 2024 23:41:57 +0800 Subject: [PATCH 132/249] Use transform functions as parameter to transform texture X --- .../EditorPlayfieldSpectrogramSlice.cs | 49 +++++++++---------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs index 8c9c4369b4..a43918cf58 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs @@ -27,7 +27,7 @@ public class EditorPlayfieldSpectrogramSlice : Sprite private int SampleRate { get; set; } - private int ReferenceWidth { get; } = 1024; + private int ReferenceWidth { get; } public EditorPlayfieldSpectrogramSlice(EditorPlayfieldSpectrogram spectrogram, EditorPlayfield playfield, float lengthMs, int sliceSize, float[,] sliceData, @@ -39,6 +39,7 @@ public EditorPlayfieldSpectrogramSlice(EditorPlayfieldSpectrogram spectrogram, E SliceTimeMilliSeconds = sliceTime; LengthMs = lengthMs; SampleRate = sampleRate; + ReferenceWidth = spectrogram.FftCount; CreateSlice(sliceData); } @@ -92,27 +93,21 @@ private void CreateSlice(float[,] sliceData) { for (var x = 0; x < Spectrogram.FftCount; x++) { - var textureX = CalculateTextureXLinear(x); + var textureX = CalculateTextureX(x, Linear); if (textureX == -1) continue; var intensity = GetIntensity(sliceData, y, x); var index = DataColorIndex(textureHeight, y, textureX); - var nextTextureX = CalculateTextureXLinear(x + 1); + var nextTextureX = CalculateTextureX(x + 1, Linear); if (nextTextureX == -1) nextTextureX = ReferenceWidth - 1; var nextIntensity = x == Spectrogram.FftCount - 1 ? intensity : GetIntensity(sliceData, y, x + 1); var curColor = SpectrogramColormap.GetColor(intensity); - var nextColor = SpectrogramColormap.GetColor(nextIntensity); var nextDataColorIndex = DataColorIndex(textureHeight, y, nextTextureX); for (var i = index; i < nextDataColorIndex && i < dataColors.Length; i++) { - // dataColors[i] = curColor; - dataColors[i] = Color.Lerp(curColor, nextColor, - nextDataColorIndex == index ? 0 : (float)(i - index) / (nextDataColorIndex - index)); + dataColors[i] = curColor; } - - // if (index + (int)Playfield.Width < dataColors.Length) - // dataColors[index + (int)Playfield.Width] = new Color(intensity, 0, 0, 1); } } @@ -142,7 +137,7 @@ private float GetIntensity(float[,] sliceData, int y, int x) private int CalculateTextureXLog(int x) { var minFrequency = (float)SampleRate / Spectrogram.FftCount; - const float maxFrequency = 20000; + const float maxFrequency = 10000; var a = 1 / MathF.Log(maxFrequency / minFrequency); var b = -a * MathF.Log(minFrequency); var frequency = (float)x * SampleRate / Spectrogram.FftCount; @@ -151,31 +146,33 @@ private int CalculateTextureXLog(int x) return (int)(processedProgress * ReferenceWidth); } - private int CalculateTextureXMel(int x) + private int CalculateTextureX(int x, Func transform) { - var maxMel = Mel(20000); + var minFrequency = transform(0); + var maxFrequency = transform(20000); var frequency = (float)x * SampleRate / Spectrogram.FftCount; - var mel = Mel(frequency); - if (mel < 0 || mel > maxMel) return -1; - var processedProgress = mel / maxMel; - return (int)(processedProgress * ReferenceWidth); + var transformedFrequency = transform(frequency); + if (transformedFrequency > maxFrequency || transformedFrequency < minFrequency) return -1; + return (int)((transformedFrequency - minFrequency) / (maxFrequency - minFrequency) * ReferenceWidth); } - private int CalculateTextureXLinear(int x) - { - var minFrequency = (float)SampleRate / Spectrogram.FftCount; - const float maxFrequency = 20000; - var frequency = (float)x * SampleRate / Spectrogram.FftCount; - if (frequency < minFrequency || frequency > maxFrequency) return -1; - - return (int)((frequency - minFrequency) / (maxFrequency - minFrequency) * ReferenceWidth); - } + private float Linear(float x) => x; private float Mel(float frequency) { return 2595 * MathF.Log10(1 + frequency / 700); } + + private float Erb1(float frequency) + { + return 6.23f * frequency * frequency / 1000 / 1000 + 93.39f * frequency / 1000 + 28.52f; + } + private float Erb2(float frequency) + { + return 24.7f * (4.37f * frequency / 1000 + 1); + } + private float Sigmoid(float x) { return x < 0.2f ? 0 : (MathF.Tanh(x * 2 - 1) + 1) / 2; From fc155c2a0f19cdca1eadcfb5530ca81f52027927 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Mon, 8 Apr 2024 23:45:15 +0800 Subject: [PATCH 133/249] Don't block timing lines --- .../Screens/Edit/UI/Playfield/EditorPlayfield.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs index 6733f6d183..cfe0001119 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs @@ -465,14 +465,14 @@ public override void Draw(GameTime gameTime) var transformMatrix = Matrix.CreateTranslation(0, TrackPositionY, 0) * WindowManager.Scale; GameBase.Game.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, null, null, null, transformMatrix); - + + if (ShowSpectrogram.Value) + Spectrogram?.Draw(gameTime); + Timeline.Draw(gameTime); if (ShowWaveform.Value) Waveform?.Draw(gameTime); - - if (ShowSpectrogram.Value) - Spectrogram?.Draw(gameTime); LineContainer.Draw(gameTime); DrawHitObjects(gameTime); From e249611d32370612ffbbcaa382acf91bcc6b8779 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 9 Apr 2024 00:43:05 +0800 Subject: [PATCH 134/249] Use double lerp for spectrogram colormap --- .../Spectrogram/SpectrogramColormap.cs | 299 ++---------------- 1 file changed, 26 insertions(+), 273 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/SpectrogramColormap.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/SpectrogramColormap.cs index 01abb0c55b..ce70db79eb 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/SpectrogramColormap.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/SpectrogramColormap.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.Xna.Framework; namespace Quaver.Shared.Screens.Edit.UI.Playfield.Spectrogram @@ -7,281 +8,33 @@ namespace Quaver.Shared.Screens.Edit.UI.Playfield.Spectrogram /// public static class SpectrogramColormap { - // Adapted from https://github.com/BIDS/colormap/blob/master/option_c.py - private static float[,] Data = - { - { 5.03832136e-02f, 2.98028976e-02f, 5.27974883e-01f }, - { 6.35363639e-02f, 2.84259729e-02f, 5.33123681e-01f }, - { 7.53531234e-02f, 2.72063728e-02f, 5.38007001e-01f }, - { 8.62217979e-02f, 2.61253206e-02f, 5.42657691e-01f }, - { 9.63786097e-02f, 2.51650976e-02f, 5.47103487e-01f }, - { 1.05979704e-01f, 2.43092436e-02f, 5.51367851e-01f }, - { 1.15123641e-01f, 2.35562500e-02f, 5.55467728e-01f }, - { 1.23902903e-01f, 2.28781011e-02f, 5.59423480e-01f }, - { 1.32380720e-01f, 2.22583774e-02f, 5.63250116e-01f }, - { 1.40603076e-01f, 2.16866674e-02f, 5.66959485e-01f }, - { 1.48606527e-01f, 2.11535876e-02f, 5.70561711e-01f }, - { 1.56420649e-01f, 2.06507174e-02f, 5.74065446e-01f }, - { 1.64069722e-01f, 2.01705326e-02f, 5.77478074e-01f }, - { 1.71573925e-01f, 1.97063415e-02f, 5.80805890e-01f }, - { 1.78950212e-01f, 1.92522243e-02f, 5.84054243e-01f }, - { 1.86212958e-01f, 1.88029767e-02f, 5.87227661e-01f }, - { 1.93374449e-01f, 1.83540593e-02f, 5.90329954e-01f }, - { 2.00445260e-01f, 1.79015512e-02f, 5.93364304e-01f }, - { 2.07434551e-01f, 1.74421086e-02f, 5.96333341e-01f }, - { 2.14350298e-01f, 1.69729276e-02f, 5.99239207e-01f }, - { 2.21196750e-01f, 1.64970484e-02f, 6.02083323e-01f }, - { 2.27982971e-01f, 1.60071509e-02f, 6.04867403e-01f }, - { 2.34714537e-01f, 1.55015065e-02f, 6.07592438e-01f }, - { 2.41396253e-01f, 1.49791041e-02f, 6.10259089e-01f }, - { 2.48032377e-01f, 1.44393586e-02f, 6.12867743e-01f }, - { 2.54626690e-01f, 1.38820918e-02f, 6.15418537e-01f }, - { 2.61182562e-01f, 1.33075156e-02f, 6.17911385e-01f }, - { 2.67702993e-01f, 1.27162163e-02f, 6.20345997e-01f }, - { 2.74190665e-01f, 1.21091423e-02f, 6.22721903e-01f }, - { 2.80647969e-01f, 1.14875915e-02f, 6.25038468e-01f }, - { 2.87076059e-01f, 1.08554862e-02f, 6.27294975e-01f }, - { 2.93477695e-01f, 1.02128849e-02f, 6.29490490e-01f }, - { 2.99855122e-01f, 9.56079551e-03f, 6.31623923e-01f }, - { 3.06209825e-01f, 8.90185346e-03f, 6.33694102e-01f }, - { 3.12543124e-01f, 8.23900704e-03f, 6.35699759e-01f }, - { 3.18856183e-01f, 7.57551051e-03f, 6.37639537e-01f }, - { 3.25150025e-01f, 6.91491734e-03f, 6.39512001e-01f }, - { 3.31425547e-01f, 6.26107379e-03f, 6.41315649e-01f }, - { 3.37683446e-01f, 5.61830889e-03f, 6.43048936e-01f }, - { 3.43924591e-01f, 4.99053080e-03f, 6.44710195e-01f }, - { 3.50149699e-01f, 4.38202557e-03f, 6.46297711e-01f }, - { 3.56359209e-01f, 3.79781761e-03f, 6.47809772e-01f }, - { 3.62553473e-01f, 3.24319591e-03f, 6.49244641e-01f }, - { 3.68732762e-01f, 2.72370721e-03f, 6.50600561e-01f }, - { 3.74897270e-01f, 2.24514897e-03f, 6.51875762e-01f }, - { 3.81047116e-01f, 1.81356205e-03f, 6.53068467e-01f }, - { 3.87182639e-01f, 1.43446923e-03f, 6.54176761e-01f }, - { 3.93304010e-01f, 1.11388259e-03f, 6.55198755e-01f }, - { 3.99410821e-01f, 8.59420809e-04f, 6.56132835e-01f }, - { 4.05502914e-01f, 6.78091517e-04f, 6.56977276e-01f }, - { 4.11580082e-01f, 5.77101735e-04f, 6.57730380e-01f }, - { 4.17642063e-01f, 5.63847476e-04f, 6.58390492e-01f }, - { 4.23688549e-01f, 6.45902780e-04f, 6.58956004e-01f }, - { 4.29719186e-01f, 8.31008207e-04f, 6.59425363e-01f }, - { 4.35733575e-01f, 1.12705875e-03f, 6.59797077e-01f }, - { 4.41732123e-01f, 1.53984779e-03f, 6.60069009e-01f }, - { 4.47713600e-01f, 2.07954744e-03f, 6.60240367e-01f }, - { 4.53677394e-01f, 2.75470302e-03f, 6.60309966e-01f }, - { 4.59622938e-01f, 3.57374415e-03f, 6.60276655e-01f }, - { 4.65549631e-01f, 4.54518084e-03f, 6.60139383e-01f }, - { 4.71456847e-01f, 5.67758762e-03f, 6.59897210e-01f }, - { 4.77343929e-01f, 6.97958743e-03f, 6.59549311e-01f }, - { 4.83210198e-01f, 8.45983494e-03f, 6.59094989e-01f }, - { 4.89054951e-01f, 1.01269996e-02f, 6.58533677e-01f }, - { 4.94877466e-01f, 1.19897486e-02f, 6.57864946e-01f }, - { 5.00677687e-01f, 1.40550640e-02f, 6.57087561e-01f }, - { 5.06454143e-01f, 1.63333443e-02f, 6.56202294e-01f }, - { 5.12206035e-01f, 1.88332232e-02f, 6.55209222e-01f }, - { 5.17932580e-01f, 2.15631918e-02f, 6.54108545e-01f }, - { 5.23632990e-01f, 2.45316468e-02f, 6.52900629e-01f }, - { 5.29306474e-01f, 2.77468735e-02f, 6.51586010e-01f }, - { 5.34952244e-01f, 3.12170300e-02f, 6.50165396e-01f }, - { 5.40569510e-01f, 3.49501310e-02f, 6.48639668e-01f }, - { 5.46157494e-01f, 3.89540334e-02f, 6.47009884e-01f }, - { 5.51715423e-01f, 4.31364795e-02f, 6.45277275e-01f }, - { 5.57242538e-01f, 4.73307585e-02f, 6.43443250e-01f }, - { 5.62738096e-01f, 5.15448092e-02f, 6.41509389e-01f }, - { 5.68201372e-01f, 5.57776706e-02f, 6.39477440e-01f }, - { 5.73631859e-01f, 6.00281369e-02f, 6.37348841e-01f }, - { 5.79028682e-01f, 6.42955547e-02f, 6.35126108e-01f }, - { 5.84391137e-01f, 6.85790261e-02f, 6.32811608e-01f }, - { 5.89718606e-01f, 7.28775875e-02f, 6.30407727e-01f }, - { 5.95010505e-01f, 7.71902878e-02f, 6.27916992e-01f }, - { 6.00266283e-01f, 8.15161895e-02f, 6.25342058e-01f }, - { 6.05485428e-01f, 8.58543713e-02f, 6.22685703e-01f }, - { 6.10667469e-01f, 9.02039303e-02f, 6.19950811e-01f }, - { 6.15811974e-01f, 9.45639838e-02f, 6.17140367e-01f }, - { 6.20918555e-01f, 9.89336721e-02f, 6.14257440e-01f }, - { 6.25986869e-01f, 1.03312160e-01f, 6.11305174e-01f }, - { 6.31016615e-01f, 1.07698641e-01f, 6.08286774e-01f }, - { 6.36007543e-01f, 1.12092335e-01f, 6.05205491e-01f }, - { 6.40959444e-01f, 1.16492495e-01f, 6.02064611e-01f }, - { 6.45872158e-01f, 1.20898405e-01f, 5.98867442e-01f }, - { 6.50745571e-01f, 1.25309384e-01f, 5.95617300e-01f }, - { 6.55579615e-01f, 1.29724785e-01f, 5.92317494e-01f }, - { 6.60374266e-01f, 1.34143997e-01f, 5.88971318e-01f }, - { 6.65129493e-01f, 1.38566428e-01f, 5.85582301e-01f }, - { 6.69845385e-01f, 1.42991540e-01f, 5.82153572e-01f }, - { 6.74522060e-01f, 1.47418835e-01f, 5.78688247e-01f }, - { 6.79159664e-01f, 1.51847851e-01f, 5.75189431e-01f }, - { 6.83758384e-01f, 1.56278163e-01f, 5.71660158e-01f }, - { 6.88318440e-01f, 1.60709387e-01f, 5.68103380e-01f }, - { 6.92840088e-01f, 1.65141174e-01f, 5.64521958e-01f }, - { 6.97323615e-01f, 1.69573215e-01f, 5.60918659e-01f }, - { 7.01769334e-01f, 1.74005236e-01f, 5.57296144e-01f }, - { 7.06177590e-01f, 1.78437000e-01f, 5.53656970e-01f }, - { 7.10548747e-01f, 1.82868306e-01f, 5.50003579e-01f }, - { 7.14883195e-01f, 1.87298986e-01f, 5.46338299e-01f }, - { 7.19181339e-01f, 1.91728906e-01f, 5.42663338e-01f }, - { 7.23443604e-01f, 1.96157962e-01f, 5.38980786e-01f }, - { 7.27670428e-01f, 2.00586086e-01f, 5.35292612e-01f }, - { 7.31862231e-01f, 2.05013174e-01f, 5.31600995e-01f }, - { 7.36019424e-01f, 2.09439071e-01f, 5.27908434e-01f }, - { 7.40142557e-01f, 2.13863965e-01f, 5.24215533e-01f }, - { 7.44232102e-01f, 2.18287899e-01f, 5.20523766e-01f }, - { 7.48288533e-01f, 2.22710942e-01f, 5.16834495e-01f }, - { 7.52312321e-01f, 2.27133187e-01f, 5.13148963e-01f }, - { 7.56303937e-01f, 2.31554749e-01f, 5.09468305e-01f }, - { 7.60263849e-01f, 2.35975765e-01f, 5.05793543e-01f }, - { 7.64192516e-01f, 2.40396394e-01f, 5.02125599e-01f }, - { 7.68090391e-01f, 2.44816813e-01f, 4.98465290e-01f }, - { 7.71957916e-01f, 2.49237220e-01f, 4.94813338e-01f }, - { 7.75795522e-01f, 2.53657797e-01f, 4.91170517e-01f }, - { 7.79603614e-01f, 2.58078397e-01f, 4.87539124e-01f }, - { 7.83382636e-01f, 2.62499662e-01f, 4.83917732e-01f }, - { 7.87132978e-01f, 2.66921859e-01f, 4.80306702e-01f }, - { 7.90855015e-01f, 2.71345267e-01f, 4.76706319e-01f }, - { 7.94549101e-01f, 2.75770179e-01f, 4.73116798e-01f }, - { 7.98215577e-01f, 2.80196901e-01f, 4.69538286e-01f }, - { 8.01854758e-01f, 2.84625750e-01f, 4.65970871e-01f }, - { 8.05466945e-01f, 2.89057057e-01f, 4.62414580e-01f }, - { 8.09052419e-01f, 2.93491117e-01f, 4.58869577e-01f }, - { 8.12611506e-01f, 2.97927865e-01f, 4.55337565e-01f }, - { 8.16144382e-01f, 3.02368130e-01f, 4.51816385e-01f }, - { 8.19651255e-01f, 3.06812282e-01f, 4.48305861e-01f }, - { 8.23132309e-01f, 3.11260703e-01f, 4.44805781e-01f }, - { 8.26587706e-01f, 3.15713782e-01f, 4.41315901e-01f }, - { 8.30017584e-01f, 3.20171913e-01f, 4.37835947e-01f }, - { 8.33422053e-01f, 3.24635499e-01f, 4.34365616e-01f }, - { 8.36801237e-01f, 3.29104836e-01f, 4.30905052e-01f }, - { 8.40155276e-01f, 3.33580106e-01f, 4.27454836e-01f }, - { 8.43484103e-01f, 3.38062109e-01f, 4.24013059e-01f }, - { 8.46787726e-01f, 3.42551272e-01f, 4.20579333e-01f }, - { 8.50066132e-01f, 3.47048028e-01f, 4.17153264e-01f }, - { 8.53319279e-01f, 3.51552815e-01f, 4.13734445e-01f }, - { 8.56547103e-01f, 3.56066072e-01f, 4.10322469e-01f }, - { 8.59749520e-01f, 3.60588229e-01f, 4.06916975e-01f }, - { 8.62926559e-01f, 3.65119408e-01f, 4.03518809e-01f }, - { 8.66077920e-01f, 3.69660446e-01f, 4.00126027e-01f }, - { 8.69203436e-01f, 3.74211795e-01f, 3.96738211e-01f }, - { 8.72302917e-01f, 3.78773910e-01f, 3.93354947e-01f }, - { 8.75376149e-01f, 3.83347243e-01f, 3.89975832e-01f }, - { 8.78422895e-01f, 3.87932249e-01f, 3.86600468e-01f }, - { 8.81442916e-01f, 3.92529339e-01f, 3.83228622e-01f }, - { 8.84435982e-01f, 3.97138877e-01f, 3.79860246e-01f }, - { 8.87401682e-01f, 4.01761511e-01f, 3.76494232e-01f }, - { 8.90339687e-01f, 4.06397694e-01f, 3.73130228e-01f }, - { 8.93249647e-01f, 4.11047871e-01f, 3.69767893e-01f }, - { 8.96131191e-01f, 4.15712489e-01f, 3.66406907e-01f }, - { 8.98983931e-01f, 4.20391986e-01f, 3.63046965e-01f }, - { 9.01807455e-01f, 4.25086807e-01f, 3.59687758e-01f }, - { 9.04601295e-01f, 4.29797442e-01f, 3.56328796e-01f }, - { 9.07364995e-01f, 4.34524335e-01f, 3.52969777e-01f }, - { 9.10098088e-01f, 4.39267908e-01f, 3.49610469e-01f }, - { 9.12800095e-01f, 4.44028574e-01f, 3.46250656e-01f }, - { 9.15470518e-01f, 4.48806744e-01f, 3.42890148e-01f }, - { 9.18108848e-01f, 4.53602818e-01f, 3.39528771e-01f }, - { 9.20714383e-01f, 4.58417420e-01f, 3.36165582e-01f }, - { 9.23286660e-01f, 4.63250828e-01f, 3.32800827e-01f }, - { 9.25825146e-01f, 4.68103387e-01f, 3.29434512e-01f }, - { 9.28329275e-01f, 4.72975465e-01f, 3.26066550e-01f }, - { 9.30798469e-01f, 4.77867420e-01f, 3.22696876e-01f }, - { 9.33232140e-01f, 4.82779603e-01f, 3.19325444e-01f }, - { 9.35629684e-01f, 4.87712357e-01f, 3.15952211e-01f }, - { 9.37990034e-01f, 4.92666544e-01f, 3.12575440e-01f }, - { 9.40312939e-01f, 4.97642038e-01f, 3.09196628e-01f }, - { 9.42597771e-01f, 5.02639147e-01f, 3.05815824e-01f }, - { 9.44843893e-01f, 5.07658169e-01f, 3.02433101e-01f }, - { 9.47050662e-01f, 5.12699390e-01f, 2.99048555e-01f }, - { 9.49217427e-01f, 5.17763087e-01f, 2.95662308e-01f }, - { 9.51343530e-01f, 5.22849522e-01f, 2.92274506e-01f }, - { 9.53427725e-01f, 5.27959550e-01f, 2.88883445e-01f }, - { 9.55469640e-01f, 5.33093083e-01f, 2.85490391e-01f }, - { 9.57468770e-01f, 5.38250172e-01f, 2.82096149e-01f }, - { 9.59424430e-01f, 5.43431038e-01f, 2.78700990e-01f }, - { 9.61335930e-01f, 5.48635890e-01f, 2.75305214e-01f }, - { 9.63202573e-01f, 5.53864931e-01f, 2.71909159e-01f }, - { 9.65023656e-01f, 5.59118349e-01f, 2.68513200e-01f }, - { 9.66798470e-01f, 5.64396327e-01f, 2.65117752e-01f }, - { 9.68525639e-01f, 5.69699633e-01f, 2.61721488e-01f }, - { 9.70204593e-01f, 5.75028270e-01f, 2.58325424e-01f }, - { 9.71835007e-01f, 5.80382015e-01f, 2.54931256e-01f }, - { 9.73416145e-01f, 5.85761012e-01f, 2.51539615e-01f }, - { 9.74947262e-01f, 5.91165394e-01f, 2.48151200e-01f }, - { 9.76427606e-01f, 5.96595287e-01f, 2.44766775e-01f }, - { 9.77856416e-01f, 6.02050811e-01f, 2.41387186e-01f }, - { 9.79232922e-01f, 6.07532077e-01f, 2.38013359e-01f }, - { 9.80556344e-01f, 6.13039190e-01f, 2.34646316e-01f }, - { 9.81825890e-01f, 6.18572250e-01f, 2.31287178e-01f }, - { 9.83040742e-01f, 6.24131362e-01f, 2.27937141e-01f }, - { 9.84198924e-01f, 6.29717516e-01f, 2.24595006e-01f }, - { 9.85300760e-01f, 6.35329876e-01f, 2.21264889e-01f }, - { 9.86345421e-01f, 6.40968508e-01f, 2.17948456e-01f }, - { 9.87332067e-01f, 6.46633475e-01f, 2.14647532e-01f }, - { 9.88259846e-01f, 6.52324832e-01f, 2.11364122e-01f }, - { 9.89127893e-01f, 6.58042630e-01f, 2.08100426e-01f }, - { 9.89935328e-01f, 6.63786914e-01f, 2.04858855e-01f }, - { 9.90681261e-01f, 6.69557720e-01f, 2.01642049e-01f }, - { 9.91364787e-01f, 6.75355082e-01f, 1.98452900e-01f }, - { 9.91984990e-01f, 6.81179025e-01f, 1.95294567e-01f }, - { 9.92540939e-01f, 6.87029567e-01f, 1.92170500e-01f }, - { 9.93031693e-01f, 6.92906719e-01f, 1.89084459e-01f }, - { 9.93456302e-01f, 6.98810484e-01f, 1.86040537e-01f }, - { 9.93813802e-01f, 7.04740854e-01f, 1.83043180e-01f }, - { 9.94103226e-01f, 7.10697814e-01f, 1.80097207e-01f }, - { 9.94323596e-01f, 7.16681336e-01f, 1.77207826e-01f }, - { 9.94473934e-01f, 7.22691379e-01f, 1.74380656e-01f }, - { 9.94553260e-01f, 7.28727890e-01f, 1.71621733e-01f }, - { 9.94560594e-01f, 7.34790799e-01f, 1.68937522e-01f }, - { 9.94494964e-01f, 7.40880020e-01f, 1.66334918e-01f }, - { 9.94355411e-01f, 7.46995448e-01f, 1.63821243e-01f }, - { 9.94140989e-01f, 7.53136955e-01f, 1.61404226e-01f }, - { 9.93850778e-01f, 7.59304390e-01f, 1.59091984e-01f }, - { 9.93482190e-01f, 7.65498551e-01f, 1.56890625e-01f }, - { 9.93033251e-01f, 7.71719833e-01f, 1.54807583e-01f }, - { 9.92505214e-01f, 7.77966775e-01f, 1.52854862e-01f }, - { 9.91897270e-01f, 7.84239120e-01f, 1.51041581e-01f }, - { 9.91208680e-01f, 7.90536569e-01f, 1.49376885e-01f }, - { 9.90438793e-01f, 7.96858775e-01f, 1.47869810e-01f }, - { 9.89587065e-01f, 8.03205337e-01f, 1.46529128e-01f }, - { 9.88647741e-01f, 8.09578605e-01f, 1.45357284e-01f }, - { 9.87620557e-01f, 8.15977942e-01f, 1.44362644e-01f }, - { 9.86509366e-01f, 8.22400620e-01f, 1.43556679e-01f }, - { 9.85314198e-01f, 8.28845980e-01f, 1.42945116e-01f }, - { 9.84031139e-01f, 8.35315360e-01f, 1.42528388e-01f }, - { 9.82652820e-01f, 8.41811730e-01f, 1.42302653e-01f }, - { 9.81190389e-01f, 8.48328902e-01f, 1.42278607e-01f }, - { 9.79643637e-01f, 8.54866468e-01f, 1.42453425e-01f }, - { 9.77994918e-01f, 8.61432314e-01f, 1.42808191e-01f }, - { 9.76264977e-01f, 8.68015998e-01f, 1.43350944e-01f }, - { 9.74443038e-01f, 8.74622194e-01f, 1.44061156e-01f }, - { 9.72530009e-01f, 8.81250063e-01f, 1.44922913e-01f }, - { 9.70532932e-01f, 8.87896125e-01f, 1.45918663e-01f }, - { 9.68443477e-01f, 8.94563989e-01f, 1.47014438e-01f }, - { 9.66271225e-01f, 9.01249365e-01f, 1.48179639e-01f }, - { 9.64021057e-01f, 9.07950379e-01f, 1.49370428e-01f }, - { 9.61681481e-01f, 9.14672479e-01f, 1.50520343e-01f }, - { 9.59275646e-01f, 9.21406537e-01f, 1.51566019e-01f }, - { 9.56808068e-01f, 9.28152065e-01f, 1.52409489e-01f }, - { 9.54286813e-01f, 9.34907730e-01f, 1.52921158e-01f }, - { 9.51726083e-01f, 9.41670605e-01f, 1.52925363e-01f }, - { 9.49150533e-01f, 9.48434900e-01f, 1.52177604e-01f }, - { 9.46602270e-01f, 9.55189860e-01f, 1.50327944e-01f }, - { 9.44151742e-01f, 9.61916487e-01f, 1.46860789e-01f }, - { 9.41896120e-01f, 9.68589814e-01f, 1.40955606e-01f }, - { 9.40015097e-01f, 9.75158357e-01f, 1.31325517e-01f }, - }; - - private static Color GetColorAt(int index) - { - return new Color(Data[index, 0], Data[index, 1], Data[index, 2]); - } - + private static Color _startColor = new Color(13, 8, 135); + private static Color _middleColor = new Color(203, 70, 121); + private static Color _endColor = new Color(240, 249, 33); public static Color GetColor(float progress) { - if (progress <= 0) return GetColorAt(0); - var dataCount = Data.GetLength(0); - if (progress >= 1) return GetColorAt(dataCount - 1); - var i = (int)(progress * (dataCount - 1)); - // var lerpWidth = 1f / Data.GetLength(0); - var lerpProgress = progress * (dataCount - 1) - i; - return Color.Lerp(GetColorAt(i), GetColorAt(i + 1), lerpProgress); + if (progress < 0.5f) + { + progress *= 2; + return new Color + { + R = (byte)(_startColor.R + (_middleColor.R - _startColor.R) * progress), + G = (byte)(_startColor.G + (_middleColor.G - _startColor.G) * progress), + B = (byte)(_startColor.B + (_middleColor.B - _startColor.B) * progress), + A = 255 + }; + } + else + { + progress = (progress - 0.5f) * 2; + return new Color + { + R = (byte)(_middleColor.R + (_endColor.R - _middleColor.R) * progress), + G = (byte)(_middleColor.G + (_endColor.G - _middleColor.G) * progress), + B = (byte)(_middleColor.B + (_endColor.B - _middleColor.B) * progress), + A = 255 + }; + } } } } \ No newline at end of file From d34dcc1ab1a175661c1d149c72c24017bb0fffaf Mon Sep 17 00:00:00 2001 From: Warp Date: Mon, 8 Apr 2024 22:29:37 +0200 Subject: [PATCH 135/249] update Quaver.API and change Quaver.Shared to net6.0 --- Quaver.API | 2 +- Quaver.Shared/Database/Profiles/UserProfileStats.cs | 2 +- Quaver.Shared/Quaver.Shared.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Quaver.API b/Quaver.API index f798abe059..256800a7f8 160000 --- a/Quaver.API +++ b/Quaver.API @@ -1 +1 @@ -Subproject commit f798abe059f966573086ab47438b7a6bff144b67 +Subproject commit 256800a7f8bc2510ba0bf67b9e5e4e7d850c880d diff --git a/Quaver.Shared/Database/Profiles/UserProfileStats.cs b/Quaver.Shared/Database/Profiles/UserProfileStats.cs index 472729c000..db3504a739 100644 --- a/Quaver.Shared/Database/Profiles/UserProfileStats.cs +++ b/Quaver.Shared/Database/Profiles/UserProfileStats.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using MoreLinq; +// using MoreLinq; using Quaver.API.Enums; using Quaver.Shared.Database.Scores; diff --git a/Quaver.Shared/Quaver.Shared.csproj b/Quaver.Shared/Quaver.Shared.csproj index a826856820..b905cca2d2 100644 --- a/Quaver.Shared/Quaver.Shared.csproj +++ b/Quaver.Shared/Quaver.Shared.csproj @@ -1,6 +1,6 @@  - netstandard2.1 + net6.0 Debug;Release;Visual Tests From 27c70740f6511bf5121977864b3c983bac3c34c6 Mon Sep 17 00:00:00 2001 From: Warp Date: Mon, 8 Apr 2024 22:51:22 +0200 Subject: [PATCH 136/249] Remove old settings menu --- .../Buttons/IconTextButtonOptions.cs | 1 - Quaver.Shared/QuaverGame.cs | 1 - .../Screens/Importing/ImportingScreenView.cs | 1 - .../Elements/SelectableBorderedTextButton.cs | 73 --- .../Screens/Settings/Elements/SettingsBool.cs | 95 ---- .../Elements/SettingsCalibrateOffset.cs | 88 --- .../Settings/Elements/SettingsCustomSkin.cs | 85 --- .../Settings/Elements/SettingsDefaultSkin.cs | 46 -- .../Elements/SettingsEditorSnapColors.cs | 55 -- .../Settings/Elements/SettingsExportSkin.cs | 53 -- .../Settings/Elements/SettingsFpsLimiter.cs | 53 -- .../Elements/SettingsHorizontalSelector.cs | 56 -- .../Screens/Settings/Elements/SettingsItem.cs | 79 --- .../Settings/Elements/SettingsKeybind.cs | 125 ----- .../Elements/SettingsKeybindMultiple.cs | 50 -- .../Elements/SettingsKeybindMultipleDialog.cs | 185 ------- .../Elements/SettingsKeybindSprite.cs | 88 --- .../Settings/Elements/SettingsResolution.cs | 80 --- .../Elements/SettingsScrollDirection.cs | 62 --- .../Settings/Elements/SettingsSlider.cs | 86 --- .../SettingsSliderAudioBufferLength.cs | 38 -- .../Elements/SettingsUploadToWorkshop.cs | 61 --- .../Settings/Elements/SettingsWorkshopSkin.cs | 97 ---- .../Screens/Settings/SettingsDialog.cs | 509 ------------------ .../Screens/Settings/SettingsSection.cs | 116 ---- .../Screens/Settings/SettingsSectionButton.cs | 139 ----- 26 files changed, 2322 deletions(-) delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SelectableBorderedTextButton.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsBool.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsCalibrateOffset.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsCustomSkin.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsDefaultSkin.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsEditorSnapColors.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsExportSkin.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsFpsLimiter.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsHorizontalSelector.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsItem.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsKeybind.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsKeybindMultiple.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsKeybindMultipleDialog.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsKeybindSprite.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsResolution.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsScrollDirection.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsSlider.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsSliderAudioBufferLength.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsUploadToWorkshop.cs delete mode 100644 Quaver.Shared/Screens/Settings/Elements/SettingsWorkshopSkin.cs delete mode 100644 Quaver.Shared/Screens/Settings/SettingsDialog.cs delete mode 100644 Quaver.Shared/Screens/Settings/SettingsSection.cs delete mode 100644 Quaver.Shared/Screens/Settings/SettingsSectionButton.cs diff --git a/Quaver.Shared/Graphics/Menu/Border/Components/Buttons/IconTextButtonOptions.cs b/Quaver.Shared/Graphics/Menu/Border/Components/Buttons/IconTextButtonOptions.cs index cbd13e5b47..651ea90294 100644 --- a/Quaver.Shared/Graphics/Menu/Border/Components/Buttons/IconTextButtonOptions.cs +++ b/Quaver.Shared/Graphics/Menu/Border/Components/Buttons/IconTextButtonOptions.cs @@ -1,6 +1,5 @@ using Quaver.Shared.Assets; using Quaver.Shared.Screens.Options; -using Quaver.Shared.Screens.Settings; using Wobble.Graphics.UI.Dialogs; using Wobble.Managers; diff --git a/Quaver.Shared/QuaverGame.cs b/Quaver.Shared/QuaverGame.cs index 870c2456cc..a198e8c184 100644 --- a/Quaver.Shared/QuaverGame.cs +++ b/Quaver.Shared/QuaverGame.cs @@ -60,7 +60,6 @@ using Quaver.Shared.Screens.Options; using Quaver.Shared.Screens.Selection; using Quaver.Shared.Screens.Selection.UI.FilterPanel; -using Quaver.Shared.Screens.Settings; using Quaver.Shared.Screens.Tests.AutoMods; using Quaver.Shared.Screens.Tests.Border; using Quaver.Shared.Screens.Tests.Chat; diff --git a/Quaver.Shared/Screens/Importing/ImportingScreenView.cs b/Quaver.Shared/Screens/Importing/ImportingScreenView.cs index 8e4533b7d4..4a712bfbc5 100644 --- a/Quaver.Shared/Screens/Importing/ImportingScreenView.cs +++ b/Quaver.Shared/Screens/Importing/ImportingScreenView.cs @@ -12,7 +12,6 @@ using Quaver.Shared.Graphics; using Quaver.Shared.Helpers; using Quaver.Shared.Screens.Menu.UI.Visualizer; -using Quaver.Shared.Screens.Settings; using Wobble; using Wobble.Graphics; using Wobble.Graphics.Animations; diff --git a/Quaver.Shared/Screens/Settings/Elements/SelectableBorderedTextButton.cs b/Quaver.Shared/Screens/Settings/Elements/SelectableBorderedTextButton.cs deleted file mode 100644 index 96951abb76..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SelectableBorderedTextButton.cs +++ /dev/null @@ -1,73 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using System; -using Microsoft.Xna.Framework; -using Quaver.Shared.Screens.Menu.UI.Navigation.User; - -namespace Quaver.Shared.Graphics.Overlays.Chat.Components.Users -{ - public class SelectableBorderedTextButton : BorderedTextButton - { - /// - /// The color of the button when it is deselected. - /// - public Color DeselectedColor { get; set; } - - /// - /// The color of the button when it is selected - /// - public Color SelectedColor { get; set; } = Color.White; - - /// - /// Determines if the button is currently "selected" - /// - private bool _selected; - public bool Selected - { - get => _selected; - set - { - _selected = value; - - if (_selected) - OriginalColor = SelectedColor; - else - OriginalColor = DeselectedColor; - } - } - - /// - /// - /// - /// - /// - /// - /// - public SelectableBorderedTextButton(string text, Color color, bool selected, EventHandler clickAction = null) - : base(text, color, clickAction) - { - DeselectedColor = color; - Selected = selected; - } - - /// - /// - /// - /// - /// - /// - /// - public SelectableBorderedTextButton(string text, Color deselectedColor, Color selectedColor, bool selected, EventHandler clickAction = null) - : base(text, deselectedColor, clickAction) - { - DeselectedColor = deselectedColor; - SelectedColor = selectedColor; - Selected = selected; - } - } -} \ No newline at end of file diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsBool.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsBool.cs deleted file mode 100644 index 8a7367df7f..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsBool.cs +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework; -using Quaver.Shared.Assets; -using Wobble.Bindables; -using Wobble.Graphics; -using Wobble.Graphics.UI.Buttons; -using Wobble.Graphics.UI.Form; -using Wobble.Managers; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public class SettingsBool : SettingsItem - { - /// - /// The binded value for this settings item. - /// - private Bindable Bindable { get; } - - /// - /// - private HorizontalSelector Selector { get; } - - /// - /// - /// - /// - /// - /// - public SettingsBool(SettingsDialog dialog, string name, Bindable bindable) : base(dialog, name) - { - Bindable = bindable; - - Selector = new HorizontalSelector(new List - { - "No", - "Yes", - }, new ScalableVector2(200, 26), FontManager.GetWobbleFont(Fonts.LatoBlack), 13, FontAwesome.Get(FontAwesomeIcon.fa_chevron_pointing_to_the_left), - FontAwesome.Get(FontAwesomeIcon.fa_right_chevron), - new ScalableVector2(30, 22), 5, OnSelectorChanged, Convert.ToInt32(Bindable.Value)) - { - Parent = this, - Alignment = Alignment.MidRight, - Tint = Color.Transparent, - SelectedItemText = - { - Tint = Color.White, - UsePreviousSpriteBatchOptions = true - }, - ButtonSelectLeft = - { - UsePreviousSpriteBatchOptions = true - }, - ButtonSelectRight = - { - UsePreviousSpriteBatchOptions = true - } - }; - - Selector.X -= 68; - - Bindable.ValueChanged += OnBindableValueChanged; - } - - /// - /// - /// - public override void Destroy() - { - // ReSharper disable once DelegateSubtraction - Bindable.ValueChanged -= OnBindableValueChanged; - - base.Destroy(); - } - - /// - /// - /// - /// - private void OnSelectorChanged(string val, int index) => Bindable.Value = Convert.ToBoolean(index); - - /// - /// - /// - /// - private void OnBindableValueChanged(object sender, BindableValueChangedEventArgs e) => Selector.SelectIndex(Convert.ToInt32(e.Value)); - } -} diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsCalibrateOffset.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsCalibrateOffset.cs deleted file mode 100644 index 1f35e9d102..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsCalibrateOffset.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using Quaver.API.Enums; -using Quaver.API.Maps; -using Quaver.Shared.Assets; -using Quaver.Shared.Audio; -using Quaver.Shared.Database.Maps; -using Quaver.Shared.Database.Scores; -using Quaver.Shared.Graphics; -using Quaver.Shared.Graphics.Backgrounds; -using Quaver.Shared.Graphics.Notifications; -using Quaver.Shared.Modifiers; -using Quaver.Shared.Screens.Gameplay; -using Quaver.Shared.Screens.Menu.UI.Navigation.User; -using Quaver.Shared.Skinning; -using Wobble; -using Wobble.Graphics; -using Wobble.Graphics.UI.Dialogs; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public class SettingsCalibrateOffset : SettingsItem - { - /// - /// - private BorderedTextButton CalibrateOffsetButton { get; set; } - - /// - /// - /// - /// - /// - public SettingsCalibrateOffset(SettingsDialog dialog, string name) : base(dialog, name) => CreateExportButton(); - - /// - /// - private void CreateExportButton() - { - CalibrateOffsetButton = new BorderedTextButton("Calibrate", Colors.MainAccent) - { - Parent = this, - X = -50, - Alignment = Alignment.MidRight, - Height = 30, - Width = 225, - Text = - { - Font = Fonts.SourceSansProSemiBold, - FontSize = 12 - } - }; - - CalibrateOffsetButton.Clicked += (o, e) => - { - var game = (QuaverGame) GameBase.Game; - - if (game.CurrentScreen.Type == QuaverScreenType.Editor) - { - NotificationManager.Show(NotificationLevel.Warning, "Finish what you're doing before calibrating a new offset"); - return; - } - - var path = $"Quaver.Resources/Maps/Offset/offset.qua"; - - var qua = Qua.Parse(GameBase.Game.Resources.Get(path)); - - if (AudioEngine.Track != null && !AudioEngine.Track.IsDisposed && AudioEngine.Track.IsPlaying) - AudioEngine.Track.Pause(); - - game.CurrentScreen?.Exit(() => - { - MapManager.Selected.Value = Map.FromQua(qua, path, true); - MapManager.Selected.Value.Qua = qua; - - // Make the user not allow to fail. - ModManager.RemoveAllMods(); - ModManager.AddMod(ModIdentifier.NoFail); - - // Load the background (usually the default one) - BackgroundHelper.Load(MapManager.Selected.Value); - DialogManager.Dismiss(Dialog); - - return new GameplayScreen(qua, "", new List(), null, false, 0, true); - }); - }; - } - } -} \ No newline at end of file diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsCustomSkin.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsCustomSkin.cs deleted file mode 100644 index 1e8e61c4bc..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsCustomSkin.cs +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using IniFileParser; -using Quaver.Shared.Config; -using Quaver.Shared.Skinning; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public class SettingsCustomSkin : SettingsHorizontalSelector - { - /// - /// - /// - /// - /// - public SettingsCustomSkin(SettingsDialog dialog, string name) - : base(dialog, name, GetCustomSkinList(), (val, index) => OnChange(dialog, val, index), GetSelectedIndex()) - { - } - - /// - /// - /// - private static List GetCustomSkinList() - { - var skins = new List { "Default Skin" }; - - var skinDirectories = Directory.GetDirectories(ConfigManager.SkinDirectory.Value); - skins.AddRange(skinDirectories.Select(dir => new DirectoryInfo(dir).Name)); - skins.Sort(); - - return skins; - } - - /// - /// - /// - /// - /// - private static void OnChange(SettingsDialog dialog, string val, int index) - { - val = new DirectoryInfo(val).Name; - var skin = ConfigManager.Skin.Value; - - switch (val) - { - // Check if the user already has the default skin enabled and switched back to it. - // User wants to choose the default skin - case "Default Skin" when string.IsNullOrEmpty(skin): - SkinManager.NewQueuedSkin = null; - break; - // User is selecting a custom skin - case "Default Skin" when !string.IsNullOrEmpty(skin): - SkinManager.NewQueuedSkin = ""; - break; - default: - if (val != skin) - SkinManager.NewQueuedSkin = val; - break; - } - } - - /// - /// Finds the index of the selected skin - /// - /// - private static int GetSelectedIndex() - { - var skins = GetCustomSkinList(); - var skin = ConfigManager.Skin.Value; - - var index = string.IsNullOrEmpty(skin) ? 0 : skins.FindIndex(x => x == skin); - return Math.Clamp(index, 0, int.MaxValue); - } - } -} diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsDefaultSkin.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsDefaultSkin.cs deleted file mode 100644 index 70cf7941b6..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsDefaultSkin.cs +++ /dev/null @@ -1,46 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using Quaver.Shared.Config; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public class SettingsDefaultSkin : SettingsHorizontalSelector - { - /// - /// - /// - /// - /// - public SettingsDefaultSkin(SettingsDialog dialog, string name) - : base(dialog, name, GetSelectorElements(), (val, i) => OnChange(dialog, val, i), GetSelectedIndex()) - { - } - - /// - /// - /// - private static List GetSelectorElements() => Enum.GetNames(typeof(DefaultSkins)).ToList(); - - /// - /// - /// - /// - /// - private static void OnChange(SettingsDialog dialog, string val, int index) - => dialog.NewQueuedDefaultSkin = (DefaultSkins) Enum.Parse(typeof(DefaultSkins), val); - - /// - /// - /// - private static int GetSelectedIndex() - => GetSelectorElements().FindIndex(x => x.ToString() == ConfigManager.DefaultSkin.Value.ToString()); - } -} diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsEditorSnapColors.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsEditorSnapColors.cs deleted file mode 100644 index 886e4b90d7..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsEditorSnapColors.cs +++ /dev/null @@ -1,55 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using Quaver.Shared.Config; -using Wobble.Bindables; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public class SettingsEditorSnapColors : SettingsHorizontalSelector - { - /// - /// - /// - /// - public SettingsEditorSnapColors(SettingsDialog dialog) - : base(dialog, "Beat Snap Color Type", BeatSnapColorTypesToList(), OnChange, (int) ConfigManager.EditorBeatSnapColorType.Value) - => ConfigManager.EditorBeatSnapColorType.ValueChanged += OnBindableValueChanged; - - /// - /// - /// - public override void Destroy() - { - // ReSharper disable once DelegateSubtraction - ConfigManager.EditorBeatSnapColorType.ValueChanged -= OnBindableValueChanged; - base.Destroy(); - } - - /// - /// - /// - /// - private static void OnChange(string val, int index) - => ConfigManager.EditorBeatSnapColorType.Value = (EditorBeatSnapColor) Enum.Parse(typeof(EditorBeatSnapColor), val); - - /// - /// - /// - private static List BeatSnapColorTypesToList() => Enum.GetNames(typeof(EditorBeatSnapColor)).ToList(); - - /// - /// - /// - /// - private void OnBindableValueChanged(object sender, BindableValueChangedEventArgs e) - => Selector.SelectIndex((int) e.Value); - } -} diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsExportSkin.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsExportSkin.cs deleted file mode 100644 index 32c4e57952..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsExportSkin.cs +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using Quaver.Shared.Assets; -using Quaver.Shared.Graphics; -using Quaver.Shared.Screens.Menu.UI.Navigation.User; -using Quaver.Shared.Skinning; -using Wobble.Assets; -using Wobble.Graphics; -using Wobble.Graphics.UI.Buttons; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public class SettingsExportSkin : SettingsItem - { - /// - /// The button used to export the skin - /// - private BorderedTextButton ExportButton { get; set; } - - /// - /// - /// - /// - /// - public SettingsExportSkin(SettingsDialog dialog, string name) : base(dialog, name) => CreateExportButton(); - - /// - /// - private void CreateExportButton() - { - ExportButton = new BorderedTextButton("Export Skin", Colors.MainAccent) - { - Parent = this, - X = -50, - Alignment = Alignment.MidRight, - Height = 30, - Width = 225, - Text = - { - Font = Fonts.SourceSansProSemiBold, - FontSize = 12 - } - }; - - ExportButton.Clicked += (o, e) => SkinManager.Export(); - } - } -} diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsFpsLimiter.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsFpsLimiter.cs deleted file mode 100644 index db34eb3a4a..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsFpsLimiter.cs +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using Quaver.Shared.Config; -using Wobble.Bindables; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public class SettingsFpsLimiter : SettingsHorizontalSelector - { - /// - /// - /// - /// - public SettingsFpsLimiter(SettingsDialog dialog) - : base(dialog, "Frame Limiter Type", FpsLimiterTypesToStringList(), OnChange, (int) ConfigManager.FpsLimiterType.Value) - => ConfigManager.FpsLimiterType.ValueChanged += OnBindableValueChanged; - - /// - /// - /// - public override void Destroy() - { - // ReSharper disable once DelegateSubtraction - ConfigManager.FpsLimiterType.ValueChanged -= OnBindableValueChanged; - base.Destroy(); - } - - /// - /// - /// - /// - private static void OnChange(string val, int index) => ConfigManager.FpsLimiterType.Value = (FpsLimitType) Enum.Parse(typeof(FpsLimitType), val); - - /// - /// - /// - private static List FpsLimiterTypesToStringList() => Enum.GetNames(typeof(FpsLimitType)).ToList(); - - /// - /// - /// - /// - private void OnBindableValueChanged(object sender, BindableValueChangedEventArgs e) => Selector.SelectIndex((int) e.Value); - } -} diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsHorizontalSelector.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsHorizontalSelector.cs deleted file mode 100644 index eed09a9ec5..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsHorizontalSelector.cs +++ /dev/null @@ -1,56 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework; -using Quaver.Shared.Assets; -using Wobble.Bindables; -using Wobble.Graphics; -using Wobble.Graphics.UI.Form; -using Wobble.Managers; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public abstract class SettingsHorizontalSelector : SettingsItem - { - /// - /// - protected HorizontalSelector Selector { get; } - - /// - /// - /// - /// - /// - /// - /// - /// - protected SettingsHorizontalSelector(SettingsDialog dialog, string name, List elements, Action onChange, int selectedIndex) - : base(dialog, name) - { - Selector = new HorizontalSelector(elements, new ScalableVector2(200, 26), - FontManager.GetWobbleFont(Fonts.LatoBlack), 13, FontAwesome.Get(FontAwesomeIcon.fa_chevron_pointing_to_the_left), - FontAwesome.Get(FontAwesomeIcon.fa_right_chevron), - new ScalableVector2(30, 22), 5, onChange, selectedIndex) - { - Parent = this, - Alignment = Alignment.MidRight, - Tint = Color.Transparent, - SelectedItemText = - { - Tint = Color.White, - UsePreviousSpriteBatchOptions = true - }, - ButtonSelectLeft = { UsePreviousSpriteBatchOptions = true }, - ButtonSelectRight = { UsePreviousSpriteBatchOptions = true } - }; - - Selector.X -= 68; - } - } -} diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsItem.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsItem.cs deleted file mode 100644 index baf08f0de4..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsItem.cs +++ /dev/null @@ -1,79 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using Microsoft.Xna.Framework; -using Quaver.Shared.Assets; -using Quaver.Shared.Helpers; -using Wobble.Graphics; -using Wobble.Graphics.Sprites; -using Wobble.Input; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public class SettingsItem : Sprite - { - /// - /// - public SpriteText Name { get; } - - protected Color HoverColor { get; set; } = ColorHelper.HexToColor("#cacaca"); - - protected Color UnhoverColor { get; set; } = Color.Black; - - protected SettingsDialog Dialog { get; } - - /// - /// - /// - /// - /// - public SettingsItem(SettingsDialog dialog, string name) - { - Dialog = dialog; - Size = new ScalableVector2(dialog.ContentContainer.Width - dialog.DividerLine.X - 10, 40); - Tint = UnhoverColor; - Alpha = 0.65f; - - Name = new SpriteText(Fonts.SourceSansProSemiBold, name, 13) - { - Parent = this, - Alignment = Alignment.MidLeft, - X = 20 - }; - } - - /// - /// - /// - /// - public SettingsItem(Drawable sprite, string name) - { - Size = new ScalableVector2(sprite.Width - 10, 40); - Tint = Color.Black; - Alpha = 0.65f; - - Name = new SpriteText(Fonts.Exo2SemiBold, name, 13) - { - Parent = this, - Alignment = Alignment.MidLeft, - X = 15 - }; - } - - /// - /// - /// - /// - public override void Update(GameTime gameTime) - { - FadeToColor(GraphicsHelper.RectangleContains(ScreenRectangle, MouseManager.CurrentState.Position) - ? HoverColor : UnhoverColor, gameTime.ElapsedGameTime.TotalMilliseconds, 70); - - base.Update(gameTime); - } - } -} diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsKeybind.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsKeybind.cs deleted file mode 100644 index 5f768fa4d1..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsKeybind.cs +++ /dev/null @@ -1,125 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using System.Linq; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Input; -using Quaver.Shared.Graphics; -using Quaver.Shared.Graphics.Overlays.Chat.Components.Users; -using Wobble.Bindables; -using Wobble.Graphics; -using Wobble.Helpers; -using Wobble.Input; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public class SettingsKeybind : SettingsItem - { - /// - /// Refeerence to the parent dialog screen. - /// - private SettingsDialog Dialog { get; } - - /// - /// The keybind that is being changed. - /// - private Bindable Bindable { get; } - - /// - /// The button that displays the keybind and used to select a new one. - /// - private SelectableBorderedTextButton Button { get; } - - /// - /// Determines if we're currently waiting for the user to press a key to change the value - /// - private bool WaitingForKeyPress { get; set; } - - /// - /// - /// - /// - /// - /// - public SettingsKeybind(SettingsDialog dialog, string name, Bindable bindable) : base(dialog, name) - { - Dialog = dialog; - Bindable = bindable; - - Button = new SelectableBorderedTextButton(XnaKeyHelper.GetStringFromKey(bindable.Value), Color.White, Colors.MainAccent, false) - { - Parent = this, - Alignment = Alignment.MidRight, - X = -20, - UsePreviousSpriteBatchOptions = true, - Border = {UsePreviousSpriteBatchOptions = true }, - Text = { UsePreviousSpriteBatchOptions = true } - }; - - Button.Height -= 6; - - Button.Clicked += (o, e) => - { - Button.Text.Text = "Press Key"; - Button.Selected = true; - WaitingForKeyPress = true; - }; - - Button.ClickedOutside += (o, e) => - { - if (!dialog.IsOnTop) - return; - - var key = XnaKeyHelper.GetStringFromKey(Bindable.Value); - - if (Button.Text.Text != key) - Button.Text.Text = key; - - Button.Selected = false; - WaitingForKeyPress = false; - }; - } - - /// - /// - /// - /// - public override void Update(GameTime gameTime) - { - if (WaitingForKeyPress && Dialog.IsOnTop) - { - var keys = KeyboardManager.CurrentState.GetPressedKeys(); - - if (keys.Length != 0) - { - if (KeyboardManager.IsUniqueKeyPress(keys.First())) - { - Bindable.Value = keys.First(); - - var key = XnaKeyHelper.GetStringFromKey(keys.First()); - - if (Button.Text.Text != key) - Button.Text.Text = key; - - if (keys.First() == Keys.Escape) - Dialog.PreventExitOnEscapeKeybindPress = true; - - Button.Selected = false; - WaitingForKeyPress = false; - } - } - } - else - { - // Important. Makes it so the dialog doesn't close when the user presses escape as a keybind. - Dialog.PreventExitOnEscapeKeybindPress = false; - } - - base.Update(gameTime); - } - } -} \ No newline at end of file diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsKeybindMultiple.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsKeybindMultiple.cs deleted file mode 100644 index 36baa58191..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsKeybindMultiple.cs +++ /dev/null @@ -1,50 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using System.Collections.Generic; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Input; -using Quaver.Shared.Screens.Menu.UI.Navigation.User; -using Wobble.Bindables; -using Wobble.Graphics; -using Wobble.Graphics.UI.Buttons; -using Wobble.Graphics.UI.Dialogs; -using Wobble.Input; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public class SettingsKeybindMultiple : SettingsItem - { - /// - /// The binded keybinds that'll be changed - /// - private List> Keybinds { get; } - - /// - /// - /// - /// - /// - /// - public SettingsKeybindMultiple(SettingsDialog dialog, string name, List> keybinds) : base(dialog, name) - { - Keybinds = keybinds; - - var btn = new BorderedTextButton("Change", Color.White) - { - Parent = this, - Alignment = Alignment.MidRight, - X = -20, - UsePreviousSpriteBatchOptions = true, - Text = { UsePreviousSpriteBatchOptions = true } - }; - - btn.Clicked += (sender, args) => DialogManager.Show(new SettingsKeybindMultipleDialog(keybinds)); - btn.Height -= 6; - } - } -} diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsKeybindMultipleDialog.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsKeybindMultipleDialog.cs deleted file mode 100644 index b1739ab845..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsKeybindMultipleDialog.cs +++ /dev/null @@ -1,185 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using System.Collections.Generic; -using System.Linq; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Input; -using Quaver.Shared.Assets; -using Quaver.Shared.Graphics; -using Wobble.Bindables; -using Wobble.Graphics; -using Wobble.Graphics.Primitives; -using Wobble.Graphics.Sprites; -using Wobble.Graphics.UI.Dialogs; -using Wobble.Input; -using Wobble.Window; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public class SettingsKeybindMultipleDialog : DialogScreen - { - /// - /// The list of bindable keys - /// - private List> Keybinds { get; } - - /// - /// - private Sprite ContainingBox { get; set; } - - /// - /// - private Line TopLine { get; set; } - - /// - /// The list of keybind sprites that display the current key. - /// - private List KeybindSprites { get; set; } - - /// - /// The index of the keybind that is currently being changed. - /// - private int CurrentChangingKeybind { get; set; } - - /// - /// - /// - public SettingsKeybindMultipleDialog(List> keybinds) : base(0.75f) - { - Keybinds = keybinds; - CreateContent(); - } - - /// - /// - /// - public sealed override void CreateContent() - { - CreateContainingBox(); - CreateTopLine(); - CreateHeader(); - CreateKeybindSprites(); - } - - /// - /// - /// - /// - public override void HandleInput(GameTime gameTime) - { - // Get the current pressed keys - var pressedKeys = GenericKeyManager.GetPressedKeys(); - - // Don't bother continuing if the user hasn't pressed any keys. - if (pressedKeys.Count == 0) - return; - - // Grab only the first key that was pressed. - var firstKey = pressedKeys[0]; - - // If the key wasn't uniquely pressed then don't handle anything else. - if (!GenericKeyManager.IsUniquePress(firstKey)) - return; - - KeybindSprites[CurrentChangingKeybind].Key.Value = firstKey; - - if (CurrentChangingKeybind + 1 < KeybindSprites.Count) - { - KeybindSprites[CurrentChangingKeybind].Selected = false; - - CurrentChangingKeybind++; - KeybindSprites[CurrentChangingKeybind].Selected = true; - } - else - { - DialogManager.Dismiss(); - } - } - - /// - /// - private void CreateContainingBox() => ContainingBox = new Sprite - { - Parent = Container, - Size = new ScalableVector2(WindowManager.Width, 130), - Alignment = Alignment.MidCenter, - Tint = Color.Black, - Alpha = 0.85f, - }; - - /// - /// - private void CreateTopLine() => TopLine = new Line(new Vector2(WindowManager.Width, ContainingBox.AbsolutePosition.Y), - Colors.MainAccent, 2) - { - Parent = ContainingBox, - Alpha = 0.75f, - UsePreviousSpriteBatchOptions = true - }; - - /// - /// Creates the heading text - /// - private void CreateHeader() - { - var icon = new Sprite - { - Parent = ContainingBox, - Alignment = Alignment.TopLeft, - Image = FontAwesome.Get(FontAwesomeIcon.fa_keyboard), - Size = new ScalableVector2(24, 24), - Y = 18 - }; - - var header = new SpriteText(Fonts.Exo2SemiBold, "Press a key to change the binding", 16) - { - Parent = icon, - Alignment = Alignment.MidLeft, - X = icon.Width + 10 - }; - - icon.X = WindowManager.Width / 2f - header.Width / 2f - 10 - icon.Width / 2f; - } - - /// - /// - private void CreateKeybindSprites() - { - KeybindSprites = new List(); - - var totalWidth = 0f; - - for (var i = 0; i < Keybinds.Count; i++) - { - var keybindSprite = new SettingsKeybindSprite(Keybinds[i]) - { - Parent = ContainingBox, - Alignment = Alignment.TopCenter, - }; - - if (i == 0) - { - totalWidth += keybindSprite.Width; - keybindSprite.Selected = true; - } - else - { - keybindSprite.Parent = KeybindSprites.First(); - keybindSprite.Alignment = Alignment.TopLeft; - keybindSprite.X = i * keybindSprite.Width + i * 20; - totalWidth += keybindSprite.Width + 20; - } - - KeybindSprites.Add(keybindSprite); - } - - KeybindSprites.First().Y = 58; - KeybindSprites.First().X = -totalWidth / 2f + 20; - } - } -} diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsKeybindSprite.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsKeybindSprite.cs deleted file mode 100644 index 81fc7c6dd3..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsKeybindSprite.cs +++ /dev/null @@ -1,88 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Input; -using Quaver.Shared.Assets; -using Quaver.Shared.Graphics; -using Quaver.Shared.Helpers; -using Wobble.Bindables; -using Wobble.Graphics; -using Wobble.Graphics.Sprites; -using Wobble.Helpers; -using Wobble.Input; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public class SettingsKeybindSprite : Sprite - { - /// - /// The binded key. - /// - public Bindable Key { get; } - - /// - /// The text that displays the current keybind - /// - private SpriteText KeyText { get; } - - /// - /// If the keybind sprite is selected. - /// - public bool Selected { get; set; } - - /// - /// - /// - /// - public SettingsKeybindSprite(Bindable key) - { - Key = key; - Image = UserInterface.BlankBox; - Tint = Color.Transparent; - AddBorder(Color.White, 2); - Size = new ScalableVector2(54, 54); - - KeyText = new SpriteText(Fonts.Exo2Regular, Key.Value.GetName(), 13) - { - Parent = this, - Alignment = Alignment.MidCenter - }; - - Key.ValueChanged += OnKeybindChanged; - } - - /// - /// - /// - /// - public override void Update(GameTime gameTime) - { - Border.FadeToColor(Selected ? Colors.MainAccent : Color.White, gameTime.ElapsedGameTime.TotalMilliseconds, 60); - KeyText.FadeToColor(Selected ? Colors.MainAccent : Color.White, gameTime.ElapsedGameTime.TotalMilliseconds, 60); - - base.Update(gameTime); - } - - /// - /// - /// - public override void Destroy() - { - // ReSharper disable once DelegateSubtraction - Key.ValueChanged -= OnKeybindChanged; - base.Destroy(); - } - - /// - /// Updates the text when the key is changed. - /// - /// - /// - private void OnKeybindChanged(object sender, BindableValueChangedEventArgs args) => KeyText.Text = args.Value.GetName(); - } -} diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsResolution.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsResolution.cs deleted file mode 100644 index 5ebb4f3785..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsResolution.cs +++ /dev/null @@ -1,80 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using Quaver.Shared.Config; -using Wobble.Graphics; -using Wobble.Graphics.UI.Form; -using Wobble.Window; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public class SettingsResolution : SettingsHorizontalSelector - { - /// - /// - /// - /// - /// - public SettingsResolution(SettingsDialog dialog, string name) - : base(dialog, name, GetElements(), (val, i) => OnChange(dialog, val, i), GetSelectedIndex()) - { - } - - /// - /// - /// - private static List GetElements() - { - var resolutions = new List - { - new Point(1024, 576), - new Point(1152, 648), - new Point(1280, 720), - new Point(1366, 768), - new Point(1600, 900), - new Point(1920, 1080), - new Point(2560, 1440) - }; - - var userRes = new Point(ConfigManager.WindowWidth.Value, ConfigManager.WindowHeight.Value); - - // Put the user's resolution where it should go (ordered by width) - if (!resolutions.Any(x => x.X == userRes.X && x.Y == userRes.Y)) - resolutions.Insert(resolutions.FindLastIndex(x => x.X > userRes.X), userRes); - - var resolutionList = new List(); - resolutions.ForEach(x => resolutionList.Add($"{x.X}x{x.Y}")); - - return resolutionList; - } - - /// - /// - /// - private static int GetSelectedIndex() => - GetElements().FindIndex(x => x == $"{ConfigManager.WindowWidth.Value}x{ConfigManager.WindowHeight.Value}"); - - /// - /// - /// - /// - /// - private static void OnChange(SettingsDialog dialog, string val, int index) - { - // Parse the value - var split = val.Split('x'); - var resolution = new Point(int.Parse(split[0]), int.Parse(split[1])); - - dialog.NewQueuedScreenResolution = resolution; - } - } -} diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsScrollDirection.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsScrollDirection.cs deleted file mode 100644 index e2f5180777..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsScrollDirection.cs +++ /dev/null @@ -1,62 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using Quaver.Shared.Config; -using Wobble.Bindables; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public class SettingsScrollDirection : SettingsHorizontalSelector - { - private Bindable BoundScrollDirection { get; } - - /// - /// - /// - /// - /// - /// - public SettingsScrollDirection(SettingsDialog dialog, string name, Bindable binded) - : base(dialog, name, ScrollDirectionToStringList(), (val, index) => OnChange(val, binded), (int)binded.Value) - { - BoundScrollDirection = binded; - BoundScrollDirection.ValueChanged += OnBindableValueChanged; - } - - /// - /// Is called when the UI is changed. - /// - /// - /// - /// - private static void OnChange(string val, Bindable binded) => binded.Value = (ScrollDirection)Enum.Parse(typeof(ScrollDirection), val); - - /// - /// - /// - private static List ScrollDirectionToStringList() => Enum.GetNames(typeof(ScrollDirection)).ToList(); - - /// - /// - /// - public override void Destroy() - { - // ReSharper disable once DelegateSubtraction - BoundScrollDirection.ValueChanged -= OnBindableValueChanged; - base.Destroy(); - } - - /// - /// - /// - /// - private void OnBindableValueChanged(object sender, BindableValueChangedEventArgs e) => Selector.SelectIndex((int)e.Value); - } -} diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsSlider.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsSlider.cs deleted file mode 100644 index 7366c47a0f..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsSlider.cs +++ /dev/null @@ -1,86 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using System; -using Microsoft.Xna.Framework; -using Quaver.Shared.Assets; -using Wobble.Bindables; -using Wobble.Graphics; -using Wobble.Graphics.Sprites; -using Wobble.Graphics.UI.Form; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public class SettingsSlider : SettingsItem - { - /// - /// The value that the slider is binded to. - /// - protected BindableInt Bindable { get; } - - /// - /// Displays the value of the bindable - /// - protected SpriteText Value { get; } - - /// - /// Converts the bindable value to string for displaying. - /// - private Func Display { get; } - - /// - /// - /// - /// - /// - /// - public SettingsSlider(SettingsDialog dialog, string name, BindableInt bindable, Func display = null) : base(dialog, name) - { - Display = display ?? (x => x.ToString()); - Bindable = bindable; - bindable.ValueChanged += OnValueChanged; - - Value = new SpriteText(Fonts.Exo2Medium, Display(bindable.Value), 13) - { - Parent = this, - Alignment = Alignment.MidRight, - X = -30, - UsePreviousSpriteBatchOptions = true - }; - - var slider = new Slider(bindable, Vector2.One, FontAwesome.Get(FontAwesomeIcon.fa_circle)) - { - Parent = this, - Alignment = Alignment.MidRight, - X = -110, - UsePreviousSpriteBatchOptions = true, - Width = 330, - Height = 2, - ProgressBall = - { - UsePreviousSpriteBatchOptions = true - } - }; - } - - /// - /// - /// - public override void Destroy() - { - // ReSharper disable once DelegateSubtraction - Bindable.ValueChanged -= OnValueChanged; - - base.Destroy(); - } - /// - /// - /// - /// - private void OnValueChanged(object sender, BindableValueChangedEventArgs e) => Value.Text = Display(e.Value); - } -} diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsSliderAudioBufferLength.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsSliderAudioBufferLength.cs deleted file mode 100644 index 827de65b80..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsSliderAudioBufferLength.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using Wobble.Bindables; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public class SettingsSliderAudioBufferLength : SettingsSlider - { - /// - /// The device period bindable. - /// - private BindableInt DevicePeriod { get; } - - /// - /// Converts the bound value and the device period value into a string for display. - /// - private Func Display { get; } - - public SettingsSliderAudioBufferLength(SettingsDialog dialog, string name, BindableInt bindable, - BindableInt devicePeriod, Func display) : base(dialog, name, bindable, x => display(x, devicePeriod.Value)) - { - DevicePeriod = devicePeriod; - Display = display; - - DevicePeriod.ValueChanged += OnDevicePeriodChanged; - } - - public override void Destroy() - { - // ReSharper disable once DelegateSubtraction - DevicePeriod.ValueChanged -= OnDevicePeriodChanged; - - base.Destroy(); - } - - private void OnDevicePeriodChanged(object sender, BindableValueChangedEventArgs e) => - Value.Text = Display(Bindable.Value, e.Value); - } -} \ No newline at end of file diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsUploadToWorkshop.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsUploadToWorkshop.cs deleted file mode 100644 index e57494fd7f..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsUploadToWorkshop.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Quaver.Shared.Assets; -using Quaver.Shared.Config; -using Quaver.Shared.Graphics; -using Quaver.Shared.Graphics.Notifications; -using Quaver.Shared.Online; -using Quaver.Shared.Screens.Menu.UI.Navigation.User; -using Quaver.Shared.Skinning; -using Wobble.Graphics; -using Wobble.Graphics.UI.Dialogs; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public class SettingUploadToWorkshop : SettingsItem - { - /// - /// - private BorderedTextButton UploadButton { get; set; } - - /// - /// - /// - /// - /// - public SettingUploadToWorkshop(SettingsDialog dialog, string name) : base(dialog, name) => CreateUploadButton(); - - /// - /// - private void CreateUploadButton() - { - UploadButton = new BorderedTextButton("Upload", Colors.MainAccent) - { - Parent = this, - X = -50, - Alignment = Alignment.MidRight, - Height = 30, - Width = 225, - Text = - { - Font = Fonts.SourceSansProSemiBold, - FontSize = 12 - } - }; - - UploadButton.Clicked += (o, e) => - { - if (string.IsNullOrEmpty(ConfigManager.Skin.Value)) - { - NotificationManager.Show(NotificationLevel.Warning, "You currently do not have a selected custom skin!"); - return; - } - - var skin = new SteamWorkshopItem(ConfigManager.Skin.Value, SkinManager.Skin.Dir.Replace("\\", "/")); - - if (skin.HasUploaded) - return; - - DialogManager.Show(new UploadWorkshopSkinDialog(skin)); - }; - } - } -} \ No newline at end of file diff --git a/Quaver.Shared/Screens/Settings/Elements/SettingsWorkshopSkin.cs b/Quaver.Shared/Screens/Settings/Elements/SettingsWorkshopSkin.cs deleted file mode 100644 index 3c269993bb..0000000000 --- a/Quaver.Shared/Screens/Settings/Elements/SettingsWorkshopSkin.cs +++ /dev/null @@ -1,97 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using IniFileParser; -using Quaver.Shared.Config; -using Quaver.Shared.Skinning; - -namespace Quaver.Shared.Screens.Settings.Elements -{ - public class SettingsWorkshopSkin : SettingsHorizontalSelector - { - /// - /// - /// - /// - /// - public SettingsWorkshopSkin(SettingsDialog dialog, string name) - : base(dialog, name, GetCustomSkinList(), (val, index) => OnChange(dialog, val, index), GetSelectedIndex()) - { - } - - /// - /// - /// - private static List GetCustomSkinList() - { - var skins = new List { "None" }; - - var steamWorkshopSkins = Directory.GetDirectories(ConfigManager.SteamWorkshopDirectory.Value); - - foreach (var directory in steamWorkshopSkins) - { - if (File.Exists($"{directory}/skin.ini")) - { - var data = new IniFileParser.IniFileParser(new ConcatenateDuplicatedKeysIniDataParser()) - .ReadFile($"{directory}/skin.ini")["General"]; - - if (data["Name"] != null) - skins.Add($"{data["Name"]} ({new DirectoryInfo(directory).Name})"); - } - else - skins.Add($"({new DirectoryInfo(directory).Name})"); - } - - return skins; - } - - /// - /// - /// - /// - /// - private static void OnChange(SettingsDialog dialog, string val, int index) - { - val = new DirectoryInfo(val).Name; - var skin = ConfigManager.Skin.Value; - - switch (val) - { - // Check if the user already has the default skin enabled and switched back to it. - // User wants to choose the default skin - case "None" when string.IsNullOrEmpty(skin): - SkinManager.NewWorkshopSkin = null; - break; - // User is selecting a custom skin - case "None" when !string.IsNullOrEmpty(skin): - SkinManager.NewWorkshopSkin = ""; - break; - default: - if (val != skin) - SkinManager.NewWorkshopSkin = val; - break; - } - } - - /// - /// Finds the index of the selected skin - /// - /// - private static int GetSelectedIndex() - { - var skins = GetCustomSkinList(); - var skin = ConfigManager.Skin.Value; - - var index = string.IsNullOrEmpty(skin) ? 0 : skins.FindIndex(x => x == skin); - return Math.Clamp(index, 0, int.MaxValue); - } - } -} diff --git a/Quaver.Shared/Screens/Settings/SettingsDialog.cs b/Quaver.Shared/Screens/Settings/SettingsDialog.cs deleted file mode 100644 index 094f80e498..0000000000 --- a/Quaver.Shared/Screens/Settings/SettingsDialog.cs +++ /dev/null @@ -1,509 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using ManagedBass; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Input; -using Quaver.Shared.Assets; -using Quaver.Shared.Config; -using Quaver.Shared.Graphics; -using Quaver.Shared.Graphics.Backgrounds; -using Quaver.Shared.Graphics.Dialogs; -using Quaver.Shared.Graphics.Notifications; -using Quaver.Shared.Graphics.Transitions; -using Quaver.Shared.Helpers; -using Quaver.Shared.Scheduling; -using Quaver.Shared.Screens.Menu.UI.Navigation.User; -using Quaver.Shared.Screens.Settings.Elements; -using Quaver.Shared.Skinning; -using Wobble; -using Wobble.Bindables; -using Wobble.Graphics; -using Wobble.Graphics.Animations; -using Wobble.Graphics.Sprites; -using Wobble.Graphics.UI.Buttons; -using Wobble.Graphics.UI.Dialogs; -using Wobble.Input; -using Wobble.Logging; -using Wobble.Window; - -namespace Quaver.Shared.Screens.Settings -{ - public class SettingsDialog : DialogScreen - { - /// - /// The entire container for the settings menu - /// - public Sprite ContentContainer { get; private set; } - - /// - /// - public Sprite HeaderContainer { get; private set; } - - /// - /// - public Sprite FooterContainer { get; private set; } - - /// - /// - public Sprite DividerLine { get; private set; } - - /// - /// The button to save changes. - /// - private BorderedTextButton ApplyButton { get; set; } - - /// - /// The button to cancel existing changes - /// - private BorderedTextButton CloseButton { get; set; } - - /// - /// The list of available settings sections. - /// - public List Sections { get; private set; } - - /// - /// The currently selected options section. - /// - public SettingsSection SelectedSection { get; private set; } - - /// - /// A newly queued default skin, if the user chooses to change it. - /// - public DefaultSkins NewQueuedDefaultSkin { get; set; } - - /// - /// If the user has changed their resolution and it needs to change when they press OK. - /// - public Point NewQueuedScreenResolution { get; set; } - - /// - /// If true, the dialog won't close if the user presses escape. - /// This is used for when keybinds are changed (and the user wants to change it to escape). - /// - public bool PreventExitOnEscapeKeybindPress { get; set; } - - /// - /// - /// - public SettingsDialog() : base(0) - { - // Important. Make sure sure the default values that are sent in config if the values - // are non nullable. - NewQueuedDefaultSkin = ConfigManager.DefaultSkin.Value; - NewQueuedScreenResolution = new Point(ConfigManager.WindowWidth.Value, ConfigManager.WindowHeight.Value); - - Animations.Add(new Animation(AnimationProperty.Alpha, Easing.OutQuint, Alpha, 0.65f, 300)); - CreateContent(); - } - - /// - /// - /// - public sealed override void CreateContent() - { - CreateContentContainer(); - CreateHeader(); - CreateFooter(); - CreateDividerLine(); - CreateSections(); - } - - /// - /// - /// - /// - public override void Update(GameTime gameTime) - { - SkinManager.HandleSkinReloading(); - base.Update(gameTime); - } - - /// - /// - /// - /// - public override void HandleInput(GameTime gameTime) - { - if (SkinManager.TimeSkinReloadRequested != 0 || PreventExitOnEscapeKeybindPress) - return; - - if (KeyboardManager.IsUniqueKeyPress(Keys.Escape)) - DialogManager.Dismiss(this); - - if (KeyboardManager.IsUniqueKeyPress(Keys.Tab)) - { - if (KeyboardManager.CurrentState.IsKeyDown(Keys.LeftControl) || - KeyboardManager.CurrentState.IsKeyDown(Keys.RightControl)) - { - var index = Sections.FindIndex(x => x == SelectedSection); - SwitchSelected(index == 0 ? Sections.Last() : Sections[index - 1]); - } - else - { - var index = Sections.FindIndex(x => x == SelectedSection); - SwitchSelected(index == Sections.Count - 1 ? Sections.First() : Sections[index + 1]); - } - } - } - - /// - /// - private void CreateContentContainer() => ContentContainer = new Sprite - { - Parent = Container, - Alignment = Alignment.MidCenter, - Size = new ScalableVector2(1200, 620), - Tint = ColorHelper.HexToColor($"#414345"), - Alpha = 1f - }; - - /// - /// - private void CreateHeader() - { - HeaderContainer = new Sprite - { - Parent = ContentContainer, - Size = new ScalableVector2(ContentContainer.Width, 45), - Tint = ColorHelper.HexToColor($"#212121") - }; - - var headerFlag = new Sprite() - { - Parent = HeaderContainer, - Size = new ScalableVector2(5, HeaderContainer.Height), - Tint = Color.LightGray, - Alpha = 0 - }; - - var headerText = new SpriteText(Fonts.Exo2Medium, "Options Menu", 16) - { - Parent = HeaderContainer, - Alignment = Alignment.MidLeft, - X = headerFlag.X + 15 - }; - - var exitButton = new ImageButton(FontAwesome.Get(FontAwesomeIcon.fa_times), (sender, args) => DialogManager.Dismiss()) - { - Parent = HeaderContainer, - Alignment = Alignment.MidRight, - Size = new ScalableVector2(25, 25) - }; - - exitButton.X -= exitButton.Width / 2f + 5; - } - - /// - /// - private void CreateFooter() - { - FooterContainer = new Sprite() - { - Parent = ContentContainer, - Size = new ScalableVector2(ContentContainer.Width, 50), - Tint = ColorHelper.HexToColor("#212121"), - Alignment = Alignment.BotLeft, - Y = 1 - }; - - CreateApplyButton(); - CreateCloseButton(); - } - - /// - /// Creates the button to save changes - /// - private void CreateApplyButton() - { - ApplyButton = new BorderedTextButton("Apply", Color.LimeGreen) - { - Parent = FooterContainer, - Alignment = Alignment.MidRight, - X = -20 - }; - - ApplyButton.Clicked += (o, e) => - { - // Determines whether we'll be dismissing the dialog if no changes have been made. - var dismissDalog = true; - - // Handle skin reloads - if (SkinManager.NewQueuedSkin != null && SkinManager.NewQueuedSkin != ConfigManager.Skin.Value || - SkinManager.NewWorkshopSkin != null && SkinManager.NewWorkshopSkin != ConfigManager.Skin.Value - || NewQueuedDefaultSkin != ConfigManager.DefaultSkin.Value) - { - ConfigManager.Skin.Value = ConfigManager.UseSteamWorkshopSkin.Value ? SkinManager.NewWorkshopSkin : SkinManager.NewQueuedSkin; - ConfigManager.DefaultSkin.Value = NewQueuedDefaultSkin; - - Transitioner.FadeIn(); - SkinManager.TimeSkinReloadRequested = GameBase.Game.TimeRunning; - dismissDalog = false; - } - - // Handle screen resolution changes. - if (NewQueuedScreenResolution.X != ConfigManager.WindowWidth.Value && - NewQueuedScreenResolution.Y != ConfigManager.WindowHeight.Value) - { - ConfigManager.WindowWidth.Value = NewQueuedScreenResolution.X; - ConfigManager.WindowHeight.Value = NewQueuedScreenResolution.Y; - WindowManager.ChangeScreenResolution(NewQueuedScreenResolution); - - dismissDalog = false; - } - - // Handle device period and buffer length changes. - if (Environment.OSVersion.Platform == PlatformID.Unix) - { - if (ConfigManager.DevicePeriod.Value != Bass.GetConfig(Configuration.DevicePeriod) - || ConfigManager.DeviceBufferLengthMultiplier.Value != - Bass.GetConfig(Configuration.DeviceBufferLength) / Bass.GetConfig(Configuration.DevicePeriod)) - { - DialogManager.Show(new ConfirmCancelDialog( - "The game must be restarted to apply the new audio device properties. Exit the game now?", - (sender, args) => - { - // Make sure the config is saved. - Task.Run(ConfigManager.WriteConfigFileAsync).Wait(); - - var game = GameBase.Game as QuaverGame; - game?.Exit(); - })); - } - } - - if (dismissDalog) - DialogManager.Dismiss(this); - }; - } - - /// - /// Creates the button to cancel all changes - /// - private void CreateCloseButton() - { - CloseButton = new BorderedTextButton("Close", Color.Crimson) - { - Parent = FooterContainer, - Alignment = Alignment.MidRight, - X = ApplyButton.X - ApplyButton.Width - 20 - }; - - CloseButton.Clicked += (o, e) => DialogManager.Dismiss(this); - } - /// - /// - private void CreateDividerLine() => DividerLine = new Sprite - { - Parent = ContentContainer, - Size = new ScalableVector2(1, ContentContainer.Height - HeaderContainer.Height - FooterContainer.Height - 20), - X = 230, - Alignment = Alignment.MidLeft, - Alpha = 0.75f - }; - - /// - /// - private void CreateSections() - { - Sections = new List - { - // Video - new SettingsSection(this, FontAwesome.Get(FontAwesomeIcon.fa_desktop_monitor), "Video", new List - { - new SettingsResolution(this, "Screen Resolution"), - new SettingsBool(this, "Fullscreen", ConfigManager.WindowFullScreen), - new SettingsFpsLimiter(this), - new SettingsBool(this, "Display FPS Counter", ConfigManager.FpsCounter) - }), - // Audio - new SettingsSection(this, FontAwesome.Get(FontAwesomeIcon.fa_volume_up_interface_symbol), "Audio", new List - { - new SettingsSlider(this, "Master Volume", ConfigManager.VolumeGlobal, x => $"{x}%"), - new SettingsSlider(this, "Music Volume", ConfigManager.VolumeMusic, x => $"{x}%"), - new SettingsSlider(this, "Effect Volume", ConfigManager.VolumeEffect, x => $"{x}%"), - new SettingsBool(this, "Enable Hitsounds", ConfigManager.EnableHitsounds), - new SettingsBool(this, "Enable Keysounds", ConfigManager.EnableKeysounds), - new SettingsBool(this, "Pitch Audio With Rate", ConfigManager.Pitched), - new SettingsSlider(this, "Global Audio Offset", ConfigManager.GlobalAudioOffset, x => $"{x} ms"), - new SettingsCalibrateOffset(this, "Calibrate Offset"), - new SettingsSlider(this, "Audio Device Period", ConfigManager.DevicePeriod, x => $"{x} ms"), - new SettingsSliderAudioBufferLength(this, "Audio Device Buffer Length", - ConfigManager.DeviceBufferLengthMultiplier, - ConfigManager.DevicePeriod, - (multiplier, period) => $"{multiplier * period} ms") - }), - // Gameplay - new SettingsSection(this, FontAwesome.Get(FontAwesomeIcon.fa_gamepad_console), "Gameplay", new List - { - new SettingsSlider(this, "Background Brightness", ConfigManager.BackgroundBrightness, x => $"{x}%"), - new SettingsSlider(this, "Scroll Speed (4 Keys)", ConfigManager.ScrollSpeed4K), - new SettingsSlider(this, "Scroll Speed (7 Keys)", ConfigManager.ScrollSpeed7K), - new SettingsScrollDirection(this, "Scroll Direction 4K", ConfigManager.ScrollDirection4K), - new SettingsScrollDirection(this, "Scroll Direction 7K", ConfigManager.ScrollDirection7K), - new SettingsBool(this, "Display Timing Lines", ConfigManager.DisplayTimingLines), - new SettingsBool(this, "Display Song Time Progress", ConfigManager.DisplaySongTimeProgress), - new SettingsBool(this, "Display Song Time Progress Numbers", ConfigManager.DisplaySongTimeProgressNumbers), - new SettingsBool(this, "Display Judgement Counter", ConfigManager.DisplayJudgementCounter), - new SettingsBool(this, "Enable Combo Alerts", ConfigManager.DisplayComboAlerts), - new SettingsBool(this, "Display Scoreboard", ConfigManager.ScoreboardVisible), - new SettingsBool(this, "Tap to Pause", ConfigManager.TapToPause), - new SettingsBool(this, "Tap to Restart", ConfigManager.TapToRestart), - new SettingsBool(this, "Skip Results Screen After Quitting", ConfigManager.SkipResultsScreenAfterQuit), - new SettingsSlider(this, "Top Lane Cover Height", ConfigManager.LaneCoverTopHeight, x => $"{x}%"), - new SettingsSlider(this, "Bottom Lane Cover Height", ConfigManager.LaneCoverBottomHeight, x => $"{x}%"), - new SettingsBool(this, "Top Lane Cover", ConfigManager.LaneCoverTop), - new SettingsBool(this, "Bottom Lane Cover", ConfigManager.LaneCoverBottom), - new SettingsBool(this, "Display UI Elements Over Lane Covers", ConfigManager.UIElementsOverLaneCover), - new SettingsBool(this, "Enable Battle Royale Background Flashing", ConfigManager.EnableBattleRoyaleBackgroundFlashing), - new SettingsBool(this, "Enable Battle Royale Alerts", ConfigManager.EnableBattleRoyaleAlerts), - new SettingsBool(this, "Display Unbeatable Scores", ConfigManager.DisplayUnbeatableScoresDuringGameplay), - new SettingsBool(this, "Show Spectators", ConfigManager.ShowSpectators) - }), - // Editor - new SettingsSection(this, FontAwesome.Get(FontAwesomeIcon.fa_beaker), "Editor", new List() - { - new SettingsEditorSnapColors(this), - new SettingsBool(this, "Enable Hitsounds", ConfigManager.EditorEnableHitsounds), - new SettingsBool(this, "Enable Keysounds", ConfigManager.EditorEnableKeysounds), - new SettingsBool(this, "Enable Metronome", ConfigManager.EditorPlayMetronome), - new SettingsBool(this, "Play Metronome Half-Beats", ConfigManager.EditorMetronomePlayHalfBeats), - new SettingsBool(this, "Show Lane Divider Lines", ConfigManager.EditorShowLaneDividerLines), - new SettingsBool(this, "Only Show Measure Lines", ConfigManager.EditorOnlyShowMeasureLines), - new SettingsBool(this, "Anchor HitObjects At Midpoint", ConfigManager.EditorHitObjectsMidpointAnchored), - }), - // Skinning - new SettingsSection(this, FontAwesome.Get(FontAwesomeIcon.fa_pencil), "Skin", new List() - { - new SettingsCustomSkin(this, "Custom Skin"), - new SettingsWorkshopSkin(this, "Steam Workshop Skin"), - new SettingsDefaultSkin(this, "Default Skin"), - new SettingsBool(this, "Use Steam Workshop Skin", ConfigManager.UseSteamWorkshopSkin), - new SettingsExportSkin(this, "Export Custom Skin"), - new SettingUploadToWorkshop(this, "Upload Custom Skin To Workshop") - }), - // Input - new SettingsSection(this, FontAwesome.Get(FontAwesomeIcon.fa_keyboard), "Input", new List - { - new SettingsKeybindMultiple(this, "Gameplay Layout (4 Keys)", new List> - { - ConfigManager.KeyMania4K1, - ConfigManager.KeyMania4K2, - ConfigManager.KeyMania4K3, - ConfigManager.KeyMania4K4 - }), - new SettingsKeybindMultiple(this, "Gameplay Layout (7 Keys)", new List> - { - ConfigManager.KeyMania7K1, - ConfigManager.KeyMania7K2, - ConfigManager.KeyMania7K3, - ConfigManager.KeyMania7K4, - ConfigManager.KeyMania7K5, - ConfigManager.KeyMania7K6, - ConfigManager.KeyMania7K7 - }), - new SettingsKeybindMultiple(this, "Co-op 2 Player Layout (4 Keys)", new List> - { - ConfigManager.KeyCoop2P4K1, - ConfigManager.KeyCoop2P4K2, - ConfigManager.KeyCoop2P4K3, - ConfigManager.KeyCoop2P4K4, - }), - new SettingsKeybindMultiple(this, "Co-op 2 Player Layout (7 Keys)", new List> - { - ConfigManager.KeyCoop2P7K1, - ConfigManager.KeyCoop2P7K2, - ConfigManager.KeyCoop2P7K3, - ConfigManager.KeyCoop2P7K4, - ConfigManager.KeyCoop2P7K5, - ConfigManager.KeyCoop2P7K6, - ConfigManager.KeyCoop2P7K7, - }), - // new SettingsKeybind(this, "Pause", ConfigManager.KeyPause), - // new SettingsKeybind(this, "Skip Intro", ConfigManager.KeySkipIntro), - new SettingsKeybind(this, "Restart Map", ConfigManager.KeyRestartMap), - new SettingsKeybind(this, "Decrease Scroll Speed", ConfigManager.KeyDecreaseScrollSpeed), - new SettingsKeybind(this, "Increase Scroll Speed", ConfigManager.KeyIncreaseScrollSpeed), - new SettingsKeybind(this, "Decrease Map Offset", ConfigManager.KeyDecreaseMapOffset), - new SettingsKeybind(this, "Increase Map Offset", ConfigManager.KeyIncreaseMapOffset), - new SettingsKeybind(this, "Toggle Scoreboard Visibility", ConfigManager.KeyScoreboardVisible), - new SettingsKeybind(this, "Quick Exit", ConfigManager.KeyQuickExit), - new SettingsKeybind(this, "Toggle Chat Overlay", ConfigManager.KeyToggleOverlay), - new SettingsKeybind(this, "Interface - Select", ConfigManager.KeyNavigateSelect), - new SettingsKeybind(this, "Interface - Back", ConfigManager.KeyNavigateBack), - new SettingsKeybind(this, "Interface - Navigate Left", ConfigManager.KeyNavigateLeft), - new SettingsKeybind(this, "Interface - Navigate Right", ConfigManager.KeyNavigateRight), - new SettingsKeybind(this, "Interface - Navigate Up", ConfigManager.KeyNavigateUp), - new SettingsKeybind(this, "Interface - Navigate Down", ConfigManager.KeyNavigateDown), - new SettingsKeybind(this, "Editor - Pause/Play Track", ConfigManager.KeyEditorPausePlay), - new SettingsKeybind(this, "Editor - Decrease Audio Playback Rate", ConfigManager.KeyEditorDecreaseAudioRate), - new SettingsKeybind(this, "Editor - Increase Audio Playback Rate", ConfigManager.KeyEditorIncreaseAudioRate) - }), - // Misc - new SettingsSection(this, FontAwesome.Get(FontAwesomeIcon.fa_question_sign), "Miscellaneous", new List - { - new SettingsBool(this, "Automatically Login To The Server", ConfigManager.AutoLoginToServer), - new SettingsBool(this, "Load Maps From Other Games", ConfigManager.AutoLoadOsuBeatmaps), - new SettingsBool(this, "Display Menu Audio Visualizer", ConfigManager.DisplayMenuAudioVisualizer), - new SettingsBool(this, "Display Failed Local Scores", ConfigManager.DisplayFailedLocalScores), - new SettingsBool(this, "Display Online Friends Notification", ConfigManager.DisplayFriendOnlineNotifications), - new SettingsBool(this, "Display Song Request Notifications", ConfigManager.DisplaySongRequestNotifications) - }) - }; - - SelectedSection = Sections.First(); - AlignSectionButtons(); - } - - /// - /// Sets the position of the section buttons - /// - private void AlignSectionButtons() - { - for (var i = 0; i < Sections.Count; i++) - { - var button = Sections[i].Button; - button.Parent = ContentContainer; - - button.X = 10; - button.Y = HeaderContainer.Height + 15 + button.Height * i + 15 * i; - - if (Sections[i] == SelectedSection) - { - button.DisplayAsSelected(); - Sections[i].Container.Parent = ContentContainer; - } - else - { - button.DisplayAsDeselected(); - Sections[i].Container.Visible = false; - } - } - } - - /// - /// - public void SwitchSelected(SettingsSection section) - { - SelectedSection.Button.DisplayAsDeselected(); - SelectedSection.Container.Visible = false; - SelectedSection.Container.Parent = null; - - SelectedSection = section; - SelectedSection.Button.DisplayAsSelected(); - SelectedSection.Container.Visible = true; - SelectedSection.Container.Parent = ContentContainer; - - Logger.Debug($"Switched to options section: {section.Name}", LogType.Runtime); - } - } -} diff --git a/Quaver.Shared/Screens/Settings/SettingsSection.cs b/Quaver.Shared/Screens/Settings/SettingsSection.cs deleted file mode 100644 index 3afc46ed72..0000000000 --- a/Quaver.Shared/Screens/Settings/SettingsSection.cs +++ /dev/null @@ -1,116 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using System.Collections.Generic; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using Quaver.Shared.Helpers; -using Wobble.Graphics; -using Wobble.Graphics.Animations; -using Wobble.Graphics.Sprites; - -namespace Quaver.Shared.Screens.Settings -{ - public class SettingsSection - { - /// - /// Reference to the parent dialog screen - /// - private SettingsDialog Dialog { get; } - - /// - /// The specified icon for the section - /// - public Texture2D Icon { get; } - - /// - /// The name of the given options section - /// - public string Name { get; } - - /// - /// The button to activate the section - /// - public SettingsSectionButton Button { get; private set; } - - /// - /// The container that holds all of the section's items. - /// - public ScrollContainer Container { get; private set; } - - /// - /// All of the options items displayed in the section. - /// - public List Items { get; } - - /// - /// - /// - /// - /// - /// - public SettingsSection(SettingsDialog dialog, Texture2D icon, string name, List items) - { - Items = items; - Dialog = dialog; - Icon = icon; - Name = name; - - CreateSectionButton(); - CreateContainer(); - } - - /// - /// Creates the button the activate the options section. - /// - private void CreateSectionButton() => Button = new SettingsSectionButton(Dialog, this, Icon, Name); - - /// - /// Creates the ScrollContainer that holds all the settings items. - /// - private void CreateContainer() - { - var size = new ScalableVector2(Dialog.ContentContainer.Width - Dialog.DividerLine.X - 20, Dialog.DividerLine.Height); - - Container = new ScrollContainer(size, size) - { - Alignment = Alignment.MidLeft, - X = Dialog.DividerLine.X + 10, - Alpha = 0, - InputEnabled = true, - DestroyIfParentIsNull = false - }; - - Container.Scrollbar.Tint = Color.White; - Container.Scrollbar.Width = 5; - Container.Scrollbar.X += 8; - Container.ScrollSpeed = 150; - Container.EasingType = Easing.OutQuint; - Container.TimeToCompleteScroll = 1500; - - var totalHeight = 0f; - - for (var i = 0; i < Items.Count; i++) - { - var item = Items[i]; - Container.AddContainedDrawable(item); - totalHeight += item.Height; - - if (i == 0) - continue; - - const int spacing = 10; - - item.Y = Items[i - 1].Y + Items[i - 1].Height + spacing; - totalHeight += spacing; - } - - if (Container.ContentContainer.Height < totalHeight) - Container.ContentContainer.Height = totalHeight; - } - } -} diff --git a/Quaver.Shared/Screens/Settings/SettingsSectionButton.cs b/Quaver.Shared/Screens/Settings/SettingsSectionButton.cs deleted file mode 100644 index 878ec05241..0000000000 --- a/Quaver.Shared/Screens/Settings/SettingsSectionButton.cs +++ /dev/null @@ -1,139 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using System; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using Quaver.Shared.Assets; -using Quaver.Shared.Graphics; -using Quaver.Shared.Graphics.Online.Playercard; -using Quaver.Shared.Helpers; -using Wobble.Graphics; -using Wobble.Graphics.Animations; -using Wobble.Graphics.Sprites; -using Wobble.Graphics.UI.Buttons; - -namespace Quaver.Shared.Screens.Settings -{ - public class SettingsSectionButton : Button - { - /// - /// Reference to the parent dialog - /// - private SettingsDialog Dialog { get; } - - /// - /// Reference to the section this button belongs to - /// - private SettingsSection Section { get; } - - /// - /// Determines if the button is selected. - /// - public bool IsSelected { get; private set; } - - /// - /// Displays the icon for the secion - /// - private Sprite Icon { get; } - - /// - /// Displays the text of the button - /// - private SpriteText Text { get; } - - /// - /// For aesthetics when the button is selected - /// - private Sprite Flag { get; } - - /// - /// - /// - /// - /// - /// - /// - public SettingsSectionButton(SettingsDialog dialog, SettingsSection section, Texture2D icon, string name) - { - Dialog = dialog; - Section = section; - Size = new ScalableVector2(206, 35); - Alpha = 0.65f; - - Icon = new Sprite - { - Parent = this, - Image = icon, - Alignment = Alignment.MidCenter, - Size = new ScalableVector2(18, 18) - }; - - Text = new SpriteText(Fonts.Exo2SemiBold, name, 13) - { - Parent = Icon, - Alignment = Alignment.MidLeft, - X = Icon.Width + 4 - }; - - Icon.X -= Icon.Width / 2f + Text.Width / 2f + 2; - - Flag = new Sprite - { - Parent = this, - Size = new ScalableVector2(4, Height), - Tint = Color.Yellow - }; - - Clicked += (o, e) => - { - if (Dialog.SelectedSection == Section) - return; - - Dialog.SwitchSelected(Section); - }; - } - - /// - /// - /// - /// - public override void Update(GameTime gameTime) - { - if (!IsSelected) - { - FadeToColor(IsHovered ? ColorHelper.HexToColor("#1e3c72") : ColorHelper.HexToColor("#1e1e1e"), Easing.Linear, 50); - } - - base.Update(gameTime); - } - - /// - /// Displays the button as selected - /// - public void DisplayAsSelected() - { - IsSelected = true; - Flag.Visible = true; - Tint = ColorHelper.HexToColor("#1e3c72"); - Alpha = 1; - } - - /// - /// Displays the button as deselected - /// - public void DisplayAsDeselected() - { - IsSelected = false; - Flag.Visible = false; - Tint = ColorHelper.HexToColor("#1e1e1e"); - Alpha = 1; - Icon.Tint = Color.White; - Text.Tint = Color.White; - } - } -} From 8c0e39dd63b0f7b21d6d7871120cf559c5c909b6 Mon Sep 17 00:00:00 2001 From: Emik Date: Tue, 9 Apr 2024 01:33:58 +0200 Subject: [PATCH 137/249] Add scrollbar TODO: Prevent editor scrollbar from scrolling while doing it on the beat snap scrollbar. --- .../UI/Footer/BeatSnapRightClickOptions.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Footer/BeatSnapRightClickOptions.cs b/Quaver.Shared/Screens/Edit/UI/Footer/BeatSnapRightClickOptions.cs index 84c26a91e3..141c6e0d44 100644 --- a/Quaver.Shared/Screens/Edit/UI/Footer/BeatSnapRightClickOptions.cs +++ b/Quaver.Shared/Screens/Edit/UI/Footer/BeatSnapRightClickOptions.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Text.RegularExpressions; using Microsoft.Xna.Framework; @@ -8,6 +9,7 @@ using Quaver.Shared.Screens.Edit.Dialogs; using Wobble.Bindables; using Wobble.Graphics; +using Wobble.Graphics.Animations; using Wobble.Graphics.Sprites; using Wobble.Graphics.UI.Dialogs; @@ -32,11 +34,15 @@ public class BeatSnapRightClickOptions : RightClickOptions /// /// public BeatSnapRightClickOptions(BindableInt beatSnap, List availableBeatSnaps) - : base(GetOptions(availableBeatSnaps), new ScalableVector2(200, 40), - 22) + : base(GetOptions(availableBeatSnaps), new ScalableVector2(200, 40), 22, maxHeight: 800) { BeatSnap = beatSnap; AvailableBeatSnaps = availableBeatSnaps; + ItemContainer.Scrollbar.Tint = Color.White; + ItemContainer.Scrollbar.Width = 2; + ItemContainer.EasingType = Easing.OutQuint; + ItemContainer.TimeToCompleteScroll = 1200; + ItemContainer.ScrollSpeed = 220; Items.ForEach(x => { @@ -74,6 +80,14 @@ public BeatSnapRightClickOptions(BindableInt beatSnap, List availableBeatSn BeatSnap.ValueChanged += OnBeatSnapChanged; } + public override void Update(GameTime gameTime) + { + ItemContainer.Scrollbar.Visible = Opened; + ItemContainer.InputEnabled = Opened; + + base.Update(gameTime); + } + /// /// /// From 72d431a763f929c38c0104b6a287acbd7ee5c03e Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 9 Apr 2024 12:09:07 +0800 Subject: [PATCH 138/249] Add API requests for multiplayer game and match information --- .../APIRequestMultiplayerGameInformation.cs | 40 +++++ .../APIRequestMultiplayerMatchInformation.cs | 40 +++++ .../MultiplayerGameInformationResponse.cs | 144 ++++++++++++++++++ .../MultiplayerMatchInformationResponse.cs | 104 +++++++++++++ 4 files changed, 328 insertions(+) create mode 100644 Quaver.Shared/Online/API/Multiplayer/APIRequestMultiplayerGameInformation.cs create mode 100644 Quaver.Shared/Online/API/Multiplayer/APIRequestMultiplayerMatchInformation.cs create mode 100644 Quaver.Shared/Online/API/Multiplayer/MultiplayerGameInformationResponse.cs create mode 100644 Quaver.Shared/Online/API/Multiplayer/MultiplayerMatchInformationResponse.cs diff --git a/Quaver.Shared/Online/API/Multiplayer/APIRequestMultiplayerGameInformation.cs b/Quaver.Shared/Online/API/Multiplayer/APIRequestMultiplayerGameInformation.cs new file mode 100644 index 0000000000..a9a67f0558 --- /dev/null +++ b/Quaver.Shared/Online/API/Multiplayer/APIRequestMultiplayerGameInformation.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Quaver.Server.Client; +using RestSharp; +using Wobble.Logging; + +namespace Quaver.Shared.Online.API.Multiplayer; + +public class APIRequestMultiplayerGameInformation : APIRequest +{ + /// + /// The id of the map to lookup + /// + public int Id { get; } + + /// + /// + /// + public APIRequestMultiplayerGameInformation(int id) => Id = id; + + /// + /// + /// + /// + public override MultiplayerGameInformationResponse ExecuteRequest() + { + var request = new RestRequest($"{APIEndpoint}multiplayer/games/{Id}", Method.GET); + var client = new RestClient(OnlineClient.API_ENDPOINT) { UserAgent = "Quaver" }; + + var response = client.Execute(request); + + Logger.Important(response.Content, LogType.Runtime); + + var json = JObject.Parse(response.Content); + + var responseParsed = JsonConvert.DeserializeObject(json.ToString()); + + return responseParsed; + } +} \ No newline at end of file diff --git a/Quaver.Shared/Online/API/Multiplayer/APIRequestMultiplayerMatchInformation.cs b/Quaver.Shared/Online/API/Multiplayer/APIRequestMultiplayerMatchInformation.cs new file mode 100644 index 0000000000..645434902e --- /dev/null +++ b/Quaver.Shared/Online/API/Multiplayer/APIRequestMultiplayerMatchInformation.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Quaver.Server.Client; +using RestSharp; +using Wobble.Logging; + +namespace Quaver.Shared.Online.API.Multiplayer; + +public class APIRequestMultiplayerMatchInformation : APIRequest +{ + /// + /// The id of the map to lookup + /// + public int Id { get; } + + /// + /// + /// + public APIRequestMultiplayerMatchInformation(int id) => Id = id; + + /// + /// + /// + /// + public override MultiplayerMatchInformationResponse ExecuteRequest() + { + var request = new RestRequest($"{APIEndpoint}multiplayer/match/{Id}", Method.GET); + var client = new RestClient(OnlineClient.API_ENDPOINT) { UserAgent = "Quaver" }; + + var response = client.Execute(request); + + Logger.Important(response.Content, LogType.Runtime); + + var json = JObject.Parse(response.Content); + + var responseParsed = JsonConvert.DeserializeObject(json.ToString()); + + return responseParsed; + } +} \ No newline at end of file diff --git a/Quaver.Shared/Online/API/Multiplayer/MultiplayerGameInformationResponse.cs b/Quaver.Shared/Online/API/Multiplayer/MultiplayerGameInformationResponse.cs new file mode 100644 index 0000000000..eea39f5c7a --- /dev/null +++ b/Quaver.Shared/Online/API/Multiplayer/MultiplayerGameInformationResponse.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Quaver.API.Enums; +using Quaver.Server.Common.Objects.Multiplayer; +using Quaver.Shared.Screens.Gameplay.Rulesets; + +namespace Quaver.Shared.Online.API.Multiplayer; + +public class MultiplayerGameInformationResponse +{ + [JsonProperty("status")] + public int Status { get; set; } + + [JsonProperty("multiplayer_game")] + public MultiplayerGameInformationResponseGame MultiplayerGame { get; set; } + + [JsonProperty("matches")] + public List Matches { get; set; } +} + +public class MultiplayerGameInformationResponseGame +{ + [JsonProperty("id")] + public int Id { get; set; } + + [JsonProperty("unique_id")] + public string UniqueId { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("type")] + public int Type { get; set; } + + [JsonProperty("time_created")] + public DateTime TimeCreated { get; set; } +} + + +public class MultiplayerGameInformationResponseMatch +{ + [JsonProperty("id")] + public int Id { get; set; } + + [JsonProperty("time_played")] + public DateTime TimePlayed { get; set; } + + [JsonProperty("aborted_early")] + public bool AbortedEarly { get; set; } + + [JsonProperty("outcome")] + public MultiplayerGameInformationResponseOutcome Outcome { get; set; } + + [JsonProperty("rules")] + public MultiplayerResponseRules Rules { get; set; } + + [JsonProperty("most_valuable_player")] + public MultiplayerGameInformationResponsePlayer MostValuablePlayer { get; set; } + + [JsonProperty("map")] + public MultiplayerGameInformationResponseMap Map { get; set; } +} + +public class MultiplayerGameInformationResponseOutcome +{ + [JsonProperty("result")] + public int Result { get; set; } + + [JsonProperty("team")] + public int Team { get; set; } +} + +public class MultiplayerResponseRules +{ + [JsonProperty("ruleset")] + public MultiplayerGameRuleset Ruleset { get; set; } + + [JsonProperty("mods")] + public ModIdentifier Mods { get; set; } + + [JsonProperty("mods_string")] + public string ModsString { get; set; } + + [JsonProperty("free_mod_type")] + public int FreeModType { get; set; } + + [JsonProperty("health_type")] + public int HealthType { get; set; } + + [JsonProperty("lives")] + public int Lives { get; set; } +} + +public class MultiplayerGameInformationResponseMap +{ + [JsonProperty("id")] + public int? Id { get; set; } + + [JsonProperty("mapset_id")] + public int? MapsetId { get; set; } + + [JsonProperty("md5")] + public string Md5 { get; set; } + + [JsonProperty("game_mode")] + public GameMode GameMode { get; set; } + + [JsonProperty("ranked_status")] + public RankedStatus RankedStatus { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } +} + +public class MultiplayerGameInformationResponsePlayer +{ + [JsonProperty("id")] + public int Id { get; set; } + + [JsonProperty("username")] + public string Username { get; set; } + + [JsonProperty("country")] + public string Country { get; set; } + + [JsonProperty("avatar_url")] + public string AvatarUrl { get; set; } + + [JsonProperty("score")] + public MultiplayerGameInformationResponseScore Score { get; set; } +} + +public class MultiplayerGameInformationResponseScore +{ + [JsonProperty("team")] + public int Team { get; set; } + + [JsonProperty("performance_rating")] + public double PerformanceRating { get; set; } + + [JsonProperty("accuracy")] + public double Accuracy { get; set; } +} \ No newline at end of file diff --git a/Quaver.Shared/Online/API/Multiplayer/MultiplayerMatchInformationResponse.cs b/Quaver.Shared/Online/API/Multiplayer/MultiplayerMatchInformationResponse.cs new file mode 100644 index 0000000000..3310d78a61 --- /dev/null +++ b/Quaver.Shared/Online/API/Multiplayer/MultiplayerMatchInformationResponse.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Quaver.API.Enums; + +namespace Quaver.Shared.Online.API.Multiplayer; + +public class MultiplayerMatchInformationResponseMap +{ + [JsonProperty("id")] public int? Id { get; set; } + + [JsonProperty("mapset_id")] public int? MapsetId { get; set; } + + [JsonProperty("md5")] public string Md5 { get; set; } + + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("game_mode")] public GameMode GameMode { get; set; } +} + +public class MultiplayerMatchInformationResponsePlayer +{ + [JsonProperty("id")] public int Id { get; set; } + + [JsonProperty("username")] public string Username { get; set; } + + [JsonProperty("country")] public string Country { get; set; } + + [JsonProperty("avatar_url")] public string AvatarUrl { get; set; } +} + +public class MultiplayerMatchInformationResponseScore +{ + [JsonProperty("team")] public int Team { get; set; } + + [JsonProperty("win_result")] public int WinResult { get; set; } + + [JsonProperty("has_failed")] public bool HasFailed { get; set; } + + [JsonProperty("mods")] public ModIdentifier Mods { get; set; } + + [JsonProperty("mods_string")] public string ModsString { get; set; } + + [JsonProperty("full_combo")] public bool FullCombo { get; set; } + + [JsonProperty("lives_left")] public int LivesLeft { get; set; } + + [JsonProperty("performance_rating")] public double PerformanceRating { get; set; } + + [JsonProperty("accuracy")] public double Accuracy { get; set; } + + [JsonProperty("score")] public int Score { get; set; } + + [JsonProperty("grade")] public string Grade { get; set; } + + [JsonProperty("max_combo")] public int MaxCombo { get; set; } + + [JsonProperty("count_marv")] public int CountMarv { get; set; } + + [JsonProperty("count_perf")] public int CountPerf { get; set; } + + [JsonProperty("count_great")] public int CountGreat { get; set; } + + [JsonProperty("count_good")] public int CountGood { get; set; } + + [JsonProperty("count_okay")] public int CountOkay { get; set; } + + [JsonProperty("count_miss")] public int CountMiss { get; set; } + + [JsonProperty("battle_royale_rank")] public int? BattleRoyaleRank { get; set; } +} + +public class MultiplayerMatchInformationResponsePlayerScore +{ + [JsonProperty("player")] public MultiplayerMatchInformationResponsePlayer Player { get; set; } + + [JsonProperty("score")] public MultiplayerMatchInformationResponseScore Score { get; set; } +} + +public class MultiplayerMatchInformationResponseMatch +{ + [JsonProperty("id")] public int Id { get; set; } + + [JsonProperty("time_played")] public DateTime TimePlayed { get; set; } + + [JsonProperty("aborted_early")] public bool AbortedEarly { get; set; } + + [JsonProperty("outcome")] public MultiplayerGameInformationResponseOutcome Outcome { get; set; } + + [JsonProperty("rules")] public MultiplayerResponseRules Rules { get; set; } + + [JsonProperty("map")] public MultiplayerMatchInformationResponseMap Map { get; set; } + + [JsonProperty("scores")] public List Scores { get; set; } +} + +public class MultiplayerMatchInformationResponse +{ + [JsonProperty("status")] public int Status { get; set; } + + [JsonProperty("game")] public MultiplayerGameInformationResponseGame Game { get; set; } + + [JsonProperty("match")] public MultiplayerMatchInformationResponseMatch Match { get; set; } +} \ No newline at end of file From fdd6f386b1e77e81fffed45e949b14d92cc500ca Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 9 Apr 2024 12:09:37 +0800 Subject: [PATCH 139/249] Load scores from api --- .../Table/ResultsMultiplayerTable.cs | 79 ++++++++++++++++--- 1 file changed, 70 insertions(+), 9 deletions(-) diff --git a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs index 08a6ec3e44..b59812774d 100644 --- a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs +++ b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs @@ -1,22 +1,28 @@ using System; using System.Collections.Generic; using System.Linq; -using MoreLinq; +using System.Threading; +using Quaver.API.Enums; using Quaver.API.Maps.Processors.Rating; using Quaver.API.Maps.Processors.Scoring; +using Quaver.API.Maps.Processors.Scoring.Multiplayer; using Quaver.Server.Common.Objects.Multiplayer; using Quaver.Shared.Assets; using Quaver.Shared.Database.Maps; using Quaver.Shared.Helpers; +using Quaver.Shared.Online.API.Multiplayer; using Quaver.Shared.Screens.Results.UI.Header.Contents.Tabs; using Quaver.Shared.Screens.Results.UI.Tabs.Multiplayer.Table.Scrolling; +using Quaver.Shared.Screens.Selection.UI.Leaderboard; using Quaver.Shared.Skinning; using Wobble.Assets; using Wobble.Bindables; using Wobble.Graphics; using Wobble.Graphics.Sprites; using Wobble.Graphics.Sprites.Text; +using Wobble.Logging; using Wobble.Managers; +using Wobble.Scheduling; namespace Quaver.Shared.Screens.Results.UI.Tabs.Multiplayer.Table { @@ -55,6 +61,8 @@ public class ResultsMultiplayerTable : Sprite /// /// private ResultsMultiplayerScrollContainer ScrollContainer { get; set; } + + private TaskHandler GetScoresTask { get; set; } /// /// @@ -73,6 +81,7 @@ public ResultsMultiplayerTable(Map map, Bindable processor, Mult Team2Players = team2; Width = ResultsScreenView.CONTENT_WIDTH - ResultsTabContainer.PADDING_X; + GetScoresTask = new TaskHandler(GetMatchScores); switch (Game.Ruleset) { @@ -172,14 +181,7 @@ private void CreateColumnHeaders() /// private void CreateScrollContainer() { - var processors = GetOrderedUserList(); - - ScrollContainer = new ResultsMultiplayerScrollContainer(new ScalableVector2(Width, Height - HeaderContainer.Height), - processors, Game, Headers, Map) - { - Parent = this, - Y = HeaderContainer.Height - }; + GetScoresTask.Run(0); } /// @@ -187,6 +189,7 @@ private void CreateScrollContainer() /// private List GetOrderedUserList() { + var players = new List(Team1Players); players = players.Concat(Team2Players).ToList(); @@ -209,6 +212,64 @@ private List GetOrderedUserList() return players; } + private int GetMatchScores(int val, CancellationToken cancellationToken) + { + var players = new List(); + Logger.Important($"Requesting {Game.GameId} id={Game.Id}", LogType.Runtime); + var gameInfoRequest = new APIRequestMultiplayerGameInformation(Game.GameId); + var gameInfoResponse = gameInfoRequest.ExecuteRequest(); + + foreach (var match in gameInfoResponse.Matches) + { + Logger.Important($"Match {match.Id} played at {match.TimePlayed}", LogType.Runtime); + } + + var recentMatch = gameInfoResponse.Matches.MaxBy(x => x.TimePlayed); + + var matchInfoRequest = new APIRequestMultiplayerMatchInformation(recentMatch.Id); + var matchInfoResponse = matchInfoRequest.ExecuteRequest(); + + foreach (var playerScore in matchInfoResponse.Match.Scores) + { + var player = playerScore.Player; + var score = playerScore.Score; + var processor = new ScoreProcessorKeys(Map.Qua, score.Mods, new ScoreProcessorMultiplayer(MultiplayerHealthType.Lives, score.LivesLeft)) + { + PlayerName = player.Username, + UserId = player.Id, + MultiplayerProcessor = + { + IsBattleRoyaleEliminated = score.BattleRoyaleRank is > 1 + }, + Accuracy = (float)score.Accuracy, + MaxCombo = score.MaxCombo, + Score = score.Score, + CurrentJudgements = + { + [Judgement.Marv] = score.CountMarv, + [Judgement.Perf] = score.CountPerf, + [Judgement.Great] = score.CountGreat, + [Judgement.Good] = score.CountGood, + [Judgement.Okay] = score.CountOkay, + [Judgement.Miss] = score.CountMiss + } + }; + + Logger.Debug($"{playerScore.Player.Username} scored {playerScore.Score.Score}", LogType.Runtime); + players.Add(processor); + } + + + ScrollContainer = new ResultsMultiplayerScrollContainer(new ScalableVector2(Width, Height - HeaderContainer.Height), + players, Game, Headers, Map) + { + Parent = this, + Y = HeaderContainer.Height + }; + + return 0; + } + /// /// /// From 6e030dfbeb1164f258fd1fe5f288799d39650e52 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 9 Apr 2024 12:13:44 +0800 Subject: [PATCH 140/249] Remove debug logs --- .../Multiplayer/APIRequestMultiplayerGameInformation.cs | 2 -- .../Multiplayer/APIRequestMultiplayerMatchInformation.cs | 2 -- .../UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs | 8 +------- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/Quaver.Shared/Online/API/Multiplayer/APIRequestMultiplayerGameInformation.cs b/Quaver.Shared/Online/API/Multiplayer/APIRequestMultiplayerGameInformation.cs index a9a67f0558..43f09f868b 100644 --- a/Quaver.Shared/Online/API/Multiplayer/APIRequestMultiplayerGameInformation.cs +++ b/Quaver.Shared/Online/API/Multiplayer/APIRequestMultiplayerGameInformation.cs @@ -28,8 +28,6 @@ public override MultiplayerGameInformationResponse ExecuteRequest() var client = new RestClient(OnlineClient.API_ENDPOINT) { UserAgent = "Quaver" }; var response = client.Execute(request); - - Logger.Important(response.Content, LogType.Runtime); var json = JObject.Parse(response.Content); diff --git a/Quaver.Shared/Online/API/Multiplayer/APIRequestMultiplayerMatchInformation.cs b/Quaver.Shared/Online/API/Multiplayer/APIRequestMultiplayerMatchInformation.cs index 645434902e..b5b7e1e053 100644 --- a/Quaver.Shared/Online/API/Multiplayer/APIRequestMultiplayerMatchInformation.cs +++ b/Quaver.Shared/Online/API/Multiplayer/APIRequestMultiplayerMatchInformation.cs @@ -29,8 +29,6 @@ public override MultiplayerMatchInformationResponse ExecuteRequest() var response = client.Execute(request); - Logger.Important(response.Content, LogType.Runtime); - var json = JObject.Parse(response.Content); var responseParsed = JsonConvert.DeserializeObject(json.ToString()); diff --git a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs index b59812774d..6d140ffa51 100644 --- a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs +++ b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs @@ -215,15 +215,10 @@ private List GetOrderedUserList() private int GetMatchScores(int val, CancellationToken cancellationToken) { var players = new List(); - Logger.Important($"Requesting {Game.GameId} id={Game.Id}", LogType.Runtime); + var gameInfoRequest = new APIRequestMultiplayerGameInformation(Game.GameId); var gameInfoResponse = gameInfoRequest.ExecuteRequest(); - foreach (var match in gameInfoResponse.Matches) - { - Logger.Important($"Match {match.Id} played at {match.TimePlayed}", LogType.Runtime); - } - var recentMatch = gameInfoResponse.Matches.MaxBy(x => x.TimePlayed); var matchInfoRequest = new APIRequestMultiplayerMatchInformation(recentMatch.Id); @@ -255,7 +250,6 @@ private int GetMatchScores(int val, CancellationToken cancellationToken) } }; - Logger.Debug($"{playerScore.Player.Username} scored {playerScore.Score.Score}", LogType.Runtime); players.Add(processor); } From 251d8f5691384d9bebe6894b04cbb928be083f40 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 9 Apr 2024 12:17:54 +0800 Subject: [PATCH 141/249] Sort scores --- .../UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs index 6d140ffa51..adbcc60f65 100644 --- a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs +++ b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs @@ -215,6 +215,7 @@ private List GetOrderedUserList() private int GetMatchScores(int val, CancellationToken cancellationToken) { var players = new List(); + var qua = Map.LoadQua(); var gameInfoRequest = new APIRequestMultiplayerGameInformation(Game.GameId); var gameInfoResponse = gameInfoRequest.ExecuteRequest(); @@ -252,7 +253,10 @@ private int GetMatchScores(int val, CancellationToken cancellationToken) players.Add(processor); } - + + players = players.OrderByDescending(x => + new RatingProcessorKeys(qua.SolveDifficulty(x.Mods, true).OverallDifficulty).CalculateRating(x)) + .ToList(); ScrollContainer = new ResultsMultiplayerScrollContainer(new ScalableVector2(Width, Height - HeaderContainer.Height), players, Game, Headers, Map) From 2fe628159abeb0fff228c9bdd839c7062c9444bc Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 9 Apr 2024 12:58:21 +0800 Subject: [PATCH 142/249] Move invert scrolling option to misc --- Quaver.Shared/Screens/Options/OptionsMenu.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Quaver.Shared/Screens/Options/OptionsMenu.cs b/Quaver.Shared/Screens/Options/OptionsMenu.cs index bf7587e810..0871acbc5d 100644 --- a/Quaver.Shared/Screens/Options/OptionsMenu.cs +++ b/Quaver.Shared/Screens/Options/OptionsMenu.cs @@ -380,12 +380,12 @@ private void CreateSections() { new OptionsItemKeybind(containerRect, "Pause/Play Track", ConfigManager.KeyEditorPausePlay), new OptionsItemKeybind(containerRect, "Decrease Playback Rate", ConfigManager.KeyEditorDecreaseAudioRate), - new OptionsItemKeybind(containerRect, "Increase Playback Rate", ConfigManager.KeyEditorIncreaseAudioRate), - new OptionsItemCheckbox(containerRect, "Invert Scrolling", ConfigManager.InvertScrolling) + new OptionsItemKeybind(containerRect, "Increase Playback Rate", ConfigManager.KeyEditorIncreaseAudioRate) }), new OptionsSubcategory("Misc", new List() { - new OptionsItemKeybind(containerRect, "Take Screenshot", ConfigManager.KeyScreenshot) + new OptionsItemKeybind(containerRect, "Take Screenshot", ConfigManager.KeyScreenshot), + new OptionsItemCheckbox(containerRect, "Invert Scrolling", ConfigManager.InvertScrolling) }) }), new OptionsSection("Miscellaneous", UserInterface.OptionsMisc, new List From 90759527a8d54a3eb51b03ba9e808432d31c0b54 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 9 Apr 2024 12:59:01 +0800 Subject: [PATCH 143/249] Remove invert scrolling option in settings --- Quaver.Shared/Screens/Settings/SettingsDialog.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Quaver.Shared/Screens/Settings/SettingsDialog.cs b/Quaver.Shared/Screens/Settings/SettingsDialog.cs index 207072c53a..094f80e498 100644 --- a/Quaver.Shared/Screens/Settings/SettingsDialog.cs +++ b/Quaver.Shared/Screens/Settings/SettingsDialog.cs @@ -446,8 +446,7 @@ private void CreateSections() new SettingsKeybind(this, "Interface - Navigate Down", ConfigManager.KeyNavigateDown), new SettingsKeybind(this, "Editor - Pause/Play Track", ConfigManager.KeyEditorPausePlay), new SettingsKeybind(this, "Editor - Decrease Audio Playback Rate", ConfigManager.KeyEditorDecreaseAudioRate), - new SettingsKeybind(this, "Editor - Increase Audio Playback Rate", ConfigManager.KeyEditorIncreaseAudioRate), - new SettingsBool(this, "Editor - Invert Scrolling", ConfigManager.InvertScrolling) + new SettingsKeybind(this, "Editor - Increase Audio Playback Rate", ConfigManager.KeyEditorIncreaseAudioRate) }), // Misc new SettingsSection(this, FontAwesome.Get(FontAwesomeIcon.fa_question_sign), "Miscellaneous", new List From 6cdbc63e25fec52e0f35ff0d81b85cc213c26bd0 Mon Sep 17 00:00:00 2001 From: Warp Date: Tue, 9 Apr 2024 11:06:18 +0200 Subject: [PATCH 144/249] update Wobble --- Wobble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wobble b/Wobble index bba6e46af3..8dd23968f1 160000 --- a/Wobble +++ b/Wobble @@ -1 +1 @@ -Subproject commit bba6e46af3b8c25c47e06e9cbae5fa95bcb23df3 +Subproject commit 8dd23968f1e1b244453a1e1f393ceb4d563207d0 From 315b86889661186b14238b4ad877bc1701d0d1a9 Mon Sep 17 00:00:00 2001 From: Warp Date: Tue, 9 Apr 2024 11:17:46 +0200 Subject: [PATCH 145/249] update Quaver.API --- Quaver.API | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.API b/Quaver.API index 256800a7f8..783211ee9a 160000 --- a/Quaver.API +++ b/Quaver.API @@ -1 +1 @@ -Subproject commit 256800a7f8bc2510ba0bf67b9e5e4e7d850c880d +Subproject commit 783211ee9a1c5e12fa2a18c855d5017ad8404b69 From 6239a1fcfb97c4e0561c5d9c63744a5e8657ed1c Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 9 Apr 2024 19:26:39 +0800 Subject: [PATCH 146/249] Add a NO button to difficulty switching confirmation dialog --- .../Dialogs/UnsavedChangesSwitchMapDialog.cs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Edit/Dialogs/UnsavedChangesSwitchMapDialog.cs b/Quaver.Shared/Screens/Edit/Dialogs/UnsavedChangesSwitchMapDialog.cs index 7c9e53b1ae..b910359e18 100644 --- a/Quaver.Shared/Screens/Edit/Dialogs/UnsavedChangesSwitchMapDialog.cs +++ b/Quaver.Shared/Screens/Edit/Dialogs/UnsavedChangesSwitchMapDialog.cs @@ -1,11 +1,15 @@ +using Quaver.Shared.Assets; using Quaver.Shared.Database.Maps; using Quaver.Shared.Graphics; using Quaver.Shared.Graphics.Notifications; +using Quaver.Shared.Screens.Menu.UI.Jukebox; +using Wobble.Graphics; namespace Quaver.Shared.Screens.Edit.Dialogs { public class UnsavedChangesSwitchMapDialog : YesNoDialog { + private IconButton YellowNoButton { get; } public UnsavedChangesSwitchMapDialog(EditScreen screen, Map map) : base("SAVE CHANGES", "You have unsaved changes. Would you like to save\n" + "before switching to another difficulty?") @@ -18,7 +22,32 @@ public UnsavedChangesSwitchMapDialog(EditScreen screen, Map map) : base("SAVE CH screen.SwitchToMap(map); }; - NoAction += () => screen.SwitchToMap(map, true); + const float scale = 0.90f; + + YesButton.Size = new ScalableVector2(YesButton.Width * scale, YesButton.Height * scale); + NoButton.Size = new ScalableVector2(NoButton.Width * scale, NoButton.Height * scale); + + YellowNoButton = new IconButton(UserInterface.NoYellowButton, (sender, args) => + { + screen.SwitchToMap(map, true); + Close(); + }) + { + Parent = Panel, + Size = YesButton.Size, + Alignment = Alignment.BotCenter, + Y = YesButton.Y + }; + + YesButton.X -= 80; + NoButton.X = -YesButton.X; + } + + public override void Close() + { + YellowNoButton.IsClickable = false; + YellowNoButton.IsPerformingFadeAnimations = false; + base.Close(); } } } \ No newline at end of file From 4bb5ecaca76bd6726a42df8c1689e3169ef8a13a Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 9 Apr 2024 19:28:20 +0800 Subject: [PATCH 147/249] Add difficulty color to map names --- Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs index 54b82f08a1..fc06f9005f 100644 --- a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs +++ b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs @@ -109,8 +109,12 @@ private void CreateFileSection() { foreach (var map in Screen.Map.Mapset.Maps) { + var color = ColorHelper.DifficultyToColor((float)map.Difficulty10X); + ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, 1)); + if (ImGui.MenuItem(map.DifficultyName, map != Screen.Map)) Screen.SwitchToMap(map); + ImGui.PopStyleColor(); } ImGui.EndMenu(); } From 58a66c6ce09f92bccfc1f22abdeab9451e33d850 Mon Sep 17 00:00:00 2001 From: Emik Date: Tue, 9 Apr 2024 15:30:15 +0200 Subject: [PATCH 148/249] Prevent note scrolling in beat snap dropdown context --- Quaver.Shared/Screens/Edit/EditScreen.cs | 28 +++++++++++++++++------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index 2f753ba9e6..caaaf9aace 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -20,6 +20,7 @@ using Quaver.Shared.Database.Scores; using Quaver.Shared.Discord; using Quaver.Shared.Graphics.Backgrounds; +using Quaver.Shared.Graphics.Form.Dropdowns; using Quaver.Shared.Graphics.Notifications; using Quaver.Shared.Helpers; using Quaver.Shared.Modifiers; @@ -47,6 +48,7 @@ using Wobble.Bindables; using Wobble.Discord.RPC.Logging; using Wobble.Graphics; +using Wobble.Graphics.UI.Buttons; using Wobble.Graphics.UI.Dialogs; using Wobble.Input; using Wobble.Logging; @@ -470,8 +472,18 @@ private void HandleInput() // To not conflict with the volume controller if (!KeyboardManager.IsAltDown() && !KeyboardManager.IsCtrlDown()) { - HandleSeekingBackwards(); - HandleSeekingForwards(); + var dropdownHovered = ButtonManager.Buttons.Any( + x => x is DropdownItem item && + GraphicsHelper.RectangleContains(x.ScreenRectangle, MouseManager.CurrentState.Position) && + item.Dropdown.Opened + ); + + if (!dropdownHovered) + { + HandleSeekingBackwards(); + HandleSeekingForwards(); + } + HandleKeyPressUp(); HandleKeyPressDown(); HandleKeyPressShiftUpDown(); @@ -775,13 +787,13 @@ private void HandleCtrlInput() if (KeyboardManager.IsUniqueKeyPress(Keys.I)) PlaceTimingPointOrScrollVelocity(); - + if (KeyboardManager.IsUniqueKeyPress(Keys.B)) DialogManager.Show(new EditorBookmarkDialog(ActionManager, Track, null)); - + if (KeyboardManager.IsUniqueKeyPress(Keys.Left)) SeekToNearestBookmark(Direction.Backward); - + if (KeyboardManager.IsUniqueKeyPress(Keys.Right)) SeekToNearestBookmark(Direction.Forward); } @@ -1406,7 +1418,7 @@ public void SeekToNearestBookmark(Direction direction) { if (WorkingMap.Bookmarks.Count == 0) return; - + BookmarkInfo nextBookmark = null; var closest = WorkingMap.Bookmarks.OrderBy(x => Math.Abs(x.StartTime - Track.Time)).First(); @@ -1432,10 +1444,10 @@ public void SeekToNearestBookmark(Direction direction) if (nextBookmark == null) return; - + Track.Seek(Math.Clamp(nextBookmark.StartTime, 0, Track.Length)); } - + /// /// Creates a new mapset from an audio file /// From 87f2e901620e57d54f201ce2f51c346198afcebc Mon Sep 17 00:00:00 2001 From: Emik Date: Tue, 9 Apr 2024 15:36:43 +0200 Subject: [PATCH 149/249] Hide "Custom" beat snap option if all beat snaps are added --- .../Screens/Edit/UI/Footer/BeatSnapRightClickOptions.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Footer/BeatSnapRightClickOptions.cs b/Quaver.Shared/Screens/Edit/UI/Footer/BeatSnapRightClickOptions.cs index 141c6e0d44..5aba7e30ff 100644 --- a/Quaver.Shared/Screens/Edit/UI/Footer/BeatSnapRightClickOptions.cs +++ b/Quaver.Shared/Screens/Edit/UI/Footer/BeatSnapRightClickOptions.cs @@ -108,7 +108,9 @@ private static Dictionary GetOptions(List availableSnaps) foreach (var snap in availableSnaps) options.Add($"1/{StringHelper.AddOrdinal(snap)}", ColorHelper.BeatSnapToColor(snap)); - options.Add("Custom", Color.White); + // You cannot add more snaps, so this option is redundant. + if (availableSnaps.Count is not 48) + options.Add("Custom", Color.White); return options; } From 06ee7a9d962a47f3d1b216a7a1878340fc588f3b Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 9 Apr 2024 21:53:18 +0800 Subject: [PATCH 150/249] Retry a maximum of 5 times with interval 500ms and notify an error --- .../Table/ResultsMultiplayerTable.cs | 50 ++++++++++++++++--- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs index adbcc60f65..73b58357ae 100644 --- a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs +++ b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using System.Threading; using Quaver.API.Enums; using Quaver.API.Maps.Processors.Rating; @@ -9,10 +10,12 @@ using Quaver.Server.Common.Objects.Multiplayer; using Quaver.Shared.Assets; using Quaver.Shared.Database.Maps; +using Quaver.Shared.Graphics.Notifications; using Quaver.Shared.Helpers; using Quaver.Shared.Online.API.Multiplayer; using Quaver.Shared.Screens.Results.UI.Header.Contents.Tabs; using Quaver.Shared.Screens.Results.UI.Tabs.Multiplayer.Table.Scrolling; +using Quaver.Shared.Screens.Results.UI.Tabs.Overview.Graphs.Footer; using Quaver.Shared.Screens.Selection.UI.Leaderboard; using Quaver.Shared.Skinning; using Wobble.Assets; @@ -102,6 +105,8 @@ public ResultsMultiplayerTable(Map map, Bindable processor, Mult CreateRulesetText(); CreateColumnHeaders(); CreateScrollContainer(); + + GetScoresTask.Run(0); } /// @@ -214,16 +219,23 @@ private List GetOrderedUserList() private int GetMatchScores(int val, CancellationToken cancellationToken) { - var players = new List(); - var qua = Map.LoadQua(); - - var gameInfoRequest = new APIRequestMultiplayerGameInformation(Game.GameId); - var gameInfoResponse = gameInfoRequest.ExecuteRequest(); + const int maxRetryCount = 5; + MultiplayerMatchInformationResponse matchInfoResponse = null; + + for (var retryCount = 0; retryCount < maxRetryCount; retryCount++) + { + if (TryFetchMatchInfo(out matchInfoResponse)) break; + Thread.Sleep(500); + } - var recentMatch = gameInfoResponse.Matches.MaxBy(x => x.TimePlayed); + if (matchInfoResponse == null) + { + NotificationManager.Show(NotificationLevel.Error, "Failed to retrieve players' scores!"); + return 0; + } - var matchInfoRequest = new APIRequestMultiplayerMatchInformation(recentMatch.Id); - var matchInfoResponse = matchInfoRequest.ExecuteRequest(); + List players = new(); + var qua = Map.LoadQua(); foreach (var playerScore in matchInfoResponse.Match.Scores) { @@ -268,6 +280,28 @@ private int GetMatchScores(int val, CancellationToken cancellationToken) return 0; } + private bool TryFetchMatchInfo(out MultiplayerMatchInformationResponse matchInfoResponse) + { + try + { + var gameInfoRequest = new APIRequestMultiplayerGameInformation(Game.GameId); + var gameInfoResponse = gameInfoRequest.ExecuteRequest(); + + var recentMatch = gameInfoResponse.Matches.MaxBy(x => x.TimePlayed); + + var matchInfoRequest = new APIRequestMultiplayerMatchInformation(recentMatch.Id); + matchInfoResponse = matchInfoRequest.ExecuteRequest(); + } + catch (JsonException jsonException) + { + Logger.Error($"Could not fetch players' result: {jsonException}", LogType.Runtime); + matchInfoResponse = null; + return false; + } + + return true; + } + /// /// /// From 3a4ae4342d6491e3b4b4776869b470f90fb5e226 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 9 Apr 2024 21:53:45 +0800 Subject: [PATCH 151/249] Add loading wheel --- .../Multiplayer/Table/ResultsMultiplayerTable.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs index 73b58357ae..62ff607d72 100644 --- a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs +++ b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs @@ -65,6 +65,8 @@ public class ResultsMultiplayerTable : Sprite /// private ResultsMultiplayerScrollContainer ScrollContainer { get; set; } + private LoadingWheelText ResultLoadingWheelText { get; set; } + private TaskHandler GetScoresTask { get; set; } /// @@ -85,6 +87,8 @@ public ResultsMultiplayerTable(Map map, Bindable processor, Mult Width = ResultsScreenView.CONTENT_WIDTH - ResultsTabContainer.PADDING_X; GetScoresTask = new TaskHandler(GetMatchScores); + GetScoresTask.OnCompleted += (_, _) => ResultLoadingWheelText.FadeOut(); + GetScoresTask.OnCancelled += (_, _) => ResultLoadingWheelText.Destroy(); switch (Game.Ruleset) { @@ -104,7 +108,7 @@ public ResultsMultiplayerTable(Map map, Bindable processor, Mult CreateHeaderContainer(); CreateRulesetText(); CreateColumnHeaders(); - CreateScrollContainer(); + CreateScoresLoadingWheelText(); GetScoresTask.Run(0); } @@ -184,9 +188,14 @@ private void CreateColumnHeaders() /// /// - private void CreateScrollContainer() + private void CreateScoresLoadingWheelText() { - GetScoresTask.Run(0); + ResultLoadingWheelText = new LoadingWheelText(25, "Loading results") + { + Parent = this, + Alignment = Alignment.TopCenter, + Y = HeaderContainer.Height + 25 + }; } /// From bc9023f43648e02e22a926342fb09082e44a822e Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 9 Apr 2024 21:57:18 +0800 Subject: [PATCH 152/249] Catch from Newtonsoft JsonException, not the System one --- .../UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs index 62ff607d72..c059b61458 100644 --- a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs +++ b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text.Json; using System.Threading; using Quaver.API.Enums; using Quaver.API.Maps.Processors.Rating; @@ -26,6 +25,7 @@ using Wobble.Logging; using Wobble.Managers; using Wobble.Scheduling; +using Newtonsoft.Json; namespace Quaver.Shared.Screens.Results.UI.Tabs.Multiplayer.Table { From 64835446ba95ddafdd5e49633efb8b93882fe881 Mon Sep 17 00:00:00 2001 From: Emik Date: Tue, 9 Apr 2024 16:01:56 +0200 Subject: [PATCH 153/249] Use DeepCloner --- Quaver.Shared/Helpers/ObjectHelper.cs | 33 ------------------- Quaver.Shared/Screens/Edit/EditScreen.cs | 7 ++-- .../Screens/Gameplay/GameplayScreen.cs | 5 +-- .../UI/Preview/SelectMapPreviewContainer.cs | 5 +-- 4 files changed, 10 insertions(+), 40 deletions(-) delete mode 100644 Quaver.Shared/Helpers/ObjectHelper.cs diff --git a/Quaver.Shared/Helpers/ObjectHelper.cs b/Quaver.Shared/Helpers/ObjectHelper.cs deleted file mode 100644 index 6e3ee162cf..0000000000 --- a/Quaver.Shared/Helpers/ObjectHelper.cs +++ /dev/null @@ -1,33 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * Copyright (c) Swan & The Quaver Team . -*/ - -using System.IO; -using System.Runtime.Serialization.Formatters.Binary; - -namespace Quaver.Shared.Helpers -{ - public static class ObjectHelper - { - /// - /// Clones an entire object. - /// - /// - /// - /// - public static T DeepClone(T obj) - { - using (var ms = new MemoryStream()) - { - var formatter = new BinaryFormatter(); - formatter.Serialize(ms, obj); - ms.Position = 0; - - return (T)formatter.Deserialize(ms); - } - } - } -} diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index da55ab530b..e58e5c608f 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; +using Force.DeepCloner; using IniFileParser; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; @@ -258,7 +259,7 @@ public EditScreen(Map map, IAudioTrack track = null, EditorVisualTestBackground try { OriginalQua = map.LoadQua(); - WorkingMap = ObjectHelper.DeepClone(OriginalQua); + WorkingMap = OriginalQua.DeepClone(); } catch (Exception e) { @@ -1389,7 +1390,7 @@ public void ExitToTestPlay(bool fromStart = false) NotificationManager.Show(NotificationLevel.Success, "Your map has been successfully saved!"); } - var map = ObjectHelper.DeepClone(WorkingMap); + var map = WorkingMap.DeepClone(); map.ApplyMods(ModManager.Mods); var startTime = fromStart ? 0 : Track.Time; @@ -1566,7 +1567,7 @@ public void CreateNewDifficulty(bool copyCurrent = true, bool force = false) { try { - var qua = ObjectHelper.DeepClone(WorkingMap); + var qua = WorkingMap.DeepClone(); qua.DifficultyName = ""; qua.MapId = -1; qua.Description = $"Created at {TimeHelper.GetUnixTimestampMilliseconds()}"; diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs index 4e24f9ee26..62af27e46f 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using Force.DeepCloner; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using Quaver.API.Enums; @@ -370,7 +371,7 @@ public GameplayScreen(Qua map, string md5, List scores, Replay replay = n { if (isPlayTesting && !isSongSelectPreview) { - var testingQua = ObjectHelper.DeepClone(map); + var testingQua = map.DeepClone(); testingQua.HitObjects.RemoveAll(x => x.StartTime + 2 < playTestTime); Qua.RestoreDefaultValues(testingQua); @@ -1618,4 +1619,4 @@ private void HandleOverlayToggleInput(GameTime gameTime) $"Gameplay overlay is now {on}. Press Shift+F6 to toggle the display.", null, true); } } -} +} \ No newline at end of file diff --git a/Quaver.Shared/Screens/Selection/UI/Preview/SelectMapPreviewContainer.cs b/Quaver.Shared/Screens/Selection/UI/Preview/SelectMapPreviewContainer.cs index ee774ac811..b57952460f 100644 --- a/Quaver.Shared/Screens/Selection/UI/Preview/SelectMapPreviewContainer.cs +++ b/Quaver.Shared/Screens/Selection/UI/Preview/SelectMapPreviewContainer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading; +using Force.DeepCloner; using Microsoft.Xna.Framework; using Quaver.API.Enums; using Quaver.API.Maps; @@ -200,7 +201,7 @@ private GameplayScreen HandleLoadGameplayScreen(Map map, CancellationToken token var qua = Qua ?? map.LoadQua(); if (qua == Qua) - qua = ObjectHelper.DeepClone(qua); + qua = qua.DeepClone(); map.Qua = qua; map.Qua.ApplyMods(ModManager.Mods); @@ -573,4 +574,4 @@ protected void RefreshScreen() /// private void OnTrackSeeked(object sender, TrackSeekedEventArgs e) => RefreshScreen(); } -} +} \ No newline at end of file From a91d0d1cab1ebd6a3bf216c69d2e61d743c25d79 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 9 Apr 2024 22:21:20 +0800 Subject: [PATCH 154/249] Check md5 of map and not latest date --- .../Table/ResultsMultiplayerTable.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs index c059b61458..b5443674db 100644 --- a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs +++ b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs @@ -293,12 +293,26 @@ private bool TryFetchMatchInfo(out MultiplayerMatchInformationResponse matchInfo { try { + var md5 = Map.Md5Checksum; var gameInfoRequest = new APIRequestMultiplayerGameInformation(Game.GameId); var gameInfoResponse = gameInfoRequest.ExecuteRequest(); - var recentMatch = gameInfoResponse.Matches.MaxBy(x => x.TimePlayed); + MultiplayerGameInformationResponseMatch match = null; + foreach (var responseMatch in gameInfoResponse.Matches) + { + if (responseMatch.Map.Md5 != md5) continue; + match = responseMatch; + break; + } + + if (match == null) + { + Logger.Error("The match is not yet updated on server", LogType.Runtime); + matchInfoResponse = null; + return false; + } - var matchInfoRequest = new APIRequestMultiplayerMatchInformation(recentMatch.Id); + var matchInfoRequest = new APIRequestMultiplayerMatchInformation(match.Id); matchInfoResponse = matchInfoRequest.ExecuteRequest(); } catch (JsonException jsonException) From 8290d5e8aad869570fe7e527f6e57332c705cb19 Mon Sep 17 00:00:00 2001 From: Emik Date: Tue, 9 Apr 2024 17:49:33 +0200 Subject: [PATCH 155/249] Don't render BPMs that are too close together --- .../Edit/UI/Playfield/Timeline/EditorPlayfieldTimeline.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Timeline/EditorPlayfieldTimeline.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Timeline/EditorPlayfieldTimeline.cs index 5ee1c249ce..28a40f9525 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Timeline/EditorPlayfieldTimeline.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Timeline/EditorPlayfieldTimeline.cs @@ -290,13 +290,19 @@ private void UpdateLinePool() /// private void DrawLines(GameTime gameTime) { + const float minimumDistanceToDraw = 10; + for (var i = 0; i < LinePool.Count; i++) { var line = LinePool[i]; line.SetPosition(); line.Tint = GetLineColor(line.Index % BeatSnap.Value, line.Index); - if (line.IsOnScreen()) + if (line.IsOnScreen() && + (i is 0 || + LinePool[i - 1].Y - line.Y > minimumDistanceToDraw || + i == LinePool.Count - 1 || + line.Y - LinePool[i + 1].Y > minimumDistanceToDraw)) line.Draw(gameTime); } } From 00c5e20a75c2ad6593f31a0a6e2588a1ead551a7 Mon Sep 17 00:00:00 2001 From: Emik Date: Tue, 9 Apr 2024 18:32:36 +0200 Subject: [PATCH 156/249] Prevent scrolling when mouse isn't within bounds --- .../Screens/Edit/UI/Footer/BeatSnapRightClickOptions.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Footer/BeatSnapRightClickOptions.cs b/Quaver.Shared/Screens/Edit/UI/Footer/BeatSnapRightClickOptions.cs index 5aba7e30ff..4bb54394a1 100644 --- a/Quaver.Shared/Screens/Edit/UI/Footer/BeatSnapRightClickOptions.cs +++ b/Quaver.Shared/Screens/Edit/UI/Footer/BeatSnapRightClickOptions.cs @@ -12,6 +12,7 @@ using Wobble.Graphics.Animations; using Wobble.Graphics.Sprites; using Wobble.Graphics.UI.Dialogs; +using Wobble.Input; namespace Quaver.Shared.Screens.Edit.UI.Footer { @@ -39,6 +40,7 @@ public BeatSnapRightClickOptions(BindableInt beatSnap, List availableBeatSn BeatSnap = beatSnap; AvailableBeatSnaps = availableBeatSnaps; ItemContainer.Scrollbar.Tint = Color.White; + ItemContainer.Scrollbar.Visible = Opened; ItemContainer.Scrollbar.Width = 2; ItemContainer.EasingType = Easing.OutQuint; ItemContainer.TimeToCompleteScroll = 1200; @@ -82,8 +84,10 @@ public BeatSnapRightClickOptions(BindableInt beatSnap, List availableBeatSn public override void Update(GameTime gameTime) { - ItemContainer.Scrollbar.Visible = Opened; - ItemContainer.InputEnabled = Opened; + ItemContainer.InputEnabled = GraphicsHelper.RectangleContains( + ItemContainer.ScreenRectangle, + MouseManager.CurrentState.Position + ); base.Update(gameTime); } From 4727b6444581e5452150ebf3e73dbf9408825bcc Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Wed, 10 Apr 2024 00:36:02 +0800 Subject: [PATCH 157/249] use only recent map to compare md5 --- .../Multiplayer/Table/ResultsMultiplayerTable.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs index b5443674db..c50e2289b5 100644 --- a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs +++ b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs @@ -297,22 +297,18 @@ private bool TryFetchMatchInfo(out MultiplayerMatchInformationResponse matchInfo var gameInfoRequest = new APIRequestMultiplayerGameInformation(Game.GameId); var gameInfoResponse = gameInfoRequest.ExecuteRequest(); - MultiplayerGameInformationResponseMatch match = null; - foreach (var responseMatch in gameInfoResponse.Matches) - { - if (responseMatch.Map.Md5 != md5) continue; - match = responseMatch; - break; - } + var recentMatch = gameInfoResponse.Matches.Count == 0 + ? null + : gameInfoResponse.Matches.MaxBy(x => x.TimePlayed); - if (match == null) + if (recentMatch == null || recentMatch.Map.Md5 != md5) { Logger.Error("The match is not yet updated on server", LogType.Runtime); matchInfoResponse = null; return false; } - var matchInfoRequest = new APIRequestMultiplayerMatchInformation(match.Id); + var matchInfoRequest = new APIRequestMultiplayerMatchInformation(recentMatch.Id); matchInfoResponse = matchInfoRequest.ExecuteRequest(); } catch (JsonException jsonException) From 46d2613a4fb83a33bd574c06d704b51e3fb2cc5a Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Wed, 10 Apr 2024 18:20:09 +0800 Subject: [PATCH 158/249] Add option to keep playing when failing the gameplay --- Quaver.Shared/Config/ConfigManager.cs | 6 ++++ .../Screens/Gameplay/GameplayScreen.cs | 30 +++++++++++++++++-- Quaver.Shared/Screens/Options/OptionsMenu.cs | 4 +++ .../Screens/Results/ResultsScreen.cs | 2 +- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index 56e377851a..5f655af6cb 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -303,6 +303,11 @@ public static class ConfigManager /// internal static Bindable TapToPause { get; private set; } + /// + /// If enabled, the user will be able to continue playing the map when dying, but with No Fail mod enabled. + /// + internal static Bindable KeepPlayingUponFailing { get; private set; } + /// /// If enabled, the user will be able to tap to restart instead of having to hold for 200ms to restart. /// @@ -1021,6 +1026,7 @@ private static void ReadConfigFile() KeyQuickExit = ReadValue(@"KeyQuickExit", Keys.F1, data); KeyScreenshot = ReadValue(@"KeyScreenshot", Keys.F12, data); TapToPause = ReadValue(@"TapToPause", false, data); + KeepPlayingUponFailing = ReadValue(@"KeepPlayingUponFailing", true, data); TapToRestart = ReadValue(@"TapToRestart", false, data); DisplayFailedLocalScores = ReadValue(@"DisplayFailedLocalScores", true, data); EditorScrollSpeedKeys = ReadInt(@"EditorScrollSpeedKeys", 16, 5, 100, data); diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs index 4e24f9ee26..e518b036ac 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs @@ -172,7 +172,8 @@ public class GameplayScreen : QuaverScreen && !OnlineManager.IsSpectatingSomeone && !IsPlayTesting && (!ModManager.IsActivated(ModIdentifier.NoFail) - && Ruleset.ScoreProcessor.Health <= 0) + && Ruleset.ScoreProcessor.Health <= 0 + && !ConfigManager.KeepPlayingUponFailing.Value) && !(this is TournamentGameplayScreen) || ForceFail || Ruleset.ScoreProcessor.ForceFail; @@ -196,6 +197,11 @@ public class GameplayScreen : QuaverScreen /// public bool HasQuit { get; set; } + /// + /// If the player fails during gameplay, but keeps playing because of the option + /// + public bool FailedDuringGameplay { get; set; } + /// /// Flag that dictates if the user is currently restarting the play. /// @@ -959,7 +965,27 @@ private void HandleResuming() /// private void HandleFailure() { - if (!Failed || FailureHandled || Ruleset.ScoreProcessor.Mods.HasFlag(ModIdentifier.NoMiss)) + // NoMiss mod should take priority: gameplay is expected to restart when NM is on + if (Ruleset.ScoreProcessor.Mods.HasFlag(ModIdentifier.NoMiss)) + return; + if (!FailedDuringGameplay + && OnlineManager.CurrentGame == null + && !OnlineManager.IsSpectatingSomeone + && !IsPlayTesting + && !IsCalibratingOffset + && (!ModManager.IsActivated(ModIdentifier.NoFail) + && Ruleset.ScoreProcessor.Health <= 0 + && ConfigManager.KeepPlayingUponFailing.Value) + && !(this is TournamentGameplayScreen) + && !ForceFail && !Ruleset.ScoreProcessor.ForceFail) + { + // Add no fail mod upon dying when AutoNoFail is on. + // Add the no fail mod to their score. + NotificationManager.Show(NotificationLevel.Warning, "WARNING! Your score will not be submitted due to failing " + + "during gameplay!", null, true); + FailedDuringGameplay = true; + } + if (!Failed || FailureHandled) return; try diff --git a/Quaver.Shared/Screens/Options/OptionsMenu.cs b/Quaver.Shared/Screens/Options/OptionsMenu.cs index 351fa14b52..13adfde5d0 100644 --- a/Quaver.Shared/Screens/Options/OptionsMenu.cs +++ b/Quaver.Shared/Screens/Options/OptionsMenu.cs @@ -247,6 +247,10 @@ private void CreateSections() new OptionsItemCheckbox(containerRect, "Enable Bottom Lane Cover", ConfigManager.LaneCoverBottom), new OptionsSlider(containerRect, "Bottom Lane Cover Height", ConfigManager.LaneCoverBottomHeight), new OptionsItemCheckbox(containerRect, "Display UI Elements Over Lane Covers", ConfigManager.UIElementsOverLaneCover) + }), + new OptionsSubcategory("Others", new List() + { + new OptionsItemCheckbox(containerRect, "Keep Playing Upon Failing", ConfigManager.KeepPlayingUponFailing) }) }), new OptionsSection("Skin", UserInterface.OptionsSkin, new List diff --git a/Quaver.Shared/Screens/Results/ResultsScreen.cs b/Quaver.Shared/Screens/Results/ResultsScreen.cs index 7de71e0bdc..a56f660b34 100644 --- a/Quaver.Shared/Screens/Results/ResultsScreen.cs +++ b/Quaver.Shared/Screens/Results/ResultsScreen.cs @@ -817,7 +817,7 @@ private bool SubmitOnlineScore(GameplayScreen screen, Replay replay) // User is playing on different windows (or multiplayer), so their score needs to be converted to Standard*. // This will validate if the user failed at any point during the play as well. Their score will need // to be submitted at the point of failure in both scenarios. - if (JudgementWindowsDatabaseCache.Selected.Value != JudgementWindowsDatabaseCache.Standard || screen.IsMultiplayerGame) + if (JudgementWindowsDatabaseCache.Selected.Value != JudgementWindowsDatabaseCache.Standard || screen.IsMultiplayerGame || screen.FailedDuringGameplay) { var virtualPlayer = new VirtualReplayPlayer(screen.ReplayCapturer.Replay, screen.Map, new JudgementWindows(), true); var originalProcessor = screen.Ruleset.ScoreProcessor; From 00029debba15eae5c52e8f96e8b8e2a844329230 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Wed, 10 Apr 2024 19:25:22 +0800 Subject: [PATCH 159/249] Support adjusting min/max frequency, cutoff/intensity factor and spectrogram layer --- Quaver.Shared/Config/ConfigManager.cs | 16 ++++++ .../Screens/Edit/UI/Menu/EditorFileMenuBar.cs | 55 +++++++++++++++++++ .../Edit/UI/Playfield/EditorPlayfield.cs | 38 +++++++++++-- .../EditorPlayfieldSpectrogramLayer.cs | 8 +++ .../EditorPlayfieldSpectrogramSlice.cs | 44 +++++---------- 5 files changed, 128 insertions(+), 33 deletions(-) create mode 100644 Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramLayer.cs diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index fda20b0c89..030dfe731b 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -22,6 +22,7 @@ using Quaver.Shared.Graphics.Overlays.Hub.OnlineUsers; using Quaver.Shared.Online; using Quaver.Shared.Scheduling; +using Quaver.Shared.Screens.Edit.UI.Playfield.Spectrogram; using Quaver.Shared.Screens.Edit.UI.Playfield.Waveform; using Quaver.Shared.Screens.MultiplayerLobby.UI.Filter; using Quaver.Shared.Screens.Results.UI.Tabs.Overview.Graphs; @@ -539,6 +540,16 @@ public static class ConfigManager /// internal static Bindable EditorShowSpectrogram { get; private set; } + internal static Bindable EditorSpectrogramMaximumFrequency { get; private set; } + + internal static Bindable EditorSpectrogramMinimumFrequency { get; private set; } + + internal static Bindable EditorSpectrogramLayer { get; private set; } + + internal static Bindable EditorSpectrogramCutoffFactor { get; private set; } + + internal static Bindable EditorSpectrogramIntensityFactor { get; private set; } + internal static BindableInt EditorSpectrogramFftSize { get; private set; } /// @@ -1083,6 +1094,11 @@ private static void ReadConfigFile() EditorAudioFilter = ReadValue(@"EditorAudioFilter", EditorPlayfieldWaveformFilter.None, data); EditorShowWaveform = ReadValue(@"EditorShowWaveform", true, data); EditorShowSpectrogram = ReadValue(@"EditorShowSpectrogram", false, data); + EditorSpectrogramMaximumFrequency = ReadInt(@"EditorSpectrogramMaximumFrequency", 10000, 2500, 20000, data); + EditorSpectrogramMinimumFrequency = ReadInt("EditorSpectrogramMinimumFrequency", 0, 0, 17500, data); + EditorSpectrogramLayer = ReadValue("EditorSpectrogramLayer", EditorPlayfieldSpectrogramLayer.BehindTimingLines, data); + EditorSpectrogramCutoffFactor = ReadValue("EditorSpectrogramCutoffFactor", 0.3f, data); + EditorSpectrogramIntensityFactor = ReadValue("EditorSpectrogramIntensityFactor", 7.5f, data); EditorSpectrogramFftSize = ReadInt(@"EditorSpectrumFftSize", 256, 256, 16384, data); EditorAudioDirection = ReadValue(@"EditorAudioDirection", EditorPlayfieldWaveformAudioDirection.Both, data); EditorWaveformColorR = ReadInt(@"EditorWaveformColorR", 0, 0, 255, data); diff --git a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs index 388c7499e5..3c44cffb1f 100644 --- a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs +++ b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs @@ -21,6 +21,7 @@ using Quaver.Shared.Screens.Edit.Dialogs.Metadata; using Quaver.Shared.Screens.Edit.Plugins; using Quaver.Shared.Screens.Edit.UI.Playfield; +using Quaver.Shared.Screens.Edit.UI.Playfield.Spectrogram; using Quaver.Shared.Screens.Edit.UI.Playfield.Waveform; using Quaver.Shared.Screens.Editor; using Wobble; @@ -507,6 +508,60 @@ private void CreateViewSection() ImGui.EndMenu(); } + if (ImGui.BeginMenu("Layer")) + { + foreach (var layer in Enum.GetValues()) + { + if (ImGui.MenuItem($"{layer}", "", layer == ConfigManager.EditorSpectrogramLayer.Value)) + ConfigManager.EditorSpectrogramLayer.Value = layer; + } + ImGui.EndMenu(); + } + + if (ImGui.BeginMenu("Cutoff Factor")) + { + for (var i = 0; i <= 10; i++) + { + var f = 0.2f + 0.02f * i; + if (ImGui.MenuItem($"{f:0.00}", "", Math.Abs(f - ConfigManager.EditorSpectrogramCutoffFactor.Value) < 0.01f)) + ConfigManager.EditorSpectrogramCutoffFactor.Value = f; + } + ImGui.EndMenu(); + } + + if (ImGui.BeginMenu("Intensity Factor")) + { + for (var i = 0; i < 10; i++) + { + var f = 0.5f * i + 5.0f; + if (ImGui.MenuItem($"{f}", "", Math.Abs(f - ConfigManager.EditorSpectrogramIntensityFactor.Value) < 0.01f)) + ConfigManager.EditorSpectrogramIntensityFactor.Value = f; + } + ImGui.EndMenu(); + } + + if (ImGui.BeginMenu("Maximum Frequency")) + { + for (var f = 5000; f <= 10000; f += 1000) + { + if (ImGui.MenuItem($"{f}", "", ConfigManager.EditorSpectrogramMaximumFrequency.Value == f, + ConfigManager.EditorSpectrogramMinimumFrequency.Value < f)) + ConfigManager.EditorSpectrogramMaximumFrequency.Value = f; + } + ImGui.EndMenu(); + } + + if (ImGui.BeginMenu("Minimum Frequency")) + { + for (var f = 0; f <= 1500; f += 125) + { + if (ImGui.MenuItem($"{f}", "", ConfigManager.EditorSpectrogramMinimumFrequency.Value == f, + ConfigManager.EditorSpectrogramMaximumFrequency.Value > f)) + ConfigManager.EditorSpectrogramMinimumFrequency.Value = f; + } + ImGui.EndMenu(); + } + ImGui.EndMenu(); } diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs index cfe0001119..56413dd550 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs @@ -409,6 +409,10 @@ public EditorPlayfield(Qua map, EditorActionManager manager, Bindable WaveFormAudioDirection.ValueChanged += OnWaveFormAudioDirectionChanged; WaveformFilter.ValueChanged += OnWaveformFilterChanged; SpectrogramFftSize.ValueChanged += OnSpectrogramFftSizeChanged; + ConfigManager.EditorSpectrogramMaximumFrequency.ValueChanged += OnSpectrogramFrequencyWindowSizeChanged; + ConfigManager.EditorSpectrogramMinimumFrequency.ValueChanged += OnSpectrogramMinimumFrequencyChanged; + ConfigManager.EditorSpectrogramCutoffFactor.ValueChanged += OnSpectrogramCutoffFactorChanged; + ConfigManager.EditorSpectrogramIntensityFactor.ValueChanged += OnSpectrogramIntensityFactorChanged; } /// @@ -465,17 +469,27 @@ public override void Draw(GameTime gameTime) var transformMatrix = Matrix.CreateTranslation(0, TrackPositionY, 0) * WindowManager.Scale; GameBase.Game.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, null, null, null, transformMatrix); - - if (ShowSpectrogram.Value) + + if (ShowSpectrogram.Value && ConfigManager.EditorSpectrogramLayer.Value == + EditorPlayfieldSpectrogramLayer.BehindTimingLines) Spectrogram?.Draw(gameTime); - + Timeline.Draw(gameTime); + if (ShowSpectrogram.Value && + ConfigManager.EditorSpectrogramLayer.Value == EditorPlayfieldSpectrogramLayer.BehindNotes) + Spectrogram?.Draw(gameTime); + if (ShowWaveform.Value) Waveform?.Draw(gameTime); LineContainer.Draw(gameTime); - DrawHitObjects(gameTime); + if (ShowSpectrogram.Value && + ConfigManager.EditorSpectrogramLayer.Value == EditorPlayfieldSpectrogramLayer.FrontMost) + Spectrogram?.Draw(gameTime); + else + DrawHitObjects(gameTime); + GameBase.Game.SpriteBatch.End(); // Draw the button on top of the hitobjects because it serves as a dimming @@ -534,6 +548,10 @@ public override void Destroy() WaveformFilter.ValueChanged -= OnWaveformFilterChanged; SpectrogramFftSize.ValueChanged -= OnSpectrogramFftSizeChanged; + ConfigManager.EditorSpectrogramMaximumFrequency.ValueChanged -= OnSpectrogramFrequencyWindowSizeChanged; + ConfigManager.EditorSpectrogramMinimumFrequency.ValueChanged -= OnSpectrogramMinimumFrequencyChanged; + ConfigManager.EditorSpectrogramCutoffFactor.ValueChanged -= OnSpectrogramCutoffFactorChanged; + ConfigManager.EditorSpectrogramIntensityFactor.ValueChanged -= OnSpectrogramIntensityFactorChanged; base.Destroy(); } @@ -1607,6 +1625,18 @@ private void OnWaveFormAudioDirectionChanged(object sender, private void OnSpectrogramFftSizeChanged(object sender, BindableValueChangedEventArgs e) => ReloadSpectrogram(); + private void OnSpectrogramFrequencyWindowSizeChanged(object sender, BindableValueChangedEventArgs e) + => ReloadSpectrogram(); + + private void OnSpectrogramMinimumFrequencyChanged(object sender, BindableValueChangedEventArgs e) + => ReloadSpectrogram(); + + private void OnSpectrogramCutoffFactorChanged(object sender, BindableValueChangedEventArgs e) + => ReloadSpectrogram(); + + private void OnSpectrogramIntensityFactorChanged(object sender, BindableValueChangedEventArgs e) + => ReloadSpectrogram(); + private void ReloadWaveform() { Waveform?.Destroy(); diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramLayer.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramLayer.cs new file mode 100644 index 0000000000..bd981c2d87 --- /dev/null +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramLayer.cs @@ -0,0 +1,8 @@ +namespace Quaver.Shared.Screens.Edit.UI.Playfield.Spectrogram; + +public enum EditorPlayfieldSpectrogramLayer +{ + BehindTimingLines, + BehindNotes, + FrontMost +} \ No newline at end of file diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs index a43918cf58..211f7e3652 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs @@ -29,7 +29,8 @@ public class EditorPlayfieldSpectrogramSlice : Sprite private int ReferenceWidth { get; } - public EditorPlayfieldSpectrogramSlice(EditorPlayfieldSpectrogram spectrogram, EditorPlayfield playfield, float lengthMs, int sliceSize, + public EditorPlayfieldSpectrogramSlice(EditorPlayfieldSpectrogram spectrogram, EditorPlayfield playfield, + float lengthMs, int sliceSize, float[,] sliceData, double sliceTime, int sampleRate) { @@ -82,28 +83,26 @@ private void CreateSlice(float[,] sliceData) { SliceSprite = new Sprite { Alpha = 0 }; - var textureHeight = SliceSize; + SliceTexture = new Texture2D(GameBase.Game.GraphicsDevice, ReferenceWidth, SliceSize); - SliceTexture = new Texture2D(GameBase.Game.GraphicsDevice, ReferenceWidth, textureHeight); + var dataColors = new Color[ReferenceWidth * SliceSize]; + Logger.Debug($"Slice {ReferenceWidth} x {SliceSize}", LogType.Runtime); - var dataColors = new Color[ReferenceWidth * textureHeight]; - Logger.Debug($"Slice {ReferenceWidth} x {textureHeight}", LogType.Runtime); - - for (var y = 0; y < textureHeight; y++) + for (var y = 0; y < SliceSize; y++) { for (var x = 0; x < Spectrogram.FftCount; x++) { var textureX = CalculateTextureX(x, Linear); if (textureX == -1) continue; var intensity = GetIntensity(sliceData, y, x); - var index = DataColorIndex(textureHeight, y, textureX); + var index = DataColorIndex(SliceSize, y, textureX); var nextTextureX = CalculateTextureX(x + 1, Linear); if (nextTextureX == -1) nextTextureX = ReferenceWidth - 1; var nextIntensity = x == Spectrogram.FftCount - 1 ? intensity : GetIntensity(sliceData, y, x + 1); var curColor = SpectrogramColormap.GetColor(intensity); - var nextDataColorIndex = DataColorIndex(textureHeight, y, nextTextureX); + var nextDataColorIndex = DataColorIndex(SliceSize, y, nextTextureX); for (var i = index; i < nextDataColorIndex && i < dataColors.Length; i++) { dataColors[i] = curColor; @@ -120,36 +119,23 @@ private void CreateSlice(float[,] sliceData) private float GetIntensity(float[,] sliceData, int y, int x) { - // var intensity = MathF.Sqrt(GetAverageData(sliceData, y, x)) * 3; // scale it (sqrt to make low values more visible) var rawIntensity = GetAverageData(sliceData, y, x); var db = MathF.Abs(rawIntensity) < 1e-4f ? -100 : 20 * MathF.Log10(rawIntensity); var intensity = Math.Clamp(1 + db / 100, 0f, 1f); - - var cutoffFactor = 0.3f; + + var cutoffFactor = ConfigManager.EditorSpectrogramCutoffFactor.Value; intensity = MathF.Max(intensity, cutoffFactor); intensity = (intensity - cutoffFactor) * (1 - cutoffFactor); - intensity *= intensity * 7.5f; + intensity *= intensity * ConfigManager.EditorSpectrogramIntensityFactor.Value; intensity = Sigmoid(Math.Clamp(intensity, 0, 1)); return intensity; } - private int CalculateTextureXLog(int x) - { - var minFrequency = (float)SampleRate / Spectrogram.FftCount; - const float maxFrequency = 10000; - var a = 1 / MathF.Log(maxFrequency / minFrequency); - var b = -a * MathF.Log(minFrequency); - var frequency = (float)x * SampleRate / Spectrogram.FftCount; - if (frequency < minFrequency || frequency > maxFrequency) return -1; - var processedProgress = a * MathF.Log(frequency) + b; - return (int)(processedProgress * ReferenceWidth); - } - private int CalculateTextureX(int x, Func transform) { - var minFrequency = transform(0); - var maxFrequency = transform(20000); + var minFrequency = transform(ConfigManager.EditorSpectrogramMinimumFrequency.Value); + var maxFrequency = transform(ConfigManager.EditorSpectrogramMaximumFrequency.Value); var frequency = (float)x * SampleRate / Spectrogram.FftCount; var transformedFrequency = transform(frequency); if (transformedFrequency > maxFrequency || transformedFrequency < minFrequency) return -1; @@ -162,7 +148,7 @@ private float Mel(float frequency) { return 2595 * MathF.Log10(1 + frequency / 700); } - + private float Erb1(float frequency) { return 6.23f * frequency * frequency / 1000 / 1000 + 93.39f * frequency / 1000 + 28.52f; @@ -172,7 +158,7 @@ private float Erb2(float frequency) { return 24.7f * (4.37f * frequency / 1000 + 1); } - + private float Sigmoid(float x) { return x < 0.2f ? 0 : (MathF.Tanh(x * 2 - 1) + 1) / 2; From 0cc993ea9c466d25f9ef50d92043383d6dabc5bc Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Wed, 10 Apr 2024 19:36:06 +0800 Subject: [PATCH 160/249] Support changing frequency scale of spectrogram --- Quaver.Shared/Config/ConfigManager.cs | 3 ++ .../Screens/Edit/UI/Menu/EditorFileMenuBar.cs | 32 ++++++++++++------- .../Edit/UI/Playfield/EditorPlayfield.cs | 5 +++ ...ditorPlayfieldSpectrogramFrequencyScale.cs | 9 ++++++ .../EditorPlayfieldSpectrogramSlice.cs | 14 ++++++-- 5 files changed, 50 insertions(+), 13 deletions(-) create mode 100644 Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramFrequencyScale.cs diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index 030dfe731b..0ba75abae9 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -550,6 +550,8 @@ public static class ConfigManager internal static Bindable EditorSpectrogramIntensityFactor { get; private set; } + internal static Bindable EditorSpectrogramFrequencyScale { get; private set; } + internal static BindableInt EditorSpectrogramFftSize { get; private set; } /// @@ -1099,6 +1101,7 @@ private static void ReadConfigFile() EditorSpectrogramLayer = ReadValue("EditorSpectrogramLayer", EditorPlayfieldSpectrogramLayer.BehindTimingLines, data); EditorSpectrogramCutoffFactor = ReadValue("EditorSpectrogramCutoffFactor", 0.3f, data); EditorSpectrogramIntensityFactor = ReadValue("EditorSpectrogramIntensityFactor", 7.5f, data); + EditorSpectrogramFrequencyScale = ReadValue("EditorSpectrogramFrequencyScale", EditorPlayfieldSpectrogramFrequencyScale.Linear, data); EditorSpectrogramFftSize = ReadInt(@"EditorSpectrumFftSize", 256, 256, 16384, data); EditorAudioDirection = ReadValue(@"EditorAudioDirection", EditorPlayfieldWaveformAudioDirection.Both, data); EditorWaveformColorR = ReadInt(@"EditorWaveformColorR", 0, 0, 255, data); diff --git a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs index 3c44cffb1f..f4e02260ca 100644 --- a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs +++ b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs @@ -517,6 +517,16 @@ private void CreateViewSection() } ImGui.EndMenu(); } + + if (ImGui.BeginMenu("Frequency Scale")) + { + foreach (var scale in Enum.GetValues()) + { + if (ImGui.MenuItem($"{scale}", "", scale == ConfigManager.EditorSpectrogramFrequencyScale.Value)) + ConfigManager.EditorSpectrogramFrequencyScale.Value = scale; + } + ImGui.EndMenu(); + } if (ImGui.BeginMenu("Cutoff Factor")) { @@ -540,17 +550,6 @@ private void CreateViewSection() ImGui.EndMenu(); } - if (ImGui.BeginMenu("Maximum Frequency")) - { - for (var f = 5000; f <= 10000; f += 1000) - { - if (ImGui.MenuItem($"{f}", "", ConfigManager.EditorSpectrogramMaximumFrequency.Value == f, - ConfigManager.EditorSpectrogramMinimumFrequency.Value < f)) - ConfigManager.EditorSpectrogramMaximumFrequency.Value = f; - } - ImGui.EndMenu(); - } - if (ImGui.BeginMenu("Minimum Frequency")) { for (var f = 0; f <= 1500; f += 125) @@ -562,6 +561,17 @@ private void CreateViewSection() ImGui.EndMenu(); } + if (ImGui.BeginMenu("Maximum Frequency")) + { + for (var f = 5000; f <= 10000; f += 1000) + { + if (ImGui.MenuItem($"{f}", "", ConfigManager.EditorSpectrogramMaximumFrequency.Value == f, + ConfigManager.EditorSpectrogramMinimumFrequency.Value < f)) + ConfigManager.EditorSpectrogramMaximumFrequency.Value = f; + } + ImGui.EndMenu(); + } + ImGui.EndMenu(); } diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs index 56413dd550..10c034fe5f 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs @@ -413,6 +413,7 @@ public EditorPlayfield(Qua map, EditorActionManager manager, Bindable ConfigManager.EditorSpectrogramMinimumFrequency.ValueChanged += OnSpectrogramMinimumFrequencyChanged; ConfigManager.EditorSpectrogramCutoffFactor.ValueChanged += OnSpectrogramCutoffFactorChanged; ConfigManager.EditorSpectrogramIntensityFactor.ValueChanged += OnSpectrogramIntensityFactorChanged; + ConfigManager.EditorSpectrogramFrequencyScale.ValueChanged += OnSpectrogramFrequencyScaleChanged; } /// @@ -552,6 +553,7 @@ public override void Destroy() ConfigManager.EditorSpectrogramMinimumFrequency.ValueChanged -= OnSpectrogramMinimumFrequencyChanged; ConfigManager.EditorSpectrogramCutoffFactor.ValueChanged -= OnSpectrogramCutoffFactorChanged; ConfigManager.EditorSpectrogramIntensityFactor.ValueChanged -= OnSpectrogramIntensityFactorChanged; + ConfigManager.EditorSpectrogramFrequencyScale.ValueChanged -= OnSpectrogramFrequencyScaleChanged; base.Destroy(); } @@ -1636,6 +1638,9 @@ private void OnSpectrogramCutoffFactorChanged(object sender, BindableValueChange private void OnSpectrogramIntensityFactorChanged(object sender, BindableValueChangedEventArgs e) => ReloadSpectrogram(); + + private void OnSpectrogramFrequencyScaleChanged(object sender, BindableValueChangedEventArgs e) + => ReloadSpectrogram(); private void ReloadWaveform() { diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramFrequencyScale.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramFrequencyScale.cs new file mode 100644 index 0000000000..66fea0898f --- /dev/null +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramFrequencyScale.cs @@ -0,0 +1,9 @@ +namespace Quaver.Shared.Screens.Edit.UI.Playfield.Spectrogram; + +public enum EditorPlayfieldSpectrogramFrequencyScale +{ + Mel, + Erb1, + Erb2, + Linear +} \ No newline at end of file diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs index 211f7e3652..aafb4776d5 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs @@ -29,6 +29,16 @@ public class EditorPlayfieldSpectrogramSlice : Sprite private int ReferenceWidth { get; } + private Func FrequencyTransform => + ConfigManager.EditorSpectrogramFrequencyScale.Value switch + { + EditorPlayfieldSpectrogramFrequencyScale.Mel => Mel, + EditorPlayfieldSpectrogramFrequencyScale.Erb1 => Erb1, + EditorPlayfieldSpectrogramFrequencyScale.Erb2 => Erb2, + EditorPlayfieldSpectrogramFrequencyScale.Linear => Linear, + _ => throw new ArgumentOutOfRangeException() + }; + public EditorPlayfieldSpectrogramSlice(EditorPlayfieldSpectrogram spectrogram, EditorPlayfield playfield, float lengthMs, int sliceSize, float[,] sliceData, @@ -92,11 +102,11 @@ private void CreateSlice(float[,] sliceData) { for (var x = 0; x < Spectrogram.FftCount; x++) { - var textureX = CalculateTextureX(x, Linear); + var textureX = CalculateTextureX(x, FrequencyTransform); if (textureX == -1) continue; var intensity = GetIntensity(sliceData, y, x); var index = DataColorIndex(SliceSize, y, textureX); - var nextTextureX = CalculateTextureX(x + 1, Linear); + var nextTextureX = CalculateTextureX(x + 1, FrequencyTransform); if (nextTextureX == -1) nextTextureX = ReferenceWidth - 1; var nextIntensity = x == Spectrogram.FftCount - 1 ? intensity From b6785a0bae689d1a58802dc76b7574c68cceede5 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Wed, 10 Apr 2024 19:42:48 +0800 Subject: [PATCH 161/249] Change default settings for spectrogram --- Quaver.Shared/Config/ConfigManager.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index 0ba75abae9..915da97086 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -1096,13 +1096,13 @@ private static void ReadConfigFile() EditorAudioFilter = ReadValue(@"EditorAudioFilter", EditorPlayfieldWaveformFilter.None, data); EditorShowWaveform = ReadValue(@"EditorShowWaveform", true, data); EditorShowSpectrogram = ReadValue(@"EditorShowSpectrogram", false, data); - EditorSpectrogramMaximumFrequency = ReadInt(@"EditorSpectrogramMaximumFrequency", 10000, 2500, 20000, data); - EditorSpectrogramMinimumFrequency = ReadInt("EditorSpectrogramMinimumFrequency", 0, 0, 17500, data); + EditorSpectrogramMaximumFrequency = ReadInt(@"EditorSpectrogramMaximumFrequency", 7000, 5000, 10000, data); + EditorSpectrogramMinimumFrequency = ReadInt("EditorSpectrogramMinimumFrequency", 250, 0, 1500, data); EditorSpectrogramLayer = ReadValue("EditorSpectrogramLayer", EditorPlayfieldSpectrogramLayer.BehindTimingLines, data); - EditorSpectrogramCutoffFactor = ReadValue("EditorSpectrogramCutoffFactor", 0.3f, data); - EditorSpectrogramIntensityFactor = ReadValue("EditorSpectrogramIntensityFactor", 7.5f, data); + EditorSpectrogramCutoffFactor = ReadValue("EditorSpectrogramCutoffFactor", 0.34f, data); + EditorSpectrogramIntensityFactor = ReadValue("EditorSpectrogramIntensityFactor", 9.5f, data); EditorSpectrogramFrequencyScale = ReadValue("EditorSpectrogramFrequencyScale", EditorPlayfieldSpectrogramFrequencyScale.Linear, data); - EditorSpectrogramFftSize = ReadInt(@"EditorSpectrumFftSize", 256, 256, 16384, data); + EditorSpectrogramFftSize = ReadInt(@"EditorSpectrumFftSize", 512, 256, 16384, data); EditorAudioDirection = ReadValue(@"EditorAudioDirection", EditorPlayfieldWaveformAudioDirection.Both, data); EditorWaveformColorR = ReadInt(@"EditorWaveformColorR", 0, 0, 255, data); EditorWaveformColorG = ReadInt(@"EditorWaveformColorG", 200, 0, 255, data); From 0238ce1a4ad27b01af024137741dc900c9e76f8b Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Wed, 10 Apr 2024 19:55:27 +0800 Subject: [PATCH 162/249] Make namespaces scoped --- .../EditorPlayfieldSpectrogramFrequencyScale.cs | 15 ++++++++------- .../EditorPlayfieldSpectrogramLayer.cs | 13 +++++++------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramFrequencyScale.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramFrequencyScale.cs index 66fea0898f..247712bfd9 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramFrequencyScale.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramFrequencyScale.cs @@ -1,9 +1,10 @@ -namespace Quaver.Shared.Screens.Edit.UI.Playfield.Spectrogram; - -public enum EditorPlayfieldSpectrogramFrequencyScale +namespace Quaver.Shared.Screens.Edit.UI.Playfield.Spectrogram { - Mel, - Erb1, - Erb2, - Linear + public enum EditorPlayfieldSpectrogramFrequencyScale + { + Mel, + Erb1, + Erb2, + Linear + } } \ No newline at end of file diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramLayer.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramLayer.cs index bd981c2d87..dabf29f9aa 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramLayer.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramLayer.cs @@ -1,8 +1,9 @@ -namespace Quaver.Shared.Screens.Edit.UI.Playfield.Spectrogram; - -public enum EditorPlayfieldSpectrogramLayer +namespace Quaver.Shared.Screens.Edit.UI.Playfield.Spectrogram { - BehindTimingLines, - BehindNotes, - FrontMost + public enum EditorPlayfieldSpectrogramLayer + { + BehindTimingLines, + BehindNotes, + FrontMost + } } \ No newline at end of file From d3c978a72d84a58d6faa868e62990c97f9590b34 Mon Sep 17 00:00:00 2001 From: Cuckson <31227094+Cuckson@users.noreply.github.com> Date: Wed, 10 Apr 2024 14:24:42 +0100 Subject: [PATCH 163/249] Keep Custom Beat Snaps when entering/exiting Test Play --- Quaver.Shared/Screens/Edit/EditScreen.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index d4d4e9a371..d176d05c8e 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -110,7 +110,7 @@ public sealed class EditScreen : QuaverScreen, IHasLeftPanel /// /// All of the available beat snaps to use in the editor. /// - public List AvailableBeatSnaps { get; } = new List { 1, 2, 3, 4, 6, 8, 12, 16 }; + public static List AvailableBeatSnaps { get; set; } = new List { 1, 2, 3, 4, 6, 8, 12, 16 }; /// /// @@ -926,6 +926,11 @@ public void ChangeBeatSnap(Direction direction) } } + /// + /// Removes All Custom Beat Snaps added by the user in CustomBeatSnapDialog. + /// + private void RemoveCustomBeatSnaps() => AvailableBeatSnaps = new List { 1, 2, 3, 4, 6, 8, 12, 16 }; + /// /// private void HandleKeyPressPlayfieldZoom() @@ -1341,7 +1346,7 @@ public void RefreshFileCache() } /// - /// Exits the enditor and returns to song select + /// Exits the editor and returns to song select /// public void LeaveEditor() { @@ -1362,6 +1367,7 @@ public void ExitToSongSelect() GameBase.Game.GlobalUserInterface.Cursor.Alpha = 1; ModManager.RemoveAllMods(); + RemoveCustomBeatSnaps(); Exit(() => new SelectionScreen()); } From 73ce05280737d1ca75e5dffbf429eb8ad506e42c Mon Sep 17 00:00:00 2001 From: Cuckson <31227094+Cuckson@users.noreply.github.com> Date: Wed, 10 Apr 2024 14:25:31 +0100 Subject: [PATCH 164/249] Replace instance references --- Quaver.Shared/Screens/Edit/UI/Footer/IconTextButtonBeatSnap.cs | 2 +- Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Footer/IconTextButtonBeatSnap.cs b/Quaver.Shared/Screens/Edit/UI/Footer/IconTextButtonBeatSnap.cs index 306f686431..90cd7c9f8a 100644 --- a/Quaver.Shared/Screens/Edit/UI/Footer/IconTextButtonBeatSnap.cs +++ b/Quaver.Shared/Screens/Edit/UI/Footer/IconTextButtonBeatSnap.cs @@ -15,7 +15,7 @@ public class IconTextButtonBeatSnap : IconTextButton { public IconTextButtonBeatSnap(EditScreen screen) : base(FontAwesome.Get(FontAwesomeIcon.fa_sun), FontManager.GetWobbleFont(Fonts.LatoBlack),"Beat Snap", - (sender, args) => screen?.ActivateRightClickOptions(new BeatSnapRightClickOptions(screen.BeatSnap, screen.AvailableBeatSnaps))) + (sender, args) => screen?.ActivateRightClickOptions(new BeatSnapRightClickOptions(screen.BeatSnap, EditScreen.AvailableBeatSnaps))) { var tooltip = new Tooltip("Change the current beat snap divisor.\n" + "Hotkeys: CTRL + Up/Down/Scroll Wheel", ColorHelper.HexToColor("#808080")); diff --git a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs index fc06f9005f..abc34bdb51 100644 --- a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs +++ b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs @@ -408,7 +408,7 @@ private void CreateViewSection() if (ImGui.BeginMenu("Beat Snap Divisor")) { - foreach (var snap in Screen.AvailableBeatSnaps) + foreach (var snap in EditScreen.AvailableBeatSnaps) { if (ImGui.MenuItem($"1/{StringHelper.AddOrdinal(snap)}", "", Screen.BeatSnap.Value == snap)) Screen.BeatSnap.Value = snap; From 55e7e2330ed93c4c0ba753b53a62d2d608092dcc Mon Sep 17 00:00:00 2001 From: Warp Date: Wed, 10 Apr 2024 15:42:18 +0200 Subject: [PATCH 165/249] Update Quaver.API --- Quaver.API | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.API b/Quaver.API index 783211ee9a..9d206592b0 160000 --- a/Quaver.API +++ b/Quaver.API @@ -1 +1 @@ -Subproject commit 783211ee9a1c5e12fa2a18c855d5017ad8404b69 +Subproject commit 9d206592b0e02fb3d9c3e6d6df2b507e3d7c4fc1 From f4d5f5fcd4ec51d22a14e206ed68baa0a5755a77 Mon Sep 17 00:00:00 2001 From: AiAe Date: Wed, 10 Apr 2024 16:55:07 +0300 Subject: [PATCH 166/249] Update server client --- Quaver.Server.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Server.Client b/Quaver.Server.Client index fc07fde9ce..05b8650e0e 160000 --- a/Quaver.Server.Client +++ b/Quaver.Server.Client @@ -1 +1 @@ -Subproject commit fc07fde9ce4f89e7a3873c006cab97fa3549f3d4 +Subproject commit 05b8650e0e93b1d40011a90bc795be79c0072473 From ec09dc2de0a370956fda972495972b160304f92b Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Wed, 10 Apr 2024 22:01:47 +0800 Subject: [PATCH 167/249] Draw hit position line frontmost --- Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs index 10c034fe5f..3b704bcbe7 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs @@ -501,6 +501,8 @@ public override void Draw(GameTime gameTime) if (ShowSpectrogram.Value) LoadingSpectrogram?.Draw(gameTime); + + HitPositionLine?.Draw(gameTime); } /// @@ -604,7 +606,6 @@ private void CreateDividerLines() /// private void CreateHitPositionLine() => HitPositionLine = new Sprite { - Parent = this, Alignment = Alignment.TopCenter, Y = HitPositionY, Size = new ScalableVector2(Width - BorderLeft.Width * 2, 6), From 82737c3b0192e9755866995c68a696edecf2b853 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Wed, 10 Apr 2024 23:21:19 +0800 Subject: [PATCH 168/249] Mark bookmark actions MoonSharpUserData --- .../Edit/Actions/Bookmarks/Add/EditorActionAddBookmark.cs | 2 ++ .../Edit/Actions/Bookmarks/Edit/EditorActionEditBookmark.cs | 6 ++++++ .../Offset/EditorActionChangeBookmarkOffsetBatch.cs | 6 ++++++ .../Actions/Bookmarks/Remove/EditorActionRemoveBookmark.cs | 4 ++++ 4 files changed, 18 insertions(+) diff --git a/Quaver.Shared/Screens/Edit/Actions/Bookmarks/Add/EditorActionAddBookmark.cs b/Quaver.Shared/Screens/Edit/Actions/Bookmarks/Add/EditorActionAddBookmark.cs index f98db0475e..cf904a9876 100644 --- a/Quaver.Shared/Screens/Edit/Actions/Bookmarks/Add/EditorActionAddBookmark.cs +++ b/Quaver.Shared/Screens/Edit/Actions/Bookmarks/Add/EditorActionAddBookmark.cs @@ -1,3 +1,4 @@ +using MoonSharp.Interpreter; using MoonSharp.Interpreter.Interop; using Quaver.API.Maps; using Quaver.API.Maps.Structures; @@ -5,6 +6,7 @@ namespace Quaver.Shared.Screens.Edit.Actions.Bookmarks.Add { + [MoonSharpUserData] public class EditorActionAddBookmark : IEditorAction { public EditorActionType Type { get; } = EditorActionType.AddBookmark; diff --git a/Quaver.Shared/Screens/Edit/Actions/Bookmarks/Edit/EditorActionEditBookmark.cs b/Quaver.Shared/Screens/Edit/Actions/Bookmarks/Edit/EditorActionEditBookmark.cs index 398de21a01..91397b850e 100644 --- a/Quaver.Shared/Screens/Edit/Actions/Bookmarks/Edit/EditorActionEditBookmark.cs +++ b/Quaver.Shared/Screens/Edit/Actions/Bookmarks/Edit/EditorActionEditBookmark.cs @@ -1,8 +1,11 @@ +using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Interop; using Quaver.API.Maps; using Quaver.API.Maps.Structures; namespace Quaver.Shared.Screens.Edit.Actions.Bookmarks.Edit { + [MoonSharpUserData] public class EditorActionEditBookmark : IEditorAction { public EditorActionType Type { get; } = EditorActionType.EditBookmark; @@ -17,6 +20,7 @@ public class EditorActionEditBookmark : IEditorAction private string OldNote { get; } + [MoonSharpVisible(false)] public EditorActionEditBookmark(EditorActionManager manager, Qua workingMap, BookmarkInfo bookmark, string newNote) { ActionManager = manager; @@ -26,12 +30,14 @@ public EditorActionEditBookmark(EditorActionManager manager, Qua workingMap, Boo OldNote = Bookmark.Note; } + [MoonSharpVisible(false)] public void Perform() { Bookmark.Note = NewNote; ActionManager.TriggerEvent(Type, new EditorActionBookmarkEditedEventArgs(Bookmark)); } + [MoonSharpVisible(false)] public void Undo() => new EditorActionEditBookmark(ActionManager, WorkingMap, Bookmark, OldNote).Perform(); } } \ No newline at end of file diff --git a/Quaver.Shared/Screens/Edit/Actions/Bookmarks/Offset/EditorActionChangeBookmarkOffsetBatch.cs b/Quaver.Shared/Screens/Edit/Actions/Bookmarks/Offset/EditorActionChangeBookmarkOffsetBatch.cs index 8547950c95..8f7df7edac 100644 --- a/Quaver.Shared/Screens/Edit/Actions/Bookmarks/Offset/EditorActionChangeBookmarkOffsetBatch.cs +++ b/Quaver.Shared/Screens/Edit/Actions/Bookmarks/Offset/EditorActionChangeBookmarkOffsetBatch.cs @@ -1,9 +1,12 @@ using System.Collections.Generic; +using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Interop; using Quaver.API.Maps; using Quaver.API.Maps.Structures; namespace Quaver.Shared.Screens.Edit.Actions.Bookmarks.Offset { + [MoonSharpUserData] public class EditorActionChangeBookmarkOffsetBatch : IEditorAction { public EditorActionType Type { get; } = EditorActionType.ChangeBookmarkOffsetBatch; @@ -16,6 +19,7 @@ public class EditorActionChangeBookmarkOffsetBatch : IEditorAction private int Offset { get; } + [MoonSharpVisible(false)] public EditorActionChangeBookmarkOffsetBatch(EditorActionManager manager, Qua map, List bookmarks, int offset) { ActionManager = manager; @@ -24,6 +28,7 @@ public EditorActionChangeBookmarkOffsetBatch(EditorActionManager manager, Qua ma Offset = offset; } + [MoonSharpVisible(false)] public void Perform() { foreach (var bookmark in Bookmarks) @@ -33,6 +38,7 @@ public void Perform() ActionManager.TriggerEvent(Type, new EditorActionChangeBookmarkOffsetBatchEventArgs(Bookmarks, Offset)); } + [MoonSharpVisible(false)] public void Undo() => new EditorActionChangeBookmarkOffsetBatch(ActionManager, WorkingMap, Bookmarks, -Offset).Perform(); } } \ No newline at end of file diff --git a/Quaver.Shared/Screens/Edit/Actions/Bookmarks/Remove/EditorActionRemoveBookmark.cs b/Quaver.Shared/Screens/Edit/Actions/Bookmarks/Remove/EditorActionRemoveBookmark.cs index 1489670b75..d7cfa1ca97 100644 --- a/Quaver.Shared/Screens/Edit/Actions/Bookmarks/Remove/EditorActionRemoveBookmark.cs +++ b/Quaver.Shared/Screens/Edit/Actions/Bookmarks/Remove/EditorActionRemoveBookmark.cs @@ -1,3 +1,4 @@ +using MoonSharp.Interpreter.Interop; using Quaver.API.Maps; using Quaver.API.Maps.Structures; using Quaver.Shared.Screens.Edit.Actions.Bookmarks.Add; @@ -14,6 +15,7 @@ public class EditorActionRemoveBookmark : IEditorAction private BookmarkInfo Bookmark { get; } + [MoonSharpVisible(false)] public EditorActionRemoveBookmark(EditorActionManager manager, Qua map, BookmarkInfo bookmark) { ActionManager = manager; @@ -21,6 +23,7 @@ public EditorActionRemoveBookmark(EditorActionManager manager, Qua map, Bookmark Bookmark = bookmark; } + [MoonSharpVisible(false)] public void Perform() { WorkingMap.Bookmarks.Remove(Bookmark); @@ -29,6 +32,7 @@ public void Perform() ActionManager.TriggerEvent(Type, new EditorActionBookmarkRemovedEventArgs(Bookmark)); } + [MoonSharpVisible(false)] public void Undo() => new EditorActionAddBookmark(ActionManager, WorkingMap, Bookmark).Perform(); } } \ No newline at end of file From 38d428195b5ce4ecce72b3affa2d55159db94e7f Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Wed, 10 Apr 2024 23:25:09 +0800 Subject: [PATCH 169/249] Default KeepPlayingUponFailing to false --- Quaver.Shared/Config/ConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index 5f655af6cb..e66a9db30c 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -1026,7 +1026,7 @@ private static void ReadConfigFile() KeyQuickExit = ReadValue(@"KeyQuickExit", Keys.F1, data); KeyScreenshot = ReadValue(@"KeyScreenshot", Keys.F12, data); TapToPause = ReadValue(@"TapToPause", false, data); - KeepPlayingUponFailing = ReadValue(@"KeepPlayingUponFailing", true, data); + KeepPlayingUponFailing = ReadValue(@"KeepPlayingUponFailing", false, data); TapToRestart = ReadValue(@"TapToRestart", false, data); DisplayFailedLocalScores = ReadValue(@"DisplayFailedLocalScores", true, data); EditorScrollSpeedKeys = ReadInt(@"EditorScrollSpeedKeys", 16, 5, 100, data); From c84d94fba433a7b7758708c44b2260678984e78e Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Wed, 10 Apr 2024 20:11:29 +0300 Subject: [PATCH 170/249] Add missing parent for hit position line --- Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs index d1c6d3a032..b71ae6d56e 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs @@ -609,6 +609,7 @@ private void CreateDividerLines() /// private void CreateHitPositionLine() => HitPositionLine = new Sprite { + Parent = this, Alignment = Alignment.TopCenter, Y = HitPositionY, Size = new ScalableVector2(Width - BorderLeft.Width * 2, 6), @@ -1673,4 +1674,4 @@ private void ReloadSpectrogram() SpectrogramLoadTask.Run(0); } } -} \ No newline at end of file +} From 28c6f8d587705c013e6e2b4e6bbf1e730696310b Mon Sep 17 00:00:00 2001 From: Emik Date: Wed, 10 Apr 2024 19:36:05 +0200 Subject: [PATCH 171/249] Fix race conditions --- Quaver.Shared/Screens/Edit/EditScreen.cs | 4 ++-- .../Edit/UI/Playfield/Selection/EditorRectangleSelector.cs | 2 +- .../UI/Selected/Table/DrawableMultiplayerTableItem.cs | 2 +- .../Screens/Options/Content/OptionsContentContainer.cs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index caaaf9aace..6dbe3fa555 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -472,11 +472,11 @@ private void HandleInput() // To not conflict with the volume controller if (!KeyboardManager.IsAltDown() && !KeyboardManager.IsCtrlDown()) { - var dropdownHovered = ButtonManager.Buttons.Any( + var dropdownHovered = ButtonManager.Buttons.Find( x => x is DropdownItem item && GraphicsHelper.RectangleContains(x.ScreenRectangle, MouseManager.CurrentState.Position) && item.Dropdown.Opened - ); + ) is not null; if (!dropdownHovered) { diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Selection/EditorRectangleSelector.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Selection/EditorRectangleSelector.cs index 06949a56f4..a55219e338 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Selection/EditorRectangleSelector.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Selection/EditorRectangleSelector.cs @@ -126,7 +126,7 @@ private void HandleButtonInitiallyPressed() if (Playfield.GetHoveredHitObject() != null) return; - if (ButtonManager.Buttons.Any(x => x.IsHovered) && ! Playfield.Button.IsHovered) + if (ButtonManager.Buttons.Find(x => x.IsHovered) is not null && ! Playfield.Button.IsHovered) return; var clickArea = new RectangleF(Playfield.ScreenRectangle.X - 300, Playfield.ScreenRectangle.Y, diff --git a/Quaver.Shared/Screens/MultiplayerLobby/UI/Selected/Table/DrawableMultiplayerTableItem.cs b/Quaver.Shared/Screens/MultiplayerLobby/UI/Selected/Table/DrawableMultiplayerTableItem.cs index 900e374954..04bbdc383b 100644 --- a/Quaver.Shared/Screens/MultiplayerLobby/UI/Selected/Table/DrawableMultiplayerTableItem.cs +++ b/Quaver.Shared/Screens/MultiplayerLobby/UI/Selected/Table/DrawableMultiplayerTableItem.cs @@ -173,7 +173,7 @@ private bool IsSelectorHovered() /// private void HandleClick() { - if (!IsHovered() || IsSelectorHovered() || DialogManager.Dialogs.Count != 0 || ButtonManager.Buttons.Any(x => x.IsHovered)) + if (!IsHovered() || IsSelectorHovered() || DialogManager.Dialogs.Count != 0 || ButtonManager.Buttons.Find(x => x.IsHovered) is not null) return; if (!MouseManager.IsUniqueClick(MouseButton.Left)) diff --git a/Quaver.Shared/Screens/Options/Content/OptionsContentContainer.cs b/Quaver.Shared/Screens/Options/Content/OptionsContentContainer.cs index c6b0bc5656..4d99825d26 100644 --- a/Quaver.Shared/Screens/Options/Content/OptionsContentContainer.cs +++ b/Quaver.Shared/Screens/Options/Content/OptionsContentContainer.cs @@ -54,9 +54,9 @@ public OptionsContentContainer(OptionsSection section, ScalableVector2 size) : b /// public override void Update(GameTime gameTime) { - var dropdownHovered = ButtonManager.Buttons.Any(x => x is DropdownItem item && + var dropdownHovered = ButtonManager.Buttons.Find(x => x is DropdownItem item && GraphicsHelper.RectangleContains(x.ScreenRectangle, MouseManager.CurrentState.Position) && - item.Dropdown.Opened); + item.Dropdown.Opened) is not null; InputEnabled = GraphicsHelper.RectangleContains(ScreenRectangle, MouseManager.CurrentState.Position) && !KeyboardManager.CurrentState.IsKeyDown(Keys.LeftAlt) From 129c75f18e6b10d4de4a75d302bbfca650505b19 Mon Sep 17 00:00:00 2001 From: Cuckson <31227094+Cuckson@users.noreply.github.com> Date: Wed, 10 Apr 2024 19:18:09 +0100 Subject: [PATCH 172/249] add new editor scrolling option --- Quaver.Shared/Screens/Options/OptionsMenu.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Options/OptionsMenu.cs b/Quaver.Shared/Screens/Options/OptionsMenu.cs index 13adfde5d0..40d7a6cf65 100644 --- a/Quaver.Shared/Screens/Options/OptionsMenu.cs +++ b/Quaver.Shared/Screens/Options/OptionsMenu.cs @@ -387,7 +387,8 @@ private void CreateSections() { new OptionsItemKeybind(containerRect, "Pause/Play Track", ConfigManager.KeyEditorPausePlay), new OptionsItemKeybind(containerRect, "Decrease Playback Rate", ConfigManager.KeyEditorDecreaseAudioRate), - new OptionsItemKeybind(containerRect, "Increase Playback Rate", ConfigManager.KeyEditorIncreaseAudioRate) + new OptionsItemKeybind(containerRect, "Increase Playback Rate", ConfigManager.KeyEditorIncreaseAudioRate), + new OptionsItemCheckbox(containerRect, "Invert Editor Scrolling", ConfigManager.InvertEditorScrolling) }), new OptionsSubcategory("Misc", new List() { From c5a48713caadd6cf439c8d542fd32e7517d00ab8 Mon Sep 17 00:00:00 2001 From: Cuckson <31227094+Cuckson@users.noreply.github.com> Date: Wed, 10 Apr 2024 19:18:44 +0100 Subject: [PATCH 173/249] use new editor invert scrolling in Editor --- Quaver.Shared/Graphics/Graphs/DifficultySeekBar.cs | 4 ++-- Quaver.Shared/Screens/Edit/EditScreen.cs | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Quaver.Shared/Graphics/Graphs/DifficultySeekBar.cs b/Quaver.Shared/Graphics/Graphs/DifficultySeekBar.cs index 24ec89159b..2fd06667ce 100644 --- a/Quaver.Shared/Graphics/Graphs/DifficultySeekBar.cs +++ b/Quaver.Shared/Graphics/Graphs/DifficultySeekBar.cs @@ -126,9 +126,9 @@ public override void Update(GameTime gameTime) (game?.CurrentScreen?.Type == QuaverScreenType.Select || game?.CurrentScreen?.Type == QuaverScreenType.Multiplayer)) { - if (MouseManager.IsScrollingUp(ConfigManager.InvertScrolling.Value)) + if (MouseManager.IsScrollingUp(ConfigManager.InvertEditorScrolling.Value)) SeekInDirection(Direction.Forward); - else if (MouseManager.IsScrollingDown(ConfigManager.InvertScrolling.Value)) + else if (MouseManager.IsScrollingDown(ConfigManager.InvertEditorScrolling.Value)) SeekInDirection(Direction.Backward); } diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index f51d3cbfdc..ee4e38aff6 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -205,6 +205,10 @@ public sealed class EditScreen : QuaverScreen, IHasLeftPanel /// public Bindable LiveMapping { get; } = ConfigManager.EditorLiveMapping ?? new Bindable(true); + /// + /// + public Bindable InvertBeatSnapScroll { get; } = ConfigManager.EditorInvertBeatSnapScroll ?? new Bindable(false); + /// /// private Metronome Metronome { get; } @@ -695,7 +699,7 @@ private void HandleSeekingBackwards() { var leftPressed = KeyboardManager.IsUniqueKeyPress(Keys.Left); - if (!leftPressed && !MouseManager.IsScrollingDown(ConfigManager.InvertScrolling.Value)) + if (!leftPressed && !MouseManager.IsScrollingDown(ConfigManager.InvertEditorScrolling.Value)) return; if (Track == null || Track.IsDisposed || (!CanSeek() && !leftPressed)) @@ -710,7 +714,7 @@ private void HandleSeekingForwards() { var rightPressed = KeyboardManager.IsUniqueKeyPress(Keys.Right); - if (!rightPressed && !MouseManager.IsScrollingUp(ConfigManager.InvertScrolling.Value)) + if (!rightPressed && !MouseManager.IsScrollingUp(ConfigManager.InvertEditorScrolling.Value)) return; if (Track == null || Track.IsDisposed || (!CanSeek() && !rightPressed)) @@ -723,8 +727,8 @@ private void HandleSeekingForwards() /// private void HandleBeatSnapChanges() { - var scrolledForward = MouseManager.IsScrollingUp(ConfigManager.InvertScrolling.Value); - var scrolledBackward = MouseManager.IsScrollingDown(ConfigManager.InvertScrolling.Value); + var scrolledForward = MouseManager.IsScrollingUp(InvertBeatSnapScroll.Value); + var scrolledBackward = MouseManager.IsScrollingDown(InvertBeatSnapScroll.Value); if (KeyboardManager.IsCtrlDown() && (scrolledForward || KeyboardManager.IsUniqueKeyPress(Keys.Down))) ChangeBeatSnap(Direction.Forward); From 39cab2e2c4a5293b98ad774a47464b1259bf398f Mon Sep 17 00:00:00 2001 From: Cuckson <31227094+Cuckson@users.noreply.github.com> Date: Wed, 10 Apr 2024 19:19:06 +0100 Subject: [PATCH 174/249] add Invert Beat Snap Scroll as a separate option --- Quaver.Shared/Config/ConfigManager.cs | 14 +++++++++++++- .../Screens/Edit/UI/Menu/EditorFileMenuBar.cs | 3 +++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index 8d34a1d226..77906aedcc 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -592,6 +592,10 @@ public static class ConfigManager /// internal static Bindable EditorLiveMapping { get; private set; } + /// + /// + internal static Bindable EditorInvertBeatSnapScroll { get; private set; } + /// /// internal static BindableInt EditorLongNoteOpacity { get; private set; } @@ -644,6 +648,7 @@ public static class ConfigManager /// internal static Bindable DisplayNotificationsInGameplay { get; private set; } + /// /// internal static Bindable DisplayPauseWarning { get; private set; } @@ -820,10 +825,15 @@ public static class ConfigManager internal static Bindable KeyEditorIncreaseAudioRate { get; private set; } /// - /// Whether scrolling in editor is inverted. + /// Whether global scrolling is inverted. /// internal static Bindable InvertScrolling { get; private set; } + /// + /// Whether scrolling in editor is inverted. + /// + internal static Bindable InvertEditorScrolling { get; private set; } + /// /// internal static Bindable KeyScreenshot { get; private set; } @@ -1057,6 +1067,7 @@ private static void ReadConfigFile() KeyEditorDecreaseAudioRate = ReadValue(@"KeyEditorDecreaseAudioRate", Keys.OemMinus, data); KeyEditorIncreaseAudioRate = ReadValue(@"KeyEditorIncreaseAudioRate", Keys.OemPlus, data); InvertScrolling = ReadValue(@"InvertScrolling", false, data); + InvertEditorScrolling = ReadValue(@"InvertEditorScrolling", true, data); EditorEnableHitsounds = ReadValue(@"EditorEnableHitsounds", true, data); EditorEnableKeysounds = ReadValue(@"EditorEnableKeysounds", true, data); EditorBeatSnapColorType = ReadValue(@"EditorBeatSnapColorType", EditorBeatSnapColor.Default, data); @@ -1109,6 +1120,7 @@ private static void ReadConfigFile() GameplayNoteScale = ReadInt(@"GameplayNoteScale", 100, 25, 100, data); EditorDisplayGameplayPreview = ReadValue(@"EditorDisplayGameplayPreview", false, data); EditorPlaceObjectsOnNearestTick = ReadValue(@"EditorPlaceObjectsOnNearestTick", true, data); + EditorInvertBeatSnapScroll = ReadValue(@"EditorInvertBeatSnapScroll", false, data); EditorLiveMapping = ReadValue(@"EditorLiveMapping", true, data); EditorAudioFilter = ReadValue(@"EditorAudioFilter", EditorPlayfieldWaveformFilter.None, data); EditorShowWaveform = ReadValue(@"EditorShowWaveform", true, data); diff --git a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs index 091e8b7c83..a824b6f3c3 100644 --- a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs +++ b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs @@ -461,6 +461,9 @@ private void CreateViewSection() if (ImGui.MenuItem("Place Objects With Top Row Numbers", "", Screen.LiveMapping.Value)) Screen.LiveMapping.Value = !Screen.LiveMapping.Value; + if (ImGui.MenuItem("Invert Beat Snap Scroll", "", Screen.InvertBeatSnapScroll.Value)) + Screen.InvertBeatSnapScroll.Value = !Screen.InvertBeatSnapScroll.Value; + ImGui.Separator(); if (ImGui.BeginMenu("Waveform")) From 5d1dcb727d1e894038f6f93750c63477296bdad2 Mon Sep 17 00:00:00 2001 From: Cuckson <31227094+Cuckson@users.noreply.github.com> Date: Wed, 10 Apr 2024 20:50:06 +0100 Subject: [PATCH 175/249] Update ConfigManager.cs --- Quaver.Shared/Config/ConfigManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index 77906aedcc..a5337b1364 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -647,7 +647,6 @@ public static class ConfigManager /// /// internal static Bindable DisplayNotificationsInGameplay { get; private set; } - /// /// From 2c284694b195b296c441a4f38a760784c5294e9d Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Thu, 11 Apr 2024 08:48:12 +0800 Subject: [PATCH 176/249] Fallback to replay result if api fails --- .../Table/ResultsMultiplayerTable.cs | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs index c50e2289b5..085cb68023 100644 --- a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs +++ b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs @@ -237,20 +237,23 @@ private int GetMatchScores(int val, CancellationToken cancellationToken) Thread.Sleep(500); } + List players; if (matchInfoResponse == null) { NotificationManager.Show(NotificationLevel.Error, "Failed to retrieve players' scores!"); - return 0; + players = GetOrderedUserList(); } - - List players = new(); - var qua = Map.LoadQua(); - - foreach (var playerScore in matchInfoResponse.Match.Scores) + else { - var player = playerScore.Player; - var score = playerScore.Score; - var processor = new ScoreProcessorKeys(Map.Qua, score.Mods, new ScoreProcessorMultiplayer(MultiplayerHealthType.Lives, score.LivesLeft)) + players = new List(); + var qua = Map.LoadQua(); + + foreach (var playerScore in matchInfoResponse.Match.Scores) + { + var player = playerScore.Player; + var score = playerScore.Score; + var processor = new ScoreProcessorKeys(Map.Qua, score.Mods, + new ScoreProcessorMultiplayer(MultiplayerHealthType.Lives, score.LivesLeft)) { PlayerName = player.Username, UserId = player.Id, @@ -272,12 +275,13 @@ private int GetMatchScores(int val, CancellationToken cancellationToken) } }; - players.Add(processor); - } + players.Add(processor); + } - players = players.OrderByDescending(x => - new RatingProcessorKeys(qua.SolveDifficulty(x.Mods, true).OverallDifficulty).CalculateRating(x)) - .ToList(); + players = players.OrderByDescending(x => + new RatingProcessorKeys(qua.SolveDifficulty(x.Mods, true).OverallDifficulty).CalculateRating(x)) + .ToList(); + } ScrollContainer = new ResultsMultiplayerScrollContainer(new ScalableVector2(Width, Height - HeaderContainer.Height), players, Game, Headers, Map) From 4d5ab3ef5853a97c9dc0a98cb6eb48e2c30b11a8 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Thu, 11 Apr 2024 09:19:19 +0800 Subject: [PATCH 177/249] Fix hit position line draw order --- .../Screens/Edit/UI/Playfield/EditorPlayfield.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs index b71ae6d56e..89108d514f 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs @@ -471,6 +471,7 @@ public override void Draw(GameTime gameTime) var transformMatrix = Matrix.CreateTranslation(0, TrackPositionY, 0) * WindowManager.Scale; + HitPositionLine.Y = HitPositionY - TrackPositionY; GameBase.Game.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, null, null, null, transformMatrix); if (ShowSpectrogram.Value && ConfigManager.EditorSpectrogramLayer.Value == @@ -489,9 +490,15 @@ public override void Draw(GameTime gameTime) LineContainer.Draw(gameTime); if (ShowSpectrogram.Value && ConfigManager.EditorSpectrogramLayer.Value == EditorPlayfieldSpectrogramLayer.FrontMost) + { Spectrogram?.Draw(gameTime); + HitPositionLine?.Draw(gameTime); + } else + { + HitPositionLine?.Draw(gameTime); DrawHitObjects(gameTime); + } GameBase.Game.SpriteBatch.End(); @@ -504,7 +511,6 @@ public override void Draw(GameTime gameTime) if (ShowSpectrogram.Value) LoadingSpectrogram?.Draw(gameTime); - HitPositionLine?.Draw(gameTime); } /// From 34fccc0d6bc1fa45982f2527d536cb67dccdc0f2 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Thu, 11 Apr 2024 09:29:11 +0800 Subject: [PATCH 178/249] Retry 3 times before fallback --- .../UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs index 085cb68023..b17e8829db 100644 --- a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs +++ b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs @@ -228,7 +228,7 @@ private List GetOrderedUserList() private int GetMatchScores(int val, CancellationToken cancellationToken) { - const int maxRetryCount = 5; + const int maxRetryCount = 3; MultiplayerMatchInformationResponse matchInfoResponse = null; for (var retryCount = 0; retryCount < maxRetryCount; retryCount++) From 41625ff4d0bed04afe2be1bc98b13e70969e5b02 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Thu, 11 Apr 2024 09:35:14 +0800 Subject: [PATCH 179/249] Remove logging spectrogram slice size --- .../UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs index aafb4776d5..23295802db 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs @@ -96,7 +96,6 @@ private void CreateSlice(float[,] sliceData) SliceTexture = new Texture2D(GameBase.Game.GraphicsDevice, ReferenceWidth, SliceSize); var dataColors = new Color[ReferenceWidth * SliceSize]; - Logger.Debug($"Slice {ReferenceWidth} x {SliceSize}", LogType.Runtime); for (var y = 0; y < SliceSize; y++) { From ede97fcee3e46e5a5cf60a4b06b052e50c41e562 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Thu, 11 Apr 2024 09:50:28 +0800 Subject: [PATCH 180/249] Add an option to toggle warning when failing with KeepPlayingUponFailing on --- Quaver.Shared/Config/ConfigManager.cs | 5 +++++ Quaver.Shared/Screens/Gameplay/GameplayScreen.cs | 7 +++---- Quaver.Shared/Screens/Options/OptionsMenu.cs | 3 ++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index a5337b1364..bbb7384bdd 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -652,6 +652,10 @@ public static class ConfigManager /// internal static Bindable DisplayPauseWarning { get; private set; } + /// + /// + internal static Bindable DisplayFailWarning { get; private set; } + /// /// internal static Bindable TournamentPlayer2Skin { get; private set; } @@ -1150,6 +1154,7 @@ private static void ReadConfigFile() EnableHighProcessPriority = ReadValue(@"EnableHighProcessPriority", false, data); DisplayNotificationsInGameplay = ReadValue(@"DisplayNotificationsInGameplay", false, data); DisplayPauseWarning = ReadValue(@"DisplayPauseWarning", true, data); + DisplayFailWarning = ReadValue(@"DisplayFailWarning", true, data); TournamentPlayer2Skin = ReadValue(@"TournamentPlayer2Skin", "", data); ResultGraph = ReadValue(@"ResultGraph", ResultGraphs.Deviance, data); AudioOutputDevice = ReadValue(@"AudioOutputDevice", "Default", data); diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs index 1af7adbbad..072e3cdcd4 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs @@ -980,10 +980,9 @@ private void HandleFailure() && !(this is TournamentGameplayScreen) && !ForceFail && !Ruleset.ScoreProcessor.ForceFail) { - // Add no fail mod upon dying when AutoNoFail is on. - // Add the no fail mod to their score. - NotificationManager.Show(NotificationLevel.Warning, "WARNING! Your score will not be submitted due to failing " + - "during gameplay!", null, true); + if (ConfigManager.DisplayFailWarning.Value) + NotificationManager.Show(NotificationLevel.Warning, + "WARNING! Your score will not be submitted due to failing during gameplay!", null, true); FailedDuringGameplay = true; } if (!Failed || FailureHandled) diff --git a/Quaver.Shared/Screens/Options/OptionsMenu.cs b/Quaver.Shared/Screens/Options/OptionsMenu.cs index 40d7a6cf65..c208c1cc80 100644 --- a/Quaver.Shared/Screens/Options/OptionsMenu.cs +++ b/Quaver.Shared/Screens/Options/OptionsMenu.cs @@ -424,7 +424,8 @@ private void CreateSections() new OptionsItemCheckbox(containerRect, "Display Notifications From Bottom-To-Top", ConfigManager.DisplayNotificationsBottomToTop), new OptionsItemCheckbox(containerRect, "Display Online Friend Notifications", ConfigManager.DisplayFriendOnlineNotifications), new OptionsItemCheckbox(containerRect, "Display Song Request Notifications", ConfigManager.DisplaySongRequestNotifications), - new OptionsItemCheckbox(containerRect, "Display Warning For Pausing", ConfigManager.DisplayPauseWarning) + new OptionsItemCheckbox(containerRect, "Display Warning For Pausing", ConfigManager.DisplayPauseWarning), + new OptionsItemCheckbox(containerRect, "Display Warning For Failing", ConfigManager.DisplayFailWarning) }), new OptionsSubcategory("Effects", new List() { From 201e22a2389f58f9287a05c78518fbcd596748f3 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Thu, 11 Apr 2024 19:38:20 +0800 Subject: [PATCH 181/249] Multiply by scale factor y for counter-translation --- Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs index 89108d514f..eccb0612c9 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs @@ -471,7 +471,7 @@ public override void Draw(GameTime gameTime) var transformMatrix = Matrix.CreateTranslation(0, TrackPositionY, 0) * WindowManager.Scale; - HitPositionLine.Y = HitPositionY - TrackPositionY; + HitPositionLine.Y = HitPositionY - TrackPositionY * WindowManager.Scale.M22; GameBase.Game.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, null, null, null, transformMatrix); if (ShowSpectrogram.Value && ConfigManager.EditorSpectrogramLayer.Value == From 987c459cbbfbe11a0b1be3e6e34b143bdb7ac0a2 Mon Sep 17 00:00:00 2001 From: Daniel Dimitrov Date: Fri, 12 Apr 2024 02:16:34 +0300 Subject: [PATCH 182/249] Revert back to netstandard2.1 --- Quaver.Shared/Quaver.Shared.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Quaver.Shared.csproj b/Quaver.Shared/Quaver.Shared.csproj index b905cca2d2..a826856820 100644 --- a/Quaver.Shared/Quaver.Shared.csproj +++ b/Quaver.Shared/Quaver.Shared.csproj @@ -1,6 +1,6 @@  - net6.0 + netstandard2.1 Debug;Release;Visual Tests From dfef167a0f99ea7d2c4b86b59f24c05ceeb81d26 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Thu, 11 Apr 2024 23:16:33 +0800 Subject: [PATCH 183/249] Fix hit position line again --- Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs index eccb0612c9..1d03fcb734 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs @@ -458,6 +458,7 @@ public override void Update(GameTime gameTime) /// public override void Draw(GameTime gameTime) { + HitPositionLine.Visible = false; base.Draw(gameTime); try @@ -471,7 +472,8 @@ public override void Draw(GameTime gameTime) var transformMatrix = Matrix.CreateTranslation(0, TrackPositionY, 0) * WindowManager.Scale; - HitPositionLine.Y = HitPositionY - TrackPositionY * WindowManager.Scale.M22; + HitPositionLine.Visible = true; + HitPositionLine.Y = HitPositionY - TrackPositionY; GameBase.Game.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, null, null, null, transformMatrix); if (ShowSpectrogram.Value && ConfigManager.EditorSpectrogramLayer.Value == From f78160a008024bab2bc21b6da57e052ff52bbeff Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Fri, 12 Apr 2024 08:42:35 +0800 Subject: [PATCH 184/249] Add precision tweaking for spectrogram --- Quaver.Shared/Config/ConfigManager.cs | 6 +++ .../Screens/Edit/UI/Menu/EditorFileMenuBar.cs | 13 +++++++ .../Edit/UI/Playfield/EditorPlayfield.cs | 5 +++ .../Spectrogram/EditorPlayfieldSpectrogram.cs | 39 ++++++++++++------- 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index bbb7384bdd..37b724ba71 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -559,6 +559,11 @@ public static class ConfigManager internal static Bindable EditorSpectrogramFrequencyScale { get; private set; } internal static BindableInt EditorSpectrogramFftSize { get; private set; } + + /// + /// The number of times the song's fft will be taken. Linearly increases the time to load + /// + internal static BindableInt EditorSpectrogramInterleaveCount { get; private set; } /// /// @@ -1135,6 +1140,7 @@ private static void ReadConfigFile() EditorSpectrogramIntensityFactor = ReadValue("EditorSpectrogramIntensityFactor", 9.5f, data); EditorSpectrogramFrequencyScale = ReadValue("EditorSpectrogramFrequencyScale", EditorPlayfieldSpectrogramFrequencyScale.Linear, data); EditorSpectrogramFftSize = ReadInt(@"EditorSpectrumFftSize", 512, 256, 16384, data); + EditorSpectrogramInterleaveCount = ReadInt(@"EditorSpectrogramInterleaveCount", 4, 1, 16, data); EditorAudioDirection = ReadValue(@"EditorAudioDirection", EditorPlayfieldWaveformAudioDirection.Both, data); EditorWaveformColorR = ReadInt(@"EditorWaveformColorR", 0, 0, 255, data); EditorWaveformColorG = ReadInt(@"EditorWaveformColorG", 200, 0, 255, data); diff --git a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs index a824b6f3c3..73ae47b171 100644 --- a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs +++ b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs @@ -535,6 +535,19 @@ private void CreateViewSection() ImGui.EndMenu(); } + + if (ImGui.BeginMenu("Precision")) + { + for (var interleaveCount = 1; interleaveCount <= 16; interleaveCount *= 2) + { + if (ImGui.MenuItem($"{interleaveCount}x", "", + ConfigManager.EditorSpectrogramInterleaveCount.Value == interleaveCount)) + { + ConfigManager.EditorSpectrogramInterleaveCount.Value = interleaveCount; + } + } + ImGui.EndMenu(); + } if (ImGui.BeginMenu("FFT Size")) { diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs index eccb0612c9..ce8af5cafd 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs @@ -416,6 +416,7 @@ public EditorPlayfield(Qua map, EditorActionManager manager, Bindable ConfigManager.EditorSpectrogramCutoffFactor.ValueChanged += OnSpectrogramCutoffFactorChanged; ConfigManager.EditorSpectrogramIntensityFactor.ValueChanged += OnSpectrogramIntensityFactorChanged; ConfigManager.EditorSpectrogramFrequencyScale.ValueChanged += OnSpectrogramFrequencyScaleChanged; + ConfigManager.EditorSpectrogramInterleaveCount.ValueChanged += OnSpectrogramInterleaveCountChanged; } /// @@ -565,6 +566,7 @@ public override void Destroy() ConfigManager.EditorSpectrogramCutoffFactor.ValueChanged -= OnSpectrogramCutoffFactorChanged; ConfigManager.EditorSpectrogramIntensityFactor.ValueChanged -= OnSpectrogramIntensityFactorChanged; ConfigManager.EditorSpectrogramFrequencyScale.ValueChanged -= OnSpectrogramFrequencyScaleChanged; + ConfigManager.EditorSpectrogramInterleaveCount.ValueChanged -= OnSpectrogramInterleaveCountChanged; base.Destroy(); } @@ -1665,6 +1667,9 @@ private void OnSpectrogramIntensityFactorChanged(object sender, BindableValueCha private void OnSpectrogramFrequencyScaleChanged(object sender, BindableValueChangedEventArgs e) => ReloadSpectrogram(); + + private void OnSpectrogramInterleaveCountChanged(object sender, BindableValueChangedEventArgs e) + => ReloadSpectrogram(); private void ReloadWaveform() { diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs index abbf33d5a6..9c5ae40885 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs @@ -29,6 +29,10 @@ public class EditorPlayfieldSpectrogram : Container private double TrackLengthMilliSeconds { get; set; } private int FftPerSlice { get; set; } + + private int InterleaveCount { get; set; } = 1; + + private int InterleavedFftPerSlice => FftPerSlice * InterleaveCount; private int Stream { get; set; } @@ -49,6 +53,7 @@ public EditorPlayfieldSpectrogram(EditorPlayfield playfield, CancellationToken t Token = token; FftCount = ConfigManager.EditorSpectrogramFftSize.Value; FftFlag = (int)GetFftDataFlag(FftCount); + InterleaveCount = ConfigManager.EditorSpectrogramInterleaveCount.Value; Slices = new List(); VisibleSlices = new List(); @@ -93,17 +98,17 @@ public void GenerateWaveform() var tempSlices = new List(); var millisecondPerFft = Bass.ChannelBytes2Seconds(Stream, BytesReadPerFft) * 1000; + Logger.Debug($"Precision of spectrogram: {millisecondPerFft / InterleaveCount}ms", LogType.Runtime); var millisecondPerSlice = FftPerSlice * millisecondPerFft; var sampleRate = Bass.ChannelGetInfo(Stream).Frequency; - for (var fftRound = 0; fftRound < FftRoundsTaken; fftRound += FftPerSlice) + for (var fftRound = 0; fftRound < FftRoundsTaken; fftRound +=FftPerSlice) { var t = (int)(fftRound * millisecondPerFft); - var trackSliceData = new float[FftPerSlice, FftResultCount]; - - for (var y = 0; y < FftPerSlice; y++) + var trackSliceData = new float[InterleavedFftPerSlice, FftResultCount]; + for (var y = 0; y < InterleavedFftPerSlice; y++) { - var currentFftIndex = fftRound + y; + var currentFftIndex = fftRound * InterleaveCount + y; if (currentFftIndex >= TrackData.GetLength(0)) break; for (var x = 0; x < FftResultCount; x++) @@ -112,7 +117,7 @@ public void GenerateWaveform() } } - var slice = new EditorPlayfieldSpectrogramSlice(this, Playfield, (float)millisecondPerSlice, FftPerSlice, + var slice = new EditorPlayfieldSpectrogramSlice(this, Playfield, (float)millisecondPerSlice, InterleavedFftPerSlice, trackSliceData, t, sampleRate); tempSlices.Add(slice); } @@ -132,18 +137,24 @@ private void GenerateTrackData() TrackByteLength = Bass.ChannelGetLength(Stream); FftResultCount = FftCount; BytesReadPerFft = sizeof(float) * FftResultCount * Bass.ChannelGetInfo(Stream).Channels * 2; - TrackData = new float[TrackByteLength / BytesReadPerFft + 1, FftResultCount]; + FftRoundsTaken = (int)(TrackByteLength / BytesReadPerFft); + TrackData = new float[(FftRoundsTaken + 1) * InterleaveCount, FftResultCount]; - var trackDataFft = new float[FftResultCount]; - FftRoundsTaken = 0; - - while (Bass.ChannelGetData(Stream, trackDataFft, FftFlag | (int)DataFlags.FFTRemoveDC) > 0) + for (var interleaveRound = 0; interleaveRound < InterleaveCount; interleaveRound++) { - for (var i = 0; i < FftResultCount; i++) + Bass.ChannelSetPosition(Stream, BytesReadPerFft / InterleaveCount * interleaveRound); + var trackDataFft = new float[FftResultCount]; + var currentFftRound = 0; + + while (Bass.ChannelGetData(Stream, trackDataFft, FftFlag | (int)DataFlags.FFTRemoveDC) > 0) { - TrackData[FftRoundsTaken, i] = trackDataFft[i]; + for (var i = 0; i < FftResultCount; i++) + { + TrackData[currentFftRound * InterleaveCount + interleaveRound, i] = trackDataFft[i]; + } + + currentFftRound++; } - FftRoundsTaken++; } TrackByteLength = Bass.ChannelGetLength(Stream); From 638bfb068afaa9993271cfc0f36b49c68d73459e Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Fri, 12 Apr 2024 08:42:52 +0800 Subject: [PATCH 185/249] Set default minimum frequency to 125 instead of 250 --- Quaver.Shared/Config/ConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index 37b724ba71..bb7354a549 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -1134,7 +1134,7 @@ private static void ReadConfigFile() EditorShowWaveform = ReadValue(@"EditorShowWaveform", true, data); EditorShowSpectrogram = ReadValue(@"EditorShowSpectrogram", false, data); EditorSpectrogramMaximumFrequency = ReadInt(@"EditorSpectrogramMaximumFrequency", 7000, 5000, 10000, data); - EditorSpectrogramMinimumFrequency = ReadInt("EditorSpectrogramMinimumFrequency", 250, 0, 1500, data); + EditorSpectrogramMinimumFrequency = ReadInt("EditorSpectrogramMinimumFrequency", 125, 0, 1500, data); EditorSpectrogramLayer = ReadValue("EditorSpectrogramLayer", EditorPlayfieldSpectrogramLayer.BehindTimingLines, data); EditorSpectrogramCutoffFactor = ReadValue("EditorSpectrogramCutoffFactor", 0.34f, data); EditorSpectrogramIntensityFactor = ReadValue("EditorSpectrogramIntensityFactor", 9.5f, data); From ee19c8555c48d92a1a95d5115f1b4a8a07a49adc Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Fri, 12 Apr 2024 09:01:01 +0800 Subject: [PATCH 186/249] Avoid copying track data for each slice --- .../Spectrogram/EditorPlayfieldSpectrogram.cs | 14 ++------------ .../Spectrogram/EditorPlayfieldSpectrogramSlice.cs | 7 ++++++- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs index 9c5ae40885..8de111f249 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs @@ -105,20 +105,10 @@ public void GenerateWaveform() for (var fftRound = 0; fftRound < FftRoundsTaken; fftRound +=FftPerSlice) { var t = (int)(fftRound * millisecondPerFft); - var trackSliceData = new float[InterleavedFftPerSlice, FftResultCount]; - for (var y = 0; y < InterleavedFftPerSlice; y++) - { - var currentFftIndex = fftRound * InterleaveCount + y; - if (currentFftIndex >= TrackData.GetLength(0)) - break; - for (var x = 0; x < FftResultCount; x++) - { - trackSliceData[y, x] = TrackData[currentFftIndex, x]; - } - } + var trackDataYOffset = fftRound * InterleaveCount; var slice = new EditorPlayfieldSpectrogramSlice(this, Playfield, (float)millisecondPerSlice, InterleavedFftPerSlice, - trackSliceData, t, sampleRate); + TrackData, trackDataYOffset, t, sampleRate); tempSlices.Add(slice); } diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs index 23295802db..af334143a0 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs @@ -28,6 +28,8 @@ public class EditorPlayfieldSpectrogramSlice : Sprite private int SampleRate { get; set; } private int ReferenceWidth { get; } + + private int TrackDataYOffset { get; } private Func FrequencyTransform => ConfigManager.EditorSpectrogramFrequencyScale.Value switch @@ -42,6 +44,7 @@ public class EditorPlayfieldSpectrogramSlice : Sprite public EditorPlayfieldSpectrogramSlice(EditorPlayfieldSpectrogram spectrogram, EditorPlayfield playfield, float lengthMs, int sliceSize, float[,] sliceData, + int trackDataYOffset, double sliceTime, int sampleRate) { Spectrogram = spectrogram; @@ -51,6 +54,7 @@ public EditorPlayfieldSpectrogramSlice(EditorPlayfieldSpectrogram spectrogram, E LengthMs = lengthMs; SampleRate = sampleRate; ReferenceWidth = spectrogram.FftCount; + TrackDataYOffset = trackDataYOffset; CreateSlice(sliceData); } @@ -176,7 +180,8 @@ private float Sigmoid(float x) private float GetAverageData(float[,] data, int y, int x) { - return data[y, x]; + if (TrackDataYOffset + y >= data.GetLength(0)) return 0; + return data[TrackDataYOffset + y, x]; } private int DataColorIndex(int textureHeight, int y, int x) From 2e2a4fd541d71f47986d736757080a768d23406f Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Fri, 12 Apr 2024 09:36:49 +0800 Subject: [PATCH 187/249] Use attributes instead of properties --- .../EditorPlayfieldSpectrogramSlice.cs | 112 +++++++++--------- 1 file changed, 58 insertions(+), 54 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs index af334143a0..edc462996a 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs @@ -12,34 +12,29 @@ namespace Quaver.Shared.Screens.Edit.UI.Playfield.Spectrogram { public class EditorPlayfieldSpectrogramSlice : Sprite { - private EditorPlayfield Playfield { get; set; } - private EditorPlayfieldSpectrogram Spectrogram { get; set; } + private readonly EditorPlayfield _playfield; + private readonly EditorPlayfieldSpectrogram _spectrogram; private Sprite SliceSprite { get; set; } - private int SliceSize { get; } + private readonly int _sliceSize; - private double SliceTimeMilliSeconds { get; } + private readonly double _sliceTimeMilliSeconds; private Texture2D SliceTexture { get; set; } - private float LengthMs { get; set; } + private readonly float _lengthMs; - private int SampleRate { get; set; } + private readonly int _sampleRate; - private int ReferenceWidth { get; } - - private int TrackDataYOffset { get; } + private readonly int _referenceWidth; - private Func FrequencyTransform => - ConfigManager.EditorSpectrogramFrequencyScale.Value switch - { - EditorPlayfieldSpectrogramFrequencyScale.Mel => Mel, - EditorPlayfieldSpectrogramFrequencyScale.Erb1 => Erb1, - EditorPlayfieldSpectrogramFrequencyScale.Erb2 => Erb2, - EditorPlayfieldSpectrogramFrequencyScale.Linear => Linear, - _ => throw new ArgumentOutOfRangeException() - }; + private readonly int _trackDataYOffset; + + private readonly Func _frequencyTransform; + + private readonly int _minimumFrequency; + private readonly int _maximumFrequency; public EditorPlayfieldSpectrogramSlice(EditorPlayfieldSpectrogram spectrogram, EditorPlayfield playfield, float lengthMs, int sliceSize, @@ -47,14 +42,27 @@ public EditorPlayfieldSpectrogramSlice(EditorPlayfieldSpectrogram spectrogram, E int trackDataYOffset, double sliceTime, int sampleRate) { - Spectrogram = spectrogram; - Playfield = playfield; - SliceSize = sliceSize; - SliceTimeMilliSeconds = sliceTime; - LengthMs = lengthMs; - SampleRate = sampleRate; - ReferenceWidth = spectrogram.FftCount; - TrackDataYOffset = trackDataYOffset; + _spectrogram = spectrogram; + _playfield = playfield; + _sliceSize = sliceSize; + _sliceTimeMilliSeconds = sliceTime; + _lengthMs = lengthMs; + _sampleRate = sampleRate; + _referenceWidth = spectrogram.FftCount; + _trackDataYOffset = trackDataYOffset; + + _frequencyTransform = ConfigManager.EditorSpectrogramFrequencyScale.Value switch + { + EditorPlayfieldSpectrogramFrequencyScale.Mel => Mel, + EditorPlayfieldSpectrogramFrequencyScale.Erb1 => Erb1, + EditorPlayfieldSpectrogramFrequencyScale.Erb2 => Erb2, + EditorPlayfieldSpectrogramFrequencyScale.Linear => Linear, + _ => throw new ArgumentOutOfRangeException() + }; + + + _minimumFrequency = (int)_frequencyTransform(ConfigManager.EditorSpectrogramMinimumFrequency.Value); + _maximumFrequency = (int)_frequencyTransform(ConfigManager.EditorSpectrogramMaximumFrequency.Value); CreateSlice(sliceData); } @@ -68,10 +76,10 @@ public override void Update(GameTime gameTime) public override void Draw(GameTime gameTime) { - SliceSprite.X = Playfield.ScreenRectangle.X; - SliceSprite.Y = Playfield.HitPositionY - (float)(SliceTimeMilliSeconds + LengthMs) * Playfield.TrackSpeed - + SliceSprite.X = _playfield.ScreenRectangle.X; + SliceSprite.Y = _playfield.HitPositionY - (float)(_sliceTimeMilliSeconds + _lengthMs) * _playfield.TrackSpeed - Height; - SliceSprite.Height = LengthMs * Playfield.TrackSpeed; + SliceSprite.Height = _lengthMs * _playfield.TrackSpeed; SliceSprite.Tint = new Color( 1.0f, 1.0f, @@ -90,32 +98,30 @@ public override void Destroy() base.Destroy(); } - - public void UpdatePlayfield(EditorPlayfield playfield) => Playfield = playfield; - + private void CreateSlice(float[,] sliceData) { SliceSprite = new Sprite { Alpha = 0 }; - SliceTexture = new Texture2D(GameBase.Game.GraphicsDevice, ReferenceWidth, SliceSize); + SliceTexture = new Texture2D(GameBase.Game.GraphicsDevice, _referenceWidth, _sliceSize); - var dataColors = new Color[ReferenceWidth * SliceSize]; + var dataColors = new Color[_referenceWidth * _sliceSize]; - for (var y = 0; y < SliceSize; y++) + for (var y = 0; y < _sliceSize; y++) { - for (var x = 0; x < Spectrogram.FftCount; x++) + for (var x = 0; x < _spectrogram.FftCount; x++) { - var textureX = CalculateTextureX(x, FrequencyTransform); + var textureX = CalculateTextureX(x); if (textureX == -1) continue; var intensity = GetIntensity(sliceData, y, x); - var index = DataColorIndex(SliceSize, y, textureX); - var nextTextureX = CalculateTextureX(x + 1, FrequencyTransform); - if (nextTextureX == -1) nextTextureX = ReferenceWidth - 1; - var nextIntensity = x == Spectrogram.FftCount - 1 + var index = DataColorIndex(_sliceSize, y, textureX); + var nextTextureX = CalculateTextureX(x + 1); + if (nextTextureX == -1) nextTextureX = _referenceWidth - 1; + var nextIntensity = x == _spectrogram.FftCount - 1 ? intensity : GetIntensity(sliceData, y, x + 1); var curColor = SpectrogramColormap.GetColor(intensity); - var nextDataColorIndex = DataColorIndex(SliceSize, y, nextTextureX); + var nextDataColorIndex = DataColorIndex(_sliceSize, y, nextTextureX); for (var i = index; i < nextDataColorIndex && i < dataColors.Length; i++) { dataColors[i] = curColor; @@ -125,8 +131,8 @@ private void CreateSlice(float[,] sliceData) SliceTexture.SetData(dataColors); SliceSprite.Image = SliceTexture; - SliceSprite.Width = (int)Playfield.Width; - SliceSprite.Height = LengthMs; + SliceSprite.Width = (int)_playfield.Width; + SliceSprite.Height = _lengthMs; SliceSprite.FadeTo(1, Easing.Linear, 250); } @@ -145,14 +151,12 @@ private float GetIntensity(float[,] sliceData, int y, int x) return intensity; } - private int CalculateTextureX(int x, Func transform) + private int CalculateTextureX(int x) { - var minFrequency = transform(ConfigManager.EditorSpectrogramMinimumFrequency.Value); - var maxFrequency = transform(ConfigManager.EditorSpectrogramMaximumFrequency.Value); - var frequency = (float)x * SampleRate / Spectrogram.FftCount; - var transformedFrequency = transform(frequency); - if (transformedFrequency > maxFrequency || transformedFrequency < minFrequency) return -1; - return (int)((transformedFrequency - minFrequency) / (maxFrequency - minFrequency) * ReferenceWidth); + var frequency = (float)x * _sampleRate / _spectrogram.FftCount; + var transformedFrequency = _frequencyTransform(frequency); + if (transformedFrequency > _maximumFrequency || transformedFrequency < _minimumFrequency) return -1; + return (int)((transformedFrequency - _minimumFrequency) / (_maximumFrequency - _minimumFrequency) * _referenceWidth); } private float Linear(float x) => x; @@ -180,13 +184,13 @@ private float Sigmoid(float x) private float GetAverageData(float[,] data, int y, int x) { - if (TrackDataYOffset + y >= data.GetLength(0)) return 0; - return data[TrackDataYOffset + y, x]; + if (_trackDataYOffset + y >= data.GetLength(0)) return 0; + return data[_trackDataYOffset + y, x]; } private int DataColorIndex(int textureHeight, int y, int x) { - return (textureHeight - y - 1) * ReferenceWidth + x; + return (textureHeight - y - 1) * _referenceWidth + x; } } } \ No newline at end of file From 8d232465f3bab7a4f1986c757bac6ed07ae9a03c Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Fri, 12 Apr 2024 10:05:27 +0800 Subject: [PATCH 188/249] Use jagged array for track data to avoid copying --- .../Spectrogram/EditorPlayfieldSpectrogram.cs | 23 +++++++++---------- .../EditorPlayfieldSpectrogramSlice.cs | 12 +++++----- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs index 8de111f249..11573aa9db 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs @@ -22,7 +22,7 @@ public class EditorPlayfieldSpectrogram : Container private EditorPlayfield Playfield { get; } - private float[,] TrackData { get; set; } + private float[][] _trackData; private long TrackByteLength { get; set; } @@ -108,7 +108,7 @@ public void GenerateWaveform() var trackDataYOffset = fftRound * InterleaveCount; var slice = new EditorPlayfieldSpectrogramSlice(this, Playfield, (float)millisecondPerSlice, InterleavedFftPerSlice, - TrackData, trackDataYOffset, t, sampleRate); + _trackData, trackDataYOffset, t, sampleRate); tempSlices.Add(slice); } @@ -128,23 +128,22 @@ private void GenerateTrackData() FftResultCount = FftCount; BytesReadPerFft = sizeof(float) * FftResultCount * Bass.ChannelGetInfo(Stream).Channels * 2; FftRoundsTaken = (int)(TrackByteLength / BytesReadPerFft); - TrackData = new float[(FftRoundsTaken + 1) * InterleaveCount, FftResultCount]; + _trackData = new float[(FftRoundsTaken + 1) * InterleaveCount][]; for (var interleaveRound = 0; interleaveRound < InterleaveCount; interleaveRound++) { Bass.ChannelSetPosition(Stream, BytesReadPerFft / InterleaveCount * interleaveRound); - var trackDataFft = new float[FftResultCount]; var currentFftRound = 0; - - while (Bass.ChannelGetData(Stream, trackDataFft, FftFlag | (int)DataFlags.FFTRemoveDC) > 0) + int row; + do { - for (var i = 0; i < FftResultCount; i++) - { - TrackData[currentFftRound * InterleaveCount + interleaveRound, i] = trackDataFft[i]; - } - + row = currentFftRound * InterleaveCount + interleaveRound; + if (row >= _trackData.Length) break; + _trackData[row] = GC.AllocateUninitializedArray(FftResultCount); + currentFftRound++; - } + } while (Bass.ChannelGetData(Stream, _trackData[row], FftFlag | (int)DataFlags.FFTRemoveDC) > 0); + } TrackByteLength = Bass.ChannelGetLength(Stream); diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs index edc462996a..dd5b70e409 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs @@ -38,7 +38,7 @@ public class EditorPlayfieldSpectrogramSlice : Sprite public EditorPlayfieldSpectrogramSlice(EditorPlayfieldSpectrogram spectrogram, EditorPlayfield playfield, float lengthMs, int sliceSize, - float[,] sliceData, + float[][] sliceData, int trackDataYOffset, double sliceTime, int sampleRate) { @@ -99,7 +99,7 @@ public override void Destroy() base.Destroy(); } - private void CreateSlice(float[,] sliceData) + private void CreateSlice(float[][] sliceData) { SliceSprite = new Sprite { Alpha = 0 }; @@ -136,7 +136,7 @@ private void CreateSlice(float[,] sliceData) SliceSprite.FadeTo(1, Easing.Linear, 250); } - private float GetIntensity(float[,] sliceData, int y, int x) + private float GetIntensity(float[][] sliceData, int y, int x) { var rawIntensity = GetAverageData(sliceData, y, x); var db = MathF.Abs(rawIntensity) < 1e-4f ? -100 : 20 * MathF.Log10(rawIntensity); @@ -182,10 +182,10 @@ private float Sigmoid(float x) } - private float GetAverageData(float[,] data, int y, int x) + private float GetAverageData(float[][] data, int y, int x) { - if (_trackDataYOffset + y >= data.GetLength(0)) return 0; - return data[_trackDataYOffset + y, x]; + if (_trackDataYOffset + y >= data.Length) return 0; + return data[_trackDataYOffset + y][x]; } private int DataColorIndex(int textureHeight, int y, int x) From ae172f227ce2e56d9b9b27012f846b7811ebd3d3 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Fri, 12 Apr 2024 10:25:49 +0800 Subject: [PATCH 189/249] Make cutoff and intensity factors attributes --- .../Spectrogram/EditorPlayfieldSpectrogramSlice.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs index dd5b70e409..48de1c5ee6 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramSlice.cs @@ -36,6 +36,9 @@ public class EditorPlayfieldSpectrogramSlice : Sprite private readonly int _minimumFrequency; private readonly int _maximumFrequency; + private readonly float _cutoffFactor; + private readonly float _intensityFactor; + public EditorPlayfieldSpectrogramSlice(EditorPlayfieldSpectrogram spectrogram, EditorPlayfield playfield, float lengthMs, int sliceSize, float[][] sliceData, @@ -50,6 +53,8 @@ public EditorPlayfieldSpectrogramSlice(EditorPlayfieldSpectrogram spectrogram, E _sampleRate = sampleRate; _referenceWidth = spectrogram.FftCount; _trackDataYOffset = trackDataYOffset; + _cutoffFactor = ConfigManager.EditorSpectrogramCutoffFactor.Value; + _intensityFactor = ConfigManager.EditorSpectrogramIntensityFactor.Value; _frequencyTransform = ConfigManager.EditorSpectrogramFrequencyScale.Value switch { @@ -142,11 +147,10 @@ private float GetIntensity(float[][] sliceData, int y, int x) var db = MathF.Abs(rawIntensity) < 1e-4f ? -100 : 20 * MathF.Log10(rawIntensity); var intensity = Math.Clamp(1 + db / 100, 0f, 1f); - var cutoffFactor = ConfigManager.EditorSpectrogramCutoffFactor.Value; - intensity = MathF.Max(intensity, cutoffFactor); - intensity = (intensity - cutoffFactor) * (1 - cutoffFactor); + intensity = MathF.Max(intensity, _cutoffFactor); + intensity = (intensity - _cutoffFactor) * (1 - _cutoffFactor); - intensity *= intensity * ConfigManager.EditorSpectrogramIntensityFactor.Value; + intensity *= intensity * _intensityFactor; intensity = Sigmoid(Math.Clamp(intensity, 0, 1)); return intensity; } From de8590d9c2a930d699530fff8a6a9630b4a401c9 Mon Sep 17 00:00:00 2001 From: AiAe Date: Fri, 12 Apr 2024 13:18:15 +0300 Subject: [PATCH 190/249] Update Quaver.API --- Quaver.API | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.API b/Quaver.API index 9d206592b0..c2641dd672 160000 --- a/Quaver.API +++ b/Quaver.API @@ -1 +1 @@ -Subproject commit 9d206592b0e02fb3d9c3e6d6df2b507e3d7c4fc1 +Subproject commit c2641dd672661240a15a610b2b7a6c65e472ba1c From 060c8b8fc50bb8f01039f20355cf52051728ce0f Mon Sep 17 00:00:00 2001 From: AiAe Date: Fri, 12 Apr 2024 13:35:26 +0300 Subject: [PATCH 191/249] Revert Quaver.Shared to net6.0 --- Quaver.Shared/Quaver.Shared.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Quaver.Shared.csproj b/Quaver.Shared/Quaver.Shared.csproj index a826856820..b905cca2d2 100644 --- a/Quaver.Shared/Quaver.Shared.csproj +++ b/Quaver.Shared/Quaver.Shared.csproj @@ -1,6 +1,6 @@  - netstandard2.1 + net6.0 Debug;Release;Visual Tests From 639e6b5c4620d1f6a875afdb06664700bf0c95fe Mon Sep 17 00:00:00 2001 From: AiAe Date: Fri, 12 Apr 2024 13:39:01 +0300 Subject: [PATCH 192/249] Update Quaver.Server.Client --- Quaver.Server.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Server.Client b/Quaver.Server.Client index 05b8650e0e..e006bf8bda 160000 --- a/Quaver.Server.Client +++ b/Quaver.Server.Client @@ -1 +1 @@ -Subproject commit 05b8650e0e93b1d40011a90bc795be79c0072473 +Subproject commit e006bf8bda03f3e0da452dadec22364f7e38d802 From 1f5a815b1b74eb2100760ceaa4bec413f02291f3 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Fri, 12 Apr 2024 19:03:34 +0800 Subject: [PATCH 193/249] Revert changes to HitPositionLine --- .../Edit/UI/Playfield/EditorPlayfield.cs | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs index 1d03fcb734..e79f40d131 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs @@ -458,7 +458,6 @@ public override void Update(GameTime gameTime) /// public override void Draw(GameTime gameTime) { - HitPositionLine.Visible = false; base.Draw(gameTime); try @@ -472,35 +471,19 @@ public override void Draw(GameTime gameTime) var transformMatrix = Matrix.CreateTranslation(0, TrackPositionY, 0) * WindowManager.Scale; - HitPositionLine.Visible = true; - HitPositionLine.Y = HitPositionY - TrackPositionY; GameBase.Game.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, null, null, null, transformMatrix); - if (ShowSpectrogram.Value && ConfigManager.EditorSpectrogramLayer.Value == - EditorPlayfieldSpectrogramLayer.BehindTimingLines) - Spectrogram?.Draw(gameTime); - Timeline.Draw(gameTime); - if (ShowSpectrogram.Value && - ConfigManager.EditorSpectrogramLayer.Value == EditorPlayfieldSpectrogramLayer.BehindNotes) + if (ShowSpectrogram.Value) Spectrogram?.Draw(gameTime); if (ShowWaveform.Value) Waveform?.Draw(gameTime); LineContainer.Draw(gameTime); - if (ShowSpectrogram.Value && - ConfigManager.EditorSpectrogramLayer.Value == EditorPlayfieldSpectrogramLayer.FrontMost) - { - Spectrogram?.Draw(gameTime); - HitPositionLine?.Draw(gameTime); - } - else - { - HitPositionLine?.Draw(gameTime); - DrawHitObjects(gameTime); - } + + DrawHitObjects(gameTime); GameBase.Game.SpriteBatch.End(); From b6c6c2b458f2137a7e9e5459a2591443b25e5532 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Fri, 12 Apr 2024 19:06:17 +0800 Subject: [PATCH 194/249] Remove spectrogram layer --- Quaver.Shared/Config/ConfigManager.cs | 3 --- .../Screens/Edit/UI/Menu/EditorFileMenuBar.cs | 10 ---------- .../Spectrogram/EditorPlayfieldSpectrogramLayer.cs | 9 --------- 3 files changed, 22 deletions(-) delete mode 100644 Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramLayer.cs diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index bbb7384bdd..16b3ef6543 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -550,8 +550,6 @@ public static class ConfigManager internal static Bindable EditorSpectrogramMinimumFrequency { get; private set; } - internal static Bindable EditorSpectrogramLayer { get; private set; } - internal static Bindable EditorSpectrogramCutoffFactor { get; private set; } internal static Bindable EditorSpectrogramIntensityFactor { get; private set; } @@ -1130,7 +1128,6 @@ private static void ReadConfigFile() EditorShowSpectrogram = ReadValue(@"EditorShowSpectrogram", false, data); EditorSpectrogramMaximumFrequency = ReadInt(@"EditorSpectrogramMaximumFrequency", 7000, 5000, 10000, data); EditorSpectrogramMinimumFrequency = ReadInt("EditorSpectrogramMinimumFrequency", 250, 0, 1500, data); - EditorSpectrogramLayer = ReadValue("EditorSpectrogramLayer", EditorPlayfieldSpectrogramLayer.BehindTimingLines, data); EditorSpectrogramCutoffFactor = ReadValue("EditorSpectrogramCutoffFactor", 0.34f, data); EditorSpectrogramIntensityFactor = ReadValue("EditorSpectrogramIntensityFactor", 9.5f, data); EditorSpectrogramFrequencyScale = ReadValue("EditorSpectrogramFrequencyScale", EditorPlayfieldSpectrogramFrequencyScale.Linear, data); diff --git a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs index a824b6f3c3..305a4a6041 100644 --- a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs +++ b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs @@ -545,16 +545,6 @@ private void CreateViewSection() } ImGui.EndMenu(); } - - if (ImGui.BeginMenu("Layer")) - { - foreach (var layer in Enum.GetValues()) - { - if (ImGui.MenuItem($"{layer}", "", layer == ConfigManager.EditorSpectrogramLayer.Value)) - ConfigManager.EditorSpectrogramLayer.Value = layer; - } - ImGui.EndMenu(); - } if (ImGui.BeginMenu("Frequency Scale")) { diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramLayer.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramLayer.cs deleted file mode 100644 index dabf29f9aa..0000000000 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogramLayer.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Quaver.Shared.Screens.Edit.UI.Playfield.Spectrogram -{ - public enum EditorPlayfieldSpectrogramLayer - { - BehindTimingLines, - BehindNotes, - FrontMost - } -} \ No newline at end of file From bfc055a4bc6f39ed5b0a022e2f35c390cfd9e8f0 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Fri, 12 Apr 2024 22:31:14 +0800 Subject: [PATCH 195/249] Toggle snapping notes when live snap --- Quaver.Shared/Config/ConfigManager.cs | 3 +++ Quaver.Shared/Screens/Edit/EditScreen.cs | 5 +++++ Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs | 3 +++ 3 files changed, 11 insertions(+) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index bbb7384bdd..5f41601106 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -357,6 +357,8 @@ public static class ConfigManager /// The scroll speed used in the editor. /// internal static BindableInt EditorScrollSpeedKeys { get; private set; } + + internal static Bindable EditorLiveMapSnap { get; private set; } /// /// Whether or not to play hitsounds in the editor. @@ -1071,6 +1073,7 @@ private static void ReadConfigFile() KeyEditorIncreaseAudioRate = ReadValue(@"KeyEditorIncreaseAudioRate", Keys.OemPlus, data); InvertScrolling = ReadValue(@"InvertScrolling", false, data); InvertEditorScrolling = ReadValue(@"InvertEditorScrolling", true, data); + EditorLiveMapSnap = ReadValue(@"EditorLiveMapSnap", false, data); EditorEnableHitsounds = ReadValue(@"EditorEnableHitsounds", true, data); EditorEnableKeysounds = ReadValue(@"EditorEnableKeysounds", true, data); EditorBeatSnapColorType = ReadValue(@"EditorBeatSnapColorType", EditorBeatSnapColor.Default, data); diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index fb54f5e89c..add40a3cd9 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -867,6 +867,11 @@ private void HandleTemporaryHitObjectPlacement() continue; var time = (int)Math.Round(Track.Time, MidpointRounding.AwayFromZero); + + if (ConfigManager.EditorLiveMapSnap.Value) + { + time = ((EditScreenView)View).Playfield.GetNearestTickFromTime(time, BeatSnap.Value); + } var lane = i + 1; diff --git a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs index a824b6f3c3..d1d46bb992 100644 --- a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs +++ b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs @@ -461,6 +461,9 @@ private void CreateViewSection() if (ImGui.MenuItem("Place Objects With Top Row Numbers", "", Screen.LiveMapping.Value)) Screen.LiveMapping.Value = !Screen.LiveMapping.Value; + if (ImGui.MenuItem("Snap Notes When Live Mapping", "", ConfigManager.EditorLiveMapSnap.Value)) + ConfigManager.EditorLiveMapSnap.Value = !ConfigManager.EditorLiveMapSnap.Value; + if (ImGui.MenuItem("Invert Beat Snap Scroll", "", Screen.InvertBeatSnapScroll.Value)) Screen.InvertBeatSnapScroll.Value = !Screen.InvertBeatSnapScroll.Value; From 6afd43376597d065cff9d8cbc49dd32ccd14b99e Mon Sep 17 00:00:00 2001 From: Warp Date: Fri, 12 Apr 2024 21:21:19 +0200 Subject: [PATCH 196/249] Move some lesser used options to sperate advanced section --- Quaver.Shared/Screens/Options/OptionsMenu.cs | 96 ++++++++++---------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/Quaver.Shared/Screens/Options/OptionsMenu.cs b/Quaver.Shared/Screens/Options/OptionsMenu.cs index c208c1cc80..f9cc0fdcc1 100644 --- a/Quaver.Shared/Screens/Options/OptionsMenu.cs +++ b/Quaver.Shared/Screens/Options/OptionsMenu.cs @@ -144,15 +144,6 @@ private void CreateSections() }, new OptionsItemCustomFps(containerRect, "Set Custom FPS"), new OptionsItemCheckbox(containerRect, "Display FPS Counter", ConfigManager.FpsCounter), - new OptionsItemCheckbox(containerRect, "Lower FPS On Inactive Window", ConfigManager.LowerFpsOnWindowInactive), - new OptionsItemCheckbox(containerRect, "Enable High Process Priority", ConfigManager.EnableHighProcessPriority) - }), - new OptionsSubcategory("Linux", new List() - { - new OptionsItemCheckbox(containerRect, "Prefer Wayland", ConfigManager.PreferWayland) - { - Tags = new List {"linux"} - } }) }), new OptionsSection("Audio", UserInterface.OptionsAudio, new List @@ -183,10 +174,6 @@ private void CreateSections() Tags = new List {"speed"} } }), - new OptionsSubcategory("Experimental", new List() - { - new OptionsItemCheckbox(containerRect, "Use Smooth Audio/Frame Timing During Gameplay", ConfigManager.SmoothAudioTimingGameplay) - }), }), new OptionsSection("Gameplay", UserInterface.OptionsGameplay, new List { @@ -220,20 +207,12 @@ private void CreateSections() }), new OptionsSubcategory("User Interface", new List() { - new OptionsItemCheckbox(containerRect, "Display Gameplay Overlay (Shift + F6)", ConfigManager.DisplayGameplayOverlay), - new OptionsItemCheckbox(containerRect, "Display Notifications During Gameplay", ConfigManager.DisplayNotificationsInGameplay), - new OptionsItemCheckbox(containerRect, "Show Spectators", ConfigManager.ShowSpectators), new OptionsItemCheckbox(containerRect, "Display Timing Lines", ConfigManager.DisplayTimingLines), new OptionsItemCheckbox(containerRect, "Display Judgement Counter", ConfigManager.DisplayJudgementCounter), - new OptionsItemCheckbox(containerRect, "Display Ranked Accuracy With Custom Judgements", ConfigManager.DisplayRankedAccuracy), - new OptionsSlider(containerRect, "Hit Error Fade Time", ConfigManager.HitErrorFadeTime, i => $"{i / 1000f:0.0} sec"), - new OptionsItemCheckbox(containerRect, "Enable Combo Alerts", ConfigManager.DisplayComboAlerts), }), new OptionsSubcategory("Scoreboard", new List() { new OptionsItemCheckbox(containerRect, "Display Scoreboard", ConfigManager.ScoreboardVisible), - new OptionsItemCheckbox(containerRect, "[Donator] Enable Real-time Top 5 Online Scoreboard", ConfigManager.EnableRealtimeOnlineScoreboard), - new OptionsItemCheckbox(containerRect, "Display Unbeatable Scores", ConfigManager.DisplayUnbeatableScoresDuringGameplay) }), new OptionsSubcategory("Progress Bar", new List() { @@ -247,10 +226,6 @@ private void CreateSections() new OptionsItemCheckbox(containerRect, "Enable Bottom Lane Cover", ConfigManager.LaneCoverBottom), new OptionsSlider(containerRect, "Bottom Lane Cover Height", ConfigManager.LaneCoverBottomHeight), new OptionsItemCheckbox(containerRect, "Display UI Elements Over Lane Covers", ConfigManager.UIElementsOverLaneCover) - }), - new OptionsSubcategory("Others", new List() - { - new OptionsItemCheckbox(containerRect, "Keep Playing Upon Failing", ConfigManager.KeepPlayingUponFailing) }) }), new OptionsSection("Skin", UserInterface.OptionsSkin, new List @@ -270,11 +245,6 @@ private void CreateSections() new OptionsItemExportSkin(containerRect, "Export Skin"), new OptionsItemUploadSkinToWorkshop(containerRect, "Upload Skin To Steam Workshop") }), - new OptionsSubcategory("Tournament", new List() - { - new OptionsItemCheckbox(containerRect, "Display 1v1 Tournament Overlay", ConfigManager.Display1v1TournamentOverlay), - new OptionsItemCheckbox(containerRect, "Display 1v1 Playfield Scores", ConfigManager.TournamentDisplay1v1PlayfieldScores) - }), new OptionsSubcategory("Configuration", new List() { new OptionsSlider(containerRect, "Note & Receptor Size Scale", ConfigManager.GameplayNoteScale, i => $"{i / 100f:0.00}x") @@ -381,19 +351,16 @@ private void CreateSections() new OptionsItemKeybind(containerRect, "Decrease Gameplay Rate", ConfigManager.KeyDecreaseGameplayAudioRate), new OptionsItemKeybind(containerRect, "Increase Gameplay Rate", ConfigManager.KeyIncreaseGameplayAudioRate), new OptionsItemKeybind(containerRect, "Toggle Mirror Mod", ConfigManager.KeyToggleMirror), - new OptionsItemKeybind(containerRect, "Toggle Playtest Autoplay", ConfigManager.KeyTogglePlaytestAutoplay), }), new OptionsSubcategory("Editor", new List() { new OptionsItemKeybind(containerRect, "Pause/Play Track", ConfigManager.KeyEditorPausePlay), new OptionsItemKeybind(containerRect, "Decrease Playback Rate", ConfigManager.KeyEditorDecreaseAudioRate), new OptionsItemKeybind(containerRect, "Increase Playback Rate", ConfigManager.KeyEditorIncreaseAudioRate), - new OptionsItemCheckbox(containerRect, "Invert Editor Scrolling", ConfigManager.InvertEditorScrolling) }), new OptionsSubcategory("Misc", new List() { new OptionsItemKeybind(containerRect, "Take Screenshot", ConfigManager.KeyScreenshot), - new OptionsItemCheckbox(containerRect, "Invert Scrolling", ConfigManager.InvertScrolling) }) }), new OptionsSection("Miscellaneous", UserInterface.OptionsMisc, new List @@ -415,21 +382,10 @@ private void CreateSections() Tags = new List {"osu!", "other games", "db", "etterna", "sm", "stepmania"} }, }), - new OptionsSubcategory("Login", new List() - { - new OptionsItemCheckbox(containerRect, "Automatically Log Into The Server", ConfigManager.AutoLoginToServer), - }), new OptionsSubcategory("Notifications", new List() { new OptionsItemCheckbox(containerRect, "Display Notifications From Bottom-To-Top", ConfigManager.DisplayNotificationsBottomToTop), new OptionsItemCheckbox(containerRect, "Display Online Friend Notifications", ConfigManager.DisplayFriendOnlineNotifications), - new OptionsItemCheckbox(containerRect, "Display Song Request Notifications", ConfigManager.DisplaySongRequestNotifications), - new OptionsItemCheckbox(containerRect, "Display Warning For Pausing", ConfigManager.DisplayPauseWarning), - new OptionsItemCheckbox(containerRect, "Display Warning For Failing", ConfigManager.DisplayFailWarning) - }), - new OptionsSubcategory("Effects", new List() - { - new OptionsItemCheckbox(containerRect, "Display Menu Audio Visualizer", ConfigManager.DisplayMenuAudioVisualizer), }), new OptionsSubcategory("Song Select", new List() { @@ -438,15 +394,59 @@ private void CreateSections() new OptionsSlider(containerRect, "Prioritized 7K Difficulty", ConfigManager.PrioritizedMapDifficulty7K, i => $"{i / 10f:0.0}"), new OptionsItemSuggestDifficulty(containerRect, "Suggest Difficulty from Overall Rating") }), - new OptionsSubcategory("Leaderboard", new List() - { - new OptionsItemCheckbox(containerRect, "Display Failed Local Scores", ConfigManager.DisplayFailedLocalScores), - }), // new OptionsSubcategory("Beta", new List() // { // new OptionsItemCheckbox(containerRect, "Skip Beta Splash Screen", ConfigManager.SkipSplashScreen), // }), }), + new OptionsSection("Advanced", FontAwesome.Get(FontAwesomeIcon.fa_open_folder), new List + { + new OptionsSubcategory("Video", new List() + { + new OptionsItemCheckbox(containerRect, "Lower FPS On Inactive Window", ConfigManager.LowerFpsOnWindowInactive), + new OptionsItemCheckbox(containerRect, "Enable High Process Priority", ConfigManager.EnableHighProcessPriority), + new OptionsItemCheckbox(containerRect, "Prefer Wayland", ConfigManager.PreferWayland) + { + Tags = new List {"linux"} + } + }), + new OptionsSubcategory("Audio", new List() + { + new OptionsItemCheckbox(containerRect, "Use Smooth Audio/Frame Timing During Gameplay", ConfigManager.SmoothAudioTimingGameplay) + }), + new OptionsSubcategory("Gameplay", new List() + { + new OptionsItemCheckbox(containerRect, "Display Gameplay Overlay (Shift + F6)", ConfigManager.DisplayGameplayOverlay), + new OptionsItemCheckbox(containerRect, "Display Notifications During Gameplay", ConfigManager.DisplayNotificationsInGameplay), + new OptionsItemCheckbox(containerRect, "Show Spectators", ConfigManager.ShowSpectators), + new OptionsItemCheckbox(containerRect, "Display Ranked Accuracy With Custom Judgements", ConfigManager.DisplayRankedAccuracy), + new OptionsSlider(containerRect, "Hit Error Fade Time", ConfigManager.HitErrorFadeTime, i => $"{i / 1000f:0.0} sec"), + new OptionsItemCheckbox(containerRect, "Enable Combo Alerts", ConfigManager.DisplayComboAlerts), + new OptionsItemCheckbox(containerRect, "[Donator] Enable Real-time Top 5 Online Scoreboard", ConfigManager.EnableRealtimeOnlineScoreboard), + new OptionsItemCheckbox(containerRect, "Display Unbeatable Scores", ConfigManager.DisplayUnbeatableScoresDuringGameplay), + new OptionsItemCheckbox(containerRect, "Keep Playing Upon Failing", ConfigManager.KeepPlayingUponFailing) + }), + new OptionsSubcategory("Skin", new List() + { + new OptionsItemCheckbox(containerRect, "Display 1v1 Tournament Overlay", ConfigManager.Display1v1TournamentOverlay), + new OptionsItemCheckbox(containerRect, "Display 1v1 Playfield Scores", ConfigManager.TournamentDisplay1v1PlayfieldScores) + }), + new OptionsSubcategory("Input", new List() + { + new OptionsItemKeybind(containerRect, "Toggle Playtest Autoplay", ConfigManager.KeyTogglePlaytestAutoplay), + new OptionsItemCheckbox(containerRect, "Invert Editor Scrolling", ConfigManager.InvertEditorScrolling), + new OptionsItemCheckbox(containerRect, "Invert Scrolling", ConfigManager.InvertScrolling) + }), + new OptionsSubcategory("Miscellaneous", new List() + { + new OptionsItemCheckbox(containerRect, "Automatically Log Into The Server", ConfigManager.AutoLoginToServer), + new OptionsItemCheckbox(containerRect, "Display Song Request Notifications", ConfigManager.DisplaySongRequestNotifications), + new OptionsItemCheckbox(containerRect, "Display Warning For Pausing", ConfigManager.DisplayPauseWarning), + new OptionsItemCheckbox(containerRect, "Display Warning For Failing", ConfigManager.DisplayFailWarning), + new OptionsItemCheckbox(containerRect, "Display Menu Audio Visualizer", ConfigManager.DisplayMenuAudioVisualizer), + new OptionsItemCheckbox(containerRect, "Display Failed Local Scores", ConfigManager.DisplayFailedLocalScores), + }), + }), }; SelectedSection = new Bindable(Sections.First()) {Value = Sections.First()}; From 8f43df708291708ecd13ea7e25a00777c58deeba Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 13 Apr 2024 09:12:29 +0800 Subject: [PATCH 197/249] Refuse to load the spectrogram when the required memory is too high --- .../Spectrogram/EditorPlayfieldSpectrogram.cs | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs index 11573aa9db..091f61b1e4 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs @@ -10,6 +10,7 @@ using Wobble.Logging; using ManagedBass.Fx; using Quaver.Shared.Config; +using Quaver.Shared.Graphics.Notifications; using Quaver.Shared.Screens.Edit.UI.Playfield.Waveform; namespace Quaver.Shared.Screens.Edit.UI.Playfield.Spectrogram @@ -29,7 +30,7 @@ public class EditorPlayfieldSpectrogram : Container private double TrackLengthMilliSeconds { get; set; } private int FftPerSlice { get; set; } - + private int InterleaveCount { get; set; } = 1; private int InterleavedFftPerSlice => FftPerSlice * InterleaveCount; @@ -37,7 +38,7 @@ public class EditorPlayfieldSpectrogram : Container private int Stream { get; set; } public int FftCount { get; } - + public int FftFlag { get; } public int FftResultCount { get; set; } @@ -45,6 +46,8 @@ public class EditorPlayfieldSpectrogram : Container private int BytesReadPerFft { get; set; } + private long TotalBytes => (long)FftResultCount * InterleaveCount * FftRoundsTaken * sizeof(float); + private CancellationToken Token { get; } public EditorPlayfieldSpectrogram(EditorPlayfield playfield, CancellationToken token) @@ -94,7 +97,7 @@ public override void Draw(GameTime gameTime) public void GenerateWaveform() { FftPerSlice = (int)Playfield.Height; - GenerateTrackData(); + if (!GenerateTrackData()) return; var tempSlices = new List(); var millisecondPerFft = Bass.ChannelBytes2Seconds(Stream, BytesReadPerFft) * 1000; @@ -102,12 +105,13 @@ public void GenerateWaveform() var millisecondPerSlice = FftPerSlice * millisecondPerFft; var sampleRate = Bass.ChannelGetInfo(Stream).Frequency; - for (var fftRound = 0; fftRound < FftRoundsTaken; fftRound +=FftPerSlice) + for (var fftRound = 0; fftRound < FftRoundsTaken; fftRound += FftPerSlice) { var t = (int)(fftRound * millisecondPerFft); var trackDataYOffset = fftRound * InterleaveCount; - var slice = new EditorPlayfieldSpectrogramSlice(this, Playfield, (float)millisecondPerSlice, InterleavedFftPerSlice, + var slice = new EditorPlayfieldSpectrogramSlice(this, Playfield, (float)millisecondPerSlice, + InterleavedFftPerSlice, _trackData, trackDataYOffset, t, sampleRate); tempSlices.Add(slice); } @@ -118,7 +122,7 @@ public void GenerateWaveform() /// /// - private void GenerateTrackData() + private bool GenerateTrackData() { const BassFlags flags = BassFlags.Decode | BassFlags.Float; @@ -128,6 +132,16 @@ private void GenerateTrackData() FftResultCount = FftCount; BytesReadPerFft = sizeof(float) * FftResultCount * Bass.ChannelGetInfo(Stream).Channels * 2; FftRoundsTaken = (int)(TrackByteLength / BytesReadPerFft); + + const long bytesPerMb = 1 << 20; + Logger.Debug($"The spectrogram requires {(double)TotalBytes / bytesPerMb:#.##}MB memory", LogType.Runtime); + if (TotalBytes > 1024L * bytesPerMb) + { + NotificationManager.Show(NotificationLevel.Warning, + $"Spectrogram will not be loaded because it requires too much memory ({TotalBytes / bytesPerMb:#.##} MB)! Please lower the precision or FFT size."); + return false; + } + _trackData = new float[(FftRoundsTaken + 1) * InterleaveCount][]; for (var interleaveRound = 0; interleaveRound < InterleaveCount; interleaveRound++) @@ -140,14 +154,14 @@ private void GenerateTrackData() row = currentFftRound * InterleaveCount + interleaveRound; if (row >= _trackData.Length) break; _trackData[row] = GC.AllocateUninitializedArray(FftResultCount); - + currentFftRound++; } while (Bass.ChannelGetData(Stream, _trackData[row], FftFlag | (int)DataFlags.FFTRemoveDC) > 0); - } TrackByteLength = Bass.ChannelGetLength(Stream); TrackLengthMilliSeconds = Bass.ChannelBytes2Seconds(Stream, TrackByteLength) * 1000.0; + return true; } public static DataFlags GetFftDataFlag(int fftSize) From 011d5bcc619656705098547b35afd65a4d481c43 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 13 Apr 2024 09:14:24 +0800 Subject: [PATCH 198/249] Fix reloading spectrogram causing wave form loading wheel to hang --- Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs index ce8af5cafd..4e8afe28d6 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs @@ -1681,7 +1681,7 @@ private void ReloadWaveform() private void ReloadSpectrogram() { Spectrogram?.Destroy(); - LoadingWaveform.FadeIn(); + LoadingSpectrogram.FadeIn(); SpectrogramLoadTask.Run(0); } } From 2f78e74f3156dfc752a2140928abeeed3131c73f Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 13 Apr 2024 11:54:53 +0800 Subject: [PATCH 199/249] Add AddBookmark(BookmarkInfo bookmarkInfo) --- .../Screens/Edit/Actions/EditorActionManager.cs | 10 +++++++--- .../Screens/Edit/Actions/EditorPluginActionManager.cs | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/Actions/EditorActionManager.cs b/Quaver.Shared/Screens/Edit/Actions/EditorActionManager.cs index da736e2365..95ae353106 100644 --- a/Quaver.Shared/Screens/Edit/Actions/EditorActionManager.cs +++ b/Quaver.Shared/Screens/Edit/Actions/EditorActionManager.cs @@ -615,9 +615,13 @@ public void SetHitObjectSelection(List hitObjects) /// /// Adds a bookmark to the map /// - /// - /// - public void AddBookmark(int time, string note) => Perform(new EditorActionAddBookmark(this, WorkingMap, new BookmarkInfo { StartTime = time, Note = note })); + public void AddBookmark(BookmarkInfo bookmarkInfo) => Perform(new EditorActionAddBookmark(this, WorkingMap, bookmarkInfo)); + + public void AddBookmark(int time, string note) => AddBookmark(new BookmarkInfo + { + StartTime = time, + Note = note + }); /// /// Adds a batch of bookmarks to the map diff --git a/Quaver.Shared/Screens/Edit/Actions/EditorPluginActionManager.cs b/Quaver.Shared/Screens/Edit/Actions/EditorPluginActionManager.cs index a92c4436c8..4dd5848345 100644 --- a/Quaver.Shared/Screens/Edit/Actions/EditorPluginActionManager.cs +++ b/Quaver.Shared/Screens/Edit/Actions/EditorPluginActionManager.cs @@ -208,6 +208,7 @@ public void RemoveHitObjectBatch(List hitObjects) => public void ResnapNotes(List snaps, List hitObjectsToResnap) => ActionManager.ResnapNotes(snaps, hitObjectsToResnap); + public void AddBookmark(BookmarkInfo bookmarkInfo) => ActionManager.AddBookmark(bookmarkInfo); public void AddBookmark(int time, string note) => ActionManager.AddBookmark(time, note); public void AddBookmarkBatch(List bookmarks) => ActionManager.AddBookmarkBatch(bookmarks); From ae46435559c0d73086b54622cc7f055a6e43b06c Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 13 Apr 2024 11:55:09 +0800 Subject: [PATCH 200/249] Add GetBookmarkAt --- Quaver.Shared/Screens/Edit/Plugins/EditorPluginMap.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Quaver.Shared/Screens/Edit/Plugins/EditorPluginMap.cs b/Quaver.Shared/Screens/Edit/Plugins/EditorPluginMap.cs index c8460648e6..6fc0a01e00 100644 --- a/Quaver.Shared/Screens/Edit/Plugins/EditorPluginMap.cs +++ b/Quaver.Shared/Screens/Edit/Plugins/EditorPluginMap.cs @@ -108,6 +108,13 @@ public void SetFrameState() /// public SliderVelocityInfo GetScrollVelocityAt(double time) => Map.GetScrollVelocityAt(time); + /// + /// Gets the bookmark at a particular time in the current map + /// + /// + /// + public BookmarkInfo GetBookmarkAt(int time) => Map.GetBookmarkAt(time); + /// /// Finds the length of a timing point. /// From 5a5f2c465528ae2bee8a41d127e761df085c8ce1 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 13 Apr 2024 12:10:01 +0800 Subject: [PATCH 201/249] Snaps livemap notes only when audio is playing --- Quaver.Shared/Screens/Edit/EditScreen.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index add40a3cd9..d1bbccaa2e 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -867,8 +867,9 @@ private void HandleTemporaryHitObjectPlacement() continue; var time = (int)Math.Round(Track.Time, MidpointRounding.AwayFromZero); - - if (ConfigManager.EditorLiveMapSnap.Value) + + // Only snaps the time if the audio is playing + if (ConfigManager.EditorLiveMapSnap.Value && AudioEngine.Track.IsPlaying) { time = ((EditScreenView)View).Playfield.GetNearestTickFromTime(time, BeatSnap.Value); } From 2dd48d84747bf55aa8e9c44895a9096a186b5e10 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 13 Apr 2024 12:27:16 +0800 Subject: [PATCH 202/249] Allows setting offset for notes placed during live-mapping --- Quaver.Shared/Config/ConfigManager.cs | 3 + .../Dialogs/EditorSetLiveMapOffsetDialog.cs | 78 +++++++++++++++++++ Quaver.Shared/Screens/Edit/EditScreen.cs | 2 +- .../Screens/Edit/UI/Menu/EditorFileMenuBar.cs | 3 + 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 Quaver.Shared/Screens/Edit/Dialogs/EditorSetLiveMapOffsetDialog.cs diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index 5f41601106..38ea60c7eb 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -359,6 +359,8 @@ public static class ConfigManager internal static BindableInt EditorScrollSpeedKeys { get; private set; } internal static Bindable EditorLiveMapSnap { get; private set; } + + internal static BindableInt EditorLiveMapOffset { get; private set; } /// /// Whether or not to play hitsounds in the editor. @@ -1074,6 +1076,7 @@ private static void ReadConfigFile() InvertScrolling = ReadValue(@"InvertScrolling", false, data); InvertEditorScrolling = ReadValue(@"InvertEditorScrolling", true, data); EditorLiveMapSnap = ReadValue(@"EditorLiveMapSnap", false, data); + EditorLiveMapOffset = ReadInt(@"EditorLiveMapOffset", 0, -200, 200, data); EditorEnableHitsounds = ReadValue(@"EditorEnableHitsounds", true, data); EditorEnableKeysounds = ReadValue(@"EditorEnableKeysounds", true, data); EditorBeatSnapColorType = ReadValue(@"EditorBeatSnapColorType", EditorBeatSnapColor.Default, data); diff --git a/Quaver.Shared/Screens/Edit/Dialogs/EditorSetLiveMapOffsetDialog.cs b/Quaver.Shared/Screens/Edit/Dialogs/EditorSetLiveMapOffsetDialog.cs new file mode 100644 index 0000000000..c20ae06851 --- /dev/null +++ b/Quaver.Shared/Screens/Edit/Dialogs/EditorSetLiveMapOffsetDialog.cs @@ -0,0 +1,78 @@ +using System; +using System.Text.RegularExpressions; +using Quaver.Shared.Assets; +using Quaver.Shared.Config; +using Quaver.Shared.Graphics; +using Quaver.Shared.Helpers; +using Quaver.Shared.Screens.Edit.Actions.Offset; +using Wobble.Graphics; +using Wobble.Graphics.UI.Form; +using Wobble.Managers; + +namespace Quaver.Shared.Screens.Edit.Dialogs +{ + public class EditorSetLiveMapOffsetDialog : YesNoDialog + { + private EditScreen Screen { get; } + + /// + /// + protected Textbox Textbox { get; set; } + + /// + /// + /// + public EditorSetLiveMapOffsetDialog(EditScreen screen) : base("SET LIVEMAP OFFSET", + "Enter a value to apply an offset to notes placed during live-mapping...") + { + Screen = screen; + CreateTextbox(); + + Panel.Height += 50; + YesButton.Y = -30; + NoButton.Y = YesButton.Y; + + YesButton.Clicked += (sender, args) => OnSubmit(Textbox.RawText); + } + + /// + /// + private void CreateTextbox() + { + Textbox = new Textbox(new ScalableVector2(Panel.Width * 0.90f, 50), FontManager.GetWobbleFont(Fonts.LatoBlack), + 20, ConfigManager.EditorLiveMapOffset.Value.ToString(), "Enter an offset for live-mapping...", OnSubmit) + { + Parent = Panel, + Alignment = Alignment.BotCenter, + Y = -100, + Tint = ColorHelper.HexToColor("#2F2F2F"), + AlwaysFocused = true, + AllowedCharacters = new Regex(@"^[0-9-]*$") + }; + + Textbox.AddBorder(ColorHelper.HexToColor("#363636"), 2); + } + + /// + /// + /// + public override void Close() + { + Textbox.Visible = false; + base.Close(); + } + + private void OnSubmit(string s) + { + try + { + var val = int.Parse(s); + ConfigManager.EditorLiveMapOffset.Value = val; + } + catch (Exception) + { + // ignored + } + } + } +} \ No newline at end of file diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index d1bbccaa2e..d71a1eff97 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -871,7 +871,7 @@ private void HandleTemporaryHitObjectPlacement() // Only snaps the time if the audio is playing if (ConfigManager.EditorLiveMapSnap.Value && AudioEngine.Track.IsPlaying) { - time = ((EditScreenView)View).Playfield.GetNearestTickFromTime(time, BeatSnap.Value); + time = ((EditScreenView)View).Playfield.GetNearestTickFromTime(time - ConfigManager.EditorLiveMapOffset.Value, BeatSnap.Value); } var lane = i + 1; diff --git a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs index d1d46bb992..d214a4d8ca 100644 --- a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs +++ b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs @@ -463,6 +463,9 @@ private void CreateViewSection() if (ImGui.MenuItem("Snap Notes When Live Mapping", "", ConfigManager.EditorLiveMapSnap.Value)) ConfigManager.EditorLiveMapSnap.Value = !ConfigManager.EditorLiveMapSnap.Value; + + if (ImGui.MenuItem("Set Offset For Notes Placed During Live Mapping")) + DialogManager.Show(new EditorSetLiveMapOffsetDialog(Screen)); if (ImGui.MenuItem("Invert Beat Snap Scroll", "", Screen.InvertBeatSnapScroll.Value)) Screen.InvertBeatSnapScroll.Value = !Screen.InvertBeatSnapScroll.Value; From 848d5327b7762aae65e8da6e86a6289fba48a5a5 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 13 Apr 2024 18:02:16 +0800 Subject: [PATCH 203/249] Add option to scale ImGui in editor --- Quaver.Shared/Config/ConfigManager.cs | 6 ++++++ .../Edit/Plugins/Timing/EditorScrollVelocityPanel.cs | 3 ++- .../Edit/Plugins/Timing/EditorTimingPointPanel.cs | 3 ++- Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs | 9 +++++---- Quaver.Shared/Screens/Options/OptionsMenu.cs | 3 ++- Quaver.Shared/Scripting/LuaImGui.cs | 3 ++- 6 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index 16b3ef6543..492ff5ad8d 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -352,6 +352,11 @@ public static class ConfigManager /// /// internal static Bindable DisplayComboAlerts { get; private set; } + + /// + /// Scaling of ImGui windows and texts + /// + internal static BindableInt EditorImGuiScalePercentage { get; private set; } /// /// The scroll speed used in the editor. @@ -1064,6 +1069,7 @@ private static void ReadConfigFile() TapToRestart = ReadValue(@"TapToRestart", false, data); DisplayFailedLocalScores = ReadValue(@"DisplayFailedLocalScores", true, data); EditorScrollSpeedKeys = ReadInt(@"EditorScrollSpeedKeys", 16, 5, 100, data); + EditorImGuiScalePercentage = ReadInt(@"EditorImGuiScalePercentage", 100, 25, 400, data); KeyEditorPausePlay = ReadValue(@"KeyEditorPausePlay", Keys.Space, data); KeyEditorDecreaseAudioRate = ReadValue(@"KeyEditorDecreaseAudioRate", Keys.OemMinus, data); KeyEditorIncreaseAudioRate = ReadValue(@"KeyEditorIncreaseAudioRate", Keys.OemPlus, data); diff --git a/Quaver.Shared/Screens/Edit/Plugins/Timing/EditorScrollVelocityPanel.cs b/Quaver.Shared/Screens/Edit/Plugins/Timing/EditorScrollVelocityPanel.cs index 2f786486a8..d33c49d779 100644 --- a/Quaver.Shared/Screens/Edit/Plugins/Timing/EditorScrollVelocityPanel.cs +++ b/Quaver.Shared/Screens/Edit/Plugins/Timing/EditorScrollVelocityPanel.cs @@ -5,6 +5,7 @@ using ImGuiNET; using Microsoft.Xna.Framework.Input; using Quaver.API.Maps.Structures; +using Quaver.Shared.Config; using Wobble; using Wobble.Graphics.ImGUI; using Wobble.Input; @@ -71,7 +72,7 @@ public class EditorScrollVelocityPanel : SpriteImGui, IEditorPlugin /// /// /// - public EditorScrollVelocityPanel(EditScreen screen) : base(false, GetOptions()) + public EditorScrollVelocityPanel(EditScreen screen) : base(false, GetOptions(), ConfigManager.EditorImGuiScalePercentage.Value / 100f) { Screen = screen; Initialize(); diff --git a/Quaver.Shared/Screens/Edit/Plugins/Timing/EditorTimingPointPanel.cs b/Quaver.Shared/Screens/Edit/Plugins/Timing/EditorTimingPointPanel.cs index 3163ec0cef..6e6a1ff70f 100644 --- a/Quaver.Shared/Screens/Edit/Plugins/Timing/EditorTimingPointPanel.cs +++ b/Quaver.Shared/Screens/Edit/Plugins/Timing/EditorTimingPointPanel.cs @@ -6,6 +6,7 @@ using Microsoft.Xna.Framework.Input; using Quaver.API.Enums; using Quaver.API.Maps.Structures; +using Quaver.Shared.Config; using Quaver.Shared.Screens.Edit.Actions.Timing.AddBatch; using TagLib.Matroska; using TagLib.Riff; @@ -74,7 +75,7 @@ public class EditorTimingPointPanel : SpriteImGui, IEditorPlugin /// /// /// - public EditorTimingPointPanel(EditScreen screen) : base(false, GetOptions()) + public EditorTimingPointPanel(EditScreen screen) : base(false, GetOptions(), ConfigManager.EditorImGuiScalePercentage.Value / 100f) { Screen = screen; Initialize(); diff --git a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs index 305a4a6041..ed683ce400 100644 --- a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs +++ b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs @@ -59,7 +59,7 @@ public class EditorFileMenuBar : SpriteImGui private static bool DestroyContext { get; } = true; #endif - public EditorFileMenuBar(EditScreen screen) : base(DestroyContext, GetOptions()) => Screen = screen; + public EditorFileMenuBar(EditScreen screen) : base(DestroyContext, GetOptions(), ConfigManager.EditorImGuiScalePercentage.Value / 100f) => Screen = screen; /// @@ -67,9 +67,10 @@ public class EditorFileMenuBar : SpriteImGui /// protected override void RenderImguiLayout() { - ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 2); - ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(0, 10)); - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(12, 4)); + var scale = ConfigManager.EditorImGuiScalePercentage.Value / 100f; + ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 2 * scale); + ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(0, 10) * scale); + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(12, 4) * scale); ImGui.PushStyleColor(ImGuiCol.FrameBg, new Vector4(0, 0, 24, 0)); ImGui.PushStyleColor(ImGuiCol.WindowBg, new Vector4(0, 0, 24, 0)); diff --git a/Quaver.Shared/Screens/Options/OptionsMenu.cs b/Quaver.Shared/Screens/Options/OptionsMenu.cs index f9cc0fdcc1..4fff03051b 100644 --- a/Quaver.Shared/Screens/Options/OptionsMenu.cs +++ b/Quaver.Shared/Screens/Options/OptionsMenu.cs @@ -408,7 +408,8 @@ private void CreateSections() new OptionsItemCheckbox(containerRect, "Prefer Wayland", ConfigManager.PreferWayland) { Tags = new List {"linux"} - } + }, + new OptionsSlider(containerRect, "Editor ImGui Scale", ConfigManager.EditorImGuiScalePercentage) }), new OptionsSubcategory("Audio", new List() { diff --git a/Quaver.Shared/Scripting/LuaImGui.cs b/Quaver.Shared/Scripting/LuaImGui.cs index 7acece8d1b..50db0b0a78 100644 --- a/Quaver.Shared/Scripting/LuaImGui.cs +++ b/Quaver.Shared/Scripting/LuaImGui.cs @@ -7,6 +7,7 @@ using Microsoft.Xna.Framework.Input; using MoonSharp.Interpreter; using Quaver.API.Maps.Structures; +using Quaver.Shared.Config; using Quaver.Shared.Screens.Edit.UI.Menu; using Wobble; using Wobble.Graphics.ImGUI; @@ -45,7 +46,7 @@ public class LuaImGui : SpriteImGui /// /// /// - public LuaImGui(string filePath, bool isResource = false) : base(false, EditorFileMenuBar.GetOptions()) + public LuaImGui(string filePath, bool isResource = false) : base(false, EditorFileMenuBar.GetOptions(), ConfigManager.EditorImGuiScalePercentage.Value / 100f) { FilePath = filePath; IsResource = isResource; From 0cc0f56e6fcd952669d4b85e1fd9305cee43d927 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sun, 14 Apr 2024 16:56:51 +0800 Subject: [PATCH 204/249] Update Quaver.API --- Quaver.API | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.API b/Quaver.API index 9d206592b0..43e800efb0 160000 --- a/Quaver.API +++ b/Quaver.API @@ -1 +1 @@ -Subproject commit 9d206592b0e02fb3d9c3e6d6df2b507e3d7c4fc1 +Subproject commit 43e800efb079e9c099315c4b365490e357e2380c From 60f8e094643589d1e7f195fbb607263e100921e1 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sun, 14 Apr 2024 16:58:08 +0800 Subject: [PATCH 205/249] Update Wobble --- Wobble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wobble b/Wobble index 8dd23968f1..bbf78d5dcf 160000 --- a/Wobble +++ b/Wobble @@ -1 +1 @@ -Subproject commit 8dd23968f1e1b244453a1e1f393ceb4d563207d0 +Subproject commit bbf78d5dcfaaf108f7fa88afedbc025908d6964d From 2720760d215afb440270e660d54202ad4253c4d9 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sun, 14 Apr 2024 17:36:52 +0800 Subject: [PATCH 206/249] Don't use bindable for ImGuiScale in editor. Instead, let the user reenter the screen for it to take effect. --- Quaver.Shared/Screens/Edit/EditScreen.cs | 8 ++++++++ .../Edit/Plugins/Timing/EditorScrollVelocityPanel.cs | 2 +- .../Edit/Plugins/Timing/EditorTimingPointPanel.cs | 2 +- Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs | 9 ++++----- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index fb54f5e89c..b5d1d43a46 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -233,6 +233,13 @@ public sealed class EditScreen : QuaverScreen, IHasLeftPanel /// public Bindable SelectedLayer { get; } = new Bindable(null); + /// + /// The fraction scaling of ImGui Windows. + /// Do NOT use bindable from this. We expect the user to leave and reenter the screen to see the effect + /// Since needs to be called to update font sizes + /// + public float ImGuiScale { get; } = ConfigManager.EditorImGuiScalePercentage?.Value / 100f ?? 1f; + /// /// Objects that are currently copied /// @@ -248,6 +255,7 @@ public sealed class EditScreen : QuaverScreen, IHasLeftPanel ColorRgb = "255,255,255" }; + /// /// public EditorInputManager InputManager { get; } diff --git a/Quaver.Shared/Screens/Edit/Plugins/Timing/EditorScrollVelocityPanel.cs b/Quaver.Shared/Screens/Edit/Plugins/Timing/EditorScrollVelocityPanel.cs index d33c49d779..16f4be1476 100644 --- a/Quaver.Shared/Screens/Edit/Plugins/Timing/EditorScrollVelocityPanel.cs +++ b/Quaver.Shared/Screens/Edit/Plugins/Timing/EditorScrollVelocityPanel.cs @@ -72,7 +72,7 @@ public class EditorScrollVelocityPanel : SpriteImGui, IEditorPlugin /// /// /// - public EditorScrollVelocityPanel(EditScreen screen) : base(false, GetOptions(), ConfigManager.EditorImGuiScalePercentage.Value / 100f) + public EditorScrollVelocityPanel(EditScreen screen) : base(false, GetOptions(), screen.ImGuiScale) { Screen = screen; Initialize(); diff --git a/Quaver.Shared/Screens/Edit/Plugins/Timing/EditorTimingPointPanel.cs b/Quaver.Shared/Screens/Edit/Plugins/Timing/EditorTimingPointPanel.cs index 6e6a1ff70f..4642c444a9 100644 --- a/Quaver.Shared/Screens/Edit/Plugins/Timing/EditorTimingPointPanel.cs +++ b/Quaver.Shared/Screens/Edit/Plugins/Timing/EditorTimingPointPanel.cs @@ -75,7 +75,7 @@ public class EditorTimingPointPanel : SpriteImGui, IEditorPlugin /// /// /// - public EditorTimingPointPanel(EditScreen screen) : base(false, GetOptions(), ConfigManager.EditorImGuiScalePercentage.Value / 100f) + public EditorTimingPointPanel(EditScreen screen) : base(false, GetOptions(), screen.ImGuiScale) { Screen = screen; Initialize(); diff --git a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs index ed683ce400..08145ef2c1 100644 --- a/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs +++ b/Quaver.Shared/Screens/Edit/UI/Menu/EditorFileMenuBar.cs @@ -59,7 +59,7 @@ public class EditorFileMenuBar : SpriteImGui private static bool DestroyContext { get; } = true; #endif - public EditorFileMenuBar(EditScreen screen) : base(DestroyContext, GetOptions(), ConfigManager.EditorImGuiScalePercentage.Value / 100f) => Screen = screen; + public EditorFileMenuBar(EditScreen screen) : base(DestroyContext, GetOptions(), screen.ImGuiScale) => Screen = screen; /// @@ -67,10 +67,9 @@ public class EditorFileMenuBar : SpriteImGui /// protected override void RenderImguiLayout() { - var scale = ConfigManager.EditorImGuiScalePercentage.Value / 100f; - ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 2 * scale); - ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(0, 10) * scale); - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(12, 4) * scale); + ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 2 * Screen.ImGuiScale); + ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(0, 10) * Screen.ImGuiScale); + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(12, 4) * Screen.ImGuiScale); ImGui.PushStyleColor(ImGuiCol.FrameBg, new Vector4(0, 0, 24, 0)); ImGui.PushStyleColor(ImGuiCol.WindowBg, new Vector4(0, 0, 24, 0)); From 70d4df7ddfddbf9f1524b66b3c17b15110f30ac0 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sun, 14 Apr 2024 17:37:56 +0800 Subject: [PATCH 207/249] Change max ImGui scale percentage to 300 --- Quaver.Shared/Config/ConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Config/ConfigManager.cs b/Quaver.Shared/Config/ConfigManager.cs index 492ff5ad8d..b722f3b0b4 100644 --- a/Quaver.Shared/Config/ConfigManager.cs +++ b/Quaver.Shared/Config/ConfigManager.cs @@ -1069,7 +1069,7 @@ private static void ReadConfigFile() TapToRestart = ReadValue(@"TapToRestart", false, data); DisplayFailedLocalScores = ReadValue(@"DisplayFailedLocalScores", true, data); EditorScrollSpeedKeys = ReadInt(@"EditorScrollSpeedKeys", 16, 5, 100, data); - EditorImGuiScalePercentage = ReadInt(@"EditorImGuiScalePercentage", 100, 25, 400, data); + EditorImGuiScalePercentage = ReadInt(@"EditorImGuiScalePercentage", 100, 25, 300, data); KeyEditorPausePlay = ReadValue(@"KeyEditorPausePlay", Keys.Space, data); KeyEditorDecreaseAudioRate = ReadValue(@"KeyEditorDecreaseAudioRate", Keys.OemMinus, data); KeyEditorIncreaseAudioRate = ReadValue(@"KeyEditorIncreaseAudioRate", Keys.OemPlus, data); From 0eb6563c12a66411a7e6fd1e69c9495eb26e0010 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Fri, 12 Apr 2024 00:28:22 +0800 Subject: [PATCH 208/249] Use directory name of sm files when importing --- Quaver.Shared/Database/Maps/MapsetImporter.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Database/Maps/MapsetImporter.cs b/Quaver.Shared/Database/Maps/MapsetImporter.cs index 97b8e14768..8fa91d163f 100644 --- a/Quaver.Shared/Database/Maps/MapsetImporter.cs +++ b/Quaver.Shared/Database/Maps/MapsetImporter.cs @@ -299,9 +299,13 @@ public static void ImportMapsetsInQueue(int? selectMapIdAfterImport = null) Parallel.For(0, Queue.Count, new ParallelOptions { MaxDegreeOfParallelism = 4 }, i => { var file = Queue[i]; + var extension = Path.GetExtension(file); + var isPartOfMapset = extension == ".sm"; var time = (long) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).Milliseconds; - var folderName = Path.GetFileNameWithoutExtension(file); + var folderName = isPartOfMapset + ? Path.GetFileName(Path.GetDirectoryName(file)) + : Path.GetFileNameWithoutExtension(file); folderName = folderName.Substring(0, Math.Min(folderName.Length, 100)); var extractDirectory = $@"{ConfigManager.SongDirectory}/{folderName} - {time}/"; From 42f0ca5bf6567d7f4e710332c3a2e051cd71861f Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 13 Apr 2024 10:14:09 +0800 Subject: [PATCH 209/249] Extract the method to add map to import queue --- Quaver.Shared/Database/Maps/MapsetImporter.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Quaver.Shared/Database/Maps/MapsetImporter.cs b/Quaver.Shared/Database/Maps/MapsetImporter.cs index 8fa91d163f..e548a88ba2 100644 --- a/Quaver.Shared/Database/Maps/MapsetImporter.cs +++ b/Quaver.Shared/Database/Maps/MapsetImporter.cs @@ -134,6 +134,17 @@ private static bool AcceptedMapType(string path) return path.EndsWith(".qp") || path.EndsWith(".osz") || path.EndsWith(".sm") || path.EndsWith(".mcz") || path.EndsWith(".mc"); } + /// + /// Adds the map dragged into the window to be scheduled to import + /// + /// + private static void AddMapImportToQueue(string path) + { + NotificationManager.Show(NotificationLevel.Info, $"Scheduled {Path.GetFileName(path)} to be imported!"); + Queue.Add(path); + PostMapQueue(); + } + /// /// Tries to import the given file, be it a map, a replay, a skin, etc. /// path to the file to import @@ -146,12 +157,7 @@ public static void ImportFile(string path) // Mapset files (or directory of Mapset files) if (AcceptedMapType(path)) { - Queue.Add(path); - - var log = $"Scheduled {Path.GetFileName(path)} to be imported!"; - NotificationManager.Show(NotificationLevel.Info, log); - - PostMapQueue(); + AddMapImportToQueue(path); } // Quaver Replay else if (path.EndsWith(".qr")) @@ -271,8 +277,7 @@ public static void ImportFile(string path) { if (AcceptedMapType(subPath)) { - NotificationManager.Show(NotificationLevel.Info, $"Scheduled {Path.GetFileName(subPath)} to be imported!"); - Queue.Add(subPath); + AddMapImportToQueue(subPath); } } foreach (var subDir in dirs) From fb6a2bf65991a2276f80e5ab31ca9c3aa4f54a9f Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 13 Apr 2024 10:14:40 +0800 Subject: [PATCH 210/249] Only schedule one .mc file to be imported if multiple .mc files are dragged under the same directory --- Quaver.Shared/Database/Maps/MapsetImporter.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Quaver.Shared/Database/Maps/MapsetImporter.cs b/Quaver.Shared/Database/Maps/MapsetImporter.cs index e548a88ba2..32889c1ae6 100644 --- a/Quaver.Shared/Database/Maps/MapsetImporter.cs +++ b/Quaver.Shared/Database/Maps/MapsetImporter.cs @@ -140,6 +140,13 @@ private static bool AcceptedMapType(string path) /// private static void AddMapImportToQueue(string path) { + if (Path.GetExtension(path) == ".mc") + { + foreach (var scheduledPath in Queue) + { + if (Path.GetDirectoryName(scheduledPath) == Path.GetDirectoryName(path)) return; + } + } NotificationManager.Show(NotificationLevel.Info, $"Scheduled {Path.GetFileName(path)} to be imported!"); Queue.Add(path); PostMapQueue(); From 41085c6c332d8620fffcb17ace655ea9b5c99416 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 13 Apr 2024 10:16:31 +0800 Subject: [PATCH 211/249] Fix indent of PostMapQueue and remove redundant return; at the end of control flow --- Quaver.Shared/Database/Maps/MapsetImporter.cs | 57 +++++++++---------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/Quaver.Shared/Database/Maps/MapsetImporter.cs b/Quaver.Shared/Database/Maps/MapsetImporter.cs index 32889c1ae6..5e68393671 100644 --- a/Quaver.Shared/Database/Maps/MapsetImporter.cs +++ b/Quaver.Shared/Database/Maps/MapsetImporter.cs @@ -94,35 +94,34 @@ private static void PostMapQueue() var screen = game.CurrentScreen; if (screen.Exiting) - return; - - if (screen.Type == QuaverScreenType.Select) - { - if (OnlineManager.CurrentGame != null) - { - var select = game.CurrentScreen as SelectionScreen; - screen.Exit(() => new ImportingScreen(null, true)); - return; - } - - screen.Exit(() => new ImportingScreen()); - return; - } - - if (screen.Type == QuaverScreenType.Music) - { - screen.Exit(() => new ImportingScreen()); - return; - } - - if (screen.Type == QuaverScreenType.Multiplayer) - { - var multi = (MultiplayerGameScreen)screen; - multi.DontLeaveGameUponScreenSwitch = true; - - screen.Exit(() => new ImportingScreen()); - return; - } + return; + + if (screen.Type == QuaverScreenType.Select) + { + if (OnlineManager.CurrentGame != null) + { + var select = game.CurrentScreen as SelectionScreen; + screen.Exit(() => new ImportingScreen(null, true)); + return; + } + + screen.Exit(() => new ImportingScreen()); + return; + } + + if (screen.Type == QuaverScreenType.Music) + { + screen.Exit(() => new ImportingScreen()); + return; + } + + if (screen.Type == QuaverScreenType.Multiplayer) + { + var multi = (MultiplayerGameScreen)screen; + multi.DontLeaveGameUponScreenSwitch = true; + + screen.Exit(() => new ImportingScreen()); + } } /// From 98fa0caa4ec76c14fa8aee88994b31b8832b1823 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sun, 14 Apr 2024 18:03:21 +0800 Subject: [PATCH 212/249] Fix indent and add comment --- Quaver.Shared/Database/Maps/MapsetImporter.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Database/Maps/MapsetImporter.cs b/Quaver.Shared/Database/Maps/MapsetImporter.cs index 5e68393671..8ae27a9a82 100644 --- a/Quaver.Shared/Database/Maps/MapsetImporter.cs +++ b/Quaver.Shared/Database/Maps/MapsetImporter.cs @@ -139,6 +139,8 @@ private static bool AcceptedMapType(string path) /// private static void AddMapImportToQueue(string path) { + // Only one .mc file under the same directory should be imported + // since .mc import if (Path.GetExtension(path) == ".mc") { foreach (var scheduledPath in Queue) @@ -146,6 +148,7 @@ private static void AddMapImportToQueue(string path) if (Path.GetDirectoryName(scheduledPath) == Path.GetDirectoryName(path)) return; } } + NotificationManager.Show(NotificationLevel.Info, $"Scheduled {Path.GetFileName(path)} to be imported!"); Queue.Add(path); PostMapQueue(); @@ -311,8 +314,9 @@ public static void ImportMapsetsInQueue(int? selectMapIdAfterImport = null) { var file = Queue[i]; var extension = Path.GetExtension(file); + // Use directory of .sm files, because during scheduled bulk import, there can be multiple files named file.sm, for example var isPartOfMapset = extension == ".sm"; - var time = (long) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).Milliseconds; + var time = (long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).Milliseconds; var folderName = isPartOfMapset ? Path.GetFileName(Path.GetDirectoryName(file)) From 50c7d95d01e8c5f09aa68ef930871cb409ef5ccc Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sun, 14 Apr 2024 18:31:01 +0800 Subject: [PATCH 213/249] Flip sign of offset addition to livemap snapping --- Quaver.Shared/Screens/Edit/EditScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index d71a1eff97..c858594aa5 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -871,7 +871,7 @@ private void HandleTemporaryHitObjectPlacement() // Only snaps the time if the audio is playing if (ConfigManager.EditorLiveMapSnap.Value && AudioEngine.Track.IsPlaying) { - time = ((EditScreenView)View).Playfield.GetNearestTickFromTime(time - ConfigManager.EditorLiveMapOffset.Value, BeatSnap.Value); + time = ((EditScreenView)View).Playfield.GetNearestTickFromTime(time + ConfigManager.EditorLiveMapOffset.Value, BeatSnap.Value); } var lane = i + 1; From 52a18161126124d656eb5cc11c729b3bcc93f47b Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sun, 14 Apr 2024 18:42:55 +0800 Subject: [PATCH 214/249] Lazy load waveform and spectrogram --- .../Screens/Edit/UI/Playfield/EditorPlayfield.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs index 94983b8c16..15a1626ae2 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs @@ -376,8 +376,6 @@ public EditorPlayfield(Qua map, EditorActionManager manager, Bindable CreateDividerLines(); CreateHitPositionLine(); CreateTimeline(); - CreateWaveform(); - CreateSpectrogram(); CreateLineContainer(); CreateHitObjects(); CreateButton(); @@ -429,6 +427,12 @@ public override void Update(GameTime gameTime) Button.Position = new ScalableVector2(X + BorderLeft.Width / 2f, Y); Button.Update(gameTime); + if (ShowSpectrogram.Value && Spectrogram == null && SpectrogramLoadTask == null) + CreateSpectrogram(); + + if (ShowWaveform.Value && Waveform == null && WaveformLoadTask == null) + CreateWaveform(); + if (LoadingWaveform != null) { LoadingWaveform.Alignment = Alignment; From f64ee97729cc955cb3161b3a6f1793a20e980b4d Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sun, 14 Apr 2024 19:20:51 +0800 Subject: [PATCH 215/249] Destroy waveform and spectrogram if disabled --- .../Screens/Edit/UI/Playfield/EditorPlayfield.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs index 15a1626ae2..29bb121e1a 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/EditorPlayfield.cs @@ -428,10 +428,26 @@ public override void Update(GameTime gameTime) Button.Update(gameTime); if (ShowSpectrogram.Value && Spectrogram == null && SpectrogramLoadTask == null) + { CreateSpectrogram(); + } + else if (!ShowSpectrogram.Value && Spectrogram != null) + { + Spectrogram?.Dispose(); + Spectrogram = null; + SpectrogramLoadTask = null; + } if (ShowWaveform.Value && Waveform == null && WaveformLoadTask == null) + { CreateWaveform(); + } + else if (!ShowWaveform.Value && Waveform != null) + { + Waveform?.Dispose(); + Waveform = null; + WaveformLoadTask = null; + } if (LoadingWaveform != null) { From dca02f0614622014a4c2e9a43a12ebf242842bba Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sun, 14 Apr 2024 21:44:55 +0800 Subject: [PATCH 216/249] Allocate every sub-array in _trackData --- .../UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs index 091f61b1e4..546454de62 100644 --- a/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs +++ b/Quaver.Shared/Screens/Edit/UI/Playfield/Spectrogram/EditorPlayfieldSpectrogram.cs @@ -143,6 +143,8 @@ private bool GenerateTrackData() } _trackData = new float[(FftRoundsTaken + 1) * InterleaveCount][]; + for (var i = 0; i < _trackData.Length; i++) + _trackData[i] = GC.AllocateUninitializedArray(FftResultCount); for (var interleaveRound = 0; interleaveRound < InterleaveCount; interleaveRound++) { @@ -153,7 +155,6 @@ private bool GenerateTrackData() { row = currentFftRound * InterleaveCount + interleaveRound; if (row >= _trackData.Length) break; - _trackData[row] = GC.AllocateUninitializedArray(FftResultCount); currentFftRound++; } while (Bass.ChannelGetData(Stream, _trackData[row], FftFlag | (int)DataFlags.FFTRemoveDC) > 0); From c2208dd3072d83156f7f25e2a751e48199fc7ccc Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Tue, 16 Apr 2024 00:14:02 +0800 Subject: [PATCH 217/249] Directly use replay score in Co-op play result screen --- .../Table/ResultsMultiplayerTable.cs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs index b17e8829db..505652b562 100644 --- a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs +++ b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs @@ -228,22 +228,33 @@ private List GetOrderedUserList() private int GetMatchScores(int val, CancellationToken cancellationToken) { - const int maxRetryCount = 3; + List players = null; MultiplayerMatchInformationResponse matchInfoResponse = null; - - for (var retryCount = 0; retryCount < maxRetryCount; retryCount++) + var isCoop = Processor.Value.Mods.HasFlag(ModIdentifier.Coop); + // If this is a co-op play, we should directly use replay result instead of fetching online + if (isCoop) { - if (TryFetchMatchInfo(out matchInfoResponse)) break; - Thread.Sleep(500); + players = GetOrderedUserList(); } + else + { + // Otherwise, fetch match info from the API first + const int maxRetryCount = 3; - List players; - if (matchInfoResponse == null) + for (var retryCount = 0; retryCount < maxRetryCount; retryCount++) + { + if (TryFetchMatchInfo(out matchInfoResponse)) break; + Thread.Sleep(500); + } + } + + // Skip fetching results if it's co-op play + if (!isCoop && matchInfoResponse == null) { NotificationManager.Show(NotificationLevel.Error, "Failed to retrieve players' scores!"); players = GetOrderedUserList(); } - else + else if (!isCoop) { players = new List(); var qua = Map.LoadQua(); From 92991bfccc4dfcca60bdcd2b5cc9455781c98cc6 Mon Sep 17 00:00:00 2001 From: AiAe Date: Mon, 15 Apr 2024 19:37:18 +0300 Subject: [PATCH 218/249] Update Quaver.Server.Client --- Quaver.Server.Common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Server.Common b/Quaver.Server.Common index 89d56de33d..a441bd11ee 160000 --- a/Quaver.Server.Common +++ b/Quaver.Server.Common @@ -1 +1 @@ -Subproject commit 89d56de33d8711e4ba87c3a7f3e476565b115d10 +Subproject commit a441bd11ee4b0f6691a2556f412f3a73f269e1d5 From e9eecbe25248994b3e27f668ee7754b5a0431f9b Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Wed, 17 Apr 2024 15:18:02 +0800 Subject: [PATCH 219/249] Select the map when switching difficulty --- Quaver.Shared/Screens/Edit/EditScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Quaver.Shared/Screens/Edit/EditScreen.cs b/Quaver.Shared/Screens/Edit/EditScreen.cs index ae09dde135..6249633a77 100644 --- a/Quaver.Shared/Screens/Edit/EditScreen.cs +++ b/Quaver.Shared/Screens/Edit/EditScreen.cs @@ -1617,6 +1617,7 @@ public void SwitchToMap(Map map, bool force = false) { try { + MapManager.Selected.Value = map; var track = AudioEngine.LoadMapAudioTrack(map); Exit(() => new EditScreen(map, track)); From fdc3cd056b1f294f9593dc259affaf189dde2eb7 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 20 Apr 2024 14:37:36 +0800 Subject: [PATCH 220/249] Use standard window in tournaments --- Quaver.Shared/Screens/Gameplay/GameplayScreen.cs | 7 +++++++ Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs | 6 ++++-- Quaver.Shared/Screens/Gameplay/UI/GradeDisplay.cs | 3 ++- Quaver.Shared/Screens/Tournament/TournamentScreenView.cs | 4 ++-- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs index 072e3cdcd4..31b28ce3a8 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs @@ -341,6 +341,13 @@ public bool EligibleToSkip /// private ReplayInputManagerKeys CachedReplayInputManager { get; set; } + /// + /// true iff we are spectating a tournament + /// This is used for appropriate judgement windows applied to score, grade and rating display + /// + public bool IsSpectatingTournament => this is TournamentGameplayScreen tournamentGameplayScreen && + tournamentGameplayScreen.Type == TournamentScreenType.Spectator; + /// /// public bool IsDisposed { get; private set; } diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs index 8d4c47fb21..8fd9dfa24d 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs @@ -206,7 +206,9 @@ public class GameplayScreenView : ScreenView public GameplayScreenView(Screen screen) : base(screen) { Screen = (GameplayScreen)screen; - RatingProcessor = new RatingProcessorKeys(Screen.Map.SolveDifficulty(ModManager.Mods, true).OverallDifficulty); + RatingProcessor = new RatingProcessorKeys(Screen.Map.SolveDifficulty( + screen is TournamentGameplayScreen ? Screen.Ruleset.ScoreProcessor.Mods : ModManager.Mods, + true).OverallDifficulty); CreateBackground(); @@ -457,7 +459,7 @@ public void UpdateScoreAndAccuracyDisplays() RatingDisplay.UpdateValue(RatingProcessor.CalculateRating(Screen.Ruleset.StandardizedReplayPlayer.ScoreProcessor.Accuracy)); - if (ConfigManager.DisplayRankedAccuracy.Value) + if (ConfigManager.DisplayRankedAccuracy.Value || Screen.IsSpectatingTournament) AccuracyDisplay.UpdateValue(Screen.Ruleset.StandardizedReplayPlayer.ScoreProcessor.Accuracy); else AccuracyDisplay.UpdateValue(Screen.Ruleset.ScoreProcessor.Accuracy); diff --git a/Quaver.Shared/Screens/Gameplay/UI/GradeDisplay.cs b/Quaver.Shared/Screens/Gameplay/UI/GradeDisplay.cs index 9e5d8ebcb3..ff9164b1cf 100644 --- a/Quaver.Shared/Screens/Gameplay/UI/GradeDisplay.cs +++ b/Quaver.Shared/Screens/Gameplay/UI/GradeDisplay.cs @@ -10,6 +10,7 @@ using Quaver.API.Helpers; using Quaver.API.Maps.Processors.Scoring; using Quaver.Shared.Config; +using Quaver.Shared.Screens.Tournament.Gameplay; using Quaver.Shared.Skinning; using Wobble.Graphics.Sprites; @@ -68,7 +69,7 @@ private void ChangeGradeImage() { Visible = Scoring.Score > 0 && (ConfigManager.DisplayGameplayOverlay?.Value ?? true); - if (ConfigManager.DisplayRankedAccuracy.Value) + if (ConfigManager.DisplayRankedAccuracy.Value || Screen.IsSpectatingTournament) Grade = GradeHelper.GetGradeFromAccuracy(Screen.Ruleset.StandardizedReplayPlayer.ScoreProcessor.Accuracy); else Grade = GradeHelper.GetGradeFromAccuracy(Scoring.Accuracy); diff --git a/Quaver.Shared/Screens/Tournament/TournamentScreenView.cs b/Quaver.Shared/Screens/Tournament/TournamentScreenView.cs index ed999ad77e..904e3f2ed1 100644 --- a/Quaver.Shared/Screens/Tournament/TournamentScreenView.cs +++ b/Quaver.Shared/Screens/Tournament/TournamentScreenView.cs @@ -257,7 +257,7 @@ private void CreateOverlay() { var difficulty = screen.Map.SolveDifficulty(screen.Ruleset.ScoreProcessor.Mods).OverallDifficulty; - TournamentPlayers.Add(new TournamentPlayer(screen.SpectatorClient.Player, screen.Ruleset.ScoreProcessor, difficulty)); + TournamentPlayers.Add(new TournamentPlayer(screen.SpectatorClient.Player, screen.Ruleset.StandardizedReplayPlayer.ScoreProcessor, difficulty)); } Overlay = new TournamentOverlay(TournamentScreen.MainGameplayScreen.Map, OnlineManager.CurrentGame, TournamentPlayers) { Parent = Container }; @@ -271,7 +271,7 @@ private void CreateOverlay() CountryFlag = "US", Id = i + 1, UserGroups = UserGroups.Normal - }), screen.Ruleset.ScoreProcessor, screen.Map.SolveDifficulty(screen.Ruleset.ScoreProcessor.Mods).OverallDifficulty))); + }), screen.Ruleset.StandardizedReplayPlayer.ScoreProcessor, screen.Map.SolveDifficulty(screen.Ruleset.ScoreProcessor.Mods).OverallDifficulty))); var game = new MultiplayerGame { From 62a778c76cb5eee8f4f6822bc5654d0b07be52f3 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 20 Apr 2024 14:40:46 +0800 Subject: [PATCH 221/249] Send NewSong and FinishSong frames regardless of the presence of a spectator and fix missing last frame --- .../Screens/Gameplay/GameplayScreen.cs | 9 +++++++-- .../Screens/Gameplay/GameplayScreenView.cs | 19 ++++++------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs index 31b28ce3a8..efc76663bb 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs @@ -47,6 +47,7 @@ using Quaver.Shared.Screens.MultiplayerLobby; using Quaver.Shared.Screens.Selection; using Quaver.Shared.Screens.Selection.UI; +using Quaver.Shared.Screens.Tournament; using Quaver.Shared.Screens.Tournament.Gameplay; using Quaver.Shared.Skinning; using Wobble; @@ -484,7 +485,7 @@ public override void OnFirstUpdate() if (IsMultiplayerGame && !IsSongSelectPreview) OnlineManager.Client?.MultiplayerGameScreenLoaded(); - if (OnlineManager.IsBeingSpectated && !InReplayMode) + if (!InReplayMode) OnlineManager.Client?.SendReplaySpectatorFrames(SpectatorClientStatus.NewSong, AudioEngine.Track.Time, new List()); TimePlayed = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); @@ -1356,7 +1357,7 @@ private void HandleSpectatorSkipping() /// /// If the client is currently being spectated, replay frames should be sent to the server /// - public void SendReplayFramesToServer(bool force = false) + public void SendReplayFramesToServer(bool force = false, bool appendFinishSong = false) { if (!OnlineManager.IsBeingSpectated || InReplayMode || IsSongSelectPreview) return; @@ -1394,6 +1395,10 @@ public void SendReplayFramesToServer(bool force = false) return; OnlineManager.Client?.SendReplaySpectatorFrames(status, AudioEngine.Track.Time, frames); + + if (appendFinishSong) + OnlineManager.Client?.SendReplaySpectatorFrames(SpectatorClientStatus.FinishedSong, int.MaxValue, + new List()); }); } diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs index 8fd9dfa24d..7580da3eb7 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs @@ -735,14 +735,9 @@ private void HandlePlayCompletion(GameTime gameTime) // Force all replay frames on failure if (OnlineManager.IsBeingSpectated) { - Screen.SendReplayFramesToServer(true); - - // Send final replay frame to let spectators know the song is complete - if (OnlineManager.IsBeingSpectated) - { - OnlineManager.Client?.SendReplaySpectatorFrames(SpectatorClientStatus.FinishedSong, int.MaxValue, - new List()); - } + // Send replay frames + // FinishedSong frame as well unless we are the spectator + Screen.SendReplayFramesToServer(true, !OnlineManager.IsSpectatingSomeone); } if (Screen.IsPlayTesting) @@ -883,13 +878,11 @@ private void OnGameEnded(object sender, GameEndedEventArgs e) Screen.MultiplayerMatchEndedPrematurely = !Screen.IsPlayComplete && manager.NextHitObject != null && (Screen.Timing.Time >= Screen.Map.Length || AudioEngine.Track.Time >= AudioEngine.Track.Length); - if (Screen is TournamentGameplayScreen) + if (Screen.Exiting) return; - - Screen.IsPaused = true; - Screen.Exit(() => new ResultsScreen(Screen, OnlineManager.CurrentGame, - GetProcessorsFromScoreboard(ScoreboardLeft), GetProcessorsFromScoreboard(ScoreboardRight))); + GetProcessorsFromScoreboard(ScoreboardLeft), GetProcessorsFromScoreboard(ScoreboardRight)), + Screen is TournamentGameplayScreen ? 3500 : 0); } private List GetScoreboardUsers() From 83de9efa2b49ee45aa2729376648b5d749386e89 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 20 Apr 2024 14:42:19 +0800 Subject: [PATCH 222/249] Skip API result fetch on COOP and theater --- .../Screens/Results/ResultsScreenView.cs | 4 +++- .../Tabs/Multiplayer/ResultsMultiplayerTab.cs | 11 ++++++---- .../Table/ResultsMultiplayerTable.cs | 20 ++++++++++++------- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Quaver.Shared/Screens/Results/ResultsScreenView.cs b/Quaver.Shared/Screens/Results/ResultsScreenView.cs index 5f66550708..f5cc51373b 100644 --- a/Quaver.Shared/Screens/Results/ResultsScreenView.cs +++ b/Quaver.Shared/Screens/Results/ResultsScreenView.cs @@ -14,6 +14,7 @@ using Quaver.Shared.Screens.Results.UI.Tabs.Multiplayer; using Quaver.Shared.Screens.Results.UI.Tabs.Overview; using Quaver.Shared.Screens.Tests.UI.Borders; +using Quaver.Shared.Screens.Tournament.Gameplay; using Quaver.Shared.Skinning; using Wobble; using Wobble.Bindables; @@ -170,7 +171,8 @@ private void CreateOverviewTab() private void CreateMultiplayerTab() { MultiplayerTab = new ResultsMultiplayerTab(ResultsScreen.Map, ResultsScreen.Processor, - ResultsScreen.ActiveTab, ResultsScreen.MultiplayerGame, ResultsScreen.MultiplayerTeam1Users, ResultsScreen.MultiplayerTeam2Users) + ResultsScreen.ActiveTab, ResultsScreen.MultiplayerGame, ResultsScreen.MultiplayerTeam1Users, ResultsScreen.MultiplayerTeam2Users, + ResultsScreen.Gameplay is TournamentGameplayScreen tournamentGameplayScreen && tournamentGameplayScreen.Type != TournamentScreenType.Spectator) { Parent = Container, Alignment = Alignment.TopCenter, diff --git a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/ResultsMultiplayerTab.cs b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/ResultsMultiplayerTab.cs index 6d5b1ecd09..658c982dab 100644 --- a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/ResultsMultiplayerTab.cs +++ b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/ResultsMultiplayerTab.cs @@ -65,8 +65,10 @@ public class ResultsMultiplayerTab : ResultsTabContainer /// /// /// - public ResultsMultiplayerTab(Map map, Bindable processor, Bindable activeTab, - MultiplayerGame game, List team1, List team2) : base(map, processor, activeTab, + /// + public ResultsMultiplayerTab(Map map, Bindable processor, + Bindable activeTab, + MultiplayerGame game, List team1, List team2, bool skipApiResultFetch = false) : base(map, processor, activeTab, null, null) { Game = game; @@ -76,7 +78,7 @@ public ResultsMultiplayerTab(Map map, Bindable processor, Bindab CreateContentContainer(); CreateMatchPlayedText(); CreateWinner(); - CreateTable(); + CreateTable(skipApiResultFetch); CreateTeamHeader(); } @@ -150,7 +152,8 @@ private void CreateWinner() /// /// - private void CreateTable() => Table = new ResultsMultiplayerTable(Map, Processor, Game, Team1Players, Team2Players) + /// + private void CreateTable(bool skipApiResultFetch) => Table = new ResultsMultiplayerTable(Map, Processor, Game, Team1Players, Team2Players, skipApiResultFetch) { Parent = ContentContainer, Alignment = Alignment.BotLeft, diff --git a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs index 505652b562..23d1051b30 100644 --- a/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs +++ b/Quaver.Shared/Screens/Results/UI/Tabs/Multiplayer/Table/ResultsMultiplayerTable.cs @@ -48,6 +48,11 @@ public class ResultsMultiplayerTable : Sprite /// /// private List Team2Players { get; } + + /// + /// Skip using API to fetch player scores + /// + private bool SkipApiResultFetch { get; } /// /// @@ -67,7 +72,7 @@ public class ResultsMultiplayerTable : Sprite private LoadingWheelText ResultLoadingWheelText { get; set; } - private TaskHandler GetScoresTask { get; set; } + private TaskHandler GetScoresTask { get; set; } /// /// @@ -76,14 +81,16 @@ public class ResultsMultiplayerTable : Sprite /// /// /// + /// public ResultsMultiplayerTable(Map map, Bindable processor, MultiplayerGame game, - List team1, List team2) + List team1, List team2, bool skipApiResultFetch) { Map = map; Processor = processor; Game = game; Team1Players = team1; Team2Players = team2; + SkipApiResultFetch = skipApiResultFetch; Width = ResultsScreenView.CONTENT_WIDTH - ResultsTabContainer.PADDING_X; GetScoresTask = new TaskHandler(GetMatchScores); @@ -230,9 +237,8 @@ private int GetMatchScores(int val, CancellationToken cancellationToken) { List players = null; MultiplayerMatchInformationResponse matchInfoResponse = null; - var isCoop = Processor.Value.Mods.HasFlag(ModIdentifier.Coop); - // If this is a co-op play, we should directly use replay result instead of fetching online - if (isCoop) + + if (SkipApiResultFetch) { players = GetOrderedUserList(); } @@ -249,12 +255,12 @@ private int GetMatchScores(int val, CancellationToken cancellationToken) } // Skip fetching results if it's co-op play - if (!isCoop && matchInfoResponse == null) + if (!SkipApiResultFetch && matchInfoResponse == null) { NotificationManager.Show(NotificationLevel.Error, "Failed to retrieve players' scores!"); players = GetOrderedUserList(); } - else if (!isCoop) + else if (!SkipApiResultFetch) { players = new List(); var qua = Map.LoadQua(); From 2956c59e5246e3a28d0bea9d045af6e4423f7f7c Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 20 Apr 2024 14:44:06 +0800 Subject: [PATCH 223/249] Fix audio rate issue --- Quaver.Shared/Screens/Tournament/TournamentScreen.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Tournament/TournamentScreen.cs b/Quaver.Shared/Screens/Tournament/TournamentScreen.cs index c966d9d530..c75ad2ec48 100644 --- a/Quaver.Shared/Screens/Tournament/TournamentScreen.cs +++ b/Quaver.Shared/Screens/Tournament/TournamentScreen.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; +using Quaver.API.Enums; using Quaver.API.Helpers; using Quaver.API.Maps.Processors.Scoring; using Quaver.API.Replays; @@ -142,7 +143,16 @@ public TournamentScreen(MultiplayerGame game, IReadOnlyList spe } ModManager.RemoveAllMods(); - ModManager.AddSpeedMods(ModHelper.GetRateFromMods(spectatees.First().Replay.Mods)); + + var minimumRate = string.IsNullOrEmpty(game.Modifiers) + ? game.PlayerMods.Min(pm => + { + var rateFromMods = ModHelper.GetRateFromMods( + (ModIdentifier)long.Parse(string.IsNullOrEmpty(pm.Modifiers) ? "1.0" : pm.Modifiers)); + return rateFromMods; + }) + : ModHelper.GetRateFromMods((ModIdentifier)long.Parse(game.Modifiers)); + ModManager.AddSpeedMods(minimumRate); SetRichPresenceForTournamentViewer(); View = new TournamentScreenView(this); From 82ea228018fab526384a3b0bdcce24cca87cb7f2 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 20 Apr 2024 14:49:47 +0800 Subject: [PATCH 224/249] Stop spamming ClientStopSpectating packets --- Quaver.Shared/Screens/Tournament/TournamentScreen.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Quaver.Shared/Screens/Tournament/TournamentScreen.cs b/Quaver.Shared/Screens/Tournament/TournamentScreen.cs index c75ad2ec48..f64e9ceb48 100644 --- a/Quaver.Shared/Screens/Tournament/TournamentScreen.cs +++ b/Quaver.Shared/Screens/Tournament/TournamentScreen.cs @@ -213,15 +213,17 @@ public override void Update(GameTime gameTime) { UpdateScreens(gameTime); - if (GenericKeyManager.IsDown(ConfigManager.KeyPause.Value)) + if (TournamentType == TournamentScreenType.Spectator) { - if (TournamentType == TournamentScreenType.Spectator) + if (GenericKeyManager.IsUniquePress(ConfigManager.KeyPause.Value)) { OnlineManager.LeaveGame(); OnlineManager.Client?.StopSpectating(); } - else - MainGameplayScreen.Pause(gameTime); + } + else if (GenericKeyManager.IsDown(ConfigManager.KeyPause.Value)) + { + MainGameplayScreen.Pause(gameTime); } // Add skipping From 3395fc08f919da9b31ee502117d29e871c8b3ff5 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 20 Apr 2024 14:52:10 +0800 Subject: [PATCH 225/249] Crash fixes --- Quaver.Shared/Modifiers/ModManager.cs | 6 +++++- Quaver.Shared/Online/OnlineManager.cs | 11 +++++------ Quaver.Shared/Screens/Multi/MultiplayerGameScreen.cs | 9 ++++++++- Quaver.Shared/Screens/Tournament/TournamentScreen.cs | 4 +++- .../Screens/Tournament/TournamentScreenView.cs | 6 ++++++ 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/Quaver.Shared/Modifiers/ModManager.cs b/Quaver.Shared/Modifiers/ModManager.cs index e4d1978783..15003f2352 100644 --- a/Quaver.Shared/Modifiers/ModManager.cs +++ b/Quaver.Shared/Modifiers/ModManager.cs @@ -392,7 +392,11 @@ private static void UpdateMultiplayerMods() var rate = ModHelper.GetModsFromRate(ModHelper.GetRateFromMods(Mods)); var hostChangeableMods = CurrentModifiersList.FindAll(x => x.OnlyMultiplayerHostCanCanChange); - var ourMods = game.PlayerMods.Find(x => x.UserId == OnlineManager.Self.OnlineUser.Id); + var ourMods = game.PlayerMods.Find(x => x.UserId == OnlineManager.Self.OnlineUser.Id) ?? new MultiplayerPlayerMods + { + Modifiers = "0", + UserId = OnlineManager.Self.OnlineUser.Id + }; var otherMods = CurrentModifiersList.FindAll(x => !x.OnlyMultiplayerHostCanCanChange && x.Type != ModType.Speed && !hostChangeableMods.Contains(x)); diff --git a/Quaver.Shared/Online/OnlineManager.cs b/Quaver.Shared/Online/OnlineManager.cs index 1f770e190e..76cba7d3c9 100644 --- a/Quaver.Shared/Online/OnlineManager.cs +++ b/Quaver.Shared/Online/OnlineManager.cs @@ -12,6 +12,7 @@ using Quaver.API.Enums; using Quaver.API.Helpers; using Quaver.API.Maps.Processors.Difficulty.Rulesets.Keys; +using Quaver.API.Replays; using Quaver.Server.Client; using Quaver.Server.Client.Events; using Quaver.Server.Client.Events.Disconnnection; @@ -1258,13 +1259,11 @@ private static void OnGameStarted(object sender, GameStartedEventArgs e) BackgroundHelper.Load(MapManager.Selected.Value); - if (!game.CurrentScreen.Exiting) - { - foreach (var spect in SpectatorClients.Values) - spect.WatchUserImmediately(); + foreach (var spect in SpectatorClients.Values) + spect.WatchUserImmediately(); - game.CurrentScreen.Exit(() => new TournamentScreen(CurrentGame, SpectatorClients.Values.ToList())); - } + game.CurrentScreen.Exit(() => new TournamentScreen(CurrentGame, SpectatorClients.Values.ToList()), + delay: 500); return; } diff --git a/Quaver.Shared/Screens/Multi/MultiplayerGameScreen.cs b/Quaver.Shared/Screens/Multi/MultiplayerGameScreen.cs index 1706ffff5e..fa785ebe44 100644 --- a/Quaver.Shared/Screens/Multi/MultiplayerGameScreen.cs +++ b/Quaver.Shared/Screens/Multi/MultiplayerGameScreen.cs @@ -101,9 +101,16 @@ public MultiplayerGameScreen() /// public override void OnFirstUpdate() { - if (OnlineManager.IsSpectatingSomeone) + if (OnlineManager.IsSpectatingSomeone && !(OnlineManager.CurrentGame?.IsSpectating ?? false)) OnlineManager.Client?.StopSpectating(); + if (OnlineManager.CurrentGame == null) + { + // Tournament Screen view exited the screen + // One cause of it is the server forbids spectating a match without player number != 2 + // CurrentGame is null because we sent a LeaveGame packet in TournamentScreenView + return; + } MapLoadingScreen.AddModsFromIdentifiers(OnlineManager.GetSelfActivatedMods()); OnlineManager.SendGameDifficultyRatings(OnlineManager.CurrentGame.MapMd5, OnlineManager.CurrentGame.AlternativeMd5); diff --git a/Quaver.Shared/Screens/Tournament/TournamentScreen.cs b/Quaver.Shared/Screens/Tournament/TournamentScreen.cs index f64e9ceb48..0e05aec61e 100644 --- a/Quaver.Shared/Screens/Tournament/TournamentScreen.cs +++ b/Quaver.Shared/Screens/Tournament/TournamentScreen.cs @@ -126,7 +126,9 @@ public TournamentScreen(MultiplayerGame game, IReadOnlyList spe { var qua = MapManager.Selected.Value.LoadQua(); - spectatees[i].PlayNewMap(new List()); + if (spectatees[i].Replay == null) + spectatees[i].PlayNewMap(new List()); + qua.ApplyMods(spectatees[i].Replay.Mods); MapManager.Selected.Value.Qua = qua; diff --git a/Quaver.Shared/Screens/Tournament/TournamentScreenView.cs b/Quaver.Shared/Screens/Tournament/TournamentScreenView.cs index 904e3f2ed1..a4ac1cfd34 100644 --- a/Quaver.Shared/Screens/Tournament/TournamentScreenView.cs +++ b/Quaver.Shared/Screens/Tournament/TournamentScreenView.cs @@ -17,6 +17,7 @@ using Quaver.Shared.Online; using Quaver.Shared.Screens.Gameplay; using Quaver.Shared.Screens.Gameplay.Rulesets.Keys.Playfield; +using Quaver.Shared.Screens.MultiplayerLobby; using Quaver.Shared.Screens.Tournament.Gameplay; using Quaver.Shared.Screens.Tournament.Overlay; using Quaver.Shared.Skinning; @@ -60,6 +61,11 @@ public class TournamentScreenView : ScreenView /// public TournamentScreenView(Screen screen) : base(screen) { + if (TournamentScreen.GameplayScreens.Count == 0) + { + OnlineManager.LeaveGame(); + TournamentScreen.Exit(() => new MultiplayerLobbyScreen()); + } CreateBackground(); SetPlayfieldPositions(); PositionPlayfieldItems(); From cb7e7535a3a1077e859beaab455b9f3f6846ceea Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 20 Apr 2024 14:54:52 +0800 Subject: [PATCH 226/249] Sync every GameplayScreen to audio time --- Quaver.Shared/Screens/Gameplay/GameplayScreen.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs index efc76663bb..57805f43b1 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs @@ -1316,15 +1316,25 @@ public void SendJudgementsToServer(bool force = false) OnlineManager.Client.SendGameJudgements(judgementsToGive); } + public float SpectatorTargetSyncTime => (this is TournamentGameplayScreen && ((QuaverGame)GameBase.Game).CurrentScreen is TournamentScreen tournamentScreen) + ? tournamentScreen.GameplayScreens.Min(s => + { + // We are guaranteed to find a minimum because of the return condition above. + var replayFrames = s.SpectatorClient.Replay.Frames; + return (replayFrames?.Count ?? 0) == 0 ? int.MaxValue : replayFrames.Last()?.Time ?? int.MaxValue; + }) + : SpectatorClient.Replay.Frames.Last().Time; /// /// private void HandleSpectatorSkipping() { - if (SpectatorClient.Replay.Frames.Count == 0 || this is TournamentGameplayScreen) + if (SpectatorClient.Replay.Frames.Count == 0) return; + var targetSyncTime = SpectatorTargetSyncTime; // User can only be two seconds out of sync with the user - if (Math.Abs(AudioEngine.Track.Time - SpectatorClient.Replay.Frames.Last().Time) < 3000) + if (Math.Abs(AudioEngine.Track.Time - targetSyncTime) < 3000 + && Math.Abs(Timing.Time - targetSyncTime) < 3000) return; var skipTime = SpectatorClient.Replay.Frames.Last().Time; @@ -1337,7 +1347,7 @@ private void HandleSpectatorSkipping() Timing.Time = AudioEngine.Track.Time; } catch (Exception e) - {; + { Timing.Time = skipTime; } finally From 86791bca28a647cd70e5c5123d35729a1a8c1e2c Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 20 Apr 2024 17:18:29 +0800 Subject: [PATCH 227/249] Enforce sending FinishedSong frame --- Quaver.Shared/Screens/Gameplay/GameplayScreen.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs index 57805f43b1..20a0dbc07e 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs @@ -1402,7 +1402,12 @@ public void SendReplayFramesToServer(bool force = false, bool appendFinishSong = status = SpectatorClientStatus.Playing; if (status == SpectatorClientStatus.Playing && frames.Count == 0) + { + if (appendFinishSong) + OnlineManager.Client?.SendReplaySpectatorFrames(SpectatorClientStatus.FinishedSong, int.MaxValue, + new List()); return; + } OnlineManager.Client?.SendReplaySpectatorFrames(status, AudioEngine.Track.Time, frames); From 7865bca09e739fe8e64b0e41ccc9b6c4d9182a1d Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 20 Apr 2024 20:50:25 +0800 Subject: [PATCH 228/249] Use correct score processor for display --- .../Screens/Gameplay/GameplayScreenView.cs | 16 +++++++++------- .../Screens/Tournament/TournamentScreenView.cs | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs index 7580da3eb7..c38f87dbba 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs @@ -199,6 +199,11 @@ public class GameplayScreenView : ScreenView /// private ReplayController ReplayController { get; } + private ScoreProcessor DisplayScoreProcessor => + ConfigManager.DisplayRankedAccuracy.Value || Screen.IsSpectatingTournament + ? Screen.Ruleset.StandardizedReplayPlayer.ScoreProcessor + : Screen.Ruleset.ScoreProcessor; + /// /// /// @@ -455,14 +460,11 @@ private void CreateAccuracyDisplay() public void UpdateScoreAndAccuracyDisplays() { // Update score and accuracy displays - ScoreDisplay.UpdateValue(Screen.Ruleset.ScoreProcessor.Score); - - RatingDisplay.UpdateValue(RatingProcessor.CalculateRating(Screen.Ruleset.StandardizedReplayPlayer.ScoreProcessor.Accuracy)); + var displayScoreProcessor = DisplayScoreProcessor; - if (ConfigManager.DisplayRankedAccuracy.Value || Screen.IsSpectatingTournament) - AccuracyDisplay.UpdateValue(Screen.Ruleset.StandardizedReplayPlayer.ScoreProcessor.Accuracy); - else - AccuracyDisplay.UpdateValue(Screen.Ruleset.ScoreProcessor.Accuracy); + ScoreDisplay.UpdateValue(displayScoreProcessor.Score); + RatingDisplay.UpdateValue(RatingProcessor.CalculateRating(displayScoreProcessor.Accuracy)); + AccuracyDisplay.UpdateValue(displayScoreProcessor.Accuracy); } /// diff --git a/Quaver.Shared/Screens/Tournament/TournamentScreenView.cs b/Quaver.Shared/Screens/Tournament/TournamentScreenView.cs index a4ac1cfd34..8403a6a250 100644 --- a/Quaver.Shared/Screens/Tournament/TournamentScreenView.cs +++ b/Quaver.Shared/Screens/Tournament/TournamentScreenView.cs @@ -277,7 +277,7 @@ private void CreateOverlay() CountryFlag = "US", Id = i + 1, UserGroups = UserGroups.Normal - }), screen.Ruleset.StandardizedReplayPlayer.ScoreProcessor, screen.Map.SolveDifficulty(screen.Ruleset.ScoreProcessor.Mods).OverallDifficulty))); + }), screen.Ruleset.ScoreProcessor, screen.Map.SolveDifficulty(screen.Ruleset.ScoreProcessor.Mods).OverallDifficulty))); var game = new MultiplayerGame { From 25920f6631afcfbb296b048a30983b3c08163fff Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 20 Apr 2024 21:40:46 +0800 Subject: [PATCH 229/249] Use standardized replay player on result --- Quaver.Shared/Screens/Tournament/TournamentScreen.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Quaver.Shared/Screens/Tournament/TournamentScreen.cs b/Quaver.Shared/Screens/Tournament/TournamentScreen.cs index 0e05aec61e..d58f7a3ad7 100644 --- a/Quaver.Shared/Screens/Tournament/TournamentScreen.cs +++ b/Quaver.Shared/Screens/Tournament/TournamentScreen.cs @@ -414,9 +414,9 @@ private void HandleSpectator() foreach (var screen in GameplayScreens) { - screen.Ruleset.ScoreProcessor.PlayerName = screen.SpectatorClient.Player.OnlineUser.Username; - screen.Ruleset.ScoreProcessor.SteamId = (ulong) screen.SpectatorClient.Player.OnlineUser.SteamId; - processors.Add(screen.Ruleset.ScoreProcessor); + screen.Ruleset.StandardizedReplayPlayer.ScoreProcessor.PlayerName = screen.SpectatorClient.Player.OnlineUser.Username; + screen.Ruleset.StandardizedReplayPlayer.ScoreProcessor.SteamId = (ulong) screen.SpectatorClient.Player.OnlineUser.SteamId; + processors.Add(screen.Ruleset.StandardizedReplayPlayer.ScoreProcessor); } Exit(() => new ResultsScreen(MainGameplayScreen, OnlineManager.CurrentGame, From 675848717a7e672ddfc5322eea88f86fd6beec87 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 20 Apr 2024 22:15:05 +0800 Subject: [PATCH 230/249] Order spectator clients by their id --- Quaver.Shared/Online/OnlineManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Online/OnlineManager.cs b/Quaver.Shared/Online/OnlineManager.cs index 76cba7d3c9..efc39830da 100644 --- a/Quaver.Shared/Online/OnlineManager.cs +++ b/Quaver.Shared/Online/OnlineManager.cs @@ -1262,7 +1262,7 @@ private static void OnGameStarted(object sender, GameStartedEventArgs e) foreach (var spect in SpectatorClients.Values) spect.WatchUserImmediately(); - game.CurrentScreen.Exit(() => new TournamentScreen(CurrentGame, SpectatorClients.Values.ToList()), + game.CurrentScreen.Exit(() => new TournamentScreen(CurrentGame, SpectatorClients.Values.OrderBy(s => s.Player?.OnlineUser?.Id ?? 0).ToList()), delay: 500); return; From 2bbfd317672010dac31416d1d21832a34e5a28ec Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sat, 20 Apr 2024 22:15:35 +0800 Subject: [PATCH 231/249] Skip to 1.5s before latest frame --- Quaver.Shared/Screens/Gameplay/GameplayScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs index 20a0dbc07e..69dc85ba1b 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs @@ -1337,7 +1337,7 @@ private void HandleSpectatorSkipping() && Math.Abs(Timing.Time - targetSyncTime) < 3000) return; - var skipTime = SpectatorClient.Replay.Frames.Last().Time; + var skipTime = SpectatorClient.Replay.Frames.Last().Time - 1500; try { From 85333b7fa17a5869ef006e92dfd28f4bd16ba1c2 Mon Sep 17 00:00:00 2001 From: Emik Date: Sat, 20 Apr 2024 23:08:52 +0200 Subject: [PATCH 232/249] Display plugin errors --- Quaver.Shared/Scripting/LuaImGui.cs | 62 +++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/Quaver.Shared/Scripting/LuaImGui.cs b/Quaver.Shared/Scripting/LuaImGui.cs index 50db0b0a78..83ed841a2e 100644 --- a/Quaver.Shared/Scripting/LuaImGui.cs +++ b/Quaver.Shared/Scripting/LuaImGui.cs @@ -1,13 +1,16 @@ using System; using System.IO; +using System.Linq; using System.Numerics; using System.Reflection; using System.Text; using ImGuiNET; using Microsoft.Xna.Framework.Input; using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Debugging; using Quaver.API.Maps.Structures; using Quaver.Shared.Config; +using Quaver.Shared.Graphics.Notifications; using Quaver.Shared.Screens.Edit.UI.Menu; using Wobble; using Wobble.Graphics.ImGUI; @@ -122,7 +125,7 @@ protected override void RenderImguiLayout() } catch (Exception e) { - Logger.Error(e, LogType.Runtime, false); + HandleLuaException(e); } } @@ -187,11 +190,12 @@ private void LoadScript() ScriptText = File.ReadAllText(FilePath); } - WorkingScript.DoString(ScriptText); + if (WorkingScript.DoString(ScriptText) is var ret && ret.Type is not DataType.Void) + NotificationManager.Show(NotificationLevel.Info, $"Plugin {Path.GetFileName(Path.GetDirectoryName(FilePath))} returned {ret}."); } catch (Exception e) { - Logger.Error(e, LogType.Runtime); + HandleLuaException(e); } WorkingScript.Globals["imgui"] = typeof(ImGuiWrapper); @@ -276,5 +280,55 @@ private void RegisterAllVectors() } ); } + + /// + /// Handles an exception that comes from the lua interpreter. + /// + private void HandleLuaException(Exception e) + { + Logger.Error(e, LogType.Runtime); + var name = Path.GetFileName(Path.GetDirectoryName(FilePath)); + + var summary = e switch + { + DynamicExpressionException => "a dynamic expression", + InternalErrorException => "an internal", + ScriptRuntimeException => "a script runtime", + SyntaxErrorException => "a syntax", + InterpreterException => "an interpreter", + IOException => "an IO", + IndexOutOfRangeException => "a stack overflow", // Engine causes an IndexOutOfRangeException on stack overflows + _ => "an unknown", + }; + + var message = e switch + { + InterpreterException { DecoratedMessage: { } decorated } => $" at {decorated.Replace("chunk_0:", "")}", + IndexOutOfRangeException => ".", + _ => $": {e.Message}", + }; + + var callStack = (e as InterpreterException)?.CallStack is { } list + ? $"\nCall stack:\n{string.Join("\n", list.Select(x => $"{x.Name}{(x.Location is { } location ? $"at {FormatSource(location)}" : "")}"))}" + : ""; + + NotificationManager.Show(NotificationLevel.Error, $"Plugin {name} caused {summary} error{message}{callStack}"); + } + + private string FormatSource(SourceRef source) + { + StringBuilder sb = new("("); + sb.Append(source.FromLine); + + if (source.ToLine >= 0 && source.ToLine != source.FromLine) + sb.Append('-').Append(source.ToLine); + + sb.Append(',').Append(source.FromChar); + + if (source.ToChar >= 0 && source.ToChar != source.FromChar) + sb.Append('-').Append(source.ToChar); + + return sb.Append(')').ToString(); + } } -} \ No newline at end of file +} From 84eb642f580a7359f83e372cd44fefd9db3b50a7 Mon Sep 17 00:00:00 2001 From: Emik Date: Sat, 20 Apr 2024 23:10:24 +0200 Subject: [PATCH 233/249] Add missing space --- Quaver.Shared/Scripting/LuaImGui.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Scripting/LuaImGui.cs b/Quaver.Shared/Scripting/LuaImGui.cs index 83ed841a2e..3d66f51665 100644 --- a/Quaver.Shared/Scripting/LuaImGui.cs +++ b/Quaver.Shared/Scripting/LuaImGui.cs @@ -309,7 +309,7 @@ private void HandleLuaException(Exception e) }; var callStack = (e as InterpreterException)?.CallStack is { } list - ? $"\nCall stack:\n{string.Join("\n", list.Select(x => $"{x.Name}{(x.Location is { } location ? $"at {FormatSource(location)}" : "")}"))}" + ? $"\nCall stack:\n{string.Join("\n", list.Select(x => $"{x.Name}{(x.Location is { } location ? $" at {FormatSource(location)}" : "")}"))}" : ""; NotificationManager.Show(NotificationLevel.Error, $"Plugin {name} caused {summary} error{message}{callStack}"); From 4b42046fe9dc7cd8381d0f7ef7755b83400d259e Mon Sep 17 00:00:00 2001 From: Emik Date: Sat, 20 Apr 2024 23:23:25 +0200 Subject: [PATCH 234/249] Prevent plugin execution when exception was thrown during rendering --- Quaver.Shared/Scripting/LuaImGui.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Quaver.Shared/Scripting/LuaImGui.cs b/Quaver.Shared/Scripting/LuaImGui.cs index 3d66f51665..05d18a1b72 100644 --- a/Quaver.Shared/Scripting/LuaImGui.cs +++ b/Quaver.Shared/Scripting/LuaImGui.cs @@ -28,6 +28,11 @@ public class LuaImGui : SpriteImGui /// private string FilePath { get; } + // + // Determines whether an exception has occured. + // + private bool CausedException { get; set; } + /// /// private bool IsResource { get; } @@ -117,6 +122,10 @@ public override void Destroy() /// protected override void RenderImguiLayout() { + // Prevents exception spam + if (CausedException) + return; + try { SetFrameState(); @@ -125,6 +134,7 @@ protected override void RenderImguiLayout() } catch (Exception e) { + CausedException = true; HandleLuaException(e); } } @@ -176,6 +186,7 @@ public virtual void AfterRender() /// private void LoadScript() { + CausedException = false; WorkingScript = new Script(CoreModules.Preset_HardSandbox); try From 9172b2b841b3122ada50ea1a81a502a6c53cca27 Mon Sep 17 00:00:00 2001 From: Emik Date: Sun, 21 Apr 2024 00:06:08 +0200 Subject: [PATCH 235/249] Add custom print hook --- Quaver.Shared/Scripting/LuaImGui.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Quaver.Shared/Scripting/LuaImGui.cs b/Quaver.Shared/Scripting/LuaImGui.cs index 05d18a1b72..f0a326c4d8 100644 --- a/Quaver.Shared/Scripting/LuaImGui.cs +++ b/Quaver.Shared/Scripting/LuaImGui.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; @@ -28,6 +29,11 @@ public class LuaImGui : SpriteImGui /// private string FilePath { get; } + // + // Gets the name of the plugin + // + private string Name => Path.GetFileName(Path.GetDirectoryName(FilePath)); + // // Determines whether an exception has occured. // @@ -169,7 +175,6 @@ public virtual void SetFrameState() WorkingScript.Globals["imgui_combo_flags"] = typeof(ImGuiComboFlags); WorkingScript.Globals["imgui_focused_flags"] = typeof(ImGuiFocusedFlags); WorkingScript.Globals["imgui_hovered_flags"] = typeof(ImGuiHoveredFlags); - WorkingScript.Globals["keys"] = typeof(Keys); } @@ -188,6 +193,7 @@ private void LoadScript() { CausedException = false; WorkingScript = new Script(CoreModules.Preset_HardSandbox); + WorkingScript.Globals["print"] = CallbackFunction.FromDelegate(null, Print); try { @@ -202,7 +208,7 @@ private void LoadScript() } if (WorkingScript.DoString(ScriptText) is var ret && ret.Type is not DataType.Void) - NotificationManager.Show(NotificationLevel.Info, $"Plugin {Path.GetFileName(Path.GetDirectoryName(FilePath))} returned {ret}."); + NotificationManager.Show(NotificationLevel.Info, $"Plugin {Name} returned {ret}."); } catch (Exception e) { @@ -298,7 +304,6 @@ private void RegisterAllVectors() private void HandleLuaException(Exception e) { Logger.Error(e, LogType.Runtime); - var name = Path.GetFileName(Path.GetDirectoryName(FilePath)); var summary = e switch { @@ -323,10 +328,12 @@ private void HandleLuaException(Exception e) ? $"\nCall stack:\n{string.Join("\n", list.Select(x => $"{x.Name}{(x.Location is { } location ? $" at {FormatSource(location)}" : "")}"))}" : ""; - NotificationManager.Show(NotificationLevel.Error, $"Plugin {name} caused {summary} error{message}{callStack}"); + NotificationManager.Show(NotificationLevel.Error, $"Plugin {Name} caused {summary} error{message}{callStack}"); } - private string FormatSource(SourceRef source) + private void Print(params DynValue[] args) => NotificationManager.Show(NotificationLevel.Info, $"{Name}:\n{string.Join("\n", args.Select(x => $"{x}"))}"); + + private static string FormatSource(SourceRef source) { StringBuilder sb = new("("); sb.Append(source.FromLine); From 9fade32f6866f737a37c79dffa269863215a5932 Mon Sep 17 00:00:00 2001 From: Emik Date: Sun, 21 Apr 2024 00:53:07 +0200 Subject: [PATCH 236/249] Make LuaImGui.Name virtual --- Quaver.Shared/Screens/Edit/Plugins/EditorPlugin.cs | 5 ++--- Quaver.Shared/Scripting/LuaImGui.cs | 8 ++++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Quaver.Shared/Screens/Edit/Plugins/EditorPlugin.cs b/Quaver.Shared/Screens/Edit/Plugins/EditorPlugin.cs index 494294a671..016d6bf006 100644 --- a/Quaver.Shared/Screens/Edit/Plugins/EditorPlugin.cs +++ b/Quaver.Shared/Screens/Edit/Plugins/EditorPlugin.cs @@ -24,9 +24,8 @@ public class EditorPlugin : LuaImGui, IEditorPlugin /// public bool IsWindowHovered { get; set; } - /// - /// - public string Name { get; set; } + /// + public override string Name { get; set; } /// /// diff --git a/Quaver.Shared/Scripting/LuaImGui.cs b/Quaver.Shared/Scripting/LuaImGui.cs index f0a326c4d8..0d7d02ef6d 100644 --- a/Quaver.Shared/Scripting/LuaImGui.cs +++ b/Quaver.Shared/Scripting/LuaImGui.cs @@ -30,9 +30,13 @@ public class LuaImGui : SpriteImGui private string FilePath { get; } // - // Gets the name of the plugin + // Gets or sets the name of the plugin // - private string Name => Path.GetFileName(Path.GetDirectoryName(FilePath)); + public virtual string Name + { + get => Path.GetFileName(Path.GetDirectoryName(FilePath)); + set { } + } // // Determines whether an exception has occured. From de108f2b05f73bea553815b5607ea4ddd67c9c04 Mon Sep 17 00:00:00 2001 From: Emik Date: Sun, 21 Apr 2024 01:11:57 +0200 Subject: [PATCH 237/249] Check callability --- Quaver.Shared/Scripting/LuaImGui.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Quaver.Shared/Scripting/LuaImGui.cs b/Quaver.Shared/Scripting/LuaImGui.cs index 0d7d02ef6d..44e26f5209 100644 --- a/Quaver.Shared/Scripting/LuaImGui.cs +++ b/Quaver.Shared/Scripting/LuaImGui.cs @@ -139,7 +139,10 @@ protected override void RenderImguiLayout() try { SetFrameState(); - WorkingScript.Call(WorkingScript.Globals["draw"]); + + if (WorkingScript.Globals["draw"] is Closure draw) + WorkingScript.Call(draw); + AfterRender(); } catch (Exception e) From 490b2a8277fc6ba2e217ee692d11cefb8df577d9 Mon Sep 17 00:00:00 2001 From: Emik Date: Sun, 21 Apr 2024 01:15:33 +0200 Subject: [PATCH 238/249] Prevent script execution if startup failed --- Quaver.Shared/Scripting/LuaImGui.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Scripting/LuaImGui.cs b/Quaver.Shared/Scripting/LuaImGui.cs index 44e26f5209..9af4627a29 100644 --- a/Quaver.Shared/Scripting/LuaImGui.cs +++ b/Quaver.Shared/Scripting/LuaImGui.cs @@ -147,7 +147,6 @@ protected override void RenderImguiLayout() } catch (Exception e) { - CausedException = true; HandleLuaException(e); } } @@ -310,6 +309,7 @@ private void RegisterAllVectors() /// private void HandleLuaException(Exception e) { + CausedException = true; Logger.Error(e, LogType.Runtime); var summary = e switch From 04adb3f8a091d49673ef392299f450ab749f8f3b Mon Sep 17 00:00:00 2001 From: Emik Date: Sun, 21 Apr 2024 01:33:14 +0200 Subject: [PATCH 239/249] Improve plugin reloading --- Quaver.Shared/Scripting/LuaImGui.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Quaver.Shared/Scripting/LuaImGui.cs b/Quaver.Shared/Scripting/LuaImGui.cs index 9af4627a29..a1f3a3a4fb 100644 --- a/Quaver.Shared/Scripting/LuaImGui.cs +++ b/Quaver.Shared/Scripting/LuaImGui.cs @@ -38,11 +38,6 @@ public virtual string Name set { } } - // - // Determines whether an exception has occured. - // - private bool CausedException { get; set; } - /// /// private bool IsResource { get; } @@ -51,6 +46,11 @@ public virtual string Name /// private string ScriptText { get; set; } + // + // Determines when an exception has occured. + // + private DateTime LastException { get; set; } + /// /// private FileSystemWatcher Watcher { get; } @@ -113,6 +113,7 @@ public LuaImGui(string filePath, bool isResource = false) : base(false, EditorFi Watcher.Changed += OnFileChanged; Watcher.Created += OnFileChanged; Watcher.Deleted += OnFileChanged; + Watcher.Renamed += OnFileChanged; // Begin watching. Watcher.EnableRaisingEvents = true; @@ -133,7 +134,7 @@ public override void Destroy() protected override void RenderImguiLayout() { // Prevents exception spam - if (CausedException) + if (DateTime.Now - LastException < TimeSpan.FromSeconds(1)) return; try @@ -197,7 +198,6 @@ public virtual void AfterRender() /// private void LoadScript() { - CausedException = false; WorkingScript = new Script(CoreModules.Preset_HardSandbox); WorkingScript.Globals["print"] = CallbackFunction.FromDelegate(null, Print); @@ -309,7 +309,7 @@ private void RegisterAllVectors() /// private void HandleLuaException(Exception e) { - CausedException = true; + LastException = DateTime.Now; Logger.Error(e, LogType.Runtime); var summary = e switch From 8501fd2dd3af2c5c9b7667876a853a75844f77e6 Mon Sep 17 00:00:00 2001 From: Emik Date: Sun, 21 Apr 2024 01:37:17 +0200 Subject: [PATCH 240/249] Sleep to hopefully have Windows file lock release by then --- Quaver.Shared/Scripting/LuaImGui.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Quaver.Shared/Scripting/LuaImGui.cs b/Quaver.Shared/Scripting/LuaImGui.cs index a1f3a3a4fb..b76cfe781f 100644 --- a/Quaver.Shared/Scripting/LuaImGui.cs +++ b/Quaver.Shared/Scripting/LuaImGui.cs @@ -210,6 +210,7 @@ private void LoadScript() } else { + Thread.Sleep(1); ScriptText = File.ReadAllText(FilePath); } From 0bd420a6b17a02b8068daa9fa6d27f8ab96ee320 Mon Sep 17 00:00:00 2001 From: Emik Date: Sun, 21 Apr 2024 01:45:45 +0200 Subject: [PATCH 241/249] Sleep for longer: Fixes Windows file lock issue --- Quaver.Shared/Scripting/LuaImGui.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Scripting/LuaImGui.cs b/Quaver.Shared/Scripting/LuaImGui.cs index b76cfe781f..ea2c7245a8 100644 --- a/Quaver.Shared/Scripting/LuaImGui.cs +++ b/Quaver.Shared/Scripting/LuaImGui.cs @@ -5,6 +5,7 @@ using System.Numerics; using System.Reflection; using System.Text; +using System.Threading; using ImGuiNET; using Microsoft.Xna.Framework.Input; using MoonSharp.Interpreter; @@ -134,7 +135,8 @@ public override void Destroy() protected override void RenderImguiLayout() { // Prevents exception spam - if (DateTime.Now - LastException < TimeSpan.FromSeconds(1)) + // Prevents exception spam: No one needs more than 10 hot reloads per second. + if (DateTime.Now - LastException < TimeSpan.FromMilliseconds(100)) return; try @@ -210,7 +212,7 @@ private void LoadScript() } else { - Thread.Sleep(1); + Thread.Sleep(10); ScriptText = File.ReadAllText(FilePath); } From 19a3d5468dcfbfd4a05bb9461aa8f8395185ff03 Mon Sep 17 00:00:00 2001 From: Emik Date: Sun, 21 Apr 2024 01:46:45 +0200 Subject: [PATCH 242/249] Remove duplicate comment --- Quaver.Shared/Scripting/LuaImGui.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Quaver.Shared/Scripting/LuaImGui.cs b/Quaver.Shared/Scripting/LuaImGui.cs index ea2c7245a8..ef34488607 100644 --- a/Quaver.Shared/Scripting/LuaImGui.cs +++ b/Quaver.Shared/Scripting/LuaImGui.cs @@ -134,7 +134,6 @@ public override void Destroy() /// protected override void RenderImguiLayout() { - // Prevents exception spam // Prevents exception spam: No one needs more than 10 hot reloads per second. if (DateTime.Now - LastException < TimeSpan.FromMilliseconds(100)) return; From 75ac32dee130ec12a33cb94d901b802d41b8ed37 Mon Sep 17 00:00:00 2001 From: Emik Date: Sun, 21 Apr 2024 01:50:31 +0200 Subject: [PATCH 243/249] Do not log repeated exceptions --- Quaver.Shared/Scripting/LuaImGui.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Quaver.Shared/Scripting/LuaImGui.cs b/Quaver.Shared/Scripting/LuaImGui.cs index ef34488607..a0206a18c5 100644 --- a/Quaver.Shared/Scripting/LuaImGui.cs +++ b/Quaver.Shared/Scripting/LuaImGui.cs @@ -134,8 +134,8 @@ public override void Destroy() /// protected override void RenderImguiLayout() { - // Prevents exception spam: No one needs more than 10 hot reloads per second. - if (DateTime.Now - LastException < TimeSpan.FromMilliseconds(100)) + // Prevents exception spam: No one needs more than 2 hot reloads per second. + if (DateTime.Now - LastException < TimeSpan.FromMilliseconds(500)) return; try @@ -311,6 +311,9 @@ private void RegisterAllVectors() /// private void HandleLuaException(Exception e) { + if (DateTime.Now - LastException < TimeSpan.FromMilliseconds(100)) + return; + LastException = DateTime.Now; Logger.Error(e, LogType.Runtime); From c9aed5786769ba89d9f00c9e72072de9a030382a Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sun, 21 Apr 2024 17:48:25 +0800 Subject: [PATCH 244/249] Skip to the latest time only on first update --- Quaver.Shared/Screens/Gameplay/GameplayScreen.cs | 16 +++++++++------- .../Screens/Gameplay/GameplayScreenView.cs | 8 +++++--- .../Screens/Tournament/TournamentScreen.cs | 9 +++++++++ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs index 69dc85ba1b..a35e035c07 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreen.cs @@ -1328,27 +1328,29 @@ public void SendJudgementsToServer(bool force = false) /// private void HandleSpectatorSkipping() { - if (SpectatorClient.Replay.Frames.Count == 0) + if (SpectatorClient.Replay.Frames.Count == 0 || this is TournamentGameplayScreen) return; - var targetSyncTime = SpectatorTargetSyncTime; + var targetSyncTime = SpectatorClient.Replay.Frames.Last().Time; // User can only be two seconds out of sync with the user - if (Math.Abs(AudioEngine.Track.Time - targetSyncTime) < 3000 - && Math.Abs(Timing.Time - targetSyncTime) < 3000) + if (Math.Abs(AudioEngine.Track.Time - targetSyncTime) < 3000) return; - var skipTime = SpectatorClient.Replay.Frames.Last().Time - 1500; + SkipTo(targetSyncTime); + } + public void SkipTo(float targetSyncTime) + { try { // Skip to the time if the audio already played once. If it hasn't, then play it. AudioTrack.AllowPlayback = true; - AudioEngine.Track?.Seek(skipTime); + AudioEngine.Track?.Seek(targetSyncTime); Timing.Time = AudioEngine.Track.Time; } catch (Exception e) { - Timing.Time = skipTime; + Timing.Time = targetSyncTime; } finally { diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs index c38f87dbba..24ecbfe99c 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs @@ -880,11 +880,13 @@ private void OnGameEnded(object sender, GameEndedEventArgs e) Screen.MultiplayerMatchEndedPrematurely = !Screen.IsPlayComplete && manager.NextHitObject != null && (Screen.Timing.Time >= Screen.Map.Length || AudioEngine.Track.Time >= AudioEngine.Track.Length); - if (Screen.Exiting) + if (Screen is TournamentGameplayScreen) return; + + Screen.IsPaused = true; + Screen.Exit(() => new ResultsScreen(Screen, OnlineManager.CurrentGame, - GetProcessorsFromScoreboard(ScoreboardLeft), GetProcessorsFromScoreboard(ScoreboardRight)), - Screen is TournamentGameplayScreen ? 3500 : 0); + GetProcessorsFromScoreboard(ScoreboardLeft), GetProcessorsFromScoreboard(ScoreboardRight))); } private List GetScoreboardUsers() diff --git a/Quaver.Shared/Screens/Tournament/TournamentScreen.cs b/Quaver.Shared/Screens/Tournament/TournamentScreen.cs index d58f7a3ad7..7385d71dbb 100644 --- a/Quaver.Shared/Screens/Tournament/TournamentScreen.cs +++ b/Quaver.Shared/Screens/Tournament/TournamentScreen.cs @@ -200,6 +200,15 @@ public TournamentScreen(int players) public override void OnFirstUpdate() { GameBase.Game.GlobalUserInterface.Cursor.Alpha = 0; + if (GameplayScreens.All(s => s.SpectatorClient.Replay.Frames.Count != 0)) + { + var targetSyncTime = MainGameplayScreen.SpectatorTargetSyncTime; + foreach (var gameplayScreen in GameplayScreens) + { + gameplayScreen.SkipTo(targetSyncTime); + } + } + base.OnFirstUpdate(); } From 8205c72390d9c445bc5a0d1cddea9f80cc996a2a Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sun, 21 Apr 2024 18:27:20 +0800 Subject: [PATCH 245/249] Only skip when in spectator TournamentScreen --- Quaver.Shared/Screens/Tournament/TournamentScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Screens/Tournament/TournamentScreen.cs b/Quaver.Shared/Screens/Tournament/TournamentScreen.cs index 7385d71dbb..33915fa8f4 100644 --- a/Quaver.Shared/Screens/Tournament/TournamentScreen.cs +++ b/Quaver.Shared/Screens/Tournament/TournamentScreen.cs @@ -200,7 +200,7 @@ public TournamentScreen(int players) public override void OnFirstUpdate() { GameBase.Game.GlobalUserInterface.Cursor.Alpha = 0; - if (GameplayScreens.All(s => s.SpectatorClient.Replay.Frames.Count != 0)) + if (TournamentType == TournamentScreenType.Spectator && GameplayScreens.All(s => s.SpectatorClient.Replay.Frames.Count != 0)) { var targetSyncTime = MainGameplayScreen.SpectatorTargetSyncTime; foreach (var gameplayScreen in GameplayScreens) From 4809c9e1ec31e38a15560b59a3724d75daf6b771 Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sun, 21 Apr 2024 19:04:51 +0800 Subject: [PATCH 246/249] Fix not being able to quit to lobby when one of the tournament players leaves --- Quaver.Shared/Online/OnlineManager.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Quaver.Shared/Online/OnlineManager.cs b/Quaver.Shared/Online/OnlineManager.cs index efc39830da..76bc72c52d 100644 --- a/Quaver.Shared/Online/OnlineManager.cs +++ b/Quaver.Shared/Online/OnlineManager.cs @@ -1198,12 +1198,18 @@ private static void OnUserLeftGame(object sender, UserLeftGameEventArgs e) CurrentGame.BlueTeamPlayers.Remove(e.UserId); CurrentGame.Players.Remove(OnlineUsers[e.UserId].OnlineUser); + var currentScreen = ((QuaverGame) GameBase.Game).CurrentScreen; if (CurrentGame.PlayerIds.Count == 0) { - var quaver = (QuaverGame) GameBase.Game; - - if (quaver.CurrentScreen.Type == QuaverScreenType.Multiplayer) - quaver.CurrentScreen.Exit(() => new MultiplayerLobbyScreen()); + if (currentScreen.Type == QuaverScreenType.Multiplayer) + currentScreen.Exit(() => new MultiplayerLobbyScreen()); + } + else if (currentScreen is TournamentScreen tournamentScreen) + { + if (tournamentScreen.GameplayScreens.Any(s => s.SpectatorClient.Player.OnlineUser.Id == e.UserId)) + { + currentScreen.Exit(() => new MultiplayerLobbyScreen()); + } } } From b78dbe5062c41aba0d57eb970c74bbbd5d51c88b Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sun, 21 Apr 2024 19:31:28 +0800 Subject: [PATCH 247/249] Quit to game lobby instead of lobby screen --- Quaver.Shared/Online/OnlineManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Shared/Online/OnlineManager.cs b/Quaver.Shared/Online/OnlineManager.cs index 76bc72c52d..244f0ce896 100644 --- a/Quaver.Shared/Online/OnlineManager.cs +++ b/Quaver.Shared/Online/OnlineManager.cs @@ -1208,7 +1208,7 @@ private static void OnUserLeftGame(object sender, UserLeftGameEventArgs e) { if (tournamentScreen.GameplayScreens.Any(s => s.SpectatorClient.Player.OnlineUser.Id == e.UserId)) { - currentScreen.Exit(() => new MultiplayerLobbyScreen()); + currentScreen.Exit(() => new MultiplayerGameScreen()); } } } From 365a73fe7a5a69c34cec8e4afa6ca81575512d3b Mon Sep 17 00:00:00 2001 From: WilliamQiufeng Date: Sun, 21 Apr 2024 19:53:23 +0800 Subject: [PATCH 248/249] Return to MultiplayerGameScreen if GameEnd is received more than 10 seconds before supposed ending --- Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs b/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs index 24ecbfe99c..60e84d79ea 100644 --- a/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs +++ b/Quaver.Shared/Screens/Gameplay/GameplayScreenView.cs @@ -35,6 +35,7 @@ using Quaver.Shared.Screens.Gameplay.UI.Offset; using Quaver.Shared.Screens.Gameplay.UI.Replays; using Quaver.Shared.Screens.Gameplay.UI.Scoreboard; +using Quaver.Shared.Screens.Multi; using Quaver.Shared.Screens.Results; using Quaver.Shared.Screens.Selection; using Quaver.Shared.Screens.Tournament.Gameplay; @@ -881,7 +882,11 @@ private void OnGameEnded(object sender, GameEndedEventArgs e) && (Screen.Timing.Time >= Screen.Map.Length || AudioEngine.Track.Time >= AudioEngine.Track.Length); if (Screen is TournamentGameplayScreen) + { + if (Screen.Map.Length - Screen.Timing.Time >= 10000) + Screen.Exit(() => new MultiplayerGameScreen()); return; + } Screen.IsPaused = true; From a17d55b8f750172e3b806d58424a53b6d9433630 Mon Sep 17 00:00:00 2001 From: AiAe Date: Sun, 21 Apr 2024 18:38:01 +0300 Subject: [PATCH 249/249] Update Quaver.Server.Client --- Quaver.Server.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quaver.Server.Client b/Quaver.Server.Client index e006bf8bda..2a7cd02ed8 160000 --- a/Quaver.Server.Client +++ b/Quaver.Server.Client @@ -1 +1 @@ -Subproject commit e006bf8bda03f3e0da452dadec22364f7e38d802 +Subproject commit 2a7cd02ed84077c7d04ca81a1124814cae724684