From 2417d4de8302f7682bb5703ead03936c336fd33d Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 21:46:33 -0500 Subject: [PATCH 01/79] Add `OsuOnlineStore` for proxying external media lookups --- osu.Game/Audio/PreviewTrackManager.cs | 7 ++++--- osu.Game/Online/OsuOnlineStore.cs | 29 +++++++++++++++++++++++++++ osu.Game/OsuGame.cs | 3 +++ osu.Game/OsuGameBase.cs | 2 +- 4 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Online/OsuOnlineStore.cs diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index 1d710e6395..81564cc2e8 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -6,9 +6,10 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.IO.Stores; using osu.Framework.Logging; using osu.Game.Beatmaps; +using osu.Game.Online; +using osu.Game.Online.API; namespace osu.Game.Audio { @@ -28,9 +29,9 @@ namespace osu.Game.Audio } [BackgroundDependencyLoader] - private void load(AudioManager audioManager) + private void load(AudioManager audioManager, IAPIProvider api) { - trackStore = audioManager.GetTrackStore(new OnlineStore()); + trackStore = audioManager.GetTrackStore(new OsuOnlineStore(api.APIEndpointUrl)); } /// diff --git a/osu.Game/Online/OsuOnlineStore.cs b/osu.Game/Online/OsuOnlineStore.cs new file mode 100644 index 0000000000..bb69338b01 --- /dev/null +++ b/osu.Game/Online/OsuOnlineStore.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.IO.Stores; + +namespace osu.Game.Online +{ + /// + /// An which proxies external media lookups through osu-web. + /// + public class OsuOnlineStore : OnlineStore + { + private readonly string apiEndpointUrl; + + public OsuOnlineStore(string apiEndpointUrl) + { + this.apiEndpointUrl = apiEndpointUrl; + } + + protected override string GetLookupUrl(string url) + { + if (Uri.TryCreate(url, UriKind.Absolute, out Uri? uri) && uri.Host.EndsWith(@".ppy.sh", StringComparison.OrdinalIgnoreCase)) + return url; + + return $@"{apiEndpointUrl}/beatmapsets/discussions/media-url?url={url}"; + } + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index dce24c6ee7..1fe41baf2f 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -29,6 +29,7 @@ using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.Handlers.Tablet; +using osu.Framework.IO.Stores; using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Platform; @@ -819,6 +820,8 @@ namespace osu.Game protected override Container CreateScalingContainer() => new ScalingContainer(ScalingMode.Everything); + protected override OnlineStore CreateOnlineStore() => new OsuOnlineStore(CreateEndpoints().APIEndpointUrl); + #region Beatmap progression private void beatmapChanged(ValueChangedEvent beatmap) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index dc13924b4f..d231238699 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -279,7 +279,7 @@ namespace osu.Game dependencies.CacheAs(Storage); var largeStore = new LargeTextureStore(Host.Renderer, Host.CreateTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures"))); - largeStore.AddTextureSource(Host.CreateTextureLoaderStore(new OnlineStore())); + largeStore.AddTextureSource(Host.CreateTextureLoaderStore(CreateOnlineStore())); dependencies.Cache(largeStore); dependencies.CacheAs(LocalConfig); From 2a7133d6d3dc3bdb5a2cafddde7fc2df834b23e1 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 21:47:10 -0500 Subject: [PATCH 02/79] Add test scene using `OsuOnlineStore` to test lookups --- .../Visual/Online/TestSceneMediaProxying.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneMediaProxying.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneMediaProxying.cs b/osu.Game.Tests/Visual/Online/TestSceneMediaProxying.cs new file mode 100644 index 0000000000..868bfa6cc4 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneMediaProxying.cs @@ -0,0 +1,63 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; +using osu.Framework.Platform; +using osu.Game.Graphics.Containers.Markdown; +using osu.Game.Online; + +namespace osu.Game.Tests.Visual.Online +{ + public partial class TestSceneMediaProxying : OsuTestScene + { + [Resolved] + private GameHost host { get; set; } = null!; + + [Test] + public void TestExternalImageLink() + { + AddStep("load image", () => setup(new OsuMarkdownContainer + { + RelativeSizeAxes = Axes.Both, + Text = "![](https://github.com/ppy/osu-wiki/blob/master/wiki/Announcement_messages/img/notification.png?raw=true)", + })); + } + + [Test] + public void TestLocalImageLink() + { + AddStep("load image", () => setup(new OsuMarkdownContainer + { + RelativeSizeAxes = Axes.Both, + Text = "![](https://osu.ppy.sh/help/wiki/shared/news/banners/monthly-beatmapping-contest.png)", + })); + } + + [Test] + public void TestInvalidImageLink() + { + AddStep("load image", () => setup(new OsuMarkdownContainer + { + RelativeSizeAxes = Axes.Both, + Text = "![](https://this-site-does-not-exist.com/img.png)", + })); + } + + private void setup(Drawable drawable) + { + var onlineStore = new OsuOnlineStore(@"https://osu.ppy.sh"); + var textureStore = new TextureStore(host.Renderer, host.CreateTextureLoaderStore(onlineStore)); + + Child = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] { (typeof(TextureStore), textureStore) }, + Child = drawable, + }; + } + } +} From 9a89d402b9d6e5591a4c67668203ee0d53f27ba3 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 25 Nov 2024 00:39:32 -0500 Subject: [PATCH 03/79] Perform proxying only on osu! markdown images --- .../Graphics/Containers/Markdown/OsuMarkdownImage.cs | 3 +++ osu.Game/Online/OsuOnlineStore.cs | 11 ++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs index 10207dd389..a36bbf4f6f 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs @@ -17,5 +17,8 @@ namespace osu.Game.Graphics.Containers.Markdown { TooltipText = linkInline.Title; } + + protected override ImageContainer CreateImageContainer(string url) + => base.CreateImageContainer($@"https://osu.ppy.sh/beatmapsets/discussions/media-url?url={url}"); } } diff --git a/osu.Game/Online/OsuOnlineStore.cs b/osu.Game/Online/OsuOnlineStore.cs index bb69338b01..dddd453faf 100644 --- a/osu.Game/Online/OsuOnlineStore.cs +++ b/osu.Game/Online/OsuOnlineStore.cs @@ -3,12 +3,10 @@ using System; using osu.Framework.IO.Stores; +using osu.Framework.Logging; namespace osu.Game.Online { - /// - /// An which proxies external media lookups through osu-web. - /// public class OsuOnlineStore : OnlineStore { private readonly string apiEndpointUrl; @@ -20,8 +18,11 @@ namespace osu.Game.Online protected override string GetLookupUrl(string url) { - if (Uri.TryCreate(url, UriKind.Absolute, out Uri? uri) && uri.Host.EndsWith(@".ppy.sh", StringComparison.OrdinalIgnoreCase)) - return url; + if (!Uri.TryCreate(url, UriKind.Absolute, out Uri? uri) || !uri.Host.EndsWith(@".ppy.sh", StringComparison.OrdinalIgnoreCase)) + { + Logger.Log($@"Blocking resource lookup from external website: {url}", LoggingTarget.Network, LogLevel.Important); + return string.Empty; + } return $@"{apiEndpointUrl}/beatmapsets/discussions/media-url?url={url}"; } From 83f8fa7472175d053172459ac64ab9469832733d Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 25 Nov 2024 00:41:40 -0500 Subject: [PATCH 04/79] Update test scene --- ...aProxying.cs => TestSceneImageProxying.cs} | 27 +++++-------------- 1 file changed, 7 insertions(+), 20 deletions(-) rename osu.Game.Tests/Visual/Online/{TestSceneMediaProxying.cs => TestSceneImageProxying.cs} (59%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMediaProxying.cs b/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs similarity index 59% rename from osu.Game.Tests/Visual/Online/TestSceneMediaProxying.cs rename to osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs index 868bfa6cc4..696073c10d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMediaProxying.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs @@ -12,7 +12,7 @@ using osu.Game.Online; namespace osu.Game.Tests.Visual.Online { - public partial class TestSceneMediaProxying : OsuTestScene + public partial class TestSceneImageProxying : OsuTestScene { [Resolved] private GameHost host { get; set; } = null!; @@ -20,44 +20,31 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestExternalImageLink() { - AddStep("load image", () => setup(new OsuMarkdownContainer + AddStep("load image", () => Child = new OsuMarkdownContainer { RelativeSizeAxes = Axes.Both, Text = "![](https://github.com/ppy/osu-wiki/blob/master/wiki/Announcement_messages/img/notification.png?raw=true)", - })); + }); } [Test] public void TestLocalImageLink() { - AddStep("load image", () => setup(new OsuMarkdownContainer + AddStep("load image", () => Child = new OsuMarkdownContainer { RelativeSizeAxes = Axes.Both, Text = "![](https://osu.ppy.sh/help/wiki/shared/news/banners/monthly-beatmapping-contest.png)", - })); + }); } [Test] public void TestInvalidImageLink() { - AddStep("load image", () => setup(new OsuMarkdownContainer + AddStep("load image", () => Child = new OsuMarkdownContainer { RelativeSizeAxes = Axes.Both, Text = "![](https://this-site-does-not-exist.com/img.png)", - })); - } - - private void setup(Drawable drawable) - { - var onlineStore = new OsuOnlineStore(@"https://osu.ppy.sh"); - var textureStore = new TextureStore(host.Renderer, host.CreateTextureLoaderStore(onlineStore)); - - Child = new DependencyProvidingContainer - { - RelativeSizeAxes = Axes.Both, - CachedDependencies = new (Type, object)[] { (typeof(TextureStore), textureStore) }, - Child = drawable, - }; + }); } } } From dbe2741982ed3741ef527d79d7e37eea6ff7766b Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 22:19:44 -0500 Subject: [PATCH 05/79] Update specified endpoint --- osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs index a36bbf4f6f..ff7df18f00 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownImage.cs @@ -19,6 +19,6 @@ namespace osu.Game.Graphics.Containers.Markdown } protected override ImageContainer CreateImageContainer(string url) - => base.CreateImageContainer($@"https://osu.ppy.sh/beatmapsets/discussions/media-url?url={url}"); + => base.CreateImageContainer($@"https://osu.ppy.sh/media-url?url={url}"); } } From 9a4c419c568764fabd2d7624d288966c84986f5c Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sat, 30 Nov 2024 22:37:05 -0500 Subject: [PATCH 06/79] Remove unnecessary usage of link proxying in `OsuOnlineStore` Links are checked to be in the ppy.sh domain here. --- osu.Game/Online/OsuOnlineStore.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/OsuOnlineStore.cs b/osu.Game/Online/OsuOnlineStore.cs index dddd453faf..c3e81c503f 100644 --- a/osu.Game/Online/OsuOnlineStore.cs +++ b/osu.Game/Online/OsuOnlineStore.cs @@ -18,13 +18,14 @@ namespace osu.Game.Online protected override string GetLookupUrl(string url) { + // add leading dot to avoid matching hosts named "ppy.sh" if (!Uri.TryCreate(url, UriKind.Absolute, out Uri? uri) || !uri.Host.EndsWith(@".ppy.sh", StringComparison.OrdinalIgnoreCase)) { Logger.Log($@"Blocking resource lookup from external website: {url}", LoggingTarget.Network, LogLevel.Important); return string.Empty; } - return $@"{apiEndpointUrl}/beatmapsets/discussions/media-url?url={url}"; + return url; } } } From ee369ef86d54c6f1359742edc438237e01b718e3 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sat, 30 Nov 2024 22:38:43 -0500 Subject: [PATCH 07/79] Remove unused using directives --- osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs b/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs index 696073c10d..0cf6fec6f0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs @@ -1,14 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Textures; using osu.Framework.Platform; using osu.Game.Graphics.Containers.Markdown; -using osu.Game.Online; namespace osu.Game.Tests.Visual.Online { From 9d7b01bcd47e789d86978508f0bf1148dc9ed066 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 20 Feb 2025 21:20:50 +0800 Subject: [PATCH 08/79] update guest difficulty display to consistent with the web page --- .../API/Requests/Responses/APIBeatmap.cs | 12 +++++ osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 50 +++++++++++++++++-- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index e5ecfe2c99..c6033e3255 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -103,6 +103,9 @@ namespace osu.Game.Online.API.Requests.Responses public double BPM { get; set; } + [JsonProperty(@"owners")] + public BeatmapOwner[] BeatmapOwners { get; set; } = Array.Empty(); + #region Implementation of IBeatmapInfo public IBeatmapMetadataInfo Metadata => (BeatmapSet as IBeatmapSetInfo)?.Metadata ?? new BeatmapMetadata(); @@ -171,5 +174,14 @@ namespace osu.Game.Online.API.Requests.Responses // ReSharper disable once NonReadonlyMemberInGetHashCode public override int GetHashCode() => OnlineID; } + + public class BeatmapOwner + { + [JsonProperty(@"id")] + public int Id { get; set; } + + [JsonProperty(@"username")] + public string Username { get; set; } = string.Empty; + } } } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index a7838651a9..8e36b0ed32 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -211,19 +211,59 @@ namespace osu.Game.Overlays.BeatmapSet private void showBeatmap(APIBeatmap? beatmapInfo) { guestMapperContainer.Clear(); + var beatmapOwners = beatmapInfo?.BeatmapOwners; - if (beatmapInfo?.AuthorID != BeatmapSet?.AuthorID) + if (beatmapOwners != null && (beatmapOwners.Length != 1 || beatmapOwners.First().Id != beatmapSet?.AuthorID)) { - APIUser? user = BeatmapSet?.RelatedUsers?.SingleOrDefault(u => u.OnlineID == beatmapInfo?.AuthorID); + APIUser[]? users = BeatmapSet?.RelatedUsers?.Where(u => beatmapOwners.Any(o => o.Id == u.OnlineID)).ToArray(); - if (user != null) + if (users != null) { - guestMapperContainer.AddText("mapped by "); - guestMapperContainer.AddUserLink(user); + formatGuestUser(users); } } version.Text = beatmapInfo?.DifficultyName ?? string.Empty; + return; + + void formatGuestUser(APIUser[] users) + { + int count = users.Length; + + guestMapperContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty)); // set string.Empty here because we need link. + + switch (count) + { + case 1: + guestMapperContainer.AddUserLink(users[0]); + break; + + case 2: + guestMapperContainer.AddUserLink(users[0]); + guestMapperContainer.AddText(CommonStrings.ArrayAndTwoWordsConnector); + guestMapperContainer.AddUserLink(users[1]); + break; + + default: + { + for (int i = 0; i < count; i++) + { + guestMapperContainer.AddUserLink(users[i]); + + if (i < count - 2) + { + guestMapperContainer.AddText(CommonStrings.ArrayAndWordsConnector); + } + else if (i == count - 2) + { + guestMapperContainer.AddText(CommonStrings.ArrayAndLastWordConnector); + } + } + + break; + } + } + } } private void updateDifficultyButtons() From cde50bca762d4646ecaec261a06af2bf62325971 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 20 Feb 2025 21:31:04 +0800 Subject: [PATCH 09/79] add test --- .../Online/TestSceneBeatmapSetOverlay.cs | 57 +++++++++++++++---- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 325cb9e0cb..ced95d09d9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -297,6 +297,31 @@ namespace osu.Game.Tests.Visual.Online AddAssert("guest mapper information shown", () => overlay.ChildrenOfType().Single().ChildrenOfType().Any(s => s.Text == "BanchoBot")); } + [Test] + public void TestBeatmapsetWithALotGuestOwner() + { + AddStep("show map with 2 mapper", () => overlay.ShowBeatmapSet(createBeatmapSetWithGuestDifficulty(2))); + AddStep("move mouse to guest difficulty", () => + { + InputManager.MoveMouseTo(overlay.ChildrenOfType().ElementAt(1)); + }); + AddStep("show map with 3 mapper", () => overlay.ShowBeatmapSet(createBeatmapSetWithGuestDifficulty(3))); + AddStep("move mouse to guest difficulty", () => + { + InputManager.MoveMouseTo(overlay.ChildrenOfType().ElementAt(1)); + }); + AddStep("show map with 10 mapper", () => overlay.ShowBeatmapSet(createBeatmapSetWithGuestDifficulty(20))); + AddStep("move mouse to guest difficulty", () => + { + InputManager.MoveMouseTo(overlay.ChildrenOfType().ElementAt(1)); + }); + AddStep("show map with 20 mapper", () => overlay.ShowBeatmapSet(createBeatmapSetWithGuestDifficulty(20))); + AddStep("move mouse to guest difficulty", () => + { + InputManager.MoveMouseTo(overlay.ChildrenOfType().ElementAt(1)); + }); + } + private APIBeatmapSet createManyDifficultiesBeatmapSet() { var set = getBeatmapSet(); @@ -336,22 +361,31 @@ namespace osu.Game.Tests.Visual.Online return beatmapSet; } - private APIBeatmapSet createBeatmapSetWithGuestDifficulty() + private APIBeatmapSet createBeatmapSetWithGuestDifficulty(int guestCount = 1) { var set = getBeatmapSet(); var beatmaps = new List(); + var beatmapOwners = new List(); + var ownersAPIUser = new List(); - var guestUser = new APIUser + for (int i = 0; i < guestCount; i++) { - Username = @"BanchoBot", - Id = 3, - }; + var guestUser = new APIUser + { + Username = @$"BanchoBot{i}", + Id = i + 3, + }; - set.RelatedUsers = new[] - { - set.Author, guestUser - }; + beatmapOwners.Add(new APIBeatmap.BeatmapOwner + { + Username = @$"BanchoBot{i}", + Id = i + 3, + }); + ownersAPIUser.Add(guestUser); + } + + set.RelatedUsers = new[] { set.Author }.Concat(ownersAPIUser).ToArray(); beatmaps.Add(new APIBeatmap { @@ -366,7 +400,7 @@ namespace osu.Game.Tests.Visual.Online Fails = Enumerable.Range(1, 100).Select(j => j % 12 - 6).ToArray(), Retries = Enumerable.Range(-2, 100).Select(j => j % 12 - 6).ToArray(), }, - Status = BeatmapOnlineStatus.Graveyard + Status = BeatmapOnlineStatus.Graveyard, }); beatmaps.Add(new APIBeatmap @@ -382,7 +416,8 @@ namespace osu.Game.Tests.Visual.Online Fails = Enumerable.Range(1, 100).Select(j => j % 12 - 6).ToArray(), Retries = Enumerable.Range(-2, 100).Select(j => j % 12 - 6).ToArray(), }, - Status = BeatmapOnlineStatus.Graveyard + Status = BeatmapOnlineStatus.Graveyard, + BeatmapOwners = beatmapOwners.ToArray(), }); set.Beatmaps = beatmaps.ToArray(); From 4fa0288a20160299b9ecc5c43231f9f606a410a9 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 20 Feb 2025 22:03:57 +0800 Subject: [PATCH 10/79] maybe not a function --- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 76 +++++++++---------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 8e36b0ed32..ab3b8d882e 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -219,51 +219,45 @@ namespace osu.Game.Overlays.BeatmapSet if (users != null) { - formatGuestUser(users); + int count = users.Length; + + guestMapperContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty)); // set string.Empty here because we need user link. + + switch (count) + { + case 1: + guestMapperContainer.AddUserLink(users[0]); + break; + + case 2: + guestMapperContainer.AddUserLink(users[0]); + guestMapperContainer.AddText(CommonStrings.ArrayAndTwoWordsConnector); + guestMapperContainer.AddUserLink(users[1]); + break; + + default: + { + for (int i = 0; i < count; i++) + { + guestMapperContainer.AddUserLink(users[i]); + + if (i < count - 2) + { + guestMapperContainer.AddText(CommonStrings.ArrayAndWordsConnector); + } + else if (i == count - 2) + { + guestMapperContainer.AddText(CommonStrings.ArrayAndLastWordConnector); + } + } + + break; + } + } } } version.Text = beatmapInfo?.DifficultyName ?? string.Empty; - return; - - void formatGuestUser(APIUser[] users) - { - int count = users.Length; - - guestMapperContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty)); // set string.Empty here because we need link. - - switch (count) - { - case 1: - guestMapperContainer.AddUserLink(users[0]); - break; - - case 2: - guestMapperContainer.AddUserLink(users[0]); - guestMapperContainer.AddText(CommonStrings.ArrayAndTwoWordsConnector); - guestMapperContainer.AddUserLink(users[1]); - break; - - default: - { - for (int i = 0; i < count; i++) - { - guestMapperContainer.AddUserLink(users[i]); - - if (i < count - 2) - { - guestMapperContainer.AddText(CommonStrings.ArrayAndWordsConnector); - } - else if (i == count - 2) - { - guestMapperContainer.AddText(CommonStrings.ArrayAndLastWordConnector); - } - } - - break; - } - } - } } private void updateDifficultyButtons() From 74d9acbede330ca26e1533d657fa5919390eb1eb Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 20 Feb 2025 22:45:14 +0800 Subject: [PATCH 11/79] fix test --- osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index ced95d09d9..822e5f26bd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -289,12 +289,12 @@ namespace osu.Game.Tests.Visual.Online { InputManager.MoveMouseTo(overlay.ChildrenOfType().ElementAt(0)); }); - AddAssert("guest mapper information not shown", () => overlay.ChildrenOfType().Single().ChildrenOfType().All(s => s.Text != "BanchoBot")); + AddAssert("guest mapper information not shown", () => overlay.ChildrenOfType().Single().ChildrenOfType().All(s => s.Text != "BanchoBot0")); AddStep("move mouse to guest difficulty", () => { InputManager.MoveMouseTo(overlay.ChildrenOfType().ElementAt(1)); }); - AddAssert("guest mapper information shown", () => overlay.ChildrenOfType().Single().ChildrenOfType().Any(s => s.Text == "BanchoBot")); + AddAssert("guest mapper information shown", () => overlay.ChildrenOfType().Single().ChildrenOfType().Any(s => s.Text == "BanchoBot0")); } [Test] @@ -310,7 +310,7 @@ namespace osu.Game.Tests.Visual.Online { InputManager.MoveMouseTo(overlay.ChildrenOfType().ElementAt(1)); }); - AddStep("show map with 10 mapper", () => overlay.ShowBeatmapSet(createBeatmapSetWithGuestDifficulty(20))); + AddStep("show map with 10 mapper", () => overlay.ShowBeatmapSet(createBeatmapSetWithGuestDifficulty(10))); AddStep("move mouse to guest difficulty", () => { InputManager.MoveMouseTo(overlay.ChildrenOfType().ElementAt(1)); From 0ffb23379629176aac71a2ae7e9e7ea59a4e815f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Mar 2025 11:29:31 +0100 Subject: [PATCH 12/79] Rewrite `OverallRanking` to interface directly with `UserStatisticsWatcher` --- .../Visual/Ranking/TestSceneOverallRanking.cs | 18 ++++++--- .../Ranking/TestSceneStatisticsPanel.cs | 38 ------------------- .../Ranking/Statistics/User/OverallRanking.cs | 38 ++++++++++++++----- .../Ranking/Statistics/UserStatisticsPanel.cs | 25 +----------- 4 files changed, 42 insertions(+), 77 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs index b406ea369f..f96d272e40 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Online; using osu.Game.Scoring; @@ -12,7 +13,7 @@ namespace osu.Game.Tests.Visual.Ranking { public partial class TestSceneOverallRanking : OsuTestScene { - private OverallRanking overallRanking = null!; + private readonly Bindable statisticsUpdate = new Bindable(); [Test] public void TestUpdatePending() @@ -104,14 +105,19 @@ namespace osu.Game.Tests.Visual.Ranking displayUpdate(statistics, statistics); } - private void createDisplay() => AddStep("create display", () => Child = overallRanking = new OverallRanking + private void createDisplay() => AddStep("create display", () => { - Width = 400, - Anchor = Anchor.Centre, - Origin = Anchor.Centre + statisticsUpdate.Value = null; + Child = new OverallRanking(new ScoreInfo()) + { + Width = 400, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + DisplayedUpdate = { BindTarget = statisticsUpdate } + }; }); private void displayUpdate(UserStatistics before, UserStatistics after) => - AddStep("display update", () => overallRanking.StatisticsUpdate.Value = new ScoreBasedUserStatisticsUpdate(new ScoreInfo(), before, after)); + AddStep("display update", () => statisticsUpdate.Value = new ScoreBasedUserStatisticsUpdate(new ScoreInfo(), before, after)); } } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index c12b9d29bc..c075e75c87 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -155,44 +155,6 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, State = { Value = Visibility.Visible }, Score = { Value = score }, - DisplayedUserStatisticsUpdate = - { - Value = new ScoreBasedUserStatisticsUpdate(score, new UserStatistics - { - Level = new UserStatistics.LevelInfo - { - Current = 5, - Progress = 20, - }, - GlobalRank = 38000, - CountryRank = 12006, - PP = 2134, - RankedScore = 21123849, - Accuracy = 0.985, - PlayCount = 13375, - PlayTime = 354490, - TotalScore = 128749597, - TotalHits = 0, - MaxCombo = 1233, - }, new UserStatistics - { - Level = new UserStatistics.LevelInfo - { - Current = 5, - Progress = 30, - }, - GlobalRank = 36000, - CountryRank = 12000, - PP = (decimal)2134.5, - RankedScore = 23897015, - Accuracy = 0.984, - PlayCount = 13376, - PlayTime = 35789, - TotalScore = 132218497, - TotalHits = 0, - MaxCombo = 1233, - }) - } }; }); diff --git a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs index 9f5afea6f0..9d0a511f5a 100644 --- a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs +++ b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs @@ -5,8 +5,10 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; using osu.Game.Online; +using osu.Game.Scoring; namespace osu.Game.Screens.Ranking.Statistics.User { @@ -14,13 +16,21 @@ namespace osu.Game.Screens.Ranking.Statistics.User { private const float transition_duration = 300; - public Bindable StatisticsUpdate { get; } = new Bindable(); + public Bindable DisplayedUpdate { get; } = new Bindable(); + private readonly IBindable latestGlobalStatisticsUpdate = new Bindable(); + + private readonly ScoreInfo scoreInfo; private LoadingLayer loadingLayer = null!; private GridContainer content = null!; + public OverallRanking(ScoreInfo scoreInfo) + { + this.scoreInfo = scoreInfo; + } + [BackgroundDependencyLoader] - private void load() + private void load(UserStatisticsWatcher? userStatisticsWatcher) { AutoSizeAxes = Axes.Y; AutoSizeEasing = Easing.OutQuint; @@ -55,34 +65,44 @@ namespace osu.Game.Screens.Ranking.Statistics.User { new Drawable[] { - new GlobalRankChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, + new GlobalRankChangeRow { StatisticsUpdate = { BindTarget = DisplayedUpdate } }, new SimpleStatisticTable.Spacer(), - new PerformancePointsChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, + new PerformancePointsChangeRow { StatisticsUpdate = { BindTarget = DisplayedUpdate } }, }, [], new Drawable[] { - new MaximumComboChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, + new MaximumComboChangeRow { StatisticsUpdate = { BindTarget = DisplayedUpdate } }, new SimpleStatisticTable.Spacer(), - new AccuracyChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, + new AccuracyChangeRow { StatisticsUpdate = { BindTarget = DisplayedUpdate } }, }, [], new Drawable[] { - new RankedScoreChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, + new RankedScoreChangeRow { StatisticsUpdate = { BindTarget = DisplayedUpdate } }, new SimpleStatisticTable.Spacer(), - new TotalScoreChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, + new TotalScoreChangeRow { StatisticsUpdate = { BindTarget = DisplayedUpdate } }, } } } }; + + if (userStatisticsWatcher != null) + { + latestGlobalStatisticsUpdate.BindTo(userStatisticsWatcher.LatestUpdate); + latestGlobalStatisticsUpdate.BindValueChanged(update => + { + if (update.NewValue?.Score.MatchesOnlineID(scoreInfo) == true) + DisplayedUpdate.Value = update.NewValue; + }, true); + } } protected override void LoadComplete() { base.LoadComplete(); - StatisticsUpdate.BindValueChanged(onUpdateReceived, true); + DisplayedUpdate.BindValueChanged(onUpdateReceived, true); FinishTransforms(true); } diff --git a/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs index 86fed4a9bb..de31c234c4 100644 --- a/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs @@ -3,12 +3,8 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; -using osu.Game.Extensions; -using osu.Game.Online; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics.User; @@ -18,29 +14,11 @@ namespace osu.Game.Screens.Ranking.Statistics { private readonly ScoreInfo achievedScore; - internal readonly Bindable DisplayedUserStatisticsUpdate = new Bindable(); - - private IBindable latestGlobalStatisticsUpdate = null!; - public UserStatisticsPanel(ScoreInfo achievedScore) { this.achievedScore = achievedScore; } - [BackgroundDependencyLoader] - private void load(UserStatisticsWatcher? userStatisticsWatcher) - { - if (userStatisticsWatcher != null) - { - latestGlobalStatisticsUpdate = userStatisticsWatcher.LatestUpdate.GetBoundCopy(); - latestGlobalStatisticsUpdate.BindValueChanged(update => - { - if (update.NewValue?.Score.MatchesOnlineID(achievedScore) == true) - DisplayedUserStatisticsUpdate.Value = update.NewValue; - }, true); - } - } - protected override ICollection CreateStatisticItems(ScoreInfo newScore, IBeatmap playableBeatmap) { var items = base.CreateStatisticItems(newScore, playableBeatmap); @@ -50,12 +28,11 @@ namespace osu.Game.Screens.Ranking.Statistics && newScore.OnlineID > 0 && newScore.OnlineID == achievedScore.OnlineID) { - items = items.Append(new StatisticItem("Overall Ranking", () => new OverallRanking + items = items.Append(new StatisticItem("Overall Ranking", () => new OverallRanking(newScore) { RelativeSizeAxes = Axes.X, Anchor = Anchor.Centre, Origin = Anchor.Centre, - StatisticsUpdate = { BindTarget = DisplayedUserStatisticsUpdate } })).ToArray(); } From 5f0451eb791c83af12b5896a05f8b3d5c93f14ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Mar 2025 11:39:07 +0100 Subject: [PATCH 13/79] Remove inheritance in `StatisticsPanel` --- .../Ranking/TestSceneStatisticsPanel.cs | 8 ++-- osu.Game/Screens/Ranking/ResultsScreen.cs | 15 ++----- .../Ranking/Statistics/StatisticsPanel.cs | 31 ++++++++++++-- .../Ranking/Statistics/UserStatisticsPanel.cs | 42 ------------------- 4 files changed, 37 insertions(+), 59 deletions(-) delete mode 100644 osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index c075e75c87..b117e90260 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -137,11 +137,12 @@ namespace osu.Game.Tests.Visual.Ranking { CachedDependencies = [(typeof(UserStatisticsWatcher), userStatisticsWatcher)], RelativeSizeAxes = Axes.Both, - Child = new UserStatisticsPanel(score) + Child = new StatisticsPanel { RelativeSizeAxes = Axes.Both, State = { Value = Visibility.Visible }, - Score = { Value = score, } + Score = { Value = score, }, + AchievedScore = score, } }); AddUntilStep("overall ranking present", () => this.ChildrenOfType().Any()); @@ -150,11 +151,12 @@ namespace osu.Game.Tests.Visual.Ranking private void loadPanel(ScoreInfo score) => AddStep("load panel", () => { - Child = new UserStatisticsPanel(score) + Child = new StatisticsPanel { RelativeSizeAxes = Axes.Both, State = { Value = Visibility.Visible }, Score = { Value = score }, + AchievedScore = score, }; }); diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 24b40968d6..6f9bbd0cfb 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -121,7 +121,10 @@ namespace osu.Game.Screens.Ranking Children = new Drawable[] { new GlobalScrollAdjustsVolume(), - StatisticsPanel = createStatisticsPanel().With(panel => + StatisticsPanel = new StatisticsPanel + { + AchievedScore = ShowUserStatistics && Score != null ? Score : null + }.With(panel => { panel.RelativeSizeAxes = Axes.Both; panel.Score.BindTarget = SelectedScore; @@ -353,16 +356,6 @@ namespace osu.Game.Screens.Ranking /// The fetch direction. -1 to fetch scores greater than the current start of the list, and 1 to fetch scores lower than the current end of the list. protected virtual Task FetchNextPage(int direction) => Task.FromResult([]); - /// - /// Creates the to be used to display extended information about scores. - /// - private StatisticsPanel createStatisticsPanel() - { - return ShowUserStatistics && Score != null - ? new UserStatisticsPanel(Score) - : new StatisticsPanel(); - } - private Task addScores(ScoreInfo[] scores) { var tcs = new TaskCompletionSource(); diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index f9f5254bc2..639600391f 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Placeholders; using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Statistics.User; using osuTK; namespace osu.Game.Screens.Ranking.Statistics @@ -28,6 +29,13 @@ namespace osu.Game.Screens.Ranking.Statistics public readonly Bindable Score = new Bindable(); + /// + /// The score which was achieved by the local user. + /// If this is set to a non-null score, an component will be displayed showing changes to the local user's ranking & statistics + /// when a statistics update related to this score is received from spectator server. + /// + public ScoreInfo? AchievedScore { get; init; } + protected override bool StartHidden => true; [Resolved] @@ -97,7 +105,7 @@ namespace osu.Game.Screens.Ranking.Statistics bool hitEventsAvailable = newScore.HitEvents.Count != 0; Container container; - var statisticItems = CreateStatisticItems(newScore, task.GetResultSafely()); + var statisticItems = CreateStatisticItems(newScore, task.GetResultSafely()).ToArray(); if (!hitEventsAvailable && statisticItems.All(c => c.RequiresHitEvents)) { @@ -199,8 +207,25 @@ namespace osu.Game.Screens.Ranking.Statistics /// /// The score to create the rows for. /// The beatmap on which the score was set. - protected virtual ICollection CreateStatisticItems(ScoreInfo newScore, IBeatmap playableBeatmap) - => newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap); + protected virtual IEnumerable CreateStatisticItems(ScoreInfo newScore, IBeatmap playableBeatmap) + { + foreach (var statistic in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap)) + yield return statistic; + + if (AchievedScore != null + && newScore.UserID > 1 + && newScore.UserID == AchievedScore.UserID + && newScore.OnlineID > 0 + && newScore.OnlineID == AchievedScore.OnlineID) + { + yield return new StatisticItem("Overall Ranking", () => new OverallRanking(newScore) + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } + } protected override bool OnClick(ClickEvent e) { diff --git a/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs deleted file mode 100644 index de31c234c4..0000000000 --- a/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Scoring; -using osu.Game.Screens.Ranking.Statistics.User; - -namespace osu.Game.Screens.Ranking.Statistics -{ - public partial class UserStatisticsPanel : StatisticsPanel - { - private readonly ScoreInfo achievedScore; - - public UserStatisticsPanel(ScoreInfo achievedScore) - { - this.achievedScore = achievedScore; - } - - protected override ICollection CreateStatisticItems(ScoreInfo newScore, IBeatmap playableBeatmap) - { - var items = base.CreateStatisticItems(newScore, playableBeatmap); - - if (newScore.UserID > 1 - && newScore.UserID == achievedScore.UserID - && newScore.OnlineID > 0 - && newScore.OnlineID == achievedScore.OnlineID) - { - items = items.Append(new StatisticItem("Overall Ranking", () => new OverallRanking(newScore) - { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - })).ToArray(); - } - - return items; - } - } -} From d1ce64b3f62113e8f5597c81b10e48cbb7a9d9c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Mar 2025 11:43:23 +0100 Subject: [PATCH 14/79] Add user tag control to results screen's statistics panel --- .../Visual/Ranking/TestSceneUserTagControl.cs | 2 +- osu.Game/Screens/Ranking/ResultsScreen.cs | 13 ++++++------ .../Ranking/Statistics/StatisticsPanel.cs | 21 +++++++++++++++++++ osu.Game/Screens/Ranking/UserTagControl.cs | 14 ++++++++----- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs b/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs index ebfd553815..d622df8d76 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Ranking Child = new PopoverContainer { RelativeSizeAxes = Axes.Both, - Child = new UserTagControl + Child = new UserTagControl(Beatmap.Value.BeatmapInfo) { Width = 500, Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 6f9bbd0cfb..fcf90a3e28 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -85,6 +85,8 @@ namespace osu.Game.Screens.Ranking /// public bool ShowUserStatistics { get; init; } + public bool ShowUserTagControl { get; init; } + private Sample? popInSample; protected ResultsScreen(ScoreInfo? score) @@ -123,12 +125,11 @@ namespace osu.Game.Screens.Ranking new GlobalScrollAdjustsVolume(), StatisticsPanel = new StatisticsPanel { - AchievedScore = ShowUserStatistics && Score != null ? Score : null - }.With(panel => - { - panel.RelativeSizeAxes = Axes.Both; - panel.Score.BindTarget = SelectedScore; - }), + RelativeSizeAxes = Axes.Both, + Score = { BindTarget = SelectedScore }, + AchievedScore = ShowUserStatistics && Score != null ? Score : null, + ShowUserTagControl = ShowUserTagControl, + }, ScorePanelList = new ScorePanelList { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 639600391f..b974b2f515 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -16,6 +16,7 @@ using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Online.Placeholders; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics.User; @@ -36,11 +37,19 @@ namespace osu.Game.Screens.Ranking.Statistics /// public ScoreInfo? AchievedScore { get; init; } + /// + /// Whether to show a control that allows to assign user tags to the played beatmap. + /// + public bool ShowUserTagControl { get; init; } + protected override bool StartHidden => true; [Resolved] private BeatmapManager beatmapManager { get; set; } = null!; + [Resolved] + private IAPIProvider api { get; set; } = null!; + private readonly Container content; private readonly LoadingSpinner spinner; @@ -225,6 +234,18 @@ namespace osu.Game.Screens.Ranking.Statistics Origin = Anchor.Centre, }); } + + if (ShowUserTagControl + && newScore.BeatmapInfo!.OnlineID > 0 + && api.IsLoggedIn) + { + yield return new StatisticItem("Tag the beatmap!", () => new UserTagControl(newScore.BeatmapInfo) + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index 7600d0aaae..3817f662eb 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -37,6 +37,8 @@ namespace osu.Game.Screens.Ranking { public partial class UserTagControl : CompositeDrawable { + private readonly BeatmapInfo beatmapInfo; + public override bool HandlePositionalInput => true; private readonly Cached layout = new Cached(); @@ -53,8 +55,10 @@ namespace osu.Game.Screens.Ranking [Resolved] private IAPIProvider api { get; set; } = null!; - [Resolved] - private Bindable beatmap { get; set; } = null!; + public UserTagControl(BeatmapInfo beatmapInfo) + { + this.beatmapInfo = beatmapInfo; + } [BackgroundDependencyLoader] private void load(SessionStatics sessionStatics) @@ -104,8 +108,8 @@ namespace osu.Game.Screens.Ranking api.Queue(listTagsRequest); } - var getBeatmapSetRequest = new GetBeatmapSetRequest(beatmap.Value.BeatmapInfo.BeatmapSet!.OnlineID); - getBeatmapSetRequest.Success += set => apiBeatmap.Value = set.Beatmaps.SingleOrDefault(b => b.MatchesOnlineID(beatmap.Value.BeatmapInfo)); + var getBeatmapSetRequest = new GetBeatmapSetRequest(beatmapInfo.BeatmapSet!.OnlineID); + getBeatmapSetRequest.Success += set => apiBeatmap.Value = set.Beatmaps.SingleOrDefault(b => b.MatchesOnlineID(beatmapInfo)); api.Queue(getBeatmapSetRequest); } @@ -114,7 +118,7 @@ namespace osu.Game.Screens.Ranking loadingLayer.Show(); extraTags.Remove(tag); - var req = new AddBeatmapTagRequest(beatmap.Value.BeatmapInfo.OnlineID, tag.Id); + var req = new AddBeatmapTagRequest(beatmapInfo.OnlineID, tag.Id); req.Success += () => { tag.Voted.Value = true; From e70ad146472e3bfeeaa150b03503a2fac36c9554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Mar 2025 11:49:31 +0100 Subject: [PATCH 15/79] Show user tag control only following local user gameplay --- osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs | 5 +++++ .../Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 4 +++- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs | 1 + osu.Game/Screens/Play/Player.cs | 6 +----- osu.Game/Screens/Play/SubmittingPlayer.cs | 8 ++++++++ 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs index 4377cc6219..60cb3ba07c 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -13,7 +14,9 @@ using osu.Game.Overlays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Screens.Ranking; using osu.Game.Users; namespace osu.Game.Screens.Edit.GameplayTest @@ -228,5 +231,7 @@ namespace osu.Game.Screens.Edit.GameplayTest editor.RestoreState(editorState); return base.OnExiting(e); } + + protected override ResultsScreen CreateResults(ScoreInfo score) => throw new NotSupportedException(); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index 111b453adb..67a67cf271 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -199,10 +199,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer ? new MultiplayerTeamResultsScreen(score, Room.RoomID.Value, PlaylistItem, multiplayerLeaderboard.TeamScores) { ShowUserStatistics = true, + ShowUserTagControl = true, } : new MultiplayerResultsScreen(score, Room.RoomID.Value, PlaylistItem) { - ShowUserStatistics = true + ShowUserStatistics = true, + ShowUserTagControl = true, }; } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index b82c2404ab..80b378bdcf 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -60,6 +60,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { AllowRetry = true, ShowUserStatistics = true, + ShowUserTagControl = true, }; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b27e0b7477..a738a40993 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1276,11 +1276,7 @@ namespace osu.Game.Screens.Play /// /// The to be displayed in the results screen. /// The . - protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score) - { - AllowRetry = true, - ShowUserStatistics = true, - }; + protected abstract ResultsScreen CreateResults(ScoreInfo score); private void fadeOut() { diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index b667963a70..04cf473173 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -21,6 +21,7 @@ using osu.Game.Online.Rooms; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens.Ranking; namespace osu.Game.Screens.Play { @@ -323,5 +324,12 @@ namespace osu.Game.Screens.Play api.Queue(request); return scoreSubmissionSource.Task; } + + protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score) + { + AllowRetry = true, + ShowUserStatistics = true, + ShowUserTagControl = true, + }; } } From 266682d5854d31b0f176d62205477cd13195ef45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Mar 2025 12:10:48 +0100 Subject: [PATCH 16/79] Adjust colour of user tag popover control For some reason in actual gameplay there seems to be an `OverlayColourProvider` cached that's nowhere to be seen in tests, and without this change things look bad. Dunno, not looking for it. --- osu.Game/Screens/Ranking/UserTagControl.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index 3817f662eb..3ae6501b36 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -30,6 +30,7 @@ using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; using osuTK; using osuTK.Input; @@ -534,14 +535,14 @@ namespace osu.Game.Screens.Ranking } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, OverlayColourProvider? colourProvider) { Content.AddRange(new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.GreySeaFoamDark, + Colour = colourProvider?.Background3 ?? colours.GreySeaFoamDark, Depth = float.MaxValue, }, new FillFlowContainer From 6f74d8ad507a182a5e91863c556c93a88897555e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Mar 2025 12:16:54 +0100 Subject: [PATCH 17/79] Add visual test coverage of user tag control on results --- .../Ranking/TestSceneStatisticsPanel.cs | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index b117e90260..a64fd488bc 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -11,6 +11,7 @@ using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -18,6 +19,9 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; @@ -36,6 +40,8 @@ namespace osu.Game.Tests.Visual.Ranking { public partial class TestSceneStatisticsPanel : OsuTestScene { + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + [Test] public void TestScoreWithPositionStatistics() { @@ -149,6 +155,71 @@ namespace osu.Game.Tests.Visual.Ranking AddUntilStep("loading spinner not visible", () => this.ChildrenOfType().All(l => l.State.Value == Visibility.Hidden)); } + [Test] + public void TestTagging() + { + var score = TestResources.CreateTestScoreInfo(); + + AddStep("set up network requests", () => + { + dummyAPI.HandleRequest = request => + { + switch (request) + { + case ListTagsRequest listTagsRequest: + { + Scheduler.AddDelayed(() => listTagsRequest.TriggerSuccess(new APITagCollection + { + Tags = + [ + new APITag { Id = 1, Name = "tech", Description = "Tests uncommon skills.", }, + new APITag { Id = 2, Name = "alt", Description = "Colloquial term for maps which use rhythms that encourage the player to alternate notes. Typically distinct from burst or stream maps.", }, + new APITag { Id = 3, Name = "aim", Description = "Category for difficulty relating to cursor movement.", }, + new APITag { Id = 4, Name = "tap", Description = "Category for difficulty relating to tapping input.", }, + ] + }), 500); + return true; + } + + case GetBeatmapSetRequest getBeatmapSetRequest: + { + var beatmapSet = CreateAPIBeatmapSet(score.BeatmapInfo); + beatmapSet.Beatmaps.Single().TopTags = + [ + new APIBeatmapTag { TagId = 3, VoteCount = 9 }, + ]; + Scheduler.AddDelayed(() => getBeatmapSetRequest.TriggerSuccess(beatmapSet), 500); + return true; + } + + case AddBeatmapTagRequest: + case RemoveBeatmapTagRequest: + { + Scheduler.AddDelayed(request.TriggerSuccess, 500); + return true; + } + } + + return false; + }; + }); + AddStep("load panel", () => + { + Child = new PopoverContainer + { + RelativeSizeAxes = Axes.Both, + Child = new StatisticsPanel + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible }, + Score = { Value = score }, + AchievedScore = score, + ShowUserTagControl = true, + } + }; + }); + } + private void loadPanel(ScoreInfo score) => AddStep("load panel", () => { Child = new StatisticsPanel From 73fba15adf1cf2a5d8be1d48fb0cff6de322d1d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Mar 2025 12:38:55 +0100 Subject: [PATCH 18/79] Fix xmldoc --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index b974b2f515..16de00fcf1 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// /// The score which was achieved by the local user. - /// If this is set to a non-null score, an component will be displayed showing changes to the local user's ranking & statistics + /// If this is set to a non-null score, an component will be displayed showing changes to the local user's ranking and statistics /// when a statistics update related to this score is received from spectator server. /// public ScoreInfo? AchievedScore { get; init; } From e0a23000be9fe569cd79d2715314daa0aec3ad86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Mar 2025 12:37:57 +0900 Subject: [PATCH 19/79] Move `CreateOnlineStore` override to `OsuGameBase` and update endpoint references --- osu.Game/Audio/PreviewTrackManager.cs | 2 +- osu.Game/OsuGame.cs | 3 --- osu.Game/OsuGameBase.cs | 2 ++ 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index 81564cc2e8..452be91bc0 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -31,7 +31,7 @@ namespace osu.Game.Audio [BackgroundDependencyLoader] private void load(AudioManager audioManager, IAPIProvider api) { - trackStore = audioManager.GetTrackStore(new OsuOnlineStore(api.APIEndpointUrl)); + trackStore = audioManager.GetTrackStore(new OsuOnlineStore(api.Endpoints.APIUrl)); } /// diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 547048c5c7..4a9154f14b 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -28,7 +28,6 @@ using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.Handlers.Tablet; -using osu.Framework.IO.Stores; using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Platform; @@ -824,8 +823,6 @@ namespace osu.Game protected override Container CreateScalingContainer() => new ScalingContainer(ScalingMode.Everything); - protected override OnlineStore CreateOnlineStore() => new OsuOnlineStore(CreateEndpoints().APIEndpointUrl); - #region Beatmap progression private void beatmapChanged(ValueChangedEvent beatmap) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 5c78618a0b..257b6a532b 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -108,6 +108,8 @@ namespace osu.Game public virtual EndpointConfiguration CreateEndpoints() => UseDevelopmentServer ? new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); + protected override OnlineStore CreateOnlineStore() => new OsuOnlineStore(CreateEndpoints().APIUrl); + public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); /// From 8a5b8784e640acc0723f3d43c87d1a1b16d3b70b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Mar 2025 12:40:09 +0900 Subject: [PATCH 20/79] Remove completely redundant comment --- osu.Game/Online/OsuOnlineStore.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/OsuOnlineStore.cs b/osu.Game/Online/OsuOnlineStore.cs index c3e81c503f..72d5ecf036 100644 --- a/osu.Game/Online/OsuOnlineStore.cs +++ b/osu.Game/Online/OsuOnlineStore.cs @@ -18,7 +18,6 @@ namespace osu.Game.Online protected override string GetLookupUrl(string url) { - // add leading dot to avoid matching hosts named "ppy.sh" if (!Uri.TryCreate(url, UriKind.Absolute, out Uri? uri) || !uri.Host.EndsWith(@".ppy.sh", StringComparison.OrdinalIgnoreCase)) { Logger.Log($@"Blocking resource lookup from external website: {url}", LoggingTarget.Network, LogLevel.Important); From da71e7a3633ebdd363c577a0584d004ff1b44e08 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Mar 2025 14:24:10 +0900 Subject: [PATCH 21/79] Fix enter to select tag not working in results scren --- osu.Game/Screens/Ranking/UserTagControl.cs | 35 ++++++++++++++++++---- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index 3ae6501b36..ae4a918ae5 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -27,6 +27,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -500,21 +501,45 @@ namespace osu.Game.Screens.Ranking searchBox.Current.BindValueChanged(_ => searchContainer.SearchTerm = searchBox.Current.Value, true); } + public override bool OnPressed(KeyBindingPressEvent e) + { + if (base.OnPressed(e)) + return true; + + if (e.Repeat) + return false; + + if (State.Value == Visibility.Hidden) + return false; + + if (e.Action == GlobalAction.Select) + { + attemptSelect(); + return true; + } + + return false; + } + protected override bool OnKeyDown(KeyDownEvent e) { - var visibleItems = searchContainer.OfType().Where(d => d.IsPresent).ToArray(); - if (e.Key == Key.Enter) { - if (visibleItems.Length == 1) - select(visibleItems.Single().Tag); - + attemptSelect(); return true; } return base.OnKeyDown(e); } + private void attemptSelect() + { + var visibleItems = searchContainer.OfType().Where(d => d.IsPresent).ToArray(); + + if (visibleItems.Length == 1) + select(visibleItems.Single().Tag); + } + private void select(UserTag tag) { OnSelected?.Invoke(tag); From f78e371b1b5194cd7ee74e79ef54926d6ac7f3a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 13 Mar 2025 10:10:09 +0100 Subject: [PATCH 22/79] Simplify boolean flags --- .../Visual/Ranking/TestSceneResultsScreen.cs | 2 +- .../Visual/Ranking/TestSceneStatisticsPanel.cs | 1 - .../OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 6 ++---- .../Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs | 3 +-- osu.Game/Screens/Play/SubmittingPlayer.cs | 3 +-- osu.Game/Screens/Ranking/ResultsScreen.cs | 12 ++++-------- .../Screens/Ranking/Statistics/StatisticsPanel.cs | 7 +------ 7 files changed, 10 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index b19288fd99..4758b70526 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -404,7 +404,7 @@ namespace osu.Game.Tests.Visual.Ranking : base(score) { AllowRetry = true; - ShowUserStatistics = true; + IsLocalPlay = true; } protected override void LoadComplete() diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index a64fd488bc..02d30d12e6 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -214,7 +214,6 @@ namespace osu.Game.Tests.Visual.Ranking State = { Value = Visibility.Visible }, Score = { Value = score }, AchievedScore = score, - ShowUserTagControl = true, } }; }); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index 67a67cf271..3d4b46f49e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -198,13 +198,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return multiplayerLeaderboard.TeamScores.Count == 2 ? new MultiplayerTeamResultsScreen(score, Room.RoomID.Value, PlaylistItem, multiplayerLeaderboard.TeamScores) { - ShowUserStatistics = true, - ShowUserTagControl = true, + IsLocalPlay = true, } : new MultiplayerResultsScreen(score, Room.RoomID.Value, PlaylistItem) { - ShowUserStatistics = true, - ShowUserTagControl = true, + IsLocalPlay = true, }; } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 80b378bdcf..dc4078cb1f 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -59,8 +59,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists return new PlaylistItemScoreResultsScreen(score, Room.RoomID.Value, PlaylistItem) { AllowRetry = true, - ShowUserStatistics = true, - ShowUserTagControl = true, + IsLocalPlay = true, }; } diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 04cf473173..dc3e5f08ac 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -328,8 +328,7 @@ namespace osu.Game.Screens.Play protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score) { AllowRetry = true, - ShowUserStatistics = true, - ShowUserTagControl = true, + IsLocalPlay = true, }; } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index fcf90a3e28..6da731588f 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -79,13 +79,10 @@ namespace osu.Game.Screens.Ranking public bool AllowWatchingReplay { get; init; } = true; /// - /// Whether the user's personal statistics should be shown on the extended statistics panel - /// after clicking the score panel associated with the being presented. - /// Requires to be present. + /// Whether the provided score is for a local user's play. + /// This will trigger elements like the user's ranking to display. /// - public bool ShowUserStatistics { get; init; } - - public bool ShowUserTagControl { get; init; } + public bool IsLocalPlay { get; init; } private Sample? popInSample; @@ -127,8 +124,7 @@ namespace osu.Game.Screens.Ranking { RelativeSizeAxes = Axes.Both, Score = { BindTarget = SelectedScore }, - AchievedScore = ShowUserStatistics && Score != null ? Score : null, - ShowUserTagControl = ShowUserTagControl, + AchievedScore = IsLocalPlay && Score != null ? Score : null, }, ScorePanelList = new ScorePanelList { diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 16de00fcf1..0cee01cf77 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -37,11 +37,6 @@ namespace osu.Game.Screens.Ranking.Statistics /// public ScoreInfo? AchievedScore { get; init; } - /// - /// Whether to show a control that allows to assign user tags to the played beatmap. - /// - public bool ShowUserTagControl { get; init; } - protected override bool StartHidden => true; [Resolved] @@ -235,7 +230,7 @@ namespace osu.Game.Screens.Ranking.Statistics }); } - if (ShowUserTagControl + if (AchievedScore != null && newScore.BeatmapInfo!.OnlineID > 0 && api.IsLoggedIn) { From 5b1a4c8ed14bbf17baa11c87325226540a5fe170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 13 Mar 2025 10:13:22 +0100 Subject: [PATCH 23/79] Require high enough score rank for tagging beatmap --- .../Ranking/TestSceneStatisticsPanel.cs | 22 +++++++++++++++ .../Ranking/Statistics/StatisticsPanel.cs | 27 +++++++++++++++---- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index 02d30d12e6..168410fe4a 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -219,6 +219,28 @@ namespace osu.Game.Tests.Visual.Ranking }); } + [Test] + public void TestTaggingWhenRankTooLow() + { + var score = TestResources.CreateTestScoreInfo(); + score.Rank = ScoreRank.D; + + AddStep("load panel", () => + { + Child = new PopoverContainer + { + RelativeSizeAxes = Axes.Both, + Child = new StatisticsPanel + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible }, + Score = { Value = score }, + AchievedScore = score, + } + }; + }); + } + private void loadPanel(ScoreInfo score) => AddStep("load panel", () => { Child = new StatisticsPanel diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 0cee01cf77..758eabcf2e 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -234,12 +235,28 @@ namespace osu.Game.Screens.Ranking.Statistics && newScore.BeatmapInfo!.OnlineID > 0 && api.IsLoggedIn) { - yield return new StatisticItem("Tag the beatmap!", () => new UserTagControl(newScore.BeatmapInfo) + if ( + // We may want to iterate on this condition + AchievedScore.Rank >= ScoreRank.C + ) { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }); + yield return new StatisticItem("Tag the beatmap!", () => new UserTagControl(newScore.BeatmapInfo) + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } + else + { + yield return new StatisticItem("Tag the beatmap!", () => new OsuTextFlowContainer(cp => cp.Font = OsuFont.GetFont(size: StatisticItem.FONT_SIZE, weight: FontWeight.SemiBold)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.Centre, + Text = "Set a better score to contribute to beatmap tags!", + }); + } } } From 723a22130d61061b3f0b9062ba49b89e4ff4c0c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 13 Mar 2025 12:06:27 +0100 Subject: [PATCH 24/79] Fix test --- osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index 168410fe4a..df65023303 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -152,7 +152,9 @@ namespace osu.Game.Tests.Visual.Ranking } }); AddUntilStep("overall ranking present", () => this.ChildrenOfType().Any()); - AddUntilStep("loading spinner not visible", () => this.ChildrenOfType().All(l => l.State.Value == Visibility.Hidden)); + AddUntilStep("loading spinner not visible", + () => this.ChildrenOfType().Single() + .ChildrenOfType().All(l => l.State.Value == Visibility.Hidden)); } [Test] From 607c83abd69c09c8d5313f18ec67734151bc576a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 13 Mar 2025 12:07:19 +0100 Subject: [PATCH 25/79] Switch add beatmap tag request to using path paramx See https://github.com/ppy/osu-web/pull/11999. --- osu.Game/Online/API/Requests/AddBeatmapTagRequest.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Online/API/Requests/AddBeatmapTagRequest.cs b/osu.Game/Online/API/Requests/AddBeatmapTagRequest.cs index 4fa02dc569..2dd9303841 100644 --- a/osu.Game/Online/API/Requests/AddBeatmapTagRequest.cs +++ b/osu.Game/Online/API/Requests/AddBeatmapTagRequest.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Globalization; using System.Net.Http; using osu.Framework.IO.Network; @@ -22,10 +21,9 @@ namespace osu.Game.Online.API.Requests { var req = base.CreateWebRequest(); req.Method = HttpMethod.Post; - req.AddParameter(@"tag_id", TagID.ToString(CultureInfo.InvariantCulture), RequestParameterType.Query); return req; } - protected override string Target => $@"beatmaps/{BeatmapID}/tags"; + protected override string Target => $@"beatmaps/{BeatmapID}/tags/{TagID}"; } } From 17b17f4d714bcbfe9fbbc20776b26f4d79af3f36 Mon Sep 17 00:00:00 2001 From: Zihad Date: Wed, 12 Mar 2025 17:41:16 +0600 Subject: [PATCH 26/79] Highlight diff attribute changes in mod select --- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 2670c20d26..dedd1e336e 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -177,15 +177,18 @@ namespace osu.Game.Overlays.Mods bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate); BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); + BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(originalDifficulty); foreach (var mod in Mods.Value.OfType()) - mod.ApplyToDifficulty(originalDifficulty); + mod.ApplyToDifficulty(adjustedDifficulty); Ruleset ruleset = GameRuleset.Value.CreateInstance(); - BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); + adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(adjustedDifficulty, rate); TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty); + circleSizeDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.CircleSize, adjustedDifficulty.CircleSize); + drainRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.DrainRate, adjustedDifficulty.DrainRate); approachRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate); overallDifficultyDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty); From 993be4da41d2c63659beedd14c0c59aed0178278 Mon Sep 17 00:00:00 2001 From: GAMIS65 Date: Thu, 13 Mar 2025 23:41:41 +0100 Subject: [PATCH 27/79] Add copy version context menu --- osu.Game/Overlays/Settings/SettingsFooter.cs | 36 ++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsFooter.cs b/osu.Game/Overlays/Settings/SettingsFooter.cs index 4e9d4c0d28..ce32dc1a85 100644 --- a/osu.Game/Overlays/Settings/SettingsFooter.cs +++ b/osu.Game/Overlays/Settings/SettingsFooter.cs @@ -1,13 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; @@ -47,10 +52,21 @@ namespace osu.Game.Overlays.Settings Text = game.Name, Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold), }, - new BuildDisplay(game.Version) + new OsuContextMenuContainer() { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + + Children = new Drawable[] + { + new BuildDisplay(game.Version) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } + } } }; @@ -76,10 +92,13 @@ namespace osu.Game.Overlays.Settings } } - private partial class BuildDisplay : OsuAnimatedButton + private partial class BuildDisplay : OsuAnimatedButton, IHasContextMenu { private readonly string version; + [Resolved] + private Clipboard clipboard { get; set; } = null!; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -108,6 +127,19 @@ namespace osu.Game.Overlays.Settings Colour = DebugUtils.IsDebugBuild ? colours.Red : Color4.White, }); } + + public MenuItem[] ContextMenuItems + { + get + { + List menuItems = new List() + { + new OsuMenuItem("Copy Version", MenuItemType.Standard, () => clipboard.SetText(version)) + }; + + return menuItems.ToArray(); + } + } } } } From b6b115e2690ca261bbbc02926f8b61d3882f299a Mon Sep 17 00:00:00 2001 From: GAMIS65 Date: Fri, 14 Mar 2025 09:50:53 +0100 Subject: [PATCH 28/79] Move the context menu container to a higher level --- osu.Game/Overlays/Settings/SettingsFooter.cs | 14 +------------- osu.Game/Overlays/SettingsOverlay.cs | 9 ++++++++- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsFooter.cs b/osu.Game/Overlays/Settings/SettingsFooter.cs index ce32dc1a85..07dc2ea230 100644 --- a/osu.Game/Overlays/Settings/SettingsFooter.cs +++ b/osu.Game/Overlays/Settings/SettingsFooter.cs @@ -12,7 +12,6 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; @@ -52,21 +51,10 @@ namespace osu.Game.Overlays.Settings Text = game.Name, Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold), }, - new OsuContextMenuContainer() + new BuildDisplay(game.Version) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - - Children = new Drawable[] - { - new BuildDisplay(game.Version) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - } - } } }; diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 630675a717..a498f2fe1f 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Graphics; +using osu.Game.Graphics.Cursor; using osu.Game.Localisation; using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections; @@ -56,7 +57,13 @@ namespace osu.Game.Overlays private SettingsSubPanel lastOpenedSubPanel; protected override Drawable CreateHeader() => new SettingsHeader(Title, Description); - protected override Drawable CreateFooter() => new SettingsFooter(); + + protected override Drawable CreateFooter() => new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = new SettingsFooter() + }; public SettingsOverlay() : base(false) From f72de39e4ded4ec79810b873170adb92b27988d2 Mon Sep 17 00:00:00 2001 From: GAMIS65 Date: Fri, 14 Mar 2025 10:02:41 +0100 Subject: [PATCH 29/79] Change CopyUrlToClipboard to CopyStringToClipboard --- osu.Game/Graphics/UserInterface/ExternalLinkButton.cs | 2 +- osu.Game/Localisation/ToastStrings.cs | 4 ++-- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Overlays/Comments/DrawableComment.cs | 2 +- .../Overlays/OSD/{CopyUrlToast.cs => CopyStringToast.cs} | 6 +++--- .../Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs | 2 +- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) rename osu.Game/Overlays/OSD/{CopyUrlToast.cs => CopyStringToast.cs} (61%) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index b3ffd15816..2bc5ba91fa 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -89,7 +89,7 @@ namespace osu.Game.Graphics.UserInterface { if (Link == null) return; - game?.CopyUrlToClipboard(Link); + game?.CopyStringToClipboard(Link); } } } diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 49e8d00371..000f01ebca 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -45,9 +45,9 @@ namespace osu.Game.Localisation public static LocalisableString SkinSaved => new TranslatableString(getKey(@"skin_saved"), @"Skin saved"); /// - /// "Link copied to clipboard" + /// "Copied to clipboard" /// - public static LocalisableString UrlCopied => new TranslatableString(getKey(@"url_copied"), @"Link copied to clipboard"); + public static LocalisableString StringCopied => new TranslatableString(getKey(@"string_copied"), @"Copied to clipboard"); /// /// "Speed changed to {0:N2}x" diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4a9154f14b..c461c0fcc5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -519,10 +519,10 @@ namespace osu.Game } }); - public void CopyUrlToClipboard(string url) => waitForReady(() => onScreenDisplay, _ => + public void CopyStringToClipboard(string url) => waitForReady(() => onScreenDisplay, _ => { dependencies.Get().SetText(url); - onScreenDisplay.Display(new CopyUrlToast()); + onScreenDisplay.Display(new CopyStringToast()); }); public void OpenUrlExternally(string url, LinkWarnMode warnMode = LinkWarnMode.Default) => waitForReady(() => externalLinkOpener, _ => externalLinkOpener.OpenUrlExternally(url, warnMode)); diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 0d566174bb..2f1b7054e2 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -420,7 +420,7 @@ namespace osu.Game.Overlays.Comments private void copyUrl() { clipboard.SetText($@"{api.Endpoints.APIUrl}/comments/{Comment.Id}"); - onScreenDisplay?.Display(new CopyUrlToast()); + onScreenDisplay?.Display(new CopyStringToast()); } private void toggleReply() diff --git a/osu.Game/Overlays/OSD/CopyUrlToast.cs b/osu.Game/Overlays/OSD/CopyStringToast.cs similarity index 61% rename from osu.Game/Overlays/OSD/CopyUrlToast.cs rename to osu.Game/Overlays/OSD/CopyStringToast.cs index 2c5a9179f2..34f85dc9cb 100644 --- a/osu.Game/Overlays/OSD/CopyUrlToast.cs +++ b/osu.Game/Overlays/OSD/CopyStringToast.cs @@ -5,10 +5,10 @@ using osu.Game.Localisation; namespace osu.Game.Overlays.OSD { - public partial class CopyUrlToast : Toast + public partial class CopyStringToast : Toast { - public CopyUrlToast() - : base(CommonStrings.General, ToastStrings.UrlCopied, "") + public CopyStringToast() + : base(CommonStrings.General, ToastStrings.StringCopied, "") { } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index de5813ce0d..d18e00d643 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -355,7 +355,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { items.AddRange([ new OsuMenuItem("View in browser", MenuItemType.Standard, () => game?.OpenUrlExternally(formatRoomUrl(Room.RoomID.Value))), - new OsuMenuItem("Copy link", MenuItemType.Standard, () => game?.CopyUrlToClipboard(formatRoomUrl(Room.RoomID.Value))) + new OsuMenuItem("Copy link", MenuItemType.Standard, () => game?.CopyStringToClipboard(formatRoomUrl(Room.RoomID.Value))) ]); } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 4451cfcf32..055cb53d24 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -301,7 +301,7 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); if (beatmapInfo.GetOnlineURL(api, ruleset.Value) is string url) - items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyUrlToClipboard(url))); + items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyStringToClipboard(url))); if (manager != null) items.Add(new OsuMenuItem("Mark as played", MenuItemType.Standard, () => manager.MarkPlayed(beatmapInfo))); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 996d9ea0ab..55b2e68209 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -301,7 +301,7 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet))); if (beatmapSet.GetOnlineURL(api, ruleset.Value) is string url) - items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyUrlToClipboard(url))); + items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyStringToClipboard(url))); if (dialogOverlay != null) items.Add(new OsuMenuItem("Delete...", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); From c31fcb946b609018c9875d4cfc54d9e171c5d2c6 Mon Sep 17 00:00:00 2001 From: GAMIS65 Date: Fri, 14 Mar 2025 10:06:28 +0100 Subject: [PATCH 30/79] Rename parameter --- osu.Game/OsuGame.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c461c0fcc5..87f6d58d02 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -519,9 +519,9 @@ namespace osu.Game } }); - public void CopyStringToClipboard(string url) => waitForReady(() => onScreenDisplay, _ => + public void CopyStringToClipboard(string value) => waitForReady(() => onScreenDisplay, _ => { - dependencies.Get().SetText(url); + dependencies.Get().SetText(value); onScreenDisplay.Display(new CopyStringToast()); }); From c671eef26031770c4bf0f4e2e1eaac22b0a7716c Mon Sep 17 00:00:00 2001 From: GAMIS65 Date: Fri, 14 Mar 2025 10:09:10 +0100 Subject: [PATCH 31/79] Use CopyStringToClipboard --- osu.Game/Overlays/Settings/SettingsFooter.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/SettingsFooter.cs b/osu.Game/Overlays/Settings/SettingsFooter.cs index 07dc2ea230..70cb73581c 100644 --- a/osu.Game/Overlays/Settings/SettingsFooter.cs +++ b/osu.Game/Overlays/Settings/SettingsFooter.cs @@ -90,6 +90,9 @@ namespace osu.Game.Overlays.Settings [Resolved] private OsuColour colours { get; set; } = null!; + [Resolved] + private OsuGame game { get; set; } = null!; + public BuildDisplay(string version) { this.version = version; @@ -122,7 +125,7 @@ namespace osu.Game.Overlays.Settings { List menuItems = new List() { - new OsuMenuItem("Copy Version", MenuItemType.Standard, () => clipboard.SetText(version)) + new OsuMenuItem("Copy version", MenuItemType.Standard, () => game?.CopyStringToClipboard(version)) }; return menuItems.ToArray(); From 7715a3dd9f65049a140e450e59dbdec0560ae152 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 14 Mar 2025 19:11:15 +0900 Subject: [PATCH 32/79] Add failing test --- .../Visual/Multiplayer/TestSceneRoomListing.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomListing.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomListing.cs index 27c5758afa..45f1ff1acb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomListing.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomListing.cs @@ -11,6 +11,7 @@ using osu.Framework.Testing; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Tests.Visual.OnlinePlay; @@ -198,6 +199,22 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add rooms", () => rooms.AddRange(GenerateRooms(3, withPassword: true))); } + [Test] + public void TestFreestyleBypassesRulesetFilter() + { + AddStep("apply taiko filter", () => container.Filter.Value = new FilterCriteria { Ruleset = new TaikoRuleset().RulesetInfo }); + + AddStep("add osu + freestyle room", () => + { + var room = GenerateRooms(1, new OsuRuleset().RulesetInfo)[0]; + room.Playlist[0].Freestyle = true; + room.CurrentPlaylistItem = room.Playlist[0]; + rooms.Add(room); + }); + + AddAssert("room visible", () => container.DrawableRooms.Any()); + } + private bool checkRoomSelected(Room? room) => selectedRoom.Value == room; private Room? getRoomInFlow(int index) => From 145cff50911e5c48cc1fb7d11f5e92751f0faa66 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 14 Mar 2025 19:11:32 +0900 Subject: [PATCH 33/79] Filter freestyle rooms against all rulesets --- osu.Game/Screens/OnlinePlay/Lounge/Components/RoomListing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomListing.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomListing.cs index 0276601656..9835802fae 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomListing.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomListing.cs @@ -99,7 +99,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { bool matchingFilter = true; - matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false; + matchingFilter &= criteria.Ruleset == null || r.Room.CurrentPlaylistItem?.Freestyle == true || r.Room.PlaylistItemStats?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false; matchingFilter &= matchPermissions(r, criteria.Permissions); // Room name isn't translatable, so ToString() is used here for simplicity. From 8d10fe8d324567d65142b02b9d9b6a14a6f50d2c Mon Sep 17 00:00:00 2001 From: GAMIS65 Date: Fri, 14 Mar 2025 12:42:42 +0100 Subject: [PATCH 34/79] Fix failing test --- osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs index df0fc8de57..970949280f 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs @@ -161,6 +161,9 @@ namespace osu.Game.Tests.Visual.Settings }); Dependencies.CacheAs(dialogOverlay); + + var osuGame = new OsuGame(); + Dependencies.CacheAs(osuGame); } } } From d5074bb30f785eaada7de720959dab84a6528702 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 14 Mar 2025 20:47:11 +0900 Subject: [PATCH 35/79] Improve SFX playback behaviour of rapid kiai/star fountain activations --- osu.Game/Screens/Menu/KiaiMenuFountains.cs | 10 +-- osu.Game/Screens/Menu/StarFountainSfx.cs | 74 +++++++++++++++++++ .../Screens/Play/KiaiGameplayFountains.cs | 8 +- 3 files changed, 81 insertions(+), 11 deletions(-) create mode 100644 osu.Game/Screens/Menu/StarFountainSfx.cs diff --git a/osu.Game/Screens/Menu/KiaiMenuFountains.cs b/osu.Game/Screens/Menu/KiaiMenuFountains.cs index b57012eaf7..7baf18d526 100644 --- a/osu.Game/Screens/Menu/KiaiMenuFountains.cs +++ b/osu.Game/Screens/Menu/KiaiMenuFountains.cs @@ -6,9 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Utils; -using osu.Game.Audio; using osu.Game.Graphics.Containers; -using osu.Game.Skinning; namespace osu.Game.Screens.Menu { @@ -20,7 +18,7 @@ namespace osu.Game.Screens.Menu [Resolved] private GameHost host { get; set; } = null!; - private SkinnableSound? sample; + private StarFountainSfx sfx = null!; [BackgroundDependencyLoader] private void load() @@ -41,7 +39,7 @@ namespace osu.Game.Screens.Menu Origin = Anchor.BottomRight, X = -250, }, - sample = new SkinnableSound(new SampleInfo("Gameplay/fountain-shoot")) + sfx = new StarFountainSfx() }; } @@ -83,9 +81,9 @@ namespace osu.Game.Screens.Menu break; } - // Don't play SFX when game is in background as it can be a bit noisy. + // Don't play SFX when game is in background, as it can be a bit noisy. if (host.IsActive.Value) - sample?.Play(); + sfx.Trigger(); } } } diff --git a/osu.Game/Screens/Menu/StarFountainSfx.cs b/osu.Game/Screens/Menu/StarFountainSfx.cs new file mode 100644 index 0000000000..91337d6959 --- /dev/null +++ b/osu.Game/Screens/Menu/StarFountainSfx.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Threading; +using osu.Game.Audio; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Menu +{ + public partial class StarFountainSfx : Container + { + private const int shoot_retrigger_delay = 500; + private const int loop_fade_duration = 500; + + private double? lastPlayback; + + private SkinnableSound? shootSample; + private PausableSkinnableSound? loopSample; + + private ScheduledDelegate? loopFadeDelegate; + private ScheduledDelegate? loopStopDelegate; + + public void Trigger() + { + loopFadeDelegate?.Cancel(); + loopStopDelegate?.Cancel(); + + // Only play 'shootSample' if enough time has passed since last `Trigger()` call. + if (lastPlayback == null || Time.Current - lastPlayback > shoot_retrigger_delay) + { + loopSample?.Stop(); + shootSample?.Play(); + lastPlayback = Time.Current; + + return; + } + + if (loopSample == null) return; + + // Only call `Play()` if `loopSample` is not already playing, to prevent restarting the sample each time. + if (!loopSample.RequestedPlaying) + { + loopSample.Volume.Value = 1f; + loopSample.Play(); + } + + // Schedule a volume fadeout, followed by a `Stop()`. + loopFadeDelegate = Scheduler.AddDelayed(() => + { + this.TransformBindableTo(loopSample.Volume, 0, loop_fade_duration); + + loopStopDelegate = Scheduler.AddDelayed(() => + { + loopSample?.Stop(); + }, loop_fade_duration); + }, shoot_retrigger_delay); + + lastPlayback = Time.Current; + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + shootSample = new SkinnableSound(new SampleInfo("Gameplay/fountain-shoot")), + loopSample = new PausableSkinnableSound(new SampleInfo("Gameplay/fountain-loop")) { Looping = true }, + }; + } + } +} diff --git a/osu.Game/Screens/Play/KiaiGameplayFountains.cs b/osu.Game/Screens/Play/KiaiGameplayFountains.cs index 017e66253f..cdeb2a0700 100644 --- a/osu.Game/Screens/Play/KiaiGameplayFountains.cs +++ b/osu.Game/Screens/Play/KiaiGameplayFountains.cs @@ -6,11 +6,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Utils; -using osu.Game.Audio; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Screens.Menu; -using osu.Game.Skinning; namespace osu.Game.Screens.Play { @@ -21,7 +19,7 @@ namespace osu.Game.Screens.Play private Bindable kiaiStarFountains = null!; - private SkinnableSound? sample; + private StarFountainSfx sfx = null!; [BackgroundDependencyLoader] private void load(OsuConfigManager config) @@ -44,7 +42,7 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomRight, X = -75, }, - sample = new SkinnableSound(new SampleInfo("Gameplay/fountain-shoot")) + sfx = new StarFountainSfx(), }; } @@ -72,7 +70,7 @@ namespace osu.Game.Screens.Play leftFountain.Shoot(1); rightFountain.Shoot(-1); - sample?.Play(); + sfx.Trigger(); } public partial class GameplayStarFountain : StarFountain From f37f60b22d043e459c5cada4e17e8f15e06b4d73 Mon Sep 17 00:00:00 2001 From: GAMIS65 Date: Fri, 14 Mar 2025 13:23:10 +0100 Subject: [PATCH 36/79] Fix code quality --- osu.Game/Overlays/Settings/SettingsFooter.cs | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsFooter.cs b/osu.Game/Overlays/Settings/SettingsFooter.cs index 70cb73581c..41ea266af8 100644 --- a/osu.Game/Overlays/Settings/SettingsFooter.cs +++ b/osu.Game/Overlays/Settings/SettingsFooter.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Development; using osu.Framework.Graphics; @@ -9,7 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Logging; -using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -84,9 +82,6 @@ namespace osu.Game.Overlays.Settings { private readonly string version; - [Resolved] - private Clipboard clipboard { get; set; } = null!; - [Resolved] private OsuColour colours { get; set; } = null!; @@ -119,18 +114,10 @@ namespace osu.Game.Overlays.Settings }); } - public MenuItem[] ContextMenuItems + public MenuItem[] ContextMenuItems => new MenuItem[] { - get - { - List menuItems = new List() - { - new OsuMenuItem("Copy version", MenuItemType.Standard, () => game?.CopyStringToClipboard(version)) - }; - - return menuItems.ToArray(); - } - } + new OsuMenuItem("Copy version", MenuItemType.Standard, () => game?.CopyStringToClipboard(version)) + }; } } } From f2b57e39546075481071236bf685134eb468e528 Mon Sep 17 00:00:00 2001 From: GAMIS65 Date: Fri, 14 Mar 2025 13:33:07 +0100 Subject: [PATCH 37/79] Fix more code quality errors --- osu.Game/Overlays/Settings/SettingsFooter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/SettingsFooter.cs b/osu.Game/Overlays/Settings/SettingsFooter.cs index 41ea266af8..52abd4fa65 100644 --- a/osu.Game/Overlays/Settings/SettingsFooter.cs +++ b/osu.Game/Overlays/Settings/SettingsFooter.cs @@ -86,7 +86,7 @@ namespace osu.Game.Overlays.Settings private OsuColour colours { get; set; } = null!; [Resolved] - private OsuGame game { get; set; } = null!; + private OsuGame? game { get; set; } public BuildDisplay(string version) { From 0bde11b504394f915a64ca61f4d11477f5c4843e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 16 Mar 2025 01:21:00 +0900 Subject: [PATCH 38/79] Allow spectator button to work without begin play requests --- osu.Game/Online/Spectator/SpectatorClient.cs | 12 ---- .../Dashboard/CurrentlyOnlineDisplay.cs | 60 +++++-------------- osu.Game/Users/ExtendedUserPanel.cs | 1 + 3 files changed, 15 insertions(+), 58 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 91f009b76f..76e5cb0404 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -47,11 +47,6 @@ namespace osu.Game.Online.Spectator /// public IBindableList WatchingUsers => watchingUsers; - /// - /// A global list of all players currently playing. - /// - public IBindableList PlayingUsers => playingUsers; - /// /// Whether the local user is playing. /// @@ -91,7 +86,6 @@ namespace osu.Game.Online.Spectator private readonly BindableDictionary watchedUserStates = new BindableDictionary(); private readonly BindableList watchingUsers = new BindableList(); - private readonly BindableList playingUsers = new BindableList(); private readonly SpectatorState currentState = new SpectatorState(); private IBeatmap? currentBeatmap; @@ -134,7 +128,6 @@ namespace osu.Game.Online.Spectator } else { - playingUsers.Clear(); watchedUserStates.Clear(); watchingUsers.Clear(); } @@ -145,9 +138,6 @@ namespace osu.Game.Online.Spectator { Schedule(() => { - if (!playingUsers.Contains(userId)) - playingUsers.Add(userId); - if (watchedUsersRefCounts.ContainsKey(userId)) watchedUserStates[userId] = state; @@ -161,8 +151,6 @@ namespace osu.Game.Online.Spectator { Schedule(() => { - playingUsers.Remove(userId); - if (watchedUsersRefCounts.ContainsKey(userId)) watchedUserStates[userId] = state; diff --git a/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs index 2fb1ebc050..fce73f0198 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs @@ -2,9 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Collections.Specialized; using System.Diagnostics; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -16,10 +14,8 @@ using osu.Framework.Localisation; using osu.Framework.Screens; using osu.Game.Database; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Metadata; -using osu.Game.Online.Spectator; using osu.Game.Resources.Localisation.Web; using osu.Game.Screens; using osu.Game.Screens.OnlinePlay.Match.Components; @@ -34,19 +30,12 @@ namespace osu.Game.Overlays.Dashboard private const float search_textbox_height = 40; private const float padding = 10; - private readonly IBindableList playingUsers = new BindableList(); private readonly IBindableDictionary onlineUserPresences = new BindableDictionary(); private readonly Dictionary userPanels = new Dictionary(); private SearchContainer userFlow = null!; private BasicSearchTextBox searchTextBox = null!; - [Resolved] - private IAPIProvider api { get; set; } = null!; - - [Resolved] - private SpectatorClient spectatorClient { get; set; } = null!; - [Resolved] private MetadataClient metadataClient { get; set; } = null!; @@ -106,9 +95,6 @@ namespace osu.Game.Overlays.Dashboard onlineUserPresences.BindTo(metadataClient.UserPresences); onlineUserPresences.BindCollectionChanged(onUserPresenceUpdated, true); - - playingUsers.BindTo(spectatorClient.PlayingUsers); - playingUsers.BindCollectionChanged(onPlayingUsersChanged, true); } protected override void OnFocus(FocusEvent e) @@ -152,53 +138,27 @@ namespace osu.Game.Overlays.Dashboard } }); - private void onPlayingUsersChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - Debug.Assert(e.NewItems != null); - - foreach (int userId in e.NewItems) - { - if (userPanels.TryGetValue(userId, out var panel)) - panel.CanSpectate.Value = userId != api.LocalUser.Value.Id; - } - - break; - - case NotifyCollectionChangedAction.Remove: - Debug.Assert(e.OldItems != null); - - foreach (int userId in e.OldItems) - { - if (userPanels.TryGetValue(userId, out var panel)) - panel.CanSpectate.Value = false; - } - - break; - } - } - private OnlineUserPanel createUserPanel(APIUser user) => new OnlineUserPanel(user).With(panel => { panel.Anchor = Anchor.TopCentre; panel.Origin = Anchor.TopCentre; - panel.CanSpectate.Value = playingUsers.Contains(user.Id); }); public partial class OnlineUserPanel : CompositeDrawable, IFilterable { public readonly APIUser User; - public BindableBool CanSpectate { get; } = new BindableBool(); + private PurpleRoundedButton spectateButton = null!; public IEnumerable FilterTerms { get; } [Resolved] private IPerformFromScreenRunner? performer { get; set; } + [Resolved] + private MetadataClient? metadataClient { get; set; } + public bool FilteringActive { set; get; } public bool MatchingFilter @@ -221,6 +181,15 @@ namespace osu.Game.Overlays.Dashboard AutoSizeAxes = Axes.Both; } + protected override void Update() + { + base.Update(); + + // TODO: we probably don't want to do this every frame. + var activity = metadataClient?.GetPresence(User.Id)?.Activity; + spectateButton.Enabled.Value = activity is UserActivity.InSoloGame or UserActivity.InMultiplayerGame or UserActivity.InPlaylistGame; + } + [BackgroundDependencyLoader] private void load() { @@ -240,14 +209,13 @@ namespace osu.Game.Overlays.Dashboard Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre }, - new PurpleRoundedButton + spectateButton = new PurpleRoundedButton { RelativeSizeAxes = Axes.X, Text = "Spectate", Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Action = () => performer?.PerformFromScreen(s => s.Push(new SoloSpectatorScreen(User))), - Enabled = { BindTarget = CanSpectate } } } }, diff --git a/osu.Game/Users/ExtendedUserPanel.cs b/osu.Game/Users/ExtendedUserPanel.cs index b6fa4bbac6..0185165b36 100644 --- a/osu.Game/Users/ExtendedUserPanel.cs +++ b/osu.Game/Users/ExtendedUserPanel.cs @@ -90,6 +90,7 @@ namespace osu.Game.Users private void updatePresence() { + // TODO: we probably don't want to do this every frame. UserPresence? presence = metadata?.GetPresence(User.OnlineID); UserStatus status = presence?.Status ?? UserStatus.Offline; UserActivity? activity = presence?.Activity; From ef8448caaa2efec765951e9e74da3d037d9044d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 16 Mar 2025 12:09:10 +0900 Subject: [PATCH 39/79] Use switch statement class type matching --- .../Overlays/Dashboard/CurrentlyOnlineDisplay.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs index fce73f0198..39df3ba22c 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs @@ -187,7 +187,19 @@ namespace osu.Game.Overlays.Dashboard // TODO: we probably don't want to do this every frame. var activity = metadataClient?.GetPresence(User.Id)?.Activity; - spectateButton.Enabled.Value = activity is UserActivity.InSoloGame or UserActivity.InMultiplayerGame or UserActivity.InPlaylistGame; + + switch (activity) + { + default: + spectateButton.Enabled.Value = false; + break; + + case UserActivity.InSoloGame: + case UserActivity.InMultiplayerGame: + case UserActivity.InPlaylistGame: + spectateButton.Enabled.Value = true; + break; + } } [BackgroundDependencyLoader] From 3e368201177985198d2b91b901d955a914c6405e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 16 Mar 2025 13:28:57 +0900 Subject: [PATCH 40/79] Fix failing test --- .../Visual/Online/TestSceneCurrentlyOnlineDisplay.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs index 2e53ec2ba4..5c03325470 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs @@ -88,13 +88,12 @@ namespace osu.Game.Tests.Visual.Online { IDisposable token = null!; - AddStep("User began playing", () => spectatorClient.SendStartPlay(streamingUser.Id, 0)); AddStep("Begin watching user presence", () => token = metadataClient.BeginWatchingUserPresence()); - AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() })); - AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType().FirstOrDefault()?.User.Id == 2); + AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.InSoloGame() })); + AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType().FirstOrDefault()?.User.Id == streamingUser.Id); AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType().First().Enabled.Value, () => Is.True); - AddStep("User finished playing", () => spectatorClient.SendEndPlay(streamingUser.Id)); + AddStep("User finished playing", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() })); AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType().First().Enabled.Value, () => Is.False); AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null)); AddStep("End watching user presence", () => token.Dispose()); From 4d54f98c552d3acb23275ea1cb51441526fb5b48 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 16 Mar 2025 14:10:57 +0900 Subject: [PATCH 41/79] Fix one more test (sorry) --- .../Visual/Online/TestSceneCurrentlyOnlineDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs index 5c03325470..a1d0d40811 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs @@ -72,10 +72,10 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType().FirstOrDefault()?.User.Id == 2); AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType().First().Enabled.Value, () => Is.False); - AddStep("User began playing", () => spectatorClient.SendStartPlay(streamingUser.Id, 0)); + AddStep("User began playing", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.InSoloGame() })); AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType().First().Enabled.Value, () => Is.True); - AddStep("User finished playing", () => spectatorClient.SendEndPlay(streamingUser.Id)); + AddStep("User finished playing", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() })); AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType().First().Enabled.Value, () => Is.False); AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null)); From f9078b98dd80b6e69a5a459eb10b9e89fb0b9770 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Mar 2025 12:20:37 +0900 Subject: [PATCH 42/79] Fix tag add request using wrong method --- osu.Game/Online/API/Requests/AddBeatmapTagRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/AddBeatmapTagRequest.cs b/osu.Game/Online/API/Requests/AddBeatmapTagRequest.cs index 2dd9303841..911c4fa5f1 100644 --- a/osu.Game/Online/API/Requests/AddBeatmapTagRequest.cs +++ b/osu.Game/Online/API/Requests/AddBeatmapTagRequest.cs @@ -20,7 +20,7 @@ namespace osu.Game.Online.API.Requests protected override WebRequest CreateWebRequest() { var req = base.CreateWebRequest(); - req.Method = HttpMethod.Post; + req.Method = HttpMethod.Put; return req; } From e8d245e9f1fc8d3c4aab64084d38741a46d45f63 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 17 Mar 2025 02:16:09 -0400 Subject: [PATCH 43/79] Rewrite test to contain asserts --- .../Visual/Online/TestSceneImageProxying.cs | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs b/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs index 0cf6fec6f0..320cc9d8a9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs @@ -1,10 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Sprites; using osu.Framework.Platform; +using osu.Framework.Testing; using osu.Game.Graphics.Containers.Markdown; namespace osu.Game.Tests.Visual.Online @@ -17,31 +21,23 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestExternalImageLink() { - AddStep("load image", () => Child = new OsuMarkdownContainer + MarkdownContainer markdown = null!; + + // use base MarkdownContainer as a method of directly attempting to load an image without proxying logic. + AddStep("load external without proxying", () => Child = markdown = new MarkdownContainer { RelativeSizeAxes = Axes.Both, Text = "![](https://github.com/ppy/osu-wiki/blob/master/wiki/Announcement_messages/img/notification.png?raw=true)", }); - } + AddWaitStep("wait", 5); + AddAssert("image not loaded", () => markdown.ChildrenOfType().Single().Texture == null); - [Test] - public void TestLocalImageLink() - { - AddStep("load image", () => Child = new OsuMarkdownContainer + AddStep("load external with proxying", () => Child = markdown = new OsuMarkdownContainer { RelativeSizeAxes = Axes.Both, - Text = "![](https://osu.ppy.sh/help/wiki/shared/news/banners/monthly-beatmapping-contest.png)", - }); - } - - [Test] - public void TestInvalidImageLink() - { - AddStep("load image", () => Child = new OsuMarkdownContainer - { - RelativeSizeAxes = Axes.Both, - Text = "![](https://this-site-does-not-exist.com/img.png)", + Text = "![](https://github.com/ppy/osu-wiki/blob/master/wiki/Announcement_messages/img/notification.png?raw=true)", }); + AddUntilStep("image loaded", () => markdown.ChildrenOfType().SingleOrDefault()?.Texture != null); } } } From 0bc5a8103c278146b5c865b8f891da797dabda21 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 17 Mar 2025 02:19:03 -0400 Subject: [PATCH 44/79] Fix inconsistent access --- osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs b/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs index 320cc9d8a9..32afcc1450 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Online Text = "![](https://github.com/ppy/osu-wiki/blob/master/wiki/Announcement_messages/img/notification.png?raw=true)", }); AddWaitStep("wait", 5); - AddAssert("image not loaded", () => markdown.ChildrenOfType().Single().Texture == null); + AddAssert("image not loaded", () => markdown.ChildrenOfType().SingleOrDefault()?.Texture == null); AddStep("load external with proxying", () => Child = markdown = new OsuMarkdownContainer { From 597eec99e6b79d4ee6c18509d62eb589638f36dd Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 17 Mar 2025 03:31:25 -0400 Subject: [PATCH 45/79] Fix code quality --- osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs | 5 ----- osu.Game/Audio/PreviewTrackManager.cs | 5 ++--- osu.Game/Online/OsuOnlineStore.cs | 7 ------- osu.Game/OsuGameBase.cs | 2 +- 4 files changed, 3 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs b/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs index 32afcc1450..17b437a051 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneImageProxying.cs @@ -3,11 +3,9 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Sprites; -using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Graphics.Containers.Markdown; @@ -15,9 +13,6 @@ namespace osu.Game.Tests.Visual.Online { public partial class TestSceneImageProxying : OsuTestScene { - [Resolved] - private GameHost host { get; set; } = null!; - [Test] public void TestExternalImageLink() { diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index 452be91bc0..f9e74cd1b2 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Online; -using osu.Game.Online.API; namespace osu.Game.Audio { @@ -29,9 +28,9 @@ namespace osu.Game.Audio } [BackgroundDependencyLoader] - private void load(AudioManager audioManager, IAPIProvider api) + private void load(AudioManager audioManager) { - trackStore = audioManager.GetTrackStore(new OsuOnlineStore(api.Endpoints.APIUrl)); + trackStore = audioManager.GetTrackStore(new OsuOnlineStore()); } /// diff --git a/osu.Game/Online/OsuOnlineStore.cs b/osu.Game/Online/OsuOnlineStore.cs index 72d5ecf036..f578043c5d 100644 --- a/osu.Game/Online/OsuOnlineStore.cs +++ b/osu.Game/Online/OsuOnlineStore.cs @@ -9,13 +9,6 @@ namespace osu.Game.Online { public class OsuOnlineStore : OnlineStore { - private readonly string apiEndpointUrl; - - public OsuOnlineStore(string apiEndpointUrl) - { - this.apiEndpointUrl = apiEndpointUrl; - } - protected override string GetLookupUrl(string url) { if (!Uri.TryCreate(url, UriKind.Absolute, out Uri? uri) || !uri.Host.EndsWith(@".ppy.sh", StringComparison.OrdinalIgnoreCase)) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 257b6a532b..51c8788248 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -108,7 +108,7 @@ namespace osu.Game public virtual EndpointConfiguration CreateEndpoints() => UseDevelopmentServer ? new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); - protected override OnlineStore CreateOnlineStore() => new OsuOnlineStore(CreateEndpoints().APIUrl); + protected override OnlineStore CreateOnlineStore() => new OsuOnlineStore(); public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); From 64c726334234ca01b9b2c2bad501a54f8e849e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 Mar 2025 08:39:05 +0100 Subject: [PATCH 46/79] Fix broken text alignment in medal display Bit unfortunate that this is code that can be written and do stupid things. Unsure if additional API protections against this are desired framework-side. --- osu.Game/Overlays/MedalSplash/DrawableMedal.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 2beed6645a..6b7ffbd1db 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -107,12 +107,7 @@ namespace osu.Game.Overlays.MedalSplash }, }; - description.AddText(medal.Description, s => - { - s.Anchor = Anchor.TopCentre; - s.Origin = Anchor.TopCentre; - s.Font = s.Font.With(size: 16); - }); + description.AddText(medal.Description, s => s.Font = s.Font.With(size: 16)); medalContainer.OnLoadComplete += _ => { From 427f75a7035cb9444e6d9382675ad476b6bfe5f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 Mar 2025 08:49:32 +0100 Subject: [PATCH 47/79] Fix broken text alignment in supporter display See previous commit. --- osu.Game/Screens/Menu/SupporterDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Menu/SupporterDisplay.cs b/osu.Game/Screens/Menu/SupporterDisplay.cs index 6639300f4a..be50a54619 100644 --- a/osu.Game/Screens/Menu/SupporterDisplay.cs +++ b/osu.Game/Screens/Menu/SupporterDisplay.cs @@ -100,7 +100,6 @@ namespace osu.Game.Screens.Menu t.Padding = new MarginPadding { Left = 5, Top = 1 }; t.Font = t.Font.With(size: font_size); - t.Origin = Anchor.Centre; t.Colour = colours.Pink; Schedule(() => From 3954d8f3bea48ae8995544c308454e1957a42f68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 Mar 2025 08:53:53 +0100 Subject: [PATCH 48/79] Fix baseline misalignment in drawable comment link section `CommentReportButton` is pretty cursed but I guess I can see why it is like it is. Short of inlining it into `DrawableComment` this is probably the best escape hatch (which I wouldn't be against doing, by the way, just not sure it's the most productive use of time unless review feedback comes in saying that would be a better path forward). --- osu.Game/Graphics/Containers/OsuTextFlowContainer.cs | 2 +- osu.Game/Overlays/Comments/CommentReportButton.cs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs index d5cce1a10a..8da8b7ed7d 100644 --- a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs @@ -27,7 +27,7 @@ namespace osu.Game.Graphics.Containers private partial class ArbitraryDrawableWrapper : Container, IHasLineBaseHeight { - public float LineBaseHeight => DrawHeight; + public float LineBaseHeight => (Child as IHasLineBaseHeight)?.LineBaseHeight ?? DrawHeight; public ArbitraryDrawableWrapper() { diff --git a/osu.Game/Overlays/Comments/CommentReportButton.cs b/osu.Game/Overlays/Comments/CommentReportButton.cs index e4d4d671da..09c0fd32d0 100644 --- a/osu.Game/Overlays/Comments/CommentReportButton.cs +++ b/osu.Game/Overlays/Comments/CommentReportButton.cs @@ -1,13 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -19,7 +22,7 @@ using osuTK; namespace osu.Game.Overlays.Comments { - public partial class CommentReportButton : CompositeDrawable, IHasPopover + public partial class CommentReportButton : CompositeDrawable, IHasPopover, IHasLineBaseHeight { private readonly Comment comment; @@ -88,5 +91,7 @@ namespace osu.Game.Overlays.Comments api.Queue(request); } + + public float LineBaseHeight => link.ChildrenOfType().FirstOrDefault()?.LineBaseHeight ?? DrawHeight; } } From e430619e0e7ae05e880179fd01272875f3691249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 Mar 2025 12:34:20 +0100 Subject: [PATCH 49/79] Make logic clearer & fix issues --- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index ab3b8d882e..5b856181f5 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -212,47 +212,49 @@ namespace osu.Game.Overlays.BeatmapSet { guestMapperContainer.Clear(); var beatmapOwners = beatmapInfo?.BeatmapOwners; + bool isHostDifficulty = beatmapOwners?.Length == 1 && beatmapOwners.First().Id == beatmapSet?.AuthorID; - if (beatmapOwners != null && (beatmapOwners.Length != 1 || beatmapOwners.First().Id != beatmapSet?.AuthorID)) + if (beatmapOwners != null && !isHostDifficulty) { - APIUser[]? users = BeatmapSet?.RelatedUsers?.Where(u => beatmapOwners.Any(o => o.Id == u.OnlineID)).ToArray(); + APIUser[] users = BeatmapSet?.RelatedUsers?.Where(u => beatmapOwners.Any(o => o.Id == u.OnlineID)).ToArray() ?? []; + int count = users.Length; - if (users != null) + switch (count) { - int count = users.Length; + case 0: + break; - guestMapperContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty)); // set string.Empty here because we need user link. + case 1: + guestMapperContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty)); + guestMapperContainer.AddUserLink(users[0]); + break; - switch (count) + case 2: + guestMapperContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty)); + guestMapperContainer.AddUserLink(users[0]); + guestMapperContainer.AddText(CommonStrings.ArrayAndTwoWordsConnector); + guestMapperContainer.AddUserLink(users[1]); + break; + + default: { - case 1: - guestMapperContainer.AddUserLink(users[0]); - break; + guestMapperContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty)); - case 2: - guestMapperContainer.AddUserLink(users[0]); - guestMapperContainer.AddText(CommonStrings.ArrayAndTwoWordsConnector); - guestMapperContainer.AddUserLink(users[1]); - break; - - default: + for (int i = 0; i < count; i++) { - for (int i = 0; i < count; i++) + guestMapperContainer.AddUserLink(users[i]); + + if (i < count - 2) { - guestMapperContainer.AddUserLink(users[i]); - - if (i < count - 2) - { - guestMapperContainer.AddText(CommonStrings.ArrayAndWordsConnector); - } - else if (i == count - 2) - { - guestMapperContainer.AddText(CommonStrings.ArrayAndLastWordConnector); - } + guestMapperContainer.AddText(CommonStrings.ArrayAndWordsConnector); + } + else if (i == count - 2) + { + guestMapperContainer.AddText(CommonStrings.ArrayAndLastWordConnector); } - - break; } + + break; } } } From 87932f707da54ef20c9feaced488fe0d5ee72db8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 Mar 2025 12:52:30 +0100 Subject: [PATCH 50/79] Use link flow at a higher level to fix broken layout --- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 120 ++++++------------ .../BeatmapSet/BeatmapSetHeaderContent.cs | 2 +- 2 files changed, 41 insertions(+), 81 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 5b856181f5..eea0b087eb 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Extensions; @@ -31,9 +32,7 @@ namespace osu.Game.Overlays.BeatmapSet private const float tile_icon_padding = 7; private const float tile_spacing = 2; - private readonly OsuSpriteText version, starRating, starRatingText; - private readonly LinkFlowContainer guestMapperContainer; - private readonly FillFlowContainer starRatingContainer; + private readonly LinkFlowContainer infoContainer; private readonly Statistic plays, favourites; public readonly DifficultiesContainer Difficulties; @@ -53,6 +52,9 @@ namespace osu.Game.Overlays.BeatmapSet } } + [Resolved] + private OsuColour colours { get; set; } = null!; + public BeatmapPicker() { RelativeSizeAxes = Axes.X; @@ -72,59 +74,13 @@ namespace osu.Game.Overlays.BeatmapSet RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Margin = new MarginPadding { Left = -(tile_icon_padding + tile_spacing / 2), Bottom = 10 }, - OnLostHover = () => - { - showBeatmap(Beatmap.Value); - starRatingContainer.FadeOut(100); - }, + OnLostHover = () => showBeatmap(Beatmap.Value, withStarRating: false), }, - new FillFlowContainer + infoContainer = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 11)) { - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(5f), - Children = new Drawable[] - { - version = new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold) - }, - guestMapperContainer = new LinkFlowContainer(s => - s.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 11)) - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Bottom = 1 }, - }, - starRatingContainer = new FillFlowContainer - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Alpha = 0, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(2f, 0), - Margin = new MarginPadding { Bottom = 1 }, - Children = new[] - { - starRatingText = new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(size: 11, weight: FontWeight.Bold), - Text = BeatmapsetsStrings.ShowStatsStars, - }, - starRating = new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(size: 11, weight: FontWeight.Bold), - Text = string.Empty, - }, - } - }, - }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.BottomLeft, }, new FillFlowContainer { @@ -144,7 +100,7 @@ namespace osu.Game.Overlays.BeatmapSet Beatmap.ValueChanged += b => { - showBeatmap(b.NewValue); + showBeatmap(b.NewValue, withStarRating: Difficulties.Any(d => d.IsHovered)); updateDifficultyButtons(); }; } @@ -153,10 +109,8 @@ namespace osu.Game.Overlays.BeatmapSet private IBindable ruleset { get; set; } = null!; [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { - starRating.Colour = colours.Yellow; - starRatingText.Colour = colours.Yellow; updateDisplay(); } @@ -185,16 +139,12 @@ namespace osu.Game.Overlays.BeatmapSet State = DifficultySelectorState.NotSelected, OnHovered = beatmap => { - showBeatmap(beatmap); - starRating.Text = beatmap.StarRating.FormatStarRating(); - starRatingContainer.FadeIn(100); + showBeatmap(beatmap, withStarRating: true); }, OnClicked = beatmap => { Beatmap.Value = beatmap; }, }); } - starRatingContainer.FadeOut(100); - // If a selection is already made, try and maintain it. if (Beatmap.Value != null) Beatmap.Value = Difficulties.FirstOrDefault(b => b.Beatmap.OnlineID == Beatmap.Value.OnlineID)?.Beatmap; @@ -208,9 +158,13 @@ namespace osu.Game.Overlays.BeatmapSet updateDifficultyButtons(); } - private void showBeatmap(APIBeatmap? beatmapInfo) + private void showBeatmap(APIBeatmap? beatmapInfo, bool withStarRating) { - guestMapperContainer.Clear(); + infoContainer.Clear(); + + infoContainer.AddText(beatmapInfo?.DifficultyName ?? string.Empty, s => s.Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold)); + infoContainer.AddArbitraryDrawable(Empty().With(e => e.Width = 5)); + var beatmapOwners = beatmapInfo?.BeatmapOwners; bool isHostDifficulty = beatmapOwners?.Length == 1 && beatmapOwners.First().Id == beatmapSet?.AuthorID; @@ -225,33 +179,29 @@ namespace osu.Game.Overlays.BeatmapSet break; case 1: - guestMapperContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty)); - guestMapperContainer.AddUserLink(users[0]); + infoContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty)); + infoContainer.AddUserLink(users[0]); break; case 2: - guestMapperContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty)); - guestMapperContainer.AddUserLink(users[0]); - guestMapperContainer.AddText(CommonStrings.ArrayAndTwoWordsConnector); - guestMapperContainer.AddUserLink(users[1]); + infoContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty)); + infoContainer.AddUserLink(users[0]); + infoContainer.AddText(CommonStrings.ArrayAndTwoWordsConnector); + infoContainer.AddUserLink(users[1]); break; default: { - guestMapperContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty)); + infoContainer.AddText(BeatmapsetsStrings.ShowDetailsMappedBy(string.Empty)); for (int i = 0; i < count; i++) { - guestMapperContainer.AddUserLink(users[i]); + infoContainer.AddUserLink(users[i]); if (i < count - 2) - { - guestMapperContainer.AddText(CommonStrings.ArrayAndWordsConnector); - } + infoContainer.AddText(CommonStrings.ArrayAndWordsConnector); else if (i == count - 2) - { - guestMapperContainer.AddText(CommonStrings.ArrayAndLastWordConnector); - } + infoContainer.AddText(CommonStrings.ArrayAndLastWordConnector); } break; @@ -259,7 +209,17 @@ namespace osu.Game.Overlays.BeatmapSet } } - version.Text = beatmapInfo?.DifficultyName ?? string.Empty; + if (withStarRating) + { + infoContainer.AddArbitraryDrawable(Empty().With(e => e.Width = 5)); + infoContainer.AddText( + LocalisableString.Interpolate($"{BeatmapsetsStrings.ShowStatsStars} {beatmapInfo?.StarRating.FormatStarRating()}"), + t => + { + t.Font = OsuFont.GetFont(size: 11, weight: FontWeight.Bold); + t.Colour = colours.Yellow; + }); + } } private void updateDifficultyButtons() diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index a50043f0f0..c72c2a6698 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -98,7 +98,7 @@ namespace osu.Game.Overlays.BeatmapSet { Vertical = BeatmapSetOverlay.Y_PADDING, Left = WaveOverlayContainer.HORIZONTAL_PADDING, - Right = WaveOverlayContainer.HORIZONTAL_PADDING + BeatmapSetOverlay.RIGHT_WIDTH, + Right = WaveOverlayContainer.HORIZONTAL_PADDING + BeatmapSetOverlay.RIGHT_WIDTH + 10, }, Children = new Drawable[] { From 613c46d3756fa84b5bea7271be717f4f961c50cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 Mar 2025 14:52:09 +0100 Subject: [PATCH 51/79] Add the most basic (yet failing) test possible --- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index d1782da25f..e38ae0cbc9 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -208,5 +208,11 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Beatmap still has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor, () => Is.EqualTo(7)); AddAssert("Correct beat divisor actually active", () => Editor.BeatDivisor, () => Is.EqualTo(7)); } + + [Test] + public void TestBeatmapVersionPopulatedCorrectly() + { + AddAssert("beatmap version is populated", () => EditorBeatmap.BeatmapInfo.BeatmapVersion > 0); + } } } From 91f3be5feaab0c73c17e1a8c270516aa9bee1e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 17 Mar 2025 14:56:41 +0100 Subject: [PATCH 52/79] Move `BeatmapVersion` from `BeatmapInfo` to `IBeatmap` Closes https://github.com/ppy/osu/issues/32420. The failure cause here is that in editor the beatmap version for the beatmap affected (or... any beatmap, really), is 0 (ZERO). That is probably a regression from https://github.com/ppy/osu/pull/32315, but like... can we universally agree that calling that change "a regression" in any capacity is dumb? Like what was that code *doing* playing dumb reference games and copying stuff into an arbitrary instance that could get or not get used later on? And now you have a 50/50 chance of accessing the *correct* model's field, depending on whether you go via `BeatmapInfo` or `Beatmap.BeatmapInfo`? Moving the field to `IBeatmap`, i.e. what is by now - by consensus, since https://github.com/ppy/osu/pull/28473 - supposed to be the "decoded and materialised" beatmap, fixes this issue. I probably should have done this as part of https://github.com/ppy/osu/pull/28473 but it slipped my mind. Probably for the better too because this change has rather large chances of breaking stuff so maybe better to examine it in isolation (via diffcalc runs or whatever). For added humour points, you'd say that the field on `BeatmapInfo` was not `[Ignore]`d, so this is a realm schema change, right? No. As far as I can tell, it's not. I opened realm studio and `BeatmapVersion` *is not a listed column` on `Beatmap` models. I'm also not gonna get into the fact that I think `EditorBeatmap` doing dumb games with juggling two `BeatmapInfo` references since https://github.com/ppy/osu/pull/15075 is bad, because I don't think I have the mental capacity to hotfix this by going down that train of thought. --- .../Beatmaps/CatchBeatmapConverter.cs | 2 +- .../TestSceneLegacyHitPolicy.cs | 4 ++-- osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs | 2 +- .../Beatmaps/TaikoBeatmapConverter.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 10 ++++------ .../Beatmaps/Formats/LegacyScoreDecoderTest.cs | 11 ++++------- .../Visual/Editing/TestSceneEditorSaving.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 3 +++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 1 + osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 +- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 2 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 9 +++++++++ 17 files changed, 34 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index f5c5ffb529..756376edf8 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y, // prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance. // this results in more (or less) ticks being generated in = 6) + if (beatmap.BeatmapVersion >= 6) applyStacking(beatmap, hitObjects, 0, hitObjects.Count - 1); else applyStackingOld(beatmap, hitObjects); diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 010b1f0a7a..b784fd181f 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -210,7 +210,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps double osuVelocity = taikoVelocity * (1000f / beatLength); // osu-stable always uses the speed-adjusted beatlength to determine the osu! velocity, but only uses it for conversion if beatmap version < 8 - if (beatmap.BeatmapInfo.BeatmapVersion >= 8) + if (beatmap.BeatmapVersion >= 8) beatLength = timingPoint.BeatLength; // If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 9747b654ae..17153a3ff2 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -42,9 +42,8 @@ namespace osu.Game.Tests.Beatmaps.Formats var decoder = Decoder.GetDecoder(stream); var working = new TestWorkingBeatmap(decoder.Decode(stream)); - Assert.AreEqual(6, working.BeatmapInfo.BeatmapVersion); - Assert.AreEqual(6, working.Beatmap.BeatmapInfo.BeatmapVersion); - Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty()).BeatmapInfo.BeatmapVersion); + Assert.AreEqual(6, working.Beatmap.BeatmapVersion); + Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty()).BeatmapVersion); } } @@ -59,9 +58,8 @@ namespace osu.Game.Tests.Beatmaps.Formats ((LegacyBeatmapDecoder)decoder).ApplyOffsets = applyOffsets; var working = new TestWorkingBeatmap(decoder.Decode(stream)); - Assert.AreEqual(4, working.BeatmapInfo.BeatmapVersion); - Assert.AreEqual(4, working.Beatmap.BeatmapInfo.BeatmapVersion); - Assert.AreEqual(4, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty()).BeatmapInfo.BeatmapVersion); + Assert.AreEqual(4, working.Beatmap.BeatmapVersion); + Assert.AreEqual(4, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty()).BeatmapVersion); Assert.AreEqual(-1, working.BeatmapInfo.Metadata.PreviewTime); } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 713f2f3fb1..de07e2be01 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -155,10 +155,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var scoreInfo = TestResources.CreateTestScoreInfo(ruleset); var beatmap = new TestBeatmap(ruleset) { - BeatmapInfo = - { - BeatmapVersion = beatmapVersion - } + BeatmapVersion = beatmapVersion }; var score = new Score @@ -633,14 +630,14 @@ namespace osu.Game.Tests.Beatmaps.Formats MD5Hash = md5Hash, Ruleset = new OsuRuleset().RulesetInfo, Difficulty = new BeatmapDifficulty(), - BeatmapVersion = beatmapVersion, }, - // needs to have at least one objects so that `StandardisedScoreMigrationTools` doesn't die + // needs to have at least one object so that `StandardisedScoreMigrationTools` doesn't die // when trying to recompute total score. HitObjects = { new HitCircle() - } + }, + BeatmapVersion = beatmapVersion, }); } } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index e38ae0cbc9..2e7b55ab49 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -212,7 +212,7 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestBeatmapVersionPopulatedCorrectly() { - AddAssert("beatmap version is populated", () => EditorBeatmap.BeatmapInfo.BeatmapVersion > 0); + AddAssert("beatmap version is populated", () => EditorBeatmap.BeatmapVersion > 0); } } } diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 8ea6fa1f51..155ded5747 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -9,6 +9,7 @@ using System.Linq; using osu.Game.Beatmaps.ControlPoints; using Newtonsoft.Json; using osu.Framework.Lists; +using osu.Game.Beatmaps.Formats; using osu.Game.IO.Serialization.Converters; namespace osu.Game.Beatmaps @@ -141,6 +142,8 @@ namespace osu.Game.Beatmaps public int[] Bookmarks { get; set; } = Array.Empty(); + public int BeatmapVersion { get; set; } = LegacyBeatmapEncoder.FIRST_LAZER_VERSION; + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index 0cf10c659b..f0cb6d0484 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -86,6 +86,7 @@ namespace osu.Game.Beatmaps beatmap.Countdown = original.Countdown; beatmap.CountdownOffset = original.CountdownOffset; beatmap.Bookmarks = original.Bookmarks; + beatmap.BeatmapVersion = original.BeatmapVersion; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 333ec89eab..487b578317 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -231,8 +231,6 @@ namespace osu.Game.Beatmaps [Obsolete("Use ScoreManager.GetMaximumAchievableComboAsync instead.")] public int? MaxCombo { get; set; } - public int BeatmapVersion; - public BeatmapInfo Clone() => (BeatmapInfo)this.Detach().MemberwiseClone(); public override string ToString() => this.GetDisplayTitle(); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index b0aabe3787..729daf5b0a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -79,7 +79,7 @@ namespace osu.Game.Beatmaps.Formats protected override void ParseStreamInto(LineBufferedReader stream, Beatmap beatmap) { this.beatmap = beatmap; - this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion; + this.beatmap.BeatmapVersion = FormatVersion; parser = new ConvertHitObjectParser(getOffsetTime(), FormatVersion); ApplyLegacyDefaults(this.beatmap); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index f95fcefd7e..482bc73742 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -109,6 +109,8 @@ namespace osu.Game.Beatmaps int[] Bookmarks { get; internal set; } + int BeatmapVersion { get; } + /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index add24f7866..5c840a8357 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -344,6 +344,7 @@ namespace osu.Game.Rulesets.Difficulty public double TotalBreakTime => baseBeatmap.TotalBreakTime; public IEnumerable GetStatistics() => baseBeatmap.GetStatistics(); public double GetMostCommonBeatLength() => baseBeatmap.GetMostCommonBeatLength(); + public int BeatmapVersion => baseBeatmap.BeatmapVersion; public IBeatmap Clone() => new ProgressiveCalculationBeatmap(baseBeatmap.Clone()); public double AudioLeadIn diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 6ad118547b..2eec12ac28 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -96,7 +96,7 @@ namespace osu.Game.Scoring.Legacy scoreInfo.BeatmapInfo = currentBeatmap.BeatmapInfo; // As this is baked into hitobject timing (see `LegacyBeatmapDecoder`) we also need to apply this to replay frame timing. - beatmapOffset = currentBeatmap.BeatmapInfo.BeatmapVersion < 5 ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0; + beatmapOffset = currentBeatmap.BeatmapVersion < 5 ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0; /* score.HpGraphString = */ sr.ReadString(); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 0f00cce080..b575c02337 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -142,7 +142,7 @@ namespace osu.Game.Scoring.Legacy StringBuilder replayData = new StringBuilder(); // As this is baked into hitobject timing (see `LegacyBeatmapDecoder`) we also need to apply this to replay frame timing. - double offset = beatmap?.BeatmapInfo.BeatmapVersion < 5 ? -LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0; + double offset = beatmap?.BeatmapVersion < 5 ? -LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0; int lastTime = 0; diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 254336e963..91ae4593dd 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -13,6 +13,7 @@ using osu.Framework.Bindables; using osu.Framework.Lists; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Edit; @@ -133,6 +134,8 @@ namespace osu.Game.Screens.Edit BeatmapInfo.Metadata.PreviewTime = s.NewValue; EndChange(); }); + + BeatmapVersion = PlayableBeatmap.BeatmapVersion; } /// @@ -286,6 +289,8 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.Bookmarks = value; } + public int BeatmapVersion { get; set; } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; @@ -456,6 +461,10 @@ namespace osu.Game.Screens.Edit if (batchPendingUpdates.Count == 0 && batchPendingDeletes.Count == 0 && batchPendingInserts.Count == 0) return; + // if the user is doing edits to this beatmaps via this flow, we better bump the beatmap version + // because the beatmap encoder can only output this specific beatmap version anyway, + // so *not* bumping it could lead to results that look misleading at best. + BeatmapVersion = LegacyBeatmapEncoder.FIRST_LAZER_VERSION; beatmapProcessor.PreProcess(); foreach (var h in batchPendingDeletes) processHitObject(h); From 4d00591df00b922927c61dcd068ae105c48526f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Mar 2025 00:06:02 +0900 Subject: [PATCH 53/79] Remove silly cache --- osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs index 970949280f..df0fc8de57 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs @@ -161,9 +161,6 @@ namespace osu.Game.Tests.Visual.Settings }); Dependencies.CacheAs(dialogOverlay); - - var osuGame = new OsuGame(); - Dependencies.CacheAs(osuGame); } } } From 43d791854868ad6939eed372ffffe2633699aec1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Mar 2025 00:06:39 +0900 Subject: [PATCH 54/79] Rename localised string to something slightly more correct --- osu.Game/Graphics/UserInterface/ExternalLinkButton.cs | 2 +- osu.Game/Localisation/ToastStrings.cs | 2 +- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Overlays/Comments/DrawableComment.cs | 2 +- .../OSD/{CopyStringToast.cs => CopiedToClipboardToast.cs} | 6 +++--- osu.Game/Overlays/Settings/SettingsFooter.cs | 2 +- .../Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs | 2 +- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- .../Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) rename osu.Game/Overlays/OSD/{CopyStringToast.cs => CopiedToClipboardToast.cs} (58%) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 2bc5ba91fa..e5a4e807b5 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -89,7 +89,7 @@ namespace osu.Game.Graphics.UserInterface { if (Link == null) return; - game?.CopyStringToClipboard(Link); + game?.CopyToClipboard(Link); } } } diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 000f01ebca..b520511d8f 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -47,7 +47,7 @@ namespace osu.Game.Localisation /// /// "Copied to clipboard" /// - public static LocalisableString StringCopied => new TranslatableString(getKey(@"string_copied"), @"Copied to clipboard"); + public static LocalisableString CopiedToClipboard => new TranslatableString(getKey(@"copied_to_clipboard"), @"Copied to clipboard"); /// /// "Speed changed to {0:N2}x" diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 87f6d58d02..3381553970 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -519,10 +519,10 @@ namespace osu.Game } }); - public void CopyStringToClipboard(string value) => waitForReady(() => onScreenDisplay, _ => + public void CopyToClipboard(string value) => waitForReady(() => onScreenDisplay, _ => { dependencies.Get().SetText(value); - onScreenDisplay.Display(new CopyStringToast()); + onScreenDisplay.Display(new CopiedToClipboardToast()); }); public void OpenUrlExternally(string url, LinkWarnMode warnMode = LinkWarnMode.Default) => waitForReady(() => externalLinkOpener, _ => externalLinkOpener.OpenUrlExternally(url, warnMode)); diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 2f1b7054e2..805d997998 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -420,7 +420,7 @@ namespace osu.Game.Overlays.Comments private void copyUrl() { clipboard.SetText($@"{api.Endpoints.APIUrl}/comments/{Comment.Id}"); - onScreenDisplay?.Display(new CopyStringToast()); + onScreenDisplay?.Display(new CopiedToClipboardToast()); } private void toggleReply() diff --git a/osu.Game/Overlays/OSD/CopyStringToast.cs b/osu.Game/Overlays/OSD/CopiedToClipboardToast.cs similarity index 58% rename from osu.Game/Overlays/OSD/CopyStringToast.cs rename to osu.Game/Overlays/OSD/CopiedToClipboardToast.cs index 34f85dc9cb..4059a274ad 100644 --- a/osu.Game/Overlays/OSD/CopyStringToast.cs +++ b/osu.Game/Overlays/OSD/CopiedToClipboardToast.cs @@ -5,10 +5,10 @@ using osu.Game.Localisation; namespace osu.Game.Overlays.OSD { - public partial class CopyStringToast : Toast + public partial class CopiedToClipboardToast : Toast { - public CopyStringToast() - : base(CommonStrings.General, ToastStrings.StringCopied, "") + public CopiedToClipboardToast() + : base(CommonStrings.General, ToastStrings.CopiedToClipboard, "") { } } diff --git a/osu.Game/Overlays/Settings/SettingsFooter.cs b/osu.Game/Overlays/Settings/SettingsFooter.cs index 52abd4fa65..307d88e712 100644 --- a/osu.Game/Overlays/Settings/SettingsFooter.cs +++ b/osu.Game/Overlays/Settings/SettingsFooter.cs @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Settings public MenuItem[] ContextMenuItems => new MenuItem[] { - new OsuMenuItem("Copy version", MenuItemType.Standard, () => game?.CopyStringToClipboard(version)) + new OsuMenuItem("Copy version", MenuItemType.Standard, () => game?.CopyToClipboard(version)) }; } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index d18e00d643..491d8071f1 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -355,7 +355,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { items.AddRange([ new OsuMenuItem("View in browser", MenuItemType.Standard, () => game?.OpenUrlExternally(formatRoomUrl(Room.RoomID.Value))), - new OsuMenuItem("Copy link", MenuItemType.Standard, () => game?.CopyStringToClipboard(formatRoomUrl(Room.RoomID.Value))) + new OsuMenuItem("Copy link", MenuItemType.Standard, () => game?.CopyToClipboard(formatRoomUrl(Room.RoomID.Value))) ]); } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 055cb53d24..b99f046f4b 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -301,7 +301,7 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); if (beatmapInfo.GetOnlineURL(api, ruleset.Value) is string url) - items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyStringToClipboard(url))); + items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyToClipboard(url))); if (manager != null) items.Add(new OsuMenuItem("Mark as played", MenuItemType.Standard, () => manager.MarkPlayed(beatmapInfo))); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 55b2e68209..c410cb7d69 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -301,7 +301,7 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet))); if (beatmapSet.GetOnlineURL(api, ruleset.Value) is string url) - items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyStringToClipboard(url))); + items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyToClipboard(url))); if (dialogOverlay != null) items.Add(new OsuMenuItem("Delete...", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet)))); From bf3caacf51ef67b93121d454a6f0fdba69e70087 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 18 Mar 2025 14:31:37 +0900 Subject: [PATCH 55/79] Make freestyle not bypass ruleset filter once more --- .../Visual/Multiplayer/TestSceneRoomListing.cs | 17 ----------------- .../OnlinePlay/Lounge/Components/RoomListing.cs | 2 +- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomListing.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomListing.cs index 45f1ff1acb..27c5758afa 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomListing.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomListing.cs @@ -11,7 +11,6 @@ using osu.Framework.Testing; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Taiko; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Tests.Visual.OnlinePlay; @@ -199,22 +198,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add rooms", () => rooms.AddRange(GenerateRooms(3, withPassword: true))); } - [Test] - public void TestFreestyleBypassesRulesetFilter() - { - AddStep("apply taiko filter", () => container.Filter.Value = new FilterCriteria { Ruleset = new TaikoRuleset().RulesetInfo }); - - AddStep("add osu + freestyle room", () => - { - var room = GenerateRooms(1, new OsuRuleset().RulesetInfo)[0]; - room.Playlist[0].Freestyle = true; - room.CurrentPlaylistItem = room.Playlist[0]; - rooms.Add(room); - }); - - AddAssert("room visible", () => container.DrawableRooms.Any()); - } - private bool checkRoomSelected(Room? room) => selectedRoom.Value == room; private Room? getRoomInFlow(int index) => diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomListing.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomListing.cs index 9835802fae..0276601656 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomListing.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomListing.cs @@ -99,7 +99,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { bool matchingFilter = true; - matchingFilter &= criteria.Ruleset == null || r.Room.CurrentPlaylistItem?.Freestyle == true || r.Room.PlaylistItemStats?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false; + matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false; matchingFilter &= matchPermissions(r, criteria.Permissions); // Room name isn't translatable, so ToString() is used here for simplicity. From a55abdb9b3cc5d198bb8cf68bfc3bab0e7d1d8c9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 18 Mar 2025 15:55:02 +0900 Subject: [PATCH 56/79] Fix multiplayer join errors potentially not being logged --- osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs | 6 +++++- .../OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs | 7 +------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index 6aa366dbc5..c455020f9a 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -351,7 +351,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { joiningRoomOperation?.Dispose(); joiningRoomOperation = null; - onFailure?.Invoke(error); + + if (onFailure != null) + onFailure(error); + else + Logger.Log(error, level: LogLevel.Error); }); }); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index 56b82cdaee..51c135f042 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -88,12 +88,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (exception?.GetHubExceptionMessage() is string message) onFailure(message); else - { - const string generic_failure_message = "Failed to join multiplayer room."; - if (result.Exception != null) - Logger.Error(result.Exception, generic_failure_message); - onFailure(generic_failure_message); - } + onFailure($"Failed to join multiplayer room: {exception?.Message}"); } }); } From 2630d9437c60f56ea203d861c1b4e0abe226e205 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 18 Mar 2025 16:19:52 +0900 Subject: [PATCH 57/79] Build iOS tests project --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d75f09f184..1019569b5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -136,4 +136,4 @@ jobs: run: dotnet workload install ios --from-rollback-file https://raw.githubusercontent.com/ppy/osu-framework/refs/heads/master/workloads.json - name: Build - run: dotnet build -c Debug osu.iOS + run: dotnet build -c Debug osu.iOS.slnf From 533b0e0f887553f107d35257c136ba36344958be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Mar 2025 16:35:11 +0900 Subject: [PATCH 58/79] Allow testing fountain sound effects in tests --- .../Visual/Menus/TestSceneStarFountain.cs | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs index 0d981014b8..396d2e9027 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs @@ -50,30 +50,17 @@ namespace osu.Game.Tests.Visual.Menus [Test] public void TestGameplay() { + KiaiGameplayFountains fountains = null!; + AddStep("make fountains", () => { Children = new[] { - new KiaiGameplayFountains.GameplayStarFountain - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - X = 75, - }, - new KiaiGameplayFountains.GameplayStarFountain - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - X = -75, - }, + fountains = new KiaiGameplayFountains(), }; }); - AddStep("activate fountains", () => - { - ((StarFountain)Children[0]).Shoot(1); - ((StarFountain)Children[1]).Shoot(-1); - }); + AddStep("activate fountains", () => fountains.Shoot()); } [Test] From a4ced55640bdf3ed9846750a4c50e1da9bb65a1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Mar 2025 16:49:27 +0900 Subject: [PATCH 59/79] Code quality pass --- osu.Game/Screens/Menu/KiaiMenuFountains.cs | 6 +- osu.Game/Screens/Menu/StarFountainSfx.cs | 74 ------------------- osu.Game/Screens/Menu/StarFountainSounds.cs | 71 ++++++++++++++++++ .../Screens/Play/KiaiGameplayFountains.cs | 6 +- 4 files changed, 77 insertions(+), 80 deletions(-) delete mode 100644 osu.Game/Screens/Menu/StarFountainSfx.cs create mode 100644 osu.Game/Screens/Menu/StarFountainSounds.cs diff --git a/osu.Game/Screens/Menu/KiaiMenuFountains.cs b/osu.Game/Screens/Menu/KiaiMenuFountains.cs index 7baf18d526..6e0351f922 100644 --- a/osu.Game/Screens/Menu/KiaiMenuFountains.cs +++ b/osu.Game/Screens/Menu/KiaiMenuFountains.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Menu [Resolved] private GameHost host { get; set; } = null!; - private StarFountainSfx sfx = null!; + private StarFountainSounds sounds = null!; [BackgroundDependencyLoader] private void load() @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Menu Origin = Anchor.BottomRight, X = -250, }, - sfx = new StarFountainSfx() + sounds = new StarFountainSounds() }; } @@ -83,7 +83,7 @@ namespace osu.Game.Screens.Menu // Don't play SFX when game is in background, as it can be a bit noisy. if (host.IsActive.Value) - sfx.Trigger(); + sounds.Play(); } } } diff --git a/osu.Game/Screens/Menu/StarFountainSfx.cs b/osu.Game/Screens/Menu/StarFountainSfx.cs deleted file mode 100644 index 91337d6959..0000000000 --- a/osu.Game/Screens/Menu/StarFountainSfx.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Threading; -using osu.Game.Audio; -using osu.Game.Skinning; - -namespace osu.Game.Screens.Menu -{ - public partial class StarFountainSfx : Container - { - private const int shoot_retrigger_delay = 500; - private const int loop_fade_duration = 500; - - private double? lastPlayback; - - private SkinnableSound? shootSample; - private PausableSkinnableSound? loopSample; - - private ScheduledDelegate? loopFadeDelegate; - private ScheduledDelegate? loopStopDelegate; - - public void Trigger() - { - loopFadeDelegate?.Cancel(); - loopStopDelegate?.Cancel(); - - // Only play 'shootSample' if enough time has passed since last `Trigger()` call. - if (lastPlayback == null || Time.Current - lastPlayback > shoot_retrigger_delay) - { - loopSample?.Stop(); - shootSample?.Play(); - lastPlayback = Time.Current; - - return; - } - - if (loopSample == null) return; - - // Only call `Play()` if `loopSample` is not already playing, to prevent restarting the sample each time. - if (!loopSample.RequestedPlaying) - { - loopSample.Volume.Value = 1f; - loopSample.Play(); - } - - // Schedule a volume fadeout, followed by a `Stop()`. - loopFadeDelegate = Scheduler.AddDelayed(() => - { - this.TransformBindableTo(loopSample.Volume, 0, loop_fade_duration); - - loopStopDelegate = Scheduler.AddDelayed(() => - { - loopSample?.Stop(); - }, loop_fade_duration); - }, shoot_retrigger_delay); - - lastPlayback = Time.Current; - } - - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - shootSample = new SkinnableSound(new SampleInfo("Gameplay/fountain-shoot")), - loopSample = new PausableSkinnableSound(new SampleInfo("Gameplay/fountain-loop")) { Looping = true }, - }; - } - } -} diff --git a/osu.Game/Screens/Menu/StarFountainSounds.cs b/osu.Game/Screens/Menu/StarFountainSounds.cs new file mode 100644 index 0000000000..842e718c48 --- /dev/null +++ b/osu.Game/Screens/Menu/StarFountainSounds.cs @@ -0,0 +1,71 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Threading; +using osu.Game.Audio; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Menu +{ + public partial class StarFountainSounds : CompositeComponent + { + private const int shoot_retrigger_delay = 500; + private const int loop_fade_duration = 500; + + private double? lastPlayback; + + private SkinnableSound shootSample = null!; + private PausableSkinnableSound loopSample = null!; + + private ScheduledDelegate? loopFadeDelegate; + private ScheduledDelegate? loopStopDelegate; + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + shootSample = new SkinnableSound(new SampleInfo("Gameplay/fountain-shoot")), + loopSample = new PausableSkinnableSound(new SampleInfo("Gameplay/fountain-loop")) { Looping = true }, + }; + } + + public void Play() + { + loopFadeDelegate?.Cancel(); + loopStopDelegate?.Cancel(); + + try + { + // Only play 'shootSample' if enough time has passed since last `Play()` call. + if (lastPlayback == null || Time.Current - lastPlayback > shoot_retrigger_delay) + { + loopSample.Stop(); + shootSample.Play(); + return; + } + + // Only call `Play()` if `loopSample` is not already playing, to prevent restarting the sample each time. + if (!loopSample.RequestedPlaying) + { + this.TransformBindableTo(loopSample.Volume, 1); + loopSample.Play(); + } + + // Schedule a volume fadeout, followed by a `Stop()`. + loopFadeDelegate = Scheduler.AddDelayed(() => + { + this.TransformBindableTo(loopSample.Volume, 0, loop_fade_duration); + loopStopDelegate = Scheduler.AddDelayed(() => loopSample.Stop(), loop_fade_duration); + }, shoot_retrigger_delay); + } + finally + { + lastPlayback = Time.Current; + } + } + } +} diff --git a/osu.Game/Screens/Play/KiaiGameplayFountains.cs b/osu.Game/Screens/Play/KiaiGameplayFountains.cs index cdeb2a0700..826c60c6cf 100644 --- a/osu.Game/Screens/Play/KiaiGameplayFountains.cs +++ b/osu.Game/Screens/Play/KiaiGameplayFountains.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Play private Bindable kiaiStarFountains = null!; - private StarFountainSfx sfx = null!; + private StarFountainSounds sounds = null!; [BackgroundDependencyLoader] private void load(OsuConfigManager config) @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomRight, X = -75, }, - sfx = new StarFountainSfx(), + sounds = new StarFountainSounds(), }; } @@ -70,7 +70,7 @@ namespace osu.Game.Screens.Play leftFountain.Shoot(1); rightFountain.Shoot(-1); - sfx.Trigger(); + sounds.Play(); } public partial class GameplayStarFountain : StarFountain From 2be450c01016680ee1eed81921f179c4349884d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Mar 2025 17:10:13 +0900 Subject: [PATCH 60/79] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5b5482b3c7..438864f873 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From ab576d57a03fcad0c75774156367724f61fe36bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Mar 2025 18:12:00 +0900 Subject: [PATCH 61/79] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index b6ab7dc712..245d49abc2 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 486979487b..260b0cc0c3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 85076081d2bcf778aea1aa7dc845d43cdc8c396b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Mar 2025 12:02:12 +0100 Subject: [PATCH 62/79] Add failing test case --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 17153a3ff2..916e1e757a 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -43,6 +43,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var working = new TestWorkingBeatmap(decoder.Decode(stream)); Assert.AreEqual(6, working.Beatmap.BeatmapVersion); + Assert.That(working.Beatmap.BeatmapInfo.Ruleset.Name, Is.Not.EqualTo("null placeholder ruleset")); Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty()).BeatmapVersion); } } From 87a8281b29acb5728436066f7c9acd9770cecf41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Mar 2025 12:04:19 +0100 Subject: [PATCH 63/79] Fix editor crashing if beatmap does not have a mode explicitly specified in the `.osu` Closes https://github.com/ppy/osu/issues/32440. Probably another "regression" from https://github.com/ppy/osu/pull/32315. Trying very hard not to type out any hyperbolic rants here. --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 729daf5b0a..fae88a36a7 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -193,6 +193,7 @@ namespace osu.Game.Beatmaps.Formats internal static void ApplyLegacyDefaults(Beatmap beatmap) { beatmap.WidescreenStoryboard = false; + beatmap.BeatmapInfo.Ruleset = RulesetStore?.GetRuleset(0) ?? throw new ArgumentException("osu! ruleset is not available locally."); } protected override void ParseLine(Beatmap beatmap, Section section, string line) From efe1416aa3b2cf81b75559f8a839c6d5197cdfb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 18 Mar 2025 14:17:14 +0100 Subject: [PATCH 64/79] Fix tests --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index fae88a36a7..765f2be345 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -193,7 +193,10 @@ namespace osu.Game.Beatmaps.Formats internal static void ApplyLegacyDefaults(Beatmap beatmap) { beatmap.WidescreenStoryboard = false; - beatmap.BeatmapInfo.Ruleset = RulesetStore?.GetRuleset(0) ?? throw new ArgumentException("osu! ruleset is not available locally."); + // in a perfect world this would throw if osu! ruleset couldn't be found, + // but unfortunately there are "legitimate" cases where it's not there (i.e. ruleset test projects), + // so attempt to trudge on with whatever it is that's in `BeatmapInfo` if the lookup fails. + beatmap.BeatmapInfo.Ruleset = RulesetStore?.GetRuleset(0) ?? beatmap.BeatmapInfo.Ruleset; } protected override void ParseLine(Beatmap beatmap, Section section, string line) From 4a906b0a1e9effaab42b8726bee9846f0fd9c304 Mon Sep 17 00:00:00 2001 From: "Giovanni D." Date: Tue, 18 Mar 2025 19:26:38 -0700 Subject: [PATCH 65/79] Add spectate and multiplayer invite functionality to the drawable chat username. --- .../Overlays/Chat/DrawableChatUsername.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game/Overlays/Chat/DrawableChatUsername.cs b/osu.Game/Overlays/Chat/DrawableChatUsername.cs index 83f67d1a8a..eca2817673 100644 --- a/osu.Game/Overlays/Chat/DrawableChatUsername.cs +++ b/osu.Game/Overlays/Chat/DrawableChatUsername.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -14,6 +15,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -22,7 +24,10 @@ using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; +using osu.Game.Online.Multiplayer; using osu.Game.Resources.Localisation.Web; +using osu.Game.Screens; +using osu.Game.Screens.Play; using osuTK; using osuTK.Graphics; using ChatStrings = osu.Game.Localisation.ChatStrings; @@ -69,6 +74,12 @@ namespace osu.Game.Overlays.Chat [Resolved] private OsuColour colours { get; set; } = null!; + [Resolved] + private MultiplayerClient? multiplayerClient { get; set; } + + [Resolved] + private IPerformFromScreenRunner? performer { get; set; } + [Resolved(canBeNull: true)] private ChannelManager? chatManager { get; set; } @@ -169,6 +180,22 @@ namespace osu.Game.Overlays.Chat if (!user.Equals(api.LocalUser.Value)) items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, openUserChannel)); + if (!user.Equals(api.LocalUser.Value)) + { + if (user.IsOnline) + { + items.Add(new OsuMenuItem(ContextMenuStrings.SpectatePlayer, MenuItemType.Standard, () => + { + performer?.PerformFromScreen(s => s.Push(new SoloSpectatorScreen(user))); + })); + + if (multiplayerClient?.Room?.Users.All(u => u.UserID != user.Id) == true) + { + items.Add(new OsuMenuItem(ContextMenuStrings.InvitePlayer, MenuItemType.Standard, () => multiplayerClient.InvitePlayer(user.Id))); + } + } + } + if (currentChannel?.Value != null) { items.Add(new OsuMenuItem(ChatStrings.MentionUser, MenuItemType.Standard, () => From c9afc6b3df8e5e251a3ae38ed0f94bff419f921e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Mar 2025 12:26:50 +0900 Subject: [PATCH 66/79] Adjust ordering of menu items and fix code quality issues --- .../Overlays/Chat/DrawableChatUsername.cs | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChatUsername.cs b/osu.Game/Overlays/Chat/DrawableChatUsername.cs index eca2817673..fdcadbaa10 100644 --- a/osu.Game/Overlays/Chat/DrawableChatUsername.cs +++ b/osu.Game/Overlays/Chat/DrawableChatUsername.cs @@ -172,29 +172,10 @@ namespace osu.Game.Overlays.Chat if (user.Equals(APIUser.SYSTEM_USER)) return Array.Empty(); - List items = new List - { - new OsuMenuItem(ContextMenuStrings.ViewProfile, MenuItemType.Highlighted, openUserProfile) - }; + if (user.Equals(api.LocalUser.Value)) + return Array.Empty(); - if (!user.Equals(api.LocalUser.Value)) - items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, openUserChannel)); - - if (!user.Equals(api.LocalUser.Value)) - { - if (user.IsOnline) - { - items.Add(new OsuMenuItem(ContextMenuStrings.SpectatePlayer, MenuItemType.Standard, () => - { - performer?.PerformFromScreen(s => s.Push(new SoloSpectatorScreen(user))); - })); - - if (multiplayerClient?.Room?.Users.All(u => u.UserID != user.Id) == true) - { - items.Add(new OsuMenuItem(ContextMenuStrings.InvitePlayer, MenuItemType.Standard, () => multiplayerClient.InvitePlayer(user.Id))); - } - } - } + List items = new List(); if (currentChannel?.Value != null) { @@ -204,8 +185,27 @@ namespace osu.Game.Overlays.Chat })); } - if (!user.Equals(api.LocalUser.Value)) - items.Add(new OsuMenuItem(UsersStrings.ReportButtonText, MenuItemType.Destructive, ReportRequested)); + items.Add(new OsuMenuItem(ContextMenuStrings.ViewProfile, MenuItemType.Highlighted, openUserProfile)); + + items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, openUserChannel)); + + if (user.IsOnline) + { + items.Add(new OsuMenuItemSpacer()); + + items.Add(new OsuMenuItem(ContextMenuStrings.SpectatePlayer, MenuItemType.Standard, () => + { + performer?.PerformFromScreen(s => s.Push(new SoloSpectatorScreen(user))); + })); + + if (multiplayerClient?.Room?.Users.All(u => u.UserID != user.Id) == true) + { + items.Add(new OsuMenuItem(ContextMenuStrings.InvitePlayer, MenuItemType.Standard, () => multiplayerClient.InvitePlayer(user.Id))); + } + } + + items.Add(new OsuMenuItemSpacer()); + items.Add(new OsuMenuItem(UsersStrings.ReportButtonText, MenuItemType.Destructive, ReportRequested)); return items.ToArray(); } From 52dad654f776401e2aa757cdc8359b236e8ad11f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Mar 2025 12:45:27 +0900 Subject: [PATCH 67/79] Add some lenience around mod customisation expanding overlay It was quite easy to dismiss by accident. I've added some positional and time based lenience with numbers that feel good to me. Open to discussion on whether both are required and if the numbers feel good. Going forward, at some point, we'll likely want to standardise this across to other expand-on-hover elements (like player load overlays). Addresses https://github.com/ppy/osu/discussions/32368. --- .../Overlays/Mods/ModCustomisationPanel.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index 03a1b3d0dd..e6d73fe092 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -223,15 +223,28 @@ namespace osu.Game.Overlays.Mods inputManager = GetContainingInputManager()!; } + private double timeUntilCollapse; + + private const double collapse_grace_time = 180; + private const float collapse_grace_position = 40; + protected override void Update() { base.Update(); - if (ExpandedState.Value == ModCustomisationPanelState.Expanded - && !ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position) - && inputManager.DraggedDrawable == null) + if (ExpandedState.Value == ModCustomisationPanelState.Expanded) { - ExpandedState.Value = ModCustomisationPanelState.Collapsed; + bool canCollapse = !DrawRectangle.Inflate(new Vector2(collapse_grace_position)).Contains(ToLocalSpace(inputManager.CurrentState.Mouse.Position)) + && inputManager.DraggedDrawable == null; + + if (canCollapse) + { + if (timeUntilCollapse <= 0) + ExpandedState.Value = ModCustomisationPanelState.Collapsed; + timeUntilCollapse -= Time.Elapsed; + } + else + timeUntilCollapse = collapse_grace_time; } } } From d6a06f2af6f5278fca49f35f5f166ac975f3d733 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Mar 2025 13:15:57 +0900 Subject: [PATCH 68/79] Fade out pause loop sound when the game window is inactive Closes https://github.com/ppy/osu/issues/32432. --- osu.Game/Screens/Play/PauseOverlay.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 3a471acba4..11f62939fb 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -5,9 +5,12 @@ using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Framework.Platform; using osu.Game.Audio; using osu.Game.Input.Bindings; using osu.Game.Localisation; @@ -31,14 +34,24 @@ namespace osu.Game.Screens.Play OnResume?.Invoke(); }; + private IBindable? windowActive; + + private float targetVolume => windowActive?.Value != false && State.Value == Visibility.Visible ? 1.0f : 0; + [BackgroundDependencyLoader] - private void load() + private void load(GameHost? host) { AddInternal(pauseLoop = new SkinnableSound(new SampleInfo("Gameplay/pause-loop")) { Looping = true, Volume = { Value = 0 } }); + + if (host != null) + { + windowActive = host.IsActive.GetBoundCopy(); + windowActive.BindValueChanged(_ => Schedule(() => pauseLoop.VolumeTo(targetVolume, 1000, Easing.Out))); + } } public void StopAllSamples() @@ -53,7 +66,7 @@ namespace osu.Game.Screens.Play { base.PopIn(); - pauseLoop.VolumeTo(1.0f, TRANSITION_DURATION, Easing.InQuint); + pauseLoop.VolumeTo(targetVolume, TRANSITION_DURATION, Easing.InQuint); pauseLoop.Play(); } @@ -61,7 +74,7 @@ namespace osu.Game.Screens.Play { base.PopOut(); - pauseLoop.VolumeTo(0, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop()); + pauseLoop.VolumeTo(targetVolume, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop()); } public override bool OnPressed(KeyBindingPressEvent e) From 76f286a01b07e4fecfb219ddef80a70f6ef08607 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Mar 2025 14:14:32 +0900 Subject: [PATCH 69/79] Re-fetch status of any beatmaps stuck in qualified status Best we do this rather than leaving it up to users to fix their broken beatmaps. https://github.com/ppy/osu/discussions/32406 https://github.com/ppy/osu/discussions/32431 --- osu.Game/Beatmaps/BeatmapInfo.cs | 5 +++-- osu.Game/Database/RealmAccess.cs | 12 +++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 487b578317..a6b40a26de 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -125,9 +125,10 @@ namespace osu.Game.Beatmaps /// /// Reset any fetched online linking information (and history). /// - public void ResetOnlineInfo() + public void ResetOnlineInfo(bool resetOnlineId = true) { - OnlineID = -1; + if (resetOnlineId) + OnlineID = -1; LastOnlineUpdate = null; OnlineMD5Hash = string.Empty; if (Status != BeatmapOnlineStatus.LocallyModified) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 3212e17b7b..7142f2b300 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -97,8 +97,9 @@ namespace osu.Game.Database /// 45 2024-12-23 Change beat snap divisor adjust defaults to be Ctrl+Scroll instead of Ctrl+Shift+Scroll, if not already changed by user. /// 46 2024-12-26 Change beat snap divisor bindings to match stable directionality ¯\_(ツ)_/¯. /// 47 2025-01-21 Remove right mouse button binding for absolute scroll. Never use mouse buttons (or scroll) for global actions. + /// 48 2025-03-19 Clear online status for all qualified beatmaps (some were stuck in a qualified state due to local caching issues). /// - private const int schema_version = 47; + private const int schema_version = 48; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. @@ -1245,6 +1246,15 @@ namespace osu.Game.Database break; } + + case 48: + const int qualified = (int)BeatmapOnlineStatus.Qualified; + + var beatmaps = migration.NewRealm.All().Where(b => b.StatusInt == qualified); + + foreach (var beatmap in beatmaps) + beatmap.ResetOnlineInfo(resetOnlineId: false); + break; } Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms"); From b17ec5e69dd1c112d993536e4af6f4eb44d717c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Mar 2025 15:01:30 +0900 Subject: [PATCH 70/79] Rename `APIUser.IsOnline` and add better documentation around online checks --- osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs | 6 +++--- .../Visual/Online/TestSceneUserClickableAvatar.cs | 2 +- osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs | 2 +- .../Visual/Online/TestSceneUserProfileHeader.cs | 4 ++-- osu.Game/Online/API/Requests/Responses/APIUser.cs | 8 +++++++- osu.Game/Online/Metadata/MetadataClient.cs | 3 +++ osu.Game/Overlays/Chat/DrawableChatUsername.cs | 5 ++++- osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs | 2 +- 8 files changed, 22 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs index f7fd95a6e1..25611cf8d5 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs @@ -234,7 +234,7 @@ namespace osu.Game.Tests.Visual.Online { Username = "flyte", Id = 3103765, - IsOnline = true, + WasRecentlyOnline = true, Statistics = new UserStatistics { GlobalRank = 1111 }, CountryCode = CountryCode.JP, CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" @@ -243,7 +243,7 @@ namespace osu.Game.Tests.Visual.Online { Username = "peppy", Id = 2, - IsOnline = false, + WasRecentlyOnline = false, Statistics = new UserStatistics { GlobalRank = 2222 }, CountryCode = CountryCode.AU, CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", @@ -256,7 +256,7 @@ namespace osu.Game.Tests.Visual.Online Id = 8195163, CountryCode = CountryCode.BY, CoverUrl = "https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg", - IsOnline = false, + WasRecentlyOnline = false, LastVisit = DateTimeOffset.Now } }; diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs index fce888094d..29272f7336 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Online CountryCode = countryCode, CoverUrl = cover, Colour = color ?? "000000", - IsOnline = true + WasRecentlyOnline = true }; return new ClickableAvatar(user, showPanel) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index f4fc15da20..896bda364a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Online Id = 3103765, CountryCode = CountryCode.JP, CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg", - IsOnline = true + WasRecentlyOnline = true }) { Width = 300 }, new UserGridPanel(new APIUser { diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index 6167d1f760..193b356d71 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Online Id = 1001, Username = "IAmOnline", LastVisit = DateTimeOffset.Now, - IsOnline = true, + WasRecentlyOnline = true, }, new OsuRuleset().RulesetInfo)); AddStep("Show offline user", () => header.User.Value = new UserProfileData(new APIUser @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Online Id = 1002, Username = "IAmOffline", LastVisit = DateTimeOffset.Now.AddDays(-10), - IsOnline = false, + WasRecentlyOnline = false, }, new OsuRuleset().RulesetInfo)); } diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 92b7d9d874..4e219cdf22 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -9,6 +9,7 @@ using System.Linq; using JetBrains.Annotations; using Newtonsoft.Json; using osu.Game.Extensions; +using osu.Game.Online.Metadata; using osu.Game.Users; namespace osu.Game.Online.API.Requests.Responses @@ -111,8 +112,13 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"is_active")] public bool Active; + /// + /// From osu-web's perspective, whether a user was recently online. + /// This doesn't imply the user is online in a lazer client (may be updated from stable or web browser). + /// Use for real-time lazer online status checks. + /// [JsonProperty(@"is_online")] - public bool IsOnline; + public bool WasRecentlyOnline; [JsonProperty(@"pm_friends_only")] public bool PMFriendsOnly; diff --git a/osu.Game/Online/Metadata/MetadataClient.cs b/osu.Game/Online/Metadata/MetadataClient.cs index 9885419b65..0679191a52 100644 --- a/osu.Game/Online/Metadata/MetadataClient.cs +++ b/osu.Game/Online/Metadata/MetadataClient.cs @@ -57,6 +57,9 @@ namespace osu.Game.Online.Metadata /// /// Attempts to retrieve the presence of a user. /// + /// + /// This will return data if the client is currently receiving presence data. See . + /// /// The user ID. /// The user presence, or null if not available or the user's offline. public UserPresence? GetPresence(int userId) diff --git a/osu.Game/Overlays/Chat/DrawableChatUsername.cs b/osu.Game/Overlays/Chat/DrawableChatUsername.cs index fdcadbaa10..7cf23f6f7b 100644 --- a/osu.Game/Overlays/Chat/DrawableChatUsername.cs +++ b/osu.Game/Overlays/Chat/DrawableChatUsername.cs @@ -189,7 +189,10 @@ namespace osu.Game.Overlays.Chat items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, openUserChannel)); - if (user.IsOnline) + // Best effort checking against the recently online flag here. + // We can't use MetadataClient.GetPresence because we may not be requesting/receiving presences. + // This isn't really too bad – worst case scenario the client will open spectator view and show the user as "offline". + if (user.WasRecentlyOnline) { items.Add(new OsuMenuItemSpacer()); diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index 03c849052b..db93ec7e05 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -93,7 +93,7 @@ namespace osu.Game.Overlays.Profile.Header addSpacer(topLinkContainer); - if (user.IsOnline) + if (user.WasRecentlyOnline) { topLinkContainer.AddText(UsersStrings.ShowLastvisitOnline); addSpacer(topLinkContainer); From 40da77c409ae41a37fb91b637a4ce8c409d343d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Mar 2025 15:16:03 +0900 Subject: [PATCH 71/79] Allow vertical layout for skinnable mod display --- .../SkinComponents/SkinnableModDisplayStrings.cs | 8 +++++++- osu.Game/Screens/Play/HUD/ModDisplay.cs | 10 ++++++++-- osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs | 4 ++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game/Localisation/SkinComponents/SkinnableModDisplayStrings.cs b/osu.Game/Localisation/SkinComponents/SkinnableModDisplayStrings.cs index d3e8c0f8c8..22f9fe6d02 100644 --- a/osu.Game/Localisation/SkinComponents/SkinnableModDisplayStrings.cs +++ b/osu.Game/Localisation/SkinComponents/SkinnableModDisplayStrings.cs @@ -17,7 +17,13 @@ namespace osu.Game.Localisation.SkinComponents /// /// "Whether to show extended information for each mod." /// - public static LocalisableString ShowExtendedInformationDescription => new TranslatableString(getKey(@"whether_to_show_extended_information"), @"Whether to show extended information for each mod."); + public static LocalisableString ShowExtendedInformationDescription => + new TranslatableString(getKey(@"whether_to_show_extended_information"), @"Whether to show extended information for each mod."); + + /// + /// "Display direction" + /// + public static LocalisableString DisplayDirection => new TranslatableString(getKey(@"display_direction"), "Display direction"); /// /// "Expansion mode" diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 3ab4c15154..011b2b950a 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -67,6 +67,12 @@ namespace osu.Game.Screens.Play.HUD } } + public FillDirection FillDirection + { + get => iconsContainer.Direction; + set => iconsContainer.Direction = value; + } + private readonly FillFlowContainer iconsContainer; public ModDisplay(bool showExtendedInformation = true) @@ -122,13 +128,13 @@ namespace osu.Game.Screens.Play.HUD private void expand(double duration = 500) { if (ExpansionMode != ExpansionMode.AlwaysContracted) - iconsContainer.TransformSpacingTo(new Vector2(5, 0), duration, Easing.OutQuint); + iconsContainer.TransformSpacingTo(new Vector2(5), duration, Easing.OutQuint); } private void contract(double duration = 500) { if (ExpansionMode != ExpansionMode.AlwaysExpanded) - iconsContainer.TransformSpacingTo(new Vector2(-25, 0), duration, Easing.OutQuint); + iconsContainer.TransformSpacingTo(new Vector2(-25), duration, Easing.OutQuint); } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs b/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs index 59bb1ade41..29b8429539 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs @@ -30,6 +30,9 @@ namespace osu.Game.Screens.Play.HUD [SettingSource(typeof(SkinnableModDisplayStrings), nameof(SkinnableModDisplayStrings.ExpansionMode), nameof(SkinnableModDisplayStrings.ExpansionModeDescription))] public Bindable ExpansionModeSetting { get; } = new Bindable(); + [SettingSource(typeof(SkinnableModDisplayStrings), nameof(SkinnableModDisplayStrings.DisplayDirection))] + public Bindable Direction { get; } = new Bindable(); + [BackgroundDependencyLoader] private void load() { @@ -50,6 +53,7 @@ namespace osu.Game.Screens.Play.HUD ShowExtendedInformation.BindValueChanged(_ => modDisplay.ShowExtendedInformation = ShowExtendedInformation.Value, true); ExpansionModeSetting.BindValueChanged(_ => modDisplay.ExpansionMode = ExpansionModeSetting.Value, true); + Direction.BindValueChanged(_ => modDisplay.FillDirection = Direction.Value == Framework.Graphics.Direction.Horizontal ? FillDirection.Horizontal : FillDirection.Vertical, true); FinishTransforms(true); } From 252084de245263c580130d579d66759c03dbf351 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Mar 2025 15:23:31 +0900 Subject: [PATCH 72/79] Add note about local mod display in `HUDOverlay` --- osu.Game/Screens/Play/HUDOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 78c602d8f1..75a28a4240 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -147,6 +147,9 @@ namespace osu.Game.Screens.Play Direction = FillDirection.Vertical, Children = new Drawable[] { + // This display is potentially a duplicate of users with a local ModDisplay in their skins. + // It would be very nice to remove this, but the version here has special logic with regards to replays + // and initial states, so needs a bit of thought before doing so. ModDisplay = CreateModsContainer(), } }, From 23fa3060b2e91ffb70a107abbf95faffbcc4a078 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 19 Mar 2025 15:32:29 +0900 Subject: [PATCH 73/79] Replace superfluous method with concrete implementation --- .../Multiplayer/TestSceneMatchStartControl.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiplayer.cs | 4 ++-- .../Multiplayer/TestSceneMultiplayerPlaylist.cs | 2 +- .../TestSceneMultiplayerQueueList.cs | 2 +- .../Visual/Multiplayer/TestMultiplayerClient.cs | 17 +---------------- 5 files changed, 6 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index fb9c801fb4..3e62417892 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -127,7 +127,7 @@ namespace osu.Game.Tests.Visual.Multiplayer multiplayerRoom = new MultiplayerRoom(0) { - Playlist = { TestMultiplayerClient.CreateMultiplayerPlaylistItem(item) }, + Playlist = { new MultiplayerPlaylistItem(item) }, Users = { localUser }, Host = localUser, }; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index ec0117a990..ae939c7b9e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -924,7 +924,7 @@ namespace osu.Game.Tests.Visual.Multiplayer enterGameplay(); AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 })); - AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, TestMultiplayerClient.CreateMultiplayerPlaylistItem( + AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem( new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, @@ -956,7 +956,7 @@ namespace osu.Game.Tests.Visual.Multiplayer enterGameplay(); AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 })); - AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, TestMultiplayerClient.CreateMultiplayerPlaylistItem( + AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem( new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 7c8691d5d1..1affa08813 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -220,7 +220,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// private void addItemStep(bool expired = false, int? userId = null) => AddStep("add item", () => { - MultiplayerClient.AddUserPlaylistItem(userId ?? API.LocalUser.Value.OnlineID, TestMultiplayerClient.CreateMultiplayerPlaylistItem(new PlaylistItem(importedBeatmap) + MultiplayerClient.AddUserPlaylistItem(userId ?? API.LocalUser.Value.OnlineID, new MultiplayerPlaylistItem(new PlaylistItem(importedBeatmap) { Expired = expired, PlayedAt = DateTimeOffset.Now diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 1a7b677798..7283e3a1fe 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add playlist item", () => { - MultiplayerPlaylistItem item = TestMultiplayerClient.CreateMultiplayerPlaylistItem(new PlaylistItem(importedBeatmap)); + MultiplayerPlaylistItem item = new MultiplayerPlaylistItem(new PlaylistItem(importedBeatmap)); MultiplayerClient.AddUserPlaylistItem(userId(), item).WaitSafely(); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index cc9a82c1ba..febd7f54ff 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -238,7 +238,7 @@ namespace osu.Game.Tests.Visual.Multiplayer QueueMode = ServerAPIRoom.QueueMode, AutoStartDuration = ServerAPIRoom.AutoStartDuration }, - Playlist = ServerAPIRoom.Playlist.Select(CreateMultiplayerPlaylistItem).ToList(), + Playlist = ServerAPIRoom.Playlist.Select(item => new MultiplayerPlaylistItem(item)).ToList(), Users = { localUser }, Host = localUser }; @@ -687,21 +687,6 @@ namespace osu.Game.Tests.Visual.Multiplayer return MessagePackSerializer.Deserialize(serialized, SignalRUnionWorkaroundResolver.OPTIONS); } - public static MultiplayerPlaylistItem CreateMultiplayerPlaylistItem(PlaylistItem item) => new MultiplayerPlaylistItem - { - ID = item.ID, - OwnerID = item.OwnerID, - BeatmapID = item.Beatmap.OnlineID, - BeatmapChecksum = item.Beatmap.MD5Hash, - RulesetID = item.RulesetID, - RequiredMods = item.RequiredMods.ToArray(), - AllowedMods = item.AllowedMods.ToArray(), - Expired = item.Expired, - PlaylistOrder = item.PlaylistOrder ?? 0, - PlayedAt = item.PlayedAt, - StarRating = item.Beatmap.StarRating, - }; - public override Task DisconnectInternal() { isConnected.Value = false; From 9272ada859e7aa2be993362c968936886ecaf79e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 19 Mar 2025 16:13:56 +0900 Subject: [PATCH 74/79] Fix intermittent mod customisation panel test --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 6eb9263c7e..499b28fb49 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -993,7 +993,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("column not scrolled", () => modSelectOverlay.ChildrenOfType().Single().IsScrolledToStart()); AddStep("move mouse away", () => InputManager.MoveMouseTo(Vector2.Zero)); - AddAssert("customisation panel closed", + AddUntilStep("customisation panel closed", () => this.ChildrenOfType().Single().ExpandedState.Value, () => Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed)); @@ -1018,7 +1018,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void assertCustomisationToggleState(bool disabled, bool active) { AddUntilStep($"customisation panel is {(disabled ? "" : "not ")}disabled", () => modSelectOverlay.ChildrenOfType().Single().Enabled.Value == !disabled); - AddAssert($"customisation panel is {(active ? "" : "not ")}active", + AddUntilStep($"customisation panel is {(active ? "" : "not ")}active", () => modSelectOverlay.ChildrenOfType().Single().ExpandedState.Value, () => active ? Is.Not.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed) : Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed)); } From 67b48fd0ac1ed30e3c9e8782e44a0410dfe9634e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Mar 2025 16:18:23 +0900 Subject: [PATCH 75/79] Remove online state check altogether --- osu.Game/Overlays/Chat/DrawableChatUsername.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChatUsername.cs b/osu.Game/Overlays/Chat/DrawableChatUsername.cs index 7cf23f6f7b..57338dde9f 100644 --- a/osu.Game/Overlays/Chat/DrawableChatUsername.cs +++ b/osu.Game/Overlays/Chat/DrawableChatUsername.cs @@ -189,10 +189,9 @@ namespace osu.Game.Overlays.Chat items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, openUserChannel)); - // Best effort checking against the recently online flag here. - // We can't use MetadataClient.GetPresence because we may not be requesting/receiving presences. + // We should probably be checking against an online state here. + // But we can't use MetadataClient.GetPresence because we may not be requesting/receiving presences. // This isn't really too bad – worst case scenario the client will open spectator view and show the user as "offline". - if (user.WasRecentlyOnline) { items.Add(new OsuMenuItemSpacer()); From ad96cff7946d6bb97fbc3e8f0869a5d5802c8bef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Mar 2025 17:42:12 +0900 Subject: [PATCH 76/79] Reduce vertical spacing slightly --- osu.Game/Screens/Play/HUD/ModDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 011b2b950a..986bc525cc 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -128,7 +128,7 @@ namespace osu.Game.Screens.Play.HUD private void expand(double duration = 500) { if (ExpansionMode != ExpansionMode.AlwaysContracted) - iconsContainer.TransformSpacingTo(new Vector2(5), duration, Easing.OutQuint); + iconsContainer.TransformSpacingTo(new Vector2(5, -10), duration, Easing.OutQuint); } private void contract(double duration = 500) From c1604a797f71c5d8520f40bfbf1e3ea22bc0babc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 19 Mar 2025 19:56:27 +0900 Subject: [PATCH 77/79] Ensure `BindValueChanged` is run after `LoadComplete` --- osu.Game/Screens/Play/PauseOverlay.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 11f62939fb..18d17c1317 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -34,9 +34,9 @@ namespace osu.Game.Screens.Play OnResume?.Invoke(); }; - private IBindable? windowActive; + private readonly IBindable windowActive = new Bindable(true); - private float targetVolume => windowActive?.Value != false && State.Value == Visibility.Visible ? 1.0f : 0; + private float targetVolume => windowActive.Value && State.Value == Visibility.Visible ? 1.0f : 0; [BackgroundDependencyLoader] private void load(GameHost? host) @@ -48,10 +48,15 @@ namespace osu.Game.Screens.Play }); if (host != null) - { - windowActive = host.IsActive.GetBoundCopy(); - windowActive.BindValueChanged(_ => Schedule(() => pauseLoop.VolumeTo(targetVolume, 1000, Easing.Out))); - } + windowActive.BindTo(host.IsActive); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // Schedule required because host.IsActive doesn't seem to always run on the update thread. + windowActive.BindValueChanged(_ => Schedule(() => pauseLoop.VolumeTo(targetVolume, 1000, Easing.Out))); } public void StopAllSamples() From 44b4e04d8cecbdd45e28a612442fb0fb5152bf84 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 20 Mar 2025 12:20:00 +0900 Subject: [PATCH 78/79] Update iOS project to match Android project Pulls in `` and inclusion of test resources. --- osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj index e4b9d2ba94..da07373037 100644 --- a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj +++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj @@ -1,4 +1,5 @@  + Exe net8.0-ios @@ -6,11 +7,19 @@ osu.Game.Tests osu.Game.Tests.iOS - + + $(NoWarn);CA2007 + %(RecursiveDir)%(Filename)%(Extension) + + + %(RecursiveDir)%(Filename)%(Extension) + iOS\%(RecursiveDir)%(Filename)%(Extension) + From a7ee6a15252986e5bd744a822c11b62f2ae5a4f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Mar 2025 16:00:01 +0900 Subject: [PATCH 79/79] Rename online store class to better explain what it's doing --- osu.Game/Audio/PreviewTrackManager.cs | 2 +- .../Online/{OsuOnlineStore.cs => TrustedDomainOnlineStore.cs} | 2 +- osu.Game/OsuGameBase.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename osu.Game/Online/{OsuOnlineStore.cs => TrustedDomainOnlineStore.cs} (91%) diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index f9e74cd1b2..d3ab86a8a0 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -30,7 +30,7 @@ namespace osu.Game.Audio [BackgroundDependencyLoader] private void load(AudioManager audioManager) { - trackStore = audioManager.GetTrackStore(new OsuOnlineStore()); + trackStore = audioManager.GetTrackStore(new TrustedDomainOnlineStore()); } /// diff --git a/osu.Game/Online/OsuOnlineStore.cs b/osu.Game/Online/TrustedDomainOnlineStore.cs similarity index 91% rename from osu.Game/Online/OsuOnlineStore.cs rename to osu.Game/Online/TrustedDomainOnlineStore.cs index f578043c5d..2b47f159e6 100644 --- a/osu.Game/Online/OsuOnlineStore.cs +++ b/osu.Game/Online/TrustedDomainOnlineStore.cs @@ -7,7 +7,7 @@ using osu.Framework.Logging; namespace osu.Game.Online { - public class OsuOnlineStore : OnlineStore + public sealed class TrustedDomainOnlineStore : OnlineStore { protected override string GetLookupUrl(string url) { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 51c8788248..4087a8b71e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -108,7 +108,7 @@ namespace osu.Game public virtual EndpointConfiguration CreateEndpoints() => UseDevelopmentServer ? new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); - protected override OnlineStore CreateOnlineStore() => new OsuOnlineStore(); + protected override OnlineStore CreateOnlineStore() => new TrustedDomainOnlineStore(); public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version();