From 91fb59ee15caf75b08a43bd6508a4edf3f49e8f5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 11 Feb 2024 07:37:13 +0300 Subject: [PATCH 001/223] Introduce `LocalUserStatisticsProvider` component --- .../TestSceneLocalUserStatisticsProvider.cs | 141 ++++++++++++++++++ .../Online/LocalUserStatisticsProvider.cs | 94 ++++++++++++ osu.Game/OsuGameBase.cs | 6 +- 3 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs create mode 100644 osu.Game/Online/LocalUserStatisticsProvider.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs b/osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs new file mode 100644 index 0000000000..1a27fd1de5 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs @@ -0,0 +1,141 @@ +// 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 NUnit.Framework; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Graphics.Sprites; +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.Catch; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + public partial class TestSceneLocalUserStatisticsProvider : OsuTestScene + { + private LocalUserStatisticsProvider statisticsProvider = null!; + + private readonly Dictionary<(int userId, string rulesetName), UserStatistics> serverSideStatistics = new Dictionary<(int userId, string rulesetName), UserStatistics>(); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("clear statistics", () => serverSideStatistics.Clear()); + + setUser(1000); + + AddStep("setup provider", () => + { + OsuSpriteText text; + + ((DummyAPIAccess)API).HandleRequest = r => + { + switch (r) + { + case GetUserRequest userRequest: + int userId = int.Parse(userRequest.Lookup); + string rulesetName = userRequest.Ruleset!.ShortName; + var response = new APIUser + { + Id = userId, + Statistics = tryGetStatistics(userId, rulesetName) + }; + + userRequest.TriggerSuccess(response); + return true; + + default: + return false; + } + }; + + Clear(); + Add(statisticsProvider = new LocalUserStatisticsProvider()); + Add(text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + + statisticsProvider.Statistics.BindValueChanged(s => + { + text.Text = s.NewValue == null + ? "Statistics: (null)" + : $"Statistics: (total score: {s.NewValue.TotalScore:N0})"; + }); + + Ruleset.Value = new OsuRuleset().RulesetInfo; + }); + } + + [Test] + public void TestInitialStatistics() + { + AddAssert("initial statistics populated", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(4_000_000)); + } + + [Test] + public void TestRulesetChanges() + { + AddAssert("statistics from osu", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(4_000_000)); + AddStep("change ruleset to taiko", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); + AddAssert("statistics from taiko", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(3_000_000)); + AddStep("change ruleset to catch", () => Ruleset.Value = new CatchRuleset().RulesetInfo); + AddAssert("statistics from catch", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(2_000_000)); + AddStep("change ruleset to mania", () => Ruleset.Value = new ManiaRuleset().RulesetInfo); + AddAssert("statistics from mania", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(1_000_000)); + } + + [Test] + public void TestUserChanges() + { + setUser(1001); + + AddStep("update statistics for user 1000", () => + { + serverSideStatistics[(1000, "osu")] = new UserStatistics { TotalScore = 5_000_000 }; + serverSideStatistics[(1000, "taiko")] = new UserStatistics { TotalScore = 6_000_000 }; + }); + + AddAssert("statistics matches user 1001 from osu", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(4_000_000)); + + AddStep("change ruleset to taiko", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); + AddAssert("statistics matches user 1001 from taiko", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(3_000_000)); + + AddStep("change ruleset to osu", () => Ruleset.Value = new OsuRuleset().RulesetInfo); + setUser(1000, false); + + AddAssert("statistics matches user 1000 from osu", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(5_000_000)); + + AddStep("change ruleset to osu", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); + AddAssert("statistics matches user 1000 from taiko", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(6_000_000)); + } + + private UserStatistics tryGetStatistics(int userId, string rulesetName) + => serverSideStatistics.TryGetValue((userId, rulesetName), out var stats) ? stats : new UserStatistics(); + + private void setUser(int userId, bool generateStatistics = true) + { + AddStep($"set local user to {userId}", () => + { + if (generateStatistics) + { + serverSideStatistics[(userId, "osu")] = new UserStatistics { TotalScore = 4_000_000 }; + serverSideStatistics[(userId, "taiko")] = new UserStatistics { TotalScore = 3_000_000 }; + serverSideStatistics[(userId, "fruits")] = new UserStatistics { TotalScore = 2_000_000 }; + serverSideStatistics[(userId, "mania")] = new UserStatistics { TotalScore = 1_000_000 }; + } + + ((DummyAPIAccess)API).LocalUser.Value = new APIUser { Id = userId }; + }); + } + } +} diff --git a/osu.Game/Online/LocalUserStatisticsProvider.cs b/osu.Game/Online/LocalUserStatisticsProvider.cs new file mode 100644 index 0000000000..e2f016b336 --- /dev/null +++ b/osu.Game/Online/LocalUserStatisticsProvider.cs @@ -0,0 +1,94 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Game.Extensions; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; +using osu.Game.Users; + +namespace osu.Game.Online +{ + /// + /// A component that is responsible for providing the latest statistics of the logged-in user for the game-wide selected ruleset. + /// + public partial class LocalUserStatisticsProvider : Component + { + /// + /// The statistics of the logged-in user for the game-wide selected ruleset. + /// + public IBindable Statistics => statistics; + + private readonly Bindable statistics = new Bindable(); + + [Resolved] + private IBindable ruleset { get; set; } = null!; + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + private readonly Dictionary allStatistics = new Dictionary(); + + protected override void LoadComplete() + { + base.LoadComplete(); + + statistics.BindValueChanged(v => + { + if (api.LocalUser.Value != null && v.NewValue != null) + api.LocalUser.Value.Statistics = v.NewValue; + }); + + ruleset.BindValueChanged(_ => updateStatisticsBindable()); + + api.LocalUser.BindValueChanged(_ => + { + allStatistics.Clear(); + updateStatisticsBindable(); + }, true); + } + + private GetUserRequest? currentRequest; + + private void updateStatisticsBindable() => Schedule(() => + { + statistics.Value = null; + + if (api.LocalUser.Value == null || api.LocalUser.Value.OnlineID <= 1 || !ruleset.Value.IsLegacyRuleset()) + { + statistics.Value = new UserStatistics(); + return; + } + + if (currentRequest?.CompletionState == APIRequestCompletionState.Waiting) + { + currentRequest.Cancel(); + currentRequest = null; + } + + if (allStatistics.TryGetValue(ruleset.Value.ShortName, out var existing)) + statistics.Value = existing; + else + requestStatistics(ruleset.Value); + }); + + private void requestStatistics(RulesetInfo ruleset) + { + currentRequest = new GetUserRequest(api.LocalUser.Value.OnlineID, ruleset); + currentRequest.Success += u => statistics.Value = allStatistics[ruleset.ShortName] = u.Statistics; + api.Queue(currentRequest); + } + + internal void UpdateStatistics(UserStatistics statistics, RulesetInfo statisticsRuleset) + { + allStatistics[statisticsRuleset.ShortName] = statistics; + + if (statisticsRuleset.ShortName == ruleset.Value.ShortName) + updateStatisticsBindable(); + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a2a6322665..f574885757 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -208,6 +208,7 @@ namespace osu.Game private MetadataClient metadataClient; private SoloStatisticsWatcher soloStatisticsWatcher; + private LocalUserStatisticsProvider localUserStatisticsProvider; private RealmAccess realm; @@ -328,7 +329,9 @@ namespace osu.Game dependencies.CacheAs(SpectatorClient = new OnlineSpectatorClient(endpoints)); dependencies.CacheAs(MultiplayerClient = new OnlineMultiplayerClient(endpoints)); dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); - dependencies.CacheAs(soloStatisticsWatcher = new SoloStatisticsWatcher()); + + dependencies.CacheAs(localUserStatisticsProvider = new LocalUserStatisticsProvider()); + dependencies.CacheAs(soloStatisticsWatcher = new SoloStatisticsWatcher(localUserStatisticsProvider)); base.Content.Add(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient)); @@ -371,6 +374,7 @@ namespace osu.Game base.Content.Add(SpectatorClient); base.Content.Add(MultiplayerClient); base.Content.Add(metadataClient); + base.Content.Add(localUserStatisticsProvider); base.Content.Add(soloStatisticsWatcher); base.Content.Add(rulesetConfigCache); From 3ab60b76df0ea520f53a0c2bd68d7821e11e45f7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 11 Feb 2024 07:38:33 +0300 Subject: [PATCH 002/223] Remove `IAPIProvider.Statistics` in favour of the new component --- .../Online/TestSceneSoloStatisticsWatcher.cs | 8 ++++++-- osu.Game/Online/API/APIAccess.cs | 17 +---------------- osu.Game/Online/API/DummyAPIAccess.cs | 16 ---------------- osu.Game/Online/API/IAPIProvider.cs | 10 ---------- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 9 ++++++++- 5 files changed, 15 insertions(+), 45 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs index 3607b37c7e..0e762966d6 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Models; +using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -25,6 +26,7 @@ namespace osu.Game.Tests.Visual.Online { protected override bool UseOnlineAPI => false; + private LocalUserStatisticsProvider statisticsProvider = null!; private SoloStatisticsWatcher watcher = null!; [Resolved] @@ -109,7 +111,9 @@ namespace osu.Game.Tests.Visual.Online AddStep("create watcher", () => { - Child = watcher = new SoloStatisticsWatcher(); + Clear(); + Add(statisticsProvider = new LocalUserStatisticsProvider()); + Add(watcher = new SoloStatisticsWatcher(statisticsProvider)); }); } @@ -289,7 +293,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); AddUntilStep("update received", () => update != null); AddAssert("local user values are correct", () => dummyAPI.LocalUser.Value.Statistics.TotalScore, () => Is.EqualTo(5_000_000)); - AddAssert("statistics values are correct", () => dummyAPI.Statistics.Value!.TotalScore, () => Is.EqualTo(5_000_000)); + AddAssert("statistics values are correct", () => statisticsProvider.Statistics.Value!.TotalScore, () => Is.EqualTo(5_000_000)); } private int nextUserId = 2000; diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index d3707fe74d..5c3a8e7e92 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -55,7 +55,6 @@ namespace osu.Game.Online.API public IBindable LocalUser => localUser; public IBindableList Friends => friends; public IBindable Activity => activity; - public IBindable Statistics => statistics; public INotificationsClient NotificationsClient { get; } @@ -70,8 +69,6 @@ namespace osu.Game.Online.API private Bindable configStatus { get; } = new Bindable(); private Bindable localUserStatus { get; } = new Bindable(); - private Bindable statistics { get; } = new Bindable(); - protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password)); private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource(); @@ -595,21 +592,9 @@ namespace osu.Game.Online.API flushQueue(); } - public void UpdateStatistics(UserStatistics newStatistics) - { - statistics.Value = newStatistics; - - if (IsLoggedIn) - localUser.Value.Statistics = newStatistics; - } - private static APIUser createGuestUser() => new GuestUser(); - private void setLocalUser(APIUser user) => Scheduler.Add(() => - { - localUser.Value = user; - statistics.Value = user.Statistics; - }, false); + private void setLocalUser(APIUser user) => Scheduler.Add(() => localUser.Value = user, false); protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 4962838bd9..ca21b15b1f 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -30,8 +30,6 @@ namespace osu.Game.Online.API public Bindable Activity { get; } = new Bindable(); - public Bindable Statistics { get; } = new Bindable(); - public DummyNotificationsClient NotificationsClient { get; } = new DummyNotificationsClient(); INotificationsClient IAPIProvider.NotificationsClient => NotificationsClient; @@ -158,11 +156,6 @@ namespace osu.Game.Online.API private void onSuccessfulLogin() { state.Value = APIState.Online; - Statistics.Value = new UserStatistics - { - GlobalRank = 1, - CountryRank = 1 - }; } public void Logout() @@ -173,14 +166,6 @@ namespace osu.Game.Online.API LocalUser.Value = new GuestUser(); } - public void UpdateStatistics(UserStatistics newStatistics) - { - Statistics.Value = newStatistics; - - if (IsLoggedIn) - LocalUser.Value.Statistics = newStatistics; - } - public IHubClientConnector? GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => null; public IChatClient GetChatClient() => new TestChatClientConnector(this); @@ -196,7 +181,6 @@ namespace osu.Game.Online.API IBindable IAPIProvider.LocalUser => LocalUser; IBindableList IAPIProvider.Friends => Friends; IBindable IAPIProvider.Activity => Activity; - IBindable IAPIProvider.Statistics => Statistics; /// /// Skip 2FA requirement for next login. diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index 66f124f7c3..c1f2a52d24 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -29,11 +29,6 @@ namespace osu.Game.Online.API /// IBindable Activity { get; } - /// - /// The current user's online statistics. - /// - IBindable Statistics { get; } - /// /// The language supplied by this provider to API requests. /// @@ -123,11 +118,6 @@ namespace osu.Game.Online.API /// void Logout(); - /// - /// Sets Statistics bindable. - /// - void UpdateStatistics(UserStatistics newStatistics); - /// /// Constructs a new . May be null if not supported. /// diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 55b27fb364..eb7c385fed 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -22,6 +22,8 @@ namespace osu.Game.Online.Solo /// public partial class SoloStatisticsWatcher : Component { + private readonly LocalUserStatisticsProvider? statisticsProvider; + [Resolved] private SpectatorClient spectatorClient { get; set; } = null!; @@ -33,6 +35,11 @@ namespace osu.Game.Online.Solo private Dictionary? latestStatistics; + public SoloStatisticsWatcher(LocalUserStatisticsProvider? statisticsProvider = null) + { + this.statisticsProvider = statisticsProvider; + } + protected override void LoadComplete() { base.LoadComplete(); @@ -127,7 +134,7 @@ namespace osu.Game.Online.Solo { string rulesetName = callback.Score.Ruleset.ShortName; - api.UpdateStatistics(updatedStatistics); + statisticsProvider?.UpdateStatistics(updatedStatistics, callback.Score.Ruleset); if (latestStatistics == null) return; From 633d85431bb0b01cce1d58ad4ac461eb5a9a51fc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 11 Feb 2024 08:22:20 +0300 Subject: [PATCH 003/223] Update `UserRankPanel` implementation to use new component --- .../Visual/Online/TestSceneUserPanel.cs | 15 ++++++------- osu.Game/Users/UserRankPanel.cs | 21 +++++++++++++------ 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index 4df34e6244..bb7b83cb97 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -32,7 +33,10 @@ namespace osu.Game.Tests.Visual.Online private TestUserListPanel boundPanel2; [Cached] - private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + [Cached] + private readonly LocalUserStatisticsProvider statisticsProvider = new LocalUserStatisticsProvider(); [Resolved] private IRulesetStore rulesetStore { get; set; } @@ -163,16 +167,13 @@ namespace osu.Game.Tests.Visual.Online { AddStep("update statistics", () => { - API.UpdateStatistics(new UserStatistics + statisticsProvider.UpdateStatistics(new UserStatistics { GlobalRank = RNG.Next(100000), CountryRank = RNG.Next(100000) - }); - }); - AddStep("set statistics to empty", () => - { - API.UpdateStatistics(new UserStatistics()); + }, Ruleset.Value); }); + AddStep("set statistics to empty", () => statisticsProvider.UpdateStatistics(new UserStatistics(), Ruleset.Value)); } private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.InSoloGame(new BeatmapInfo(), rulesetStore.GetRuleset(rulesetId)!); diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index 84ff3114fc..167c34e4b8 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -7,7 +7,8 @@ using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Game.Online.API; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Resources.Localisation.Web; @@ -24,11 +25,9 @@ namespace osu.Game.Users private const int padding = 10; private const int main_content_height = 80; - [Resolved] - private IAPIProvider api { get; set; } = null!; - private ProfileValueDisplay globalRankDisplay = null!; private ProfileValueDisplay countryRankDisplay = null!; + private LoadingLayer loadingLayer = null!; private readonly IBindable statistics = new Bindable(); @@ -43,10 +42,19 @@ namespace osu.Game.Users private void load() { BorderColour = ColourProvider?.Light1 ?? Colours.GreyVioletLighter; + } - statistics.BindTo(api.Statistics); + [Resolved] + private LocalUserStatisticsProvider statisticsProvider { get; set; } = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + + statistics.BindTo(statisticsProvider.Statistics); statistics.BindValueChanged(stats => { + loadingLayer.State.Value = stats.NewValue == null ? Visibility.Visible : Visibility.Hidden; globalRankDisplay.Content = stats.NewValue?.GlobalRank?.ToLocalisableString("\\##,##0") ?? "-"; countryRankDisplay.Content = stats.NewValue?.CountryRank?.ToLocalisableString("\\##,##0") ?? "-"; }, true); @@ -173,7 +181,8 @@ namespace osu.Game.Users } } } - } + }, + loadingLayer = new LoadingLayer(true), } }; From bc2b7050635a62524ca37a3463a9097739a649c2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 11 Feb 2024 11:16:54 +0300 Subject: [PATCH 004/223] Fix `ImportTest.TestOsuGameBase` having null ruleset --- osu.Game.Tests/ImportTest.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs index 27b8d3f21e..b1e2730703 100644 --- a/osu.Game.Tests/ImportTest.cs +++ b/osu.Game.Tests/ImportTest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; @@ -64,6 +65,10 @@ namespace osu.Game.Tests // Beatmap must be imported before the collection manager is loaded. if (withBeatmap) BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely(); + + // the logic for setting the initial ruleset exists in OsuGame rather than OsuGameBase. + // the ruleset bindable is not meant to be nullable, so assign any ruleset in here. + Ruleset.Value = RulesetStore.AvailableRulesets.First(); } } } From 11b3fa8691d289d83cd8fcbd2940e7968c9ee2a4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 11 Feb 2024 11:39:12 +0300 Subject: [PATCH 005/223] Fix `TestSceneUserPanel` tests failing --- osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index bb7b83cb97..00072d52c1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -47,7 +47,11 @@ namespace osu.Game.Tests.Visual.Online activity.Value = null; status.Value = null; - Child = new FillFlowContainer + Remove(statisticsProvider, false); + Clear(); + Add(statisticsProvider); + + Add(new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -113,7 +117,7 @@ namespace osu.Game.Tests.Visual.Online Statistics = new UserStatistics { GlobalRank = null, CountryRank = null } }) { Width = 300 } } - }; + }); boundPanel1.Status.BindTo(status); boundPanel1.Activity.BindTo(activity); From 4339e2dc4afb5035221398a88cc04f6718d8d523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 12:41:07 +0200 Subject: [PATCH 006/223] Move `AudioLeadIn` out of `BeatmapInfo` --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 ++-- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | 4 ++-- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs | 4 ++-- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 4 ++-- 16 files changed, 30 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index a4cd888823..9ffb3327b9 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var metadata = beatmap.Metadata; Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", metadata.AudioFile); - Assert.AreEqual(0, beatmapInfo.AudioLeadIn); + Assert.AreEqual(0, beatmap.AudioLeadIn); Assert.AreEqual(164471, metadata.PreviewTime); Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); @@ -950,7 +950,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.Multiple(() => { - Assert.That(decoded.BeatmapInfo.AudioLeadIn, Is.EqualTo(0)); + Assert.That(decoded.AudioLeadIn, Is.EqualTo(0)); Assert.That(decoded.BeatmapInfo.StackLeniency, Is.EqualTo(0.7f)); Assert.That(decoded.BeatmapInfo.SpecialStyle, Is.False); Assert.That(decoded.BeatmapInfo.LetterboxInBreaks, Is.False); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 3764467047..3fd05b692d 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { var beatmap = decodeAsJson(normal); var beatmapInfo = beatmap.BeatmapInfo; - Assert.AreEqual(0, beatmapInfo.AudioLeadIn); + Assert.AreEqual(0, beatmap.AudioLeadIn); Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); Assert.AreEqual(false, beatmapInfo.SpecialStyle); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index 5a71369976..a6b8e679b9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay { loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) { - BeatmapInfo = { AudioLeadIn = leadIn } + AudioLeadIn = leadIn }); checkFirstFrameTime(expectedStartTime); @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Gameplay { Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} " + $"FirstHitObjectTime: {FirstHitObjectTime} " - + $"LeadInTime: {Beatmap.Value.BeatmapInfo.AudioLeadIn} " + + $"LeadInTime: {Beatmap.Value.Beatmap.AudioLeadIn} " + $"FirstFrameClockTime: {FirstFrameClockTime}" }); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 1949808dfe..c17405c2ec 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -135,7 +135,7 @@ namespace osu.Game.Tests.Visual.Gameplay var workingBeatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); // Add intro time to test quick retry skipping (TestQuickRetry). - workingBeatmap.BeatmapInfo.AudioLeadIn = 60000; + workingBeatmap.Beatmap.AudioLeadIn = 60000; // Set up data for testing disclaimer display. workingBeatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning ?? false; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs index ae10207de0..81dd23661c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay { loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) { - BeatmapInfo = { AudioLeadIn = 60000 } + AudioLeadIn = 60000 }); AddUntilStep("wait for skip overlay", () => Player.ChildrenOfType().First().IsButtonVisible); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 2b17f91e68..6108260481 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -406,13 +406,13 @@ namespace osu.Game.Tests.Visual.Multiplayer } /// - /// Tests spectating with a beatmap that has a high value. + /// Tests spectating with a beatmap that has a high value. /// /// This test is not intended not to check the correct initial time value, but only to guard against /// gameplay potentially getting stuck in a stopped state due to lead in time being present. /// [Test] - public void TestAudioLeadIn() => testLeadIn(b => b.BeatmapInfo.AudioLeadIn = 2000); + public void TestAudioLeadIn() => testLeadIn(b => b.Beatmap.AudioLeadIn = 2000); /// /// Tests spectating with a beatmap that has a storyboard element with a negative start time (i.e. intro storyboard element). diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index ae77e4adcf..f3ad02558a 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -114,6 +114,8 @@ namespace osu.Game.Beatmaps return mostCommon.beatLength; } + public double AudioLeadIn { get; set; } + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index b68c80d4b3..f33cdaf81f 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -67,6 +67,7 @@ namespace osu.Game.Beatmaps beatmap.HitObjects = convertHitObjects(original.HitObjects, original, cancellationToken).OrderBy(s => s.StartTime).ToList(); beatmap.Breaks = original.Breaks; beatmap.UnhandledEventLines = original.UnhandledEventLines; + beatmap.AudioLeadIn = original.AudioLeadIn; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 2137f33e77..b8e253527b 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -414,7 +414,6 @@ namespace osu.Game.Beatmaps Hash = hash, DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, - AudioLeadIn = decodedInfo.AudioLeadIn, StackLeniency = decodedInfo.StackLeniency, SpecialStyle = decodedInfo.SpecialStyle, LetterboxInBreaks = decodedInfo.LetterboxInBreaks, diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 425fd98d27..e1580dc74e 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -138,8 +138,6 @@ namespace osu.Game.Beatmaps #region Properties we may not want persisted (but also maybe no harm?) - public double AudioLeadIn { get; set; } - public float StackLeniency { get; set; } = 0.7f; public bool SpecialStyle { get; set; } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index c2f4097889..5966658c93 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -238,7 +238,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"AudioLeadIn": - beatmap.BeatmapInfo.AudioLeadIn = Parsing.ParseInt(pair.Value); + beatmap.AudioLeadIn = Parsing.ParseInt(pair.Value); break; case @"PreviewTime": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 186b565c39..072223c8fb 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -79,7 +79,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine("[General]"); if (!string.IsNullOrEmpty(beatmap.Metadata.AudioFile)) writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}")); - writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}")); + writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.AudioLeadIn}")); writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}")); writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}")); writer.WriteLine(FormattableString.Invariant( diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 5cc38e5b84..993155a32e 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -68,6 +68,8 @@ namespace osu.Game.Beatmaps /// double GetMostCommonBeatLength(); + double AudioLeadIn { get; internal set; } + /// /// 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 d37cfc28b9..5557051f05 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -337,6 +337,12 @@ namespace osu.Game.Rulesets.Difficulty public double GetMostCommonBeatLength() => baseBeatmap.GetMostCommonBeatLength(); public IBeatmap Clone() => new ProgressiveCalculationBeatmap(baseBeatmap.Clone()); + public double AudioLeadIn + { + get => baseBeatmap.AudioLeadIn; + set => baseBeatmap.AudioLeadIn = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 5be1d27805..7392c66a26 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -184,6 +184,12 @@ namespace osu.Game.Screens.Edit public double GetMostCommonBeatLength() => PlayableBeatmap.GetMostCommonBeatLength(); + public double AudioLeadIn + { + get => PlayableBeatmap.AudioLeadIn; + set => PlayableBeatmap.AudioLeadIn = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index b2f0ae5561..3851806788 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -95,8 +95,8 @@ namespace osu.Game.Screens.Play // some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available. // this is not available as an option in the live editor but can still be applied via .osu editing. double firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; - if (beatmap.BeatmapInfo.AudioLeadIn > 0) - time = Math.Min(time, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); + if (beatmap.Beatmap.AudioLeadIn > 0) + time = Math.Min(time, firstHitObjectTime - beatmap.Beatmap.AudioLeadIn); return time; } From 0a4560a03e0c5dfccecc6be661586d378d3b88aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 12:50:41 +0200 Subject: [PATCH 007/223] Move `StackLeniency` out of `BeatmapInfo` --- .../Mods/TestSceneOsuModFlashlight.cs | 5 +---- .../Mods/TestSceneOsuModRandom.cs | 2 +- .../TestSceneSliderLateHitJudgement.cs | 2 +- .../Beatmaps/OsuBeatmapProcessor.cs | 14 +++++++------- .../Edit/Setup/OsuSetupSection.cs | 4 ++-- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 ++-- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ .../Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ 16 files changed, 34 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs index 075fdd88ca..1a3b0310f7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs @@ -83,10 +83,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }) } }, - BeatmapInfo = - { - StackLeniency = 0, - } + StackLeniency = 0, }, ReplayFrames = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs index 060a845137..75a5d36f32 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs @@ -74,12 +74,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { BeatmapInfo = new BeatmapInfo { - StackLeniency = 0, Difficulty = new BeatmapDifficulty { ApproachRate = 8.5f } }, + StackLeniency = 0, ControlPointInfo = controlPointInfo }; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs index 1ba4a60b75..d089e924ca 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs @@ -465,7 +465,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void performTest(List frames, Beatmap beatmap) { beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo; - beatmap.BeatmapInfo.StackLeniency = 0; + beatmap.StackLeniency = 0; beatmap.BeatmapInfo.Difficulty = new BeatmapDifficulty { SliderMultiplier = 4, diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index d335913586..9cc22b764f 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -51,13 +51,13 @@ namespace osu.Game.Rulesets.Osu.Beatmaps h.StackHeight = 0; if (Beatmap.BeatmapInfo.BeatmapVersion >= 6) - applyStacking(Beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1); + applyStacking(Beatmap, hitObjects, 0, hitObjects.Count - 1); else - applyStackingOld(Beatmap.BeatmapInfo, hitObjects); + applyStackingOld(Beatmap, hitObjects); } } - private void applyStacking(BeatmapInfo beatmapInfo, List hitObjects, int startIndex, int endIndex) + private void applyStacking(IBeatmap beatmap, List hitObjects, int startIndex, int endIndex) { ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex, endIndex); ArgumentOutOfRangeException.ThrowIfNegative(startIndex); @@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps continue; double endTime = stackBaseObject.GetEndTime(); - double stackThreshold = objectN.TimePreempt * beatmapInfo.StackLeniency; + double stackThreshold = objectN.TimePreempt * beatmap.StackLeniency; if (objectN.StartTime - endTime > stackThreshold) // We are no longer within stacking range of the next object. @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps OsuHitObject objectI = hitObjects[i]; if (objectI.StackHeight != 0 || objectI is Spinner) continue; - double stackThreshold = objectI.TimePreempt * beatmapInfo.StackLeniency; + double stackThreshold = objectI.TimePreempt * beatmap.StackLeniency; /* If this object is a hitcircle, then we enter this "special" case. * It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider. @@ -209,7 +209,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps } } - private void applyStackingOld(BeatmapInfo beatmapInfo, List hitObjects) + private void applyStackingOld(IBeatmap beatmap, List hitObjects) { for (int i = 0; i < hitObjects.Count; i++) { @@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps for (int j = i + 1; j < hitObjects.Count; j++) { - double stackThreshold = hitObjects[i].TimePreempt * beatmapInfo.StackLeniency; + double stackThreshold = hitObjects[i].TimePreempt * beatmap.StackLeniency; if (hitObjects[j].StartTime - stackThreshold > startTime) break; diff --git a/osu.Game.Rulesets.Osu/Edit/Setup/OsuSetupSection.cs b/osu.Game.Rulesets.Osu/Edit/Setup/OsuSetupSection.cs index 552b887081..e1a588a32a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Setup/OsuSetupSection.cs +++ b/osu.Game.Rulesets.Osu/Edit/Setup/OsuSetupSection.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup { Label = "Stack Leniency", Description = "In play mode, osu! automatically stacks notes which occur at the same location. Increasing this value means it is more likely to snap notes of further time-distance.", - Current = new BindableFloat(Beatmap.BeatmapInfo.StackLeniency) + Current = new BindableFloat(Beatmap.StackLeniency) { Default = 0.7f, MinValue = 0, @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup private void updateBeatmap() { - Beatmap.BeatmapInfo.StackLeniency = stackLeniency.Current.Value; + Beatmap.StackLeniency = stackLeniency.Current.Value; Beatmap.UpdateAllHitObjects(); Beatmap.SaveState(); } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 9ffb3327b9..565c481920 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", metadata.AudioFile); Assert.AreEqual(0, beatmap.AudioLeadIn); Assert.AreEqual(164471, metadata.PreviewTime); - Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); + Assert.AreEqual(0.7f, beatmap.StackLeniency); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.IsFalse(beatmapInfo.LetterboxInBreaks); Assert.IsFalse(beatmapInfo.SpecialStyle); @@ -951,7 +951,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.Multiple(() => { Assert.That(decoded.AudioLeadIn, Is.EqualTo(0)); - Assert.That(decoded.BeatmapInfo.StackLeniency, Is.EqualTo(0.7f)); + Assert.That(decoded.StackLeniency, Is.EqualTo(0.7f)); Assert.That(decoded.BeatmapInfo.SpecialStyle, Is.False); Assert.That(decoded.BeatmapInfo.LetterboxInBreaks, Is.False); Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 3fd05b692d..5d2d9e006e 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var beatmap = decodeAsJson(normal); var beatmapInfo = beatmap.BeatmapInfo; Assert.AreEqual(0, beatmap.AudioLeadIn); - Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); + Assert.AreEqual(0.7f, beatmap.StackLeniency); Assert.AreEqual(false, beatmapInfo.SpecialStyle); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index f3ad02558a..ecee6e3416 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -116,6 +116,8 @@ namespace osu.Game.Beatmaps public double AudioLeadIn { get; set; } + public float StackLeniency { get; set; } = 0.7f; + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index f33cdaf81f..a5e9025404 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -68,6 +68,7 @@ namespace osu.Game.Beatmaps beatmap.Breaks = original.Breaks; beatmap.UnhandledEventLines = original.UnhandledEventLines; beatmap.AudioLeadIn = original.AudioLeadIn; + beatmap.StackLeniency = original.StackLeniency; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index b8e253527b..e589b0a754 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -414,7 +414,6 @@ namespace osu.Game.Beatmaps Hash = hash, DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, - StackLeniency = decodedInfo.StackLeniency, SpecialStyle = decodedInfo.SpecialStyle, LetterboxInBreaks = decodedInfo.LetterboxInBreaks, WidescreenStoryboard = decodedInfo.WidescreenStoryboard, diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index e1580dc74e..fa2911438b 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -138,8 +138,6 @@ namespace osu.Game.Beatmaps #region Properties we may not want persisted (but also maybe no harm?) - public float StackLeniency { get; set; } = 0.7f; - public bool SpecialStyle { get; set; } public bool LetterboxInBreaks { get; set; } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 5966658c93..86552b21dd 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -255,7 +255,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"StackLeniency": - beatmap.BeatmapInfo.StackLeniency = Parsing.ParseFloat(pair.Value); + beatmap.StackLeniency = Parsing.ParseFloat(pair.Value); break; case @"Mode": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 072223c8fb..8c371026ff 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -84,7 +84,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}")); writer.WriteLine(FormattableString.Invariant( $"SampleSet: {toLegacySampleBank(((beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePoints.FirstOrDefault() ?? SampleControlPoint.DEFAULT).SampleBank)}")); - writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.BeatmapInfo.StackLeniency}")); + writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.StackLeniency}")); writer.WriteLine(FormattableString.Invariant($"Mode: {onlineRulesetID}")); writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}")); // if (beatmap.BeatmapInfo.UseSkinSprites) diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 993155a32e..28d601620a 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -70,6 +70,8 @@ namespace osu.Game.Beatmaps double AudioLeadIn { get; internal set; } + float StackLeniency { get; internal set; } + /// /// 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 5557051f05..616d6d0848 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -343,6 +343,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.AudioLeadIn = value; } + public float StackLeniency + { + get => baseBeatmap.StackLeniency; + set => baseBeatmap.StackLeniency = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 7392c66a26..c02a22ae03 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -190,6 +190,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.AudioLeadIn = value; } + public float StackLeniency + { + get => PlayableBeatmap.StackLeniency; + set => PlayableBeatmap.StackLeniency = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; From 011c2e3651fe1485eca8663697630777a848a440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 13:12:30 +0200 Subject: [PATCH 008/223] Move `SpecialStyle` out of `BeatmapInfo` --- osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs | 4 ++-- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 ++-- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ 12 files changed, 24 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs index d5a9a311bc..8778c18c38 100644 --- a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs +++ b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup { Label = "Use special (N+1) style", Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.", - Current = { Value = Beatmap.BeatmapInfo.SpecialStyle } + Current = { Value = Beatmap.SpecialStyle } } }; } @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup private void updateBeatmap() { - Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value; + Beatmap.SpecialStyle = specialStyle.Current.Value; Beatmap.SaveState(); } } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 565c481920..ad3721220a 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(0.7f, beatmap.StackLeniency); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.IsFalse(beatmapInfo.LetterboxInBreaks); - Assert.IsFalse(beatmapInfo.SpecialStyle); + Assert.IsFalse(beatmap.SpecialStyle); Assert.IsFalse(beatmapInfo.WidescreenStoryboard); Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate); Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); @@ -952,7 +952,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { Assert.That(decoded.AudioLeadIn, Is.EqualTo(0)); Assert.That(decoded.StackLeniency, Is.EqualTo(0.7f)); - Assert.That(decoded.BeatmapInfo.SpecialStyle, Is.False); + Assert.That(decoded.SpecialStyle, Is.False); Assert.That(decoded.BeatmapInfo.LetterboxInBreaks, Is.False); Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False); Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 5d2d9e006e..18f4651e94 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var beatmapInfo = beatmap.BeatmapInfo; Assert.AreEqual(0, beatmap.AudioLeadIn); Assert.AreEqual(0.7f, beatmap.StackLeniency); - Assert.AreEqual(false, beatmapInfo.SpecialStyle); + Assert.AreEqual(false, beatmap.SpecialStyle); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks); Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index ecee6e3416..f06d884bd1 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -118,6 +118,8 @@ namespace osu.Game.Beatmaps public float StackLeniency { get; set; } = 0.7f; + public bool SpecialStyle { get; set; } + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index a5e9025404..3b3b68de0a 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -69,6 +69,7 @@ namespace osu.Game.Beatmaps beatmap.UnhandledEventLines = original.UnhandledEventLines; beatmap.AudioLeadIn = original.AudioLeadIn; beatmap.StackLeniency = original.StackLeniency; + beatmap.SpecialStyle = original.SpecialStyle; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index e589b0a754..435a282b52 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -414,7 +414,6 @@ namespace osu.Game.Beatmaps Hash = hash, DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, - SpecialStyle = decodedInfo.SpecialStyle, LetterboxInBreaks = decodedInfo.LetterboxInBreaks, WidescreenStoryboard = decodedInfo.WidescreenStoryboard, EpilepsyWarning = decodedInfo.EpilepsyWarning, diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index fa2911438b..4a1fa9b0b4 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -138,8 +138,6 @@ namespace osu.Game.Beatmaps #region Properties we may not want persisted (but also maybe no harm?) - public bool SpecialStyle { get; set; } - public bool LetterboxInBreaks { get; set; } public bool WidescreenStoryboard { get; set; } = true; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 86552b21dd..ea34c7d924 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -289,7 +289,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"SpecialStyle": - beatmap.BeatmapInfo.SpecialStyle = Parsing.ParseInt(pair.Value) == 1; + beatmap.SpecialStyle = Parsing.ParseInt(pair.Value) == 1; break; case @"WidescreenStoryboard": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 8c371026ff..805ce49ca3 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -100,7 +100,7 @@ namespace osu.Game.Beatmaps.Formats if (beatmap.BeatmapInfo.CountdownOffset > 0) writer.WriteLine(FormattableString.Invariant($@"CountdownOffset: {beatmap.BeatmapInfo.CountdownOffset}")); if (onlineRulesetID == 3) - writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.BeatmapInfo.SpecialStyle ? '1' : '0')}")); + writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.SpecialStyle ? '1' : '0')}")); writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.BeatmapInfo.WidescreenStoryboard ? '1' : '0')}")); if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate) writer.WriteLine(@"SamplesMatchPlaybackRate: 1"); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 28d601620a..3ac48c09b4 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -72,6 +72,8 @@ namespace osu.Game.Beatmaps float StackLeniency { get; internal set; } + bool SpecialStyle { get; internal set; } + /// /// 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 616d6d0848..87a20eec0b 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -349,6 +349,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.StackLeniency = value; } + public bool SpecialStyle + { + get => baseBeatmap.SpecialStyle; + set => baseBeatmap.SpecialStyle = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index c02a22ae03..96216c6b1a 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -196,6 +196,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.StackLeniency = value; } + public bool SpecialStyle + { + get => PlayableBeatmap.SpecialStyle; + set => PlayableBeatmap.SpecialStyle = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; From a6b7600bf2b85f154624d8893fd9e658b098ab3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 13:15:41 +0200 Subject: [PATCH 009/223] Move `LetterboxInBreaks` out of `BeatmapInfo` --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 ++-- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ osu.Game/Screens/Edit/Setup/DesignSection.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 2 +- 13 files changed, 25 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index ad3721220a..103bafb2d8 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(164471, metadata.PreviewTime); Assert.AreEqual(0.7f, beatmap.StackLeniency); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); - Assert.IsFalse(beatmapInfo.LetterboxInBreaks); + Assert.IsFalse(beatmap.LetterboxInBreaks); Assert.IsFalse(beatmap.SpecialStyle); Assert.IsFalse(beatmapInfo.WidescreenStoryboard); Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate); @@ -953,7 +953,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decoded.AudioLeadIn, Is.EqualTo(0)); Assert.That(decoded.StackLeniency, Is.EqualTo(0.7f)); Assert.That(decoded.SpecialStyle, Is.False); - Assert.That(decoded.BeatmapInfo.LetterboxInBreaks, Is.False); + Assert.That(decoded.LetterboxInBreaks, Is.False); Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False); Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False); Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 18f4651e94..c1c996fd42 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(0.7f, beatmap.StackLeniency); Assert.AreEqual(false, beatmap.SpecialStyle); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); - Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks); + Assert.AreEqual(false, beatmap.LetterboxInBreaks); Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard); Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); Assert.AreEqual(0, beatmapInfo.CountdownOffset); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index f06d884bd1..614bb4f42a 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -120,6 +120,8 @@ namespace osu.Game.Beatmaps public bool SpecialStyle { get; set; } + public bool LetterboxInBreaks { get; set; } + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index 3b3b68de0a..a56ce58532 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -70,6 +70,7 @@ namespace osu.Game.Beatmaps beatmap.AudioLeadIn = original.AudioLeadIn; beatmap.StackLeniency = original.StackLeniency; beatmap.SpecialStyle = original.SpecialStyle; + beatmap.LetterboxInBreaks = original.LetterboxInBreaks; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 435a282b52..c54eece9f8 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -414,7 +414,6 @@ namespace osu.Game.Beatmaps Hash = hash, DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, - LetterboxInBreaks = decodedInfo.LetterboxInBreaks, WidescreenStoryboard = decodedInfo.WidescreenStoryboard, EpilepsyWarning = decodedInfo.EpilepsyWarning, SamplesMatchPlaybackRate = decodedInfo.SamplesMatchPlaybackRate, diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 4a1fa9b0b4..fafca5e014 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -138,8 +138,6 @@ namespace osu.Game.Beatmaps #region Properties we may not want persisted (but also maybe no harm?) - public bool LetterboxInBreaks { get; set; } - public bool WidescreenStoryboard { get; set; } = true; public bool EpilepsyWarning { get; set; } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index ea34c7d924..b8469f27dd 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -285,7 +285,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"LetterboxInBreaks": - beatmap.BeatmapInfo.LetterboxInBreaks = Parsing.ParseInt(pair.Value) == 1; + beatmap.LetterboxInBreaks = Parsing.ParseInt(pair.Value) == 1; break; case @"SpecialStyle": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 805ce49ca3..5a3fd0a2f3 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -86,7 +86,7 @@ namespace osu.Game.Beatmaps.Formats $"SampleSet: {toLegacySampleBank(((beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePoints.FirstOrDefault() ?? SampleControlPoint.DEFAULT).SampleBank)}")); writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.StackLeniency}")); writer.WriteLine(FormattableString.Invariant($"Mode: {onlineRulesetID}")); - writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}")); + writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.LetterboxInBreaks ? '1' : '0')}")); // if (beatmap.BeatmapInfo.UseSkinSprites) // writer.WriteLine(@"UseSkinSprites: 1"); // if (b.AlwaysShowPlayfield) diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 3ac48c09b4..e5562b608e 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -74,6 +74,8 @@ namespace osu.Game.Beatmaps bool SpecialStyle { get; internal set; } + bool LetterboxInBreaks { get; internal set; } + /// /// 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 87a20eec0b..04dcb2a552 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -355,6 +355,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.SpecialStyle = value; } + public bool LetterboxInBreaks + { + get => baseBeatmap.LetterboxInBreaks; + set => baseBeatmap.LetterboxInBreaks = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 96216c6b1a..7470505712 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -202,6 +202,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.SpecialStyle = value; } + public bool LetterboxInBreaks + { + get => PlayableBeatmap.LetterboxInBreaks; + set => PlayableBeatmap.LetterboxInBreaks = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index b05a073146..c1fe7f405d 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Edit.Setup { Label = EditorSetupStrings.LetterboxDuringBreaks, Description = EditorSetupStrings.LetterboxDuringBreaksDescription, - Current = { Value = Beatmap.BeatmapInfo.LetterboxInBreaks } + Current = { Value = Beatmap.LetterboxInBreaks } }, samplesMatchPlaybackRate = new LabelledSwitchButton { @@ -123,7 +123,7 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.BeatmapInfo.WidescreenStoryboard = widescreenSupport.Current.Value; Beatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning.Current.Value; - Beatmap.BeatmapInfo.LetterboxInBreaks = letterboxDuringBreaks.Current.Value; + Beatmap.LetterboxInBreaks = letterboxDuringBreaks.Current.Value; Beatmap.BeatmapInfo.SamplesMatchPlaybackRate = samplesMatchPlaybackRate.Current.Value; Beatmap.SaveState(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 42ff1d74f3..eaadc236f7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -430,7 +430,7 @@ namespace osu.Game.Screens.Play Children = new[] { DimmableStoryboard.OverlayLayerContainer.CreateProxy(), - BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) + BreakOverlay = new BreakOverlay(working.Beatmap.LetterboxInBreaks, ScoreProcessor) { Clock = DrawableRuleset.FrameStableClock, ProcessCustomClock = false, From 1ab86ebd249e31812ee07af2191957d6115a02c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 13:23:53 +0200 Subject: [PATCH 010/223] Move `WidescreenStoryboard` out of `BeatmapInfo` --- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 ++-- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 10 +++++----- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ osu.Game/Screens/Edit/Setup/DesignSection.cs | 4 ++-- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 2 +- osu.Game/Storyboards/Storyboard.cs | 1 + 15 files changed, 31 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 103bafb2d8..cd36b6b986 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.IsFalse(beatmap.LetterboxInBreaks); Assert.IsFalse(beatmap.SpecialStyle); - Assert.IsFalse(beatmapInfo.WidescreenStoryboard); + Assert.IsFalse(beatmap.WidescreenStoryboard); Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate); Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); Assert.AreEqual(0, beatmapInfo.CountdownOffset); @@ -954,7 +954,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decoded.StackLeniency, Is.EqualTo(0.7f)); Assert.That(decoded.SpecialStyle, Is.False); Assert.That(decoded.LetterboxInBreaks, Is.False); - Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False); + Assert.That(decoded.WidescreenStoryboard, Is.False); Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False); Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False); Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.Normal)); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index c1c996fd42..92715b6aa2 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(false, beatmap.SpecialStyle); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.AreEqual(false, beatmap.LetterboxInBreaks); - Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard); + Assert.AreEqual(false, beatmap.WidescreenStoryboard); Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); Assert.AreEqual(0, beatmapInfo.CountdownOffset); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index 893b9f11f4..95aee43456 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("load storyboard with only video", () => { // LegacyStoryboardDecoder doesn't parse WidescreenStoryboard, so it is set manually - loadStoryboard("storyboard_only_video.osu", s => s.BeatmapInfo.WidescreenStoryboard = false); + loadStoryboard("storyboard_only_video.osu", s => s.Beatmap.WidescreenStoryboard = false); }); AddAssert("storyboard is correct width", () => Precision.AlmostEquals(storyboard?.Width ?? 0f, 480 * 16 / 9f)); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 614bb4f42a..d909e87417 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -122,6 +122,8 @@ namespace osu.Game.Beatmaps public bool LetterboxInBreaks { get; set; } + public bool WidescreenStoryboard { get; set; } = true; + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index a56ce58532..c097389c56 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -71,6 +71,7 @@ namespace osu.Game.Beatmaps beatmap.StackLeniency = original.StackLeniency; beatmap.SpecialStyle = original.SpecialStyle; beatmap.LetterboxInBreaks = original.LetterboxInBreaks; + beatmap.WidescreenStoryboard = original.WidescreenStoryboard; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index c54eece9f8..0bb2ddda7d 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -414,7 +414,6 @@ namespace osu.Game.Beatmaps Hash = hash, DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, - WidescreenStoryboard = decodedInfo.WidescreenStoryboard, EpilepsyWarning = decodedInfo.EpilepsyWarning, SamplesMatchPlaybackRate = decodedInfo.SamplesMatchPlaybackRate, DistanceSpacing = decodedInfo.DistanceSpacing, diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index fafca5e014..1bfa65a3fb 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -138,8 +138,6 @@ namespace osu.Game.Beatmaps #region Properties we may not want persisted (but also maybe no harm?) - public bool WidescreenStoryboard { get; set; } = true; - public bool EpilepsyWarning { get; set; } public bool SamplesMatchPlaybackRate { get; set; } = true; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index b8469f27dd..355114fd0b 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -81,7 +81,7 @@ namespace osu.Game.Beatmaps.Formats this.beatmap = beatmap; this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion; - applyLegacyDefaults(this.beatmap.BeatmapInfo); + applyLegacyDefaults(this.beatmap); base.ParseStreamInto(stream, beatmap); @@ -183,10 +183,10 @@ namespace osu.Game.Beatmaps.Formats /// This method's intention is to restore those legacy defaults. /// See also: https://osu.ppy.sh/wiki/en/Client/File_formats/Osu_%28file_format%29 /// - private static void applyLegacyDefaults(BeatmapInfo beatmapInfo) + private static void applyLegacyDefaults(Beatmap beatmap) { - beatmapInfo.WidescreenStoryboard = false; - beatmapInfo.SamplesMatchPlaybackRate = false; + beatmap.WidescreenStoryboard = false; + beatmap.BeatmapInfo.SamplesMatchPlaybackRate = false; } protected override void ParseLine(Beatmap beatmap, Section section, string line) @@ -293,7 +293,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"WidescreenStoryboard": - beatmap.BeatmapInfo.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1; + beatmap.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1; break; case @"EpilepsyWarning": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 5a3fd0a2f3..478b78fa29 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -101,7 +101,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($@"CountdownOffset: {beatmap.BeatmapInfo.CountdownOffset}")); if (onlineRulesetID == 3) writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.SpecialStyle ? '1' : '0')}")); - writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.BeatmapInfo.WidescreenStoryboard ? '1' : '0')}")); + writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.WidescreenStoryboard ? '1' : '0')}")); if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate) writer.WriteLine(@"SamplesMatchPlaybackRate: 1"); } diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index e5562b608e..3091e02054 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -76,6 +76,8 @@ namespace osu.Game.Beatmaps bool LetterboxInBreaks { get; internal set; } + bool WidescreenStoryboard { get; internal set; } + /// /// 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 04dcb2a552..bd051383de 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -361,6 +361,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.LetterboxInBreaks = value; } + public bool WidescreenStoryboard + { + get => baseBeatmap.WidescreenStoryboard; + set => baseBeatmap.WidescreenStoryboard = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 7470505712..a217e132d0 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -208,6 +208,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.LetterboxInBreaks = value; } + public bool WidescreenStoryboard + { + get => PlayableBeatmap.WidescreenStoryboard; + set => PlayableBeatmap.WidescreenStoryboard = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index c1fe7f405d..8c420b979f 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Edit.Setup { Label = EditorSetupStrings.WidescreenSupport, Description = EditorSetupStrings.WidescreenSupportDescription, - Current = { Value = Beatmap.BeatmapInfo.WidescreenStoryboard } + Current = { Value = Beatmap.WidescreenStoryboard } }, epilepsyWarning = new LabelledSwitchButton { @@ -121,7 +121,7 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.BeatmapInfo.Countdown = EnableCountdown.Current.Value ? CountdownSpeed.Current.Value : CountdownType.None; Beatmap.BeatmapInfo.CountdownOffset = int.TryParse(CountdownOffset.Current.Value, NumberStyles.None, CultureInfo.InvariantCulture, out int offset) ? offset : 0; - Beatmap.BeatmapInfo.WidescreenStoryboard = widescreenSupport.Current.Value; + Beatmap.WidescreenStoryboard = widescreenSupport.Current.Value; Beatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning.Current.Value; Beatmap.LetterboxInBreaks = letterboxDuringBreaks.Current.Value; Beatmap.BeatmapInfo.SamplesMatchPlaybackRate = samplesMatchPlaybackRate.Current.Value; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index fc5ef12fb8..858c257e85 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -78,7 +78,7 @@ namespace osu.Game.Storyboards.Drawables bool onlyHasVideoElements = Storyboard.Layers.SelectMany(l => l.Elements).All(e => e is StoryboardVideo); - Width = Height * (storyboard.BeatmapInfo.WidescreenStoryboard || onlyHasVideoElements ? 16 / 9f : 4 / 3f); + Width = Height * (storyboard.Beatmap.WidescreenStoryboard || onlyHasVideoElements ? 16 / 9f : 4 / 3f); Anchor = Anchor.Centre; Origin = Anchor.Centre; diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 8c43b99702..aca5fc34f4 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -16,6 +16,7 @@ namespace osu.Game.Storyboards public IEnumerable Layers => layers.Values; public BeatmapInfo BeatmapInfo = new BeatmapInfo(); + public IBeatmap Beatmap { get; set; } = new Beatmap(); /// /// Whether the storyboard should prefer textures from the current skin before using local storyboard textures. From f64a0624a5b1808a7c0ca91db99ecc7d679c4ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 13:28:41 +0200 Subject: [PATCH 011/223] Move `EpilepsyWarning` out of `BeatmapInfo` --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ osu.Game/Screens/Edit/Setup/DesignSection.cs | 4 ++-- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 13 files changed, 24 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index cd36b6b986..d3b027d253 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -955,7 +955,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decoded.SpecialStyle, Is.False); Assert.That(decoded.LetterboxInBreaks, Is.False); Assert.That(decoded.WidescreenStoryboard, Is.False); - Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False); + Assert.That(decoded.EpilepsyWarning, Is.False); Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False); Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.Normal)); Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0)); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index c17405c2ec..64211cddf3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Gameplay workingBeatmap.Beatmap.AudioLeadIn = 60000; // Set up data for testing disclaimer display. - workingBeatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning ?? false; + workingBeatmap.Beatmap.EpilepsyWarning = epilepsyWarning ?? false; workingBeatmap.BeatmapInfo.Status = onlineStatus ?? BeatmapOnlineStatus.Ranked; Beatmap.Value = workingBeatmap; diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index d909e87417..7516c8958c 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -124,6 +124,8 @@ namespace osu.Game.Beatmaps public bool WidescreenStoryboard { get; set; } = true; + public bool EpilepsyWarning { get; set; } + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index c097389c56..f9be44599f 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -72,6 +72,7 @@ namespace osu.Game.Beatmaps beatmap.SpecialStyle = original.SpecialStyle; beatmap.LetterboxInBreaks = original.LetterboxInBreaks; beatmap.WidescreenStoryboard = original.WidescreenStoryboard; + beatmap.EpilepsyWarning = original.EpilepsyWarning; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 0bb2ddda7d..232c8b7e24 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -414,7 +414,6 @@ namespace osu.Game.Beatmaps Hash = hash, DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, - EpilepsyWarning = decodedInfo.EpilepsyWarning, SamplesMatchPlaybackRate = decodedInfo.SamplesMatchPlaybackRate, DistanceSpacing = decodedInfo.DistanceSpacing, BeatDivisor = decodedInfo.BeatDivisor, diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 1bfa65a3fb..41a0260250 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -138,8 +138,6 @@ namespace osu.Game.Beatmaps #region Properties we may not want persisted (but also maybe no harm?) - public bool EpilepsyWarning { get; set; } - public bool SamplesMatchPlaybackRate { get; set; } = true; /// diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 355114fd0b..1152e5f30d 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -297,7 +297,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"EpilepsyWarning": - beatmap.BeatmapInfo.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1; + beatmap.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1; break; case @"SamplesMatchPlaybackRate": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 478b78fa29..d7078a71d9 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -95,7 +95,7 @@ namespace osu.Game.Beatmaps.Formats // writer.WriteLine(@"OverlayPosition: " + b.OverlayPosition); // if (!string.IsNullOrEmpty(b.SkinPreference)) // writer.WriteLine(@"SkinPreference:" + b.SkinPreference); - if (beatmap.BeatmapInfo.EpilepsyWarning) + if (beatmap.EpilepsyWarning) writer.WriteLine(@"EpilepsyWarning: 1"); if (beatmap.BeatmapInfo.CountdownOffset > 0) writer.WriteLine(FormattableString.Invariant($@"CountdownOffset: {beatmap.BeatmapInfo.CountdownOffset}")); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 3091e02054..3d563004d1 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -78,6 +78,8 @@ namespace osu.Game.Beatmaps bool WidescreenStoryboard { get; internal set; } + bool EpilepsyWarning { get; internal set; } + /// /// 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 bd051383de..7f9d2ae6ab 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -367,6 +367,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.WidescreenStoryboard = value; } + public bool EpilepsyWarning + { + get => baseBeatmap.EpilepsyWarning; + set => baseBeatmap.EpilepsyWarning = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index a217e132d0..063803ab42 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -214,6 +214,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.WidescreenStoryboard = value; } + public bool EpilepsyWarning + { + get => PlayableBeatmap.EpilepsyWarning; + set => PlayableBeatmap.EpilepsyWarning = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index 8c420b979f..5d729cf4f8 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -74,7 +74,7 @@ namespace osu.Game.Screens.Edit.Setup { Label = EditorSetupStrings.EpilepsyWarning, Description = EditorSetupStrings.EpilepsyWarningDescription, - Current = { Value = Beatmap.BeatmapInfo.EpilepsyWarning } + Current = { Value = Beatmap.EpilepsyWarning } }, letterboxDuringBreaks = new LabelledSwitchButton { @@ -122,7 +122,7 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.BeatmapInfo.CountdownOffset = int.TryParse(CountdownOffset.Current.Value, NumberStyles.None, CultureInfo.InvariantCulture, out int offset) ? offset : 0; Beatmap.WidescreenStoryboard = widescreenSupport.Current.Value; - Beatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning.Current.Value; + Beatmap.EpilepsyWarning = epilepsyWarning.Current.Value; Beatmap.LetterboxInBreaks = letterboxDuringBreaks.Current.Value; Beatmap.BeatmapInfo.SamplesMatchPlaybackRate = samplesMatchPlaybackRate.Current.Value; diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 51a0c94ff0..fc7e7fb58f 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -228,7 +228,7 @@ namespace osu.Game.Screens.Play sampleRestart = new SkinnableSound(new SampleInfo(@"Gameplay/restart", @"pause-retry-click")) }; - if (Beatmap.Value.BeatmapInfo.EpilepsyWarning) + if (Beatmap.Value.Beatmap.EpilepsyWarning) { disclaimers.Add(epilepsyWarning = new PlayerLoaderDisclaimer(PlayerLoaderStrings.EpilepsyWarningTitle, PlayerLoaderStrings.EpilepsyWarningContent)); } From c216283bf4ee5964e793869f2a63d941e58e6d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 13:32:23 +0200 Subject: [PATCH 012/223] Move `SamplesMatchPlaybackRate` out of `BeatmapInfo` --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 ++-- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 4 ++-- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ osu.Game/Screens/Edit/Setup/DesignSection.cs | 4 ++-- 11 files changed, 24 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index d3b027d253..51c1e51d3b 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsFalse(beatmap.LetterboxInBreaks); Assert.IsFalse(beatmap.SpecialStyle); Assert.IsFalse(beatmap.WidescreenStoryboard); - Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate); + Assert.IsFalse(beatmap.SamplesMatchPlaybackRate); Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); Assert.AreEqual(0, beatmapInfo.CountdownOffset); } @@ -956,7 +956,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decoded.LetterboxInBreaks, Is.False); Assert.That(decoded.WidescreenStoryboard, Is.False); Assert.That(decoded.EpilepsyWarning, Is.False); - Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False); + Assert.That(decoded.SamplesMatchPlaybackRate, Is.False); Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.Normal)); Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0)); Assert.That(decoded.BeatmapInfo.Metadata.PreviewTime, Is.EqualTo(-1)); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 7516c8958c..76864a1d70 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -126,6 +126,8 @@ namespace osu.Game.Beatmaps public bool EpilepsyWarning { get; set; } + public bool SamplesMatchPlaybackRate { get; set; } = true; + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index f9be44599f..b86a445aa8 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -73,6 +73,7 @@ namespace osu.Game.Beatmaps beatmap.LetterboxInBreaks = original.LetterboxInBreaks; beatmap.WidescreenStoryboard = original.WidescreenStoryboard; beatmap.EpilepsyWarning = original.EpilepsyWarning; + beatmap.SamplesMatchPlaybackRate = original.SamplesMatchPlaybackRate; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 232c8b7e24..650b0bf510 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -414,7 +414,6 @@ namespace osu.Game.Beatmaps Hash = hash, DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, - SamplesMatchPlaybackRate = decodedInfo.SamplesMatchPlaybackRate, DistanceSpacing = decodedInfo.DistanceSpacing, BeatDivisor = decodedInfo.BeatDivisor, GridSize = decodedInfo.GridSize, diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 41a0260250..93abfa8d9b 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -138,8 +138,6 @@ namespace osu.Game.Beatmaps #region Properties we may not want persisted (but also maybe no harm?) - public bool SamplesMatchPlaybackRate { get; set; } = true; - /// /// The time at which this beatmap was last played by the local user. /// diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 1152e5f30d..0c8770782d 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -186,7 +186,7 @@ namespace osu.Game.Beatmaps.Formats private static void applyLegacyDefaults(Beatmap beatmap) { beatmap.WidescreenStoryboard = false; - beatmap.BeatmapInfo.SamplesMatchPlaybackRate = false; + beatmap.SamplesMatchPlaybackRate = false; } protected override void ParseLine(Beatmap beatmap, Section section, string line) @@ -301,7 +301,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"SamplesMatchPlaybackRate": - beatmap.BeatmapInfo.SamplesMatchPlaybackRate = Parsing.ParseInt(pair.Value) == 1; + beatmap.SamplesMatchPlaybackRate = Parsing.ParseInt(pair.Value) == 1; break; case @"Countdown": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index d7078a71d9..860ca68f6b 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -102,7 +102,7 @@ namespace osu.Game.Beatmaps.Formats if (onlineRulesetID == 3) writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.SpecialStyle ? '1' : '0')}")); writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.WidescreenStoryboard ? '1' : '0')}")); - if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate) + if (beatmap.SamplesMatchPlaybackRate) writer.WriteLine(@"SamplesMatchPlaybackRate: 1"); } diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 3d563004d1..9900609f18 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -80,6 +80,8 @@ namespace osu.Game.Beatmaps bool EpilepsyWarning { get; internal set; } + bool SamplesMatchPlaybackRate { get; internal set; } + /// /// 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 7f9d2ae6ab..960eab6f2c 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -373,6 +373,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.EpilepsyWarning = value; } + public bool SamplesMatchPlaybackRate + { + get => baseBeatmap.SamplesMatchPlaybackRate; + set => baseBeatmap.SamplesMatchPlaybackRate = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 063803ab42..4303764fa7 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -220,6 +220,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.EpilepsyWarning = value; } + public bool SamplesMatchPlaybackRate + { + get => PlayableBeatmap.SamplesMatchPlaybackRate; + set => PlayableBeatmap.SamplesMatchPlaybackRate = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index 5d729cf4f8..4c4755064f 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Edit.Setup { Label = EditorSetupStrings.SamplesMatchPlaybackRate, Description = EditorSetupStrings.SamplesMatchPlaybackRateDescription, - Current = { Value = Beatmap.BeatmapInfo.SamplesMatchPlaybackRate } + Current = { Value = Beatmap.SamplesMatchPlaybackRate } } }; } @@ -124,7 +124,7 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.WidescreenStoryboard = widescreenSupport.Current.Value; Beatmap.EpilepsyWarning = epilepsyWarning.Current.Value; Beatmap.LetterboxInBreaks = letterboxDuringBreaks.Current.Value; - Beatmap.BeatmapInfo.SamplesMatchPlaybackRate = samplesMatchPlaybackRate.Current.Value; + Beatmap.SamplesMatchPlaybackRate = samplesMatchPlaybackRate.Current.Value; Beatmap.SaveState(); } From 3634307d7ceadc23604014fba8aaa077682f2988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 13:36:27 +0200 Subject: [PATCH 013/223] Move `DistanceSpacing` out of `BeatmapInfo` --- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 14 +++++++------- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- .../Visual/Editing/TestSceneHitObjectComposer.cs | 4 ++-- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 14 -------------- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 13 +++++++++++++ .../Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ .../Rulesets/Edit/ComposerDistanceSnapProvider.cs | 4 ++-- osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs | 2 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ 14 files changed, 43 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 51c1e51d3b..71dcd38bcd 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new LineBufferedReader(resStream)) { - var beatmapInfo = decoder.Decode(stream).BeatmapInfo; + var beatmap = decoder.Decode(stream); int[] expectedBookmarks = { @@ -109,13 +109,13 @@ namespace osu.Game.Tests.Beatmaps.Formats 95901, 106450, 116999, 119637, 130186, 140735, 151285, 161834, 164471, 175020, 185570, 196119, 206669, 209306 }; - Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length); + Assert.AreEqual(expectedBookmarks.Length, beatmap.BeatmapInfo.Bookmarks.Length); for (int i = 0; i < expectedBookmarks.Length; i++) - Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]); - Assert.AreEqual(1.8, beatmapInfo.DistanceSpacing); - Assert.AreEqual(4, beatmapInfo.BeatDivisor); - Assert.AreEqual(4, beatmapInfo.GridSize); - Assert.AreEqual(2, beatmapInfo.TimelineZoom); + Assert.AreEqual(expectedBookmarks[i], beatmap.BeatmapInfo.Bookmarks[i]); + Assert.AreEqual(1.8, beatmap.DistanceSpacing); + Assert.AreEqual(4, beatmap.BeatmapInfo.BeatDivisor); + Assert.AreEqual(4, beatmap.BeatmapInfo.GridSize); + Assert.AreEqual(2, beatmap.BeatmapInfo.TimelineZoom); } } diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 92715b6aa2..4832ee26b7 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length); for (int i = 0; i < expectedBookmarks.Length; i++) Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]); - Assert.AreEqual(1.8, beatmapInfo.DistanceSpacing); + Assert.AreEqual(1.8, beatmap.DistanceSpacing); Assert.AreEqual(4, beatmapInfo.BeatDivisor); Assert.AreEqual(4, beatmapInfo.GridSize); Assert.AreEqual(2, beatmapInfo.TimelineZoom); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index f392841ac7..d77c86729a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -165,7 +165,7 @@ namespace osu.Game.Tests.Visual.Editing { double originalSpacing = 0; - AddStep("retrieve original spacing", () => originalSpacing = editorBeatmap.BeatmapInfo.DistanceSpacing); + AddStep("retrieve original spacing", () => originalSpacing = editorBeatmap.DistanceSpacing); AddStep("hold ctrl", () => InputManager.PressKey(Key.LControl)); AddStep("hold alt", () => InputManager.PressKey(Key.LAlt)); @@ -175,7 +175,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("release alt", () => InputManager.ReleaseKey(Key.LAlt)); AddStep("release ctrl", () => InputManager.ReleaseKey(Key.LControl)); - AddAssert("distance spacing increased by 0.5", () => editorBeatmap.BeatmapInfo.DistanceSpacing == originalSpacing + 0.5); + AddAssert("distance spacing increased by 0.5", () => editorBeatmap.DistanceSpacing == originalSpacing + 0.5); } public partial class EditorBeatmapContainer : PopoverContainer diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 76864a1d70..2f7c00af4a 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -128,6 +128,8 @@ namespace osu.Game.Beatmaps public bool SamplesMatchPlaybackRate { get; set; } = true; + public double DistanceSpacing { get; set; } = 1.0; + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index b86a445aa8..eda7f8025f 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -74,6 +74,7 @@ namespace osu.Game.Beatmaps beatmap.WidescreenStoryboard = original.WidescreenStoryboard; beatmap.EpilepsyWarning = original.EpilepsyWarning; beatmap.SamplesMatchPlaybackRate = original.SamplesMatchPlaybackRate; + beatmap.DistanceSpacing = original.DistanceSpacing; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 650b0bf510..a8964a365a 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -414,7 +414,6 @@ namespace osu.Game.Beatmaps Hash = hash, DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, - DistanceSpacing = decodedInfo.DistanceSpacing, BeatDivisor = decodedInfo.BeatDivisor, GridSize = decodedInfo.GridSize, TimelineZoom = decodedInfo.TimelineZoom, diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 93abfa8d9b..8328e3df95 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -6,14 +6,12 @@ using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using Newtonsoft.Json; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Collections; using osu.Game.Database; using osu.Game.Models; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets; -using osu.Game.Rulesets.Edit; using osu.Game.Scoring; using Realms; @@ -143,18 +141,6 @@ namespace osu.Game.Beatmaps /// public DateTimeOffset? LastPlayed { get; set; } - /// - /// The ratio of distance travelled per time unit. - /// Generally used to decouple the spacing between hit objects from the enforced "velocity" of the beatmap (see ). - /// - /// - /// The most common method of understanding is that at a default value of 1.0, the time-to-distance ratio will match the slider velocity of the beatmap - /// at the current point in time. Increasing this value will make hit objects more spaced apart when compared to the cursor movement required to track a slider. - /// - /// This is only a hint property, used by the editor in implementations. It does not directly affect the beatmap or gameplay. - /// - public double DistanceSpacing { get; set; } = 1.0; - public int BeatDivisor { get; set; } = 4; public int GridSize { get; set; } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 0c8770782d..92a26464ee 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -329,7 +329,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"DistanceSpacing": - beatmap.BeatmapInfo.DistanceSpacing = Math.Max(0, Parsing.ParseDouble(pair.Value)); + beatmap.DistanceSpacing = Math.Max(0, Parsing.ParseDouble(pair.Value)); break; case @"BeatDivisor": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 860ca68f6b..a07e8d2226 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -112,7 +112,7 @@ namespace osu.Game.Beatmaps.Formats if (beatmap.BeatmapInfo.Bookmarks.Length > 0) writer.WriteLine(FormattableString.Invariant($"Bookmarks: {string.Join(',', beatmap.BeatmapInfo.Bookmarks)}")); - writer.WriteLine(FormattableString.Invariant($"DistanceSpacing: {beatmap.BeatmapInfo.DistanceSpacing}")); + writer.WriteLine(FormattableString.Invariant($"DistanceSpacing: {beatmap.DistanceSpacing}")); writer.WriteLine(FormattableString.Invariant($"BeatDivisor: {beatmap.BeatmapInfo.BeatDivisor}")); writer.WriteLine(FormattableString.Invariant($"GridSize: {beatmap.BeatmapInfo.GridSize}")); writer.WriteLine(FormattableString.Invariant($"TimelineZoom: {beatmap.BeatmapInfo.TimelineZoom}")); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 9900609f18..b2c8b7604a 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -82,6 +83,18 @@ namespace osu.Game.Beatmaps bool SamplesMatchPlaybackRate { get; internal set; } + /// + /// The ratio of distance travelled per time unit. + /// Generally used to decouple the spacing between hit objects from the enforced "velocity" of the beatmap (see ). + /// + /// + /// The most common method of understanding is that at a default value of 1.0, the time-to-distance ratio will match the slider velocity of the beatmap + /// at the current point in time. Increasing this value will make hit objects more spaced apart when compared to the cursor movement required to track a slider. + /// + /// This is only a hint property, used by the editor in implementations. It does not directly affect the beatmap or gameplay. + /// + double DistanceSpacing { get; internal set; } + /// /// 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 960eab6f2c..7473882c15 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -379,6 +379,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.SamplesMatchPlaybackRate = value; } + public double DistanceSpacing + { + get => baseBeatmap.DistanceSpacing; + set => baseBeatmap.DistanceSpacing = value; + } + #endregion } } diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index b9850a94a3..665e6ba074 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Edit } }); - DistanceSpacingMultiplier.Value = editorBeatmap.BeatmapInfo.DistanceSpacing; + DistanceSpacingMultiplier.Value = editorBeatmap.DistanceSpacing; DistanceSpacingMultiplier.BindValueChanged(multiplier => { distanceSpacingSlider.ContractedLabelText = $"D. S. ({multiplier.NewValue:0.##x})"; @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Edit if (multiplier.NewValue != multiplier.OldValue) onScreenDisplay?.Display(new DistanceSpacingToast(multiplier.NewValue.ToLocalisableString(@"0.##x"), multiplier)); - editorBeatmap.BeatmapInfo.DistanceSpacing = multiplier.NewValue; + editorBeatmap.DistanceSpacing = multiplier.NewValue; }, true); DistanceSpacingMultiplier.BindDisabledChanged(disabled => distanceSpacingSlider.Alpha = disabled ? 0 : 1, true); diff --git a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs index 380038eadf..c312642fbd 100644 --- a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Edit /// A multiplier which changes the ratio of distance travelled per time unit. /// Importantly, this is provided for manual usage, and not multiplied into any of the methods exposed by this interface. /// - /// + /// Bindable DistanceSpacingMultiplier { get; } /// diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 4303764fa7..aa63dfab8d 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -226,6 +226,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.SamplesMatchPlaybackRate = value; } + public double DistanceSpacing + { + get => PlayableBeatmap.DistanceSpacing; + set => PlayableBeatmap.DistanceSpacing = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; From 6685c5ab741441140e7e9ba4d1e67b29f3ce828a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 13:44:36 +0200 Subject: [PATCH 014/223] Move `GridSize` out of `BeatmapInfo` --- .../Editor/TestSceneOsuEditorGrids.cs | 2 +- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 4 ++-- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 2 +- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ 13 files changed, 24 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 48aa74c5bf..cb9347b177 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -185,6 +185,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void gridSizeIs(int size) => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing.Value == new Vector2(size) - && EditorBeatmap.BeatmapInfo.GridSize == size); + && EditorBeatmap.GridSize == size); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 21cce553b1..3740c54752 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Edit }, }; - Spacing.Value = editorBeatmap.BeatmapInfo.GridSize; + Spacing.Value = editorBeatmap.GridSize; } protected override void LoadComplete() @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Edit spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:N0}"; spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:N0}"; SpacingVector.Value = new Vector2(spacing.NewValue); - editorBeatmap.BeatmapInfo.GridSize = (int)spacing.NewValue; + editorBeatmap.GridSize = (int)spacing.NewValue; }, true); GridLinesRotation.BindValueChanged(rotation => diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 71dcd38bcd..a15485cdf1 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -114,7 +114,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(expectedBookmarks[i], beatmap.BeatmapInfo.Bookmarks[i]); Assert.AreEqual(1.8, beatmap.DistanceSpacing); Assert.AreEqual(4, beatmap.BeatmapInfo.BeatDivisor); - Assert.AreEqual(4, beatmap.BeatmapInfo.GridSize); + Assert.AreEqual(4, beatmap.GridSize); Assert.AreEqual(2, beatmap.BeatmapInfo.TimelineZoom); } } diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 4832ee26b7..95cba082a7 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]); Assert.AreEqual(1.8, beatmap.DistanceSpacing); Assert.AreEqual(4, beatmapInfo.BeatDivisor); - Assert.AreEqual(4, beatmapInfo.GridSize); + Assert.AreEqual(4, beatmap.GridSize); Assert.AreEqual(2, beatmapInfo.TimelineZoom); } diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 2f7c00af4a..aacbe359b1 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -130,6 +130,8 @@ namespace osu.Game.Beatmaps public double DistanceSpacing { get; set; } = 1.0; + public int GridSize { get; set; } + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index eda7f8025f..a70d449fc5 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -75,6 +75,7 @@ namespace osu.Game.Beatmaps beatmap.EpilepsyWarning = original.EpilepsyWarning; beatmap.SamplesMatchPlaybackRate = original.SamplesMatchPlaybackRate; beatmap.DistanceSpacing = original.DistanceSpacing; + beatmap.GridSize = original.GridSize; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index a8964a365a..ff9bf4b477 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -415,7 +415,6 @@ namespace osu.Game.Beatmaps DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, BeatDivisor = decodedInfo.BeatDivisor, - GridSize = decodedInfo.GridSize, TimelineZoom = decodedInfo.TimelineZoom, MD5Hash = memoryStream.ComputeMD5Hash(), EndTimeObjectCount = decoded.HitObjects.Count(h => h is IHasDuration), diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 8328e3df95..6b192063e8 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -143,8 +143,6 @@ namespace osu.Game.Beatmaps public int BeatDivisor { get; set; } = 4; - public int GridSize { get; set; } - public double TimelineZoom { get; set; } = 1.0; /// diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 92a26464ee..80bfae3036 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -337,7 +337,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"GridSize": - beatmap.BeatmapInfo.GridSize = Parsing.ParseInt(pair.Value); + beatmap.GridSize = Parsing.ParseInt(pair.Value); break; case @"TimelineZoom": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index a07e8d2226..1bfba0962c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -114,7 +114,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"Bookmarks: {string.Join(',', beatmap.BeatmapInfo.Bookmarks)}")); writer.WriteLine(FormattableString.Invariant($"DistanceSpacing: {beatmap.DistanceSpacing}")); writer.WriteLine(FormattableString.Invariant($"BeatDivisor: {beatmap.BeatmapInfo.BeatDivisor}")); - writer.WriteLine(FormattableString.Invariant($"GridSize: {beatmap.BeatmapInfo.GridSize}")); + writer.WriteLine(FormattableString.Invariant($"GridSize: {beatmap.GridSize}")); writer.WriteLine(FormattableString.Invariant($"TimelineZoom: {beatmap.BeatmapInfo.TimelineZoom}")); } diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index b2c8b7604a..ecfc8ea398 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -95,6 +95,8 @@ namespace osu.Game.Beatmaps /// double DistanceSpacing { get; internal set; } + int GridSize { get; internal set; } + /// /// 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 7473882c15..d4a95558cc 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -385,6 +385,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.DistanceSpacing = value; } + public int GridSize + { + get => baseBeatmap.GridSize; + set => baseBeatmap.GridSize = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index aa63dfab8d..b1df60126d 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -232,6 +232,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.DistanceSpacing = value; } + public int GridSize + { + get => PlayableBeatmap.GridSize; + set => PlayableBeatmap.GridSize = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; From 7f2a6f6f5a143e44ca427060eee77c5acad84d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 13:54:31 +0200 Subject: [PATCH 015/223] Move `TimelineZoom` out of `BeatmapInfo` --- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 2 +- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 8 ++++---- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ .../Screens/Edit/Compose/Components/Timeline/Timeline.cs | 6 +++--- osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ 13 files changed, 28 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index a15485cdf1..af0e4a8b3c 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(1.8, beatmap.DistanceSpacing); Assert.AreEqual(4, beatmap.BeatmapInfo.BeatDivisor); Assert.AreEqual(4, beatmap.GridSize); - Assert.AreEqual(2, beatmap.BeatmapInfo.TimelineZoom); + Assert.AreEqual(2, beatmap.TimelineZoom); } } diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 95cba082a7..1fed9633f7 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(1.8, beatmap.DistanceSpacing); Assert.AreEqual(4, beatmapInfo.BeatDivisor); Assert.AreEqual(4, beatmap.GridSize); - Assert.AreEqual(2, beatmapInfo.TimelineZoom); + Assert.AreEqual(2, beatmap.TimelineZoom); } [Test] diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index 64c48e74cf..429b458b9f 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("Set beat divisor", () => Editor.Dependencies.Get().Value = 16); AddStep("Set timeline zoom", () => { - originalTimelineZoom = EditorBeatmap.BeatmapInfo.TimelineZoom; + originalTimelineZoom = EditorBeatmap.TimelineZoom; var timeline = Editor.ChildrenOfType().Single(); InputManager.MoveMouseTo(timeline); @@ -81,19 +81,19 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Ensure timeline zoom changed", () => { - changedTimelineZoom = EditorBeatmap.BeatmapInfo.TimelineZoom; + changedTimelineZoom = EditorBeatmap.TimelineZoom; return !Precision.AlmostEquals(changedTimelineZoom, originalTimelineZoom); }); SaveEditor(); AddAssert("Beatmap has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16); - AddAssert("Beatmap has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom); + AddAssert("Beatmap has correct timeline zoom", () => EditorBeatmap.TimelineZoom == changedTimelineZoom); ReloadEditorToSameBeatmap(); AddAssert("Beatmap still has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16); - AddAssert("Beatmap still has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom); + AddAssert("Beatmap still has correct timeline zoom", () => EditorBeatmap.TimelineZoom == changedTimelineZoom); } [Test] diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index aacbe359b1..35bd935d66 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -132,6 +132,8 @@ namespace osu.Game.Beatmaps public int GridSize { get; set; } + public double TimelineZoom { get; set; } = 1.0; + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index a70d449fc5..140771a5d5 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -76,6 +76,7 @@ namespace osu.Game.Beatmaps beatmap.SamplesMatchPlaybackRate = original.SamplesMatchPlaybackRate; beatmap.DistanceSpacing = original.DistanceSpacing; beatmap.GridSize = original.GridSize; + beatmap.TimelineZoom = original.TimelineZoom; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index ff9bf4b477..e230f912ab 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -415,7 +415,6 @@ namespace osu.Game.Beatmaps DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, BeatDivisor = decodedInfo.BeatDivisor, - TimelineZoom = decodedInfo.TimelineZoom, MD5Hash = memoryStream.ComputeMD5Hash(), EndTimeObjectCount = decoded.HitObjects.Count(h => h is IHasDuration), TotalObjectCount = decoded.HitObjects.Count diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 6b192063e8..39cd320ad7 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -143,8 +143,6 @@ namespace osu.Game.Beatmaps public int BeatDivisor { get; set; } = 4; - public double TimelineZoom { get; set; } = 1.0; - /// /// The time in milliseconds when last exiting the editor with this beatmap loaded. /// diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 80bfae3036..cb2b1820d7 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -341,7 +341,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"TimelineZoom": - beatmap.BeatmapInfo.TimelineZoom = Math.Max(0, Parsing.ParseDouble(pair.Value)); + beatmap.TimelineZoom = Math.Max(0, Parsing.ParseDouble(pair.Value)); break; } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 1bfba0962c..d705deb5df 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -115,7 +115,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"DistanceSpacing: {beatmap.DistanceSpacing}")); writer.WriteLine(FormattableString.Invariant($"BeatDivisor: {beatmap.BeatmapInfo.BeatDivisor}")); writer.WriteLine(FormattableString.Invariant($"GridSize: {beatmap.GridSize}")); - writer.WriteLine(FormattableString.Invariant($"TimelineZoom: {beatmap.BeatmapInfo.TimelineZoom}")); + writer.WriteLine(FormattableString.Invariant($"TimelineZoom: {beatmap.TimelineZoom}")); } private void handleMetadata(TextWriter writer) diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index ecfc8ea398..99a9d31807 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -97,6 +97,8 @@ namespace osu.Game.Beatmaps int GridSize { get; internal set; } + double TimelineZoom { get; internal set; } + /// /// 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 d4a95558cc..ebdde0fca6 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -391,6 +391,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.GridSize = value; } + public double TimelineZoom + { + get => baseBeatmap.TimelineZoom; + set => baseBeatmap.TimelineZoom = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index a2704e550c..7c1f2e3730 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Scheduler.AddOnce(applyVisualOffset, beatmap); }, true); - Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); + Zoom = (float)(defaultTimelineZoom * editorBeatmap.TimelineZoom); } private void applyVisualOffset(IBindable beatmap) @@ -215,7 +215,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline float minimumZoom = getZoomLevelForVisibleMilliseconds(10000); float maximumZoom = getZoomLevelForVisibleMilliseconds(500); - float initialZoom = (float)Math.Clamp(defaultTimelineZoom * (editorBeatmap.BeatmapInfo.TimelineZoom == 0 ? 1 : editorBeatmap.BeatmapInfo.TimelineZoom), minimumZoom, maximumZoom); + float initialZoom = (float)Math.Clamp(defaultTimelineZoom * (editorBeatmap.TimelineZoom == 0 ? 1 : editorBeatmap.TimelineZoom), minimumZoom, maximumZoom); SetupZoom(initialZoom, minimumZoom, maximumZoom); @@ -237,7 +237,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override void OnZoomChanged() { base.OnZoomChanged(); - editorBeatmap.BeatmapInfo.TimelineZoom = Zoom / defaultTimelineZoom; + editorBeatmap.TimelineZoom = Zoom / defaultTimelineZoom; } protected override void UpdateAfterChildren() diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index b1df60126d..0d07e16828 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -238,6 +238,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.GridSize = value; } + public double TimelineZoom + { + get => PlayableBeatmap.TimelineZoom; + set => PlayableBeatmap.TimelineZoom = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; From d373f752d667c35ae68ef47eced2dcb3c94920ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 13:58:00 +0200 Subject: [PATCH 016/223] Move `Countdown` out of `BeatmapInfo` --- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 ++-- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs | 8 ++++---- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapInfo.cs | 3 --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ osu.Game/Screens/Edit/Setup/DesignSection.cs | 6 +++--- 11 files changed, 28 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index af0e4a8b3c..9cea6ef507 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -88,7 +88,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsFalse(beatmap.SpecialStyle); Assert.IsFalse(beatmap.WidescreenStoryboard); Assert.IsFalse(beatmap.SamplesMatchPlaybackRate); - Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); + Assert.AreEqual(CountdownType.None, beatmap.Countdown); Assert.AreEqual(0, beatmapInfo.CountdownOffset); } } @@ -957,7 +957,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decoded.WidescreenStoryboard, Is.False); Assert.That(decoded.EpilepsyWarning, Is.False); Assert.That(decoded.SamplesMatchPlaybackRate, Is.False); - Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.Normal)); + Assert.That(decoded.Countdown, Is.EqualTo(CountdownType.Normal)); Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0)); Assert.That(decoded.BeatmapInfo.Metadata.PreviewTime, Is.EqualTo(-1)); Assert.That(decoded.BeatmapInfo.Ruleset.OnlineID, Is.EqualTo(0)); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 1fed9633f7..bc6628cea0 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.AreEqual(false, beatmap.LetterboxInBreaks); Assert.AreEqual(false, beatmap.WidescreenStoryboard); - Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); + Assert.AreEqual(CountdownType.None, beatmap.Countdown); Assert.AreEqual(0, beatmapInfo.CountdownOffset); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs index 9a66e1676d..c91c22a145 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Editing { AddStep("turn countdown off", () => designSection.EnableCountdown.Current.Value = false); - AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.None); + AddAssert("beatmap has correct type", () => editorBeatmap.Countdown == CountdownType.None); AddUntilStep("other controls hidden", () => !designSection.CountdownSettings.IsPresent); } @@ -60,12 +60,12 @@ namespace osu.Game.Tests.Visual.Editing { AddStep("turn countdown on", () => designSection.EnableCountdown.Current.Value = true); - AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.Normal); + AddAssert("beatmap has correct type", () => editorBeatmap.Countdown == CountdownType.Normal); AddUntilStep("other controls shown", () => designSection.CountdownSettings.IsPresent); AddStep("change countdown speed", () => designSection.CountdownSpeed.Current.Value = CountdownType.DoubleSpeed); - AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.DoubleSpeed); + AddAssert("beatmap has correct type", () => editorBeatmap.Countdown == CountdownType.DoubleSpeed); AddUntilStep("other controls still shown", () => designSection.CountdownSettings.IsPresent); } @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.Editing { AddStep("turn countdown on", () => designSection.EnableCountdown.Current.Value = true); - AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.Normal); + AddAssert("beatmap has correct type", () => editorBeatmap.Countdown == CountdownType.Normal); checkOffsetAfter("1", 1); checkOffsetAfter(string.Empty, 0); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 35bd935d66..54fb1fd3b1 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -134,6 +134,8 @@ namespace osu.Game.Beatmaps public double TimelineZoom { get; set; } = 1.0; + public CountdownType Countdown { get; set; } = CountdownType.Normal; + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 39cd320ad7..0214ae4c3a 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -148,9 +148,6 @@ namespace osu.Game.Beatmaps /// public double? EditorTimestamp { get; set; } - [Ignored] - public CountdownType Countdown { get; set; } = CountdownType.Normal; - /// /// The number of beats to move the countdown backwards (compared to its default location). /// diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index cb2b1820d7..48959025c9 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -305,7 +305,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"Countdown": - beatmap.BeatmapInfo.Countdown = Enum.Parse(pair.Value); + beatmap.Countdown = Enum.Parse(pair.Value); break; case @"CountdownOffset": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index d705deb5df..73399e93d0 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -81,7 +81,7 @@ namespace osu.Game.Beatmaps.Formats if (!string.IsNullOrEmpty(beatmap.Metadata.AudioFile)) writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}")); writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.AudioLeadIn}")); writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}")); - writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}")); + writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.Countdown}")); writer.WriteLine(FormattableString.Invariant( $"SampleSet: {toLegacySampleBank(((beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePoints.FirstOrDefault() ?? SampleControlPoint.DEFAULT).SampleBank)}")); writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.StackLeniency}")); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 99a9d31807..cf7bd29088 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -99,6 +99,8 @@ namespace osu.Game.Beatmaps double TimelineZoom { get; internal set; } + CountdownType Countdown { get; internal set; } + /// /// 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 ebdde0fca6..a444cc135b 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -397,6 +397,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.TimelineZoom = value; } + public CountdownType Countdown + { + get => baseBeatmap.Countdown; + set => baseBeatmap.Countdown = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 0d07e16828..a86d1fbaef 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -244,6 +244,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.TimelineZoom = value; } + public CountdownType Countdown + { + get => PlayableBeatmap.Countdown; + set => PlayableBeatmap.Countdown = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index 4c4755064f..b40f1bea72 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Edit.Setup EnableCountdown = new LabelledSwitchButton { Label = EditorSetupStrings.EnableCountdown, - Current = { Value = Beatmap.BeatmapInfo.Countdown != CountdownType.None }, + Current = { Value = Beatmap.Countdown != CountdownType.None }, Description = EditorSetupStrings.CountdownDescription }, CountdownSettings = new FillFlowContainer @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Edit.Setup CountdownSpeed = new LabelledEnumDropdown { Label = EditorSetupStrings.CountdownSpeed, - Current = { Value = Beatmap.BeatmapInfo.Countdown != CountdownType.None ? Beatmap.BeatmapInfo.Countdown : CountdownType.Normal }, + Current = { Value = Beatmap.Countdown != CountdownType.None ? Beatmap.Countdown : CountdownType.Normal }, Items = Enum.GetValues().Where(type => type != CountdownType.None) }, CountdownOffset = new LabelledNumberBox @@ -118,7 +118,7 @@ namespace osu.Game.Screens.Edit.Setup private void updateBeatmap() { - Beatmap.BeatmapInfo.Countdown = EnableCountdown.Current.Value ? CountdownSpeed.Current.Value : CountdownType.None; + Beatmap.Countdown = EnableCountdown.Current.Value ? CountdownSpeed.Current.Value : CountdownType.None; Beatmap.BeatmapInfo.CountdownOffset = int.TryParse(CountdownOffset.Current.Value, NumberStyles.None, CultureInfo.InvariantCulture, out int offset) ? offset : 0; Beatmap.WidescreenStoryboard = widescreenSupport.Current.Value; From dd50d6fa6e61d02e7e0a995a8e47569a5d7564e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 14:03:02 +0200 Subject: [PATCH 017/223] Move `CountdownOffset` out of `BeatmapInfo` --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 ++-- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- .../Database/RealmSubscriptionRegistrationTests.cs | 2 +- osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapInfo.cs | 5 ----- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 4 ++-- osu.Game/Beatmaps/IBeatmap.cs | 5 +++++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ osu.Game/Screens/Edit/Setup/DesignSection.cs | 6 +++--- 13 files changed, 31 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 9cea6ef507..ab0ec7ee39 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsFalse(beatmap.WidescreenStoryboard); Assert.IsFalse(beatmap.SamplesMatchPlaybackRate); Assert.AreEqual(CountdownType.None, beatmap.Countdown); - Assert.AreEqual(0, beatmapInfo.CountdownOffset); + Assert.AreEqual(0, beatmap.CountdownOffset); } } @@ -958,7 +958,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decoded.EpilepsyWarning, Is.False); Assert.That(decoded.SamplesMatchPlaybackRate, Is.False); Assert.That(decoded.Countdown, Is.EqualTo(CountdownType.Normal)); - Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0)); + Assert.That(decoded.CountdownOffset, Is.EqualTo(0)); Assert.That(decoded.BeatmapInfo.Metadata.PreviewTime, Is.EqualTo(-1)); Assert.That(decoded.BeatmapInfo.Ruleset.OnlineID, Is.EqualTo(0)); }); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index bc6628cea0..e57a4fff62 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(false, beatmap.LetterboxInBreaks); Assert.AreEqual(false, beatmap.WidescreenStoryboard); Assert.AreEqual(CountdownType.None, beatmap.Countdown); - Assert.AreEqual(0, beatmapInfo.CountdownOffset); + Assert.AreEqual(0, beatmap.CountdownOffset); } [Test] diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index 45842a952a..541f3b0417 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Database Assert.That(lastChanges?.ModifiedIndices, Is.Empty); Assert.That(lastChanges?.NewModifiedIndices, Is.Empty); - realm.Write(r => r.All().First().Beatmaps.First().CountdownOffset = 5); + realm.Write(r => r.All().First().Beatmaps.First().EditorTimestamp = 5); realm.Run(r => r.Refresh()); Assert.That(collectionChanges, Is.EqualTo(1)); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs index c91c22a145..0011a4ceb4 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("commit text", () => InputManager.Key(Key.Enter)); AddAssert($"displayed value is {expectedFinalValue}", () => designSection.CountdownOffset.Current.Value == expectedFinalValue.ToString(CultureInfo.InvariantCulture)); - AddAssert($"beatmap value is {expectedFinalValue}", () => editorBeatmap.BeatmapInfo.CountdownOffset == expectedFinalValue); + AddAssert($"beatmap value is {expectedFinalValue}", () => editorBeatmap.CountdownOffset == expectedFinalValue); } private partial class TestDesignSection : DesignSection diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 54fb1fd3b1..19a7ee3303 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -136,6 +136,8 @@ namespace osu.Game.Beatmaps public CountdownType Countdown { get; set; } = CountdownType.Normal; + public int CountdownOffset { get; set; } + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index 140771a5d5..8e917a179e 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -77,6 +77,7 @@ namespace osu.Game.Beatmaps beatmap.DistanceSpacing = original.DistanceSpacing; beatmap.GridSize = original.GridSize; beatmap.TimelineZoom = original.TimelineZoom; + beatmap.CountdownOffset = original.CountdownOffset; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 0214ae4c3a..3ed15f52fb 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -148,11 +148,6 @@ namespace osu.Game.Beatmaps /// public double? EditorTimestamp { get; set; } - /// - /// The number of beats to move the countdown backwards (compared to its default location). - /// - public int CountdownOffset { get; set; } - #endregion public bool Equals(BeatmapInfo? other) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 48959025c9..14de31a2a3 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -309,7 +309,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"CountdownOffset": - beatmap.BeatmapInfo.CountdownOffset = Parsing.ParseInt(pair.Value); + beatmap.CountdownOffset = Parsing.ParseInt(pair.Value); break; } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 73399e93d0..b924b7aea5 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -97,8 +97,8 @@ namespace osu.Game.Beatmaps.Formats // writer.WriteLine(@"SkinPreference:" + b.SkinPreference); if (beatmap.EpilepsyWarning) writer.WriteLine(@"EpilepsyWarning: 1"); - if (beatmap.BeatmapInfo.CountdownOffset > 0) - writer.WriteLine(FormattableString.Invariant($@"CountdownOffset: {beatmap.BeatmapInfo.CountdownOffset}")); + if (beatmap.CountdownOffset > 0) + writer.WriteLine(FormattableString.Invariant($@"CountdownOffset: {beatmap.CountdownOffset}")); if (onlineRulesetID == 3) writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.SpecialStyle ? '1' : '0')}")); writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.WidescreenStoryboard ? '1' : '0')}")); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index cf7bd29088..f08fdfaf6a 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -101,6 +101,11 @@ namespace osu.Game.Beatmaps CountdownType Countdown { get; internal set; } + /// + /// The number of beats to move the countdown backwards (compared to its default location). + /// + int CountdownOffset { get; internal set; } + /// /// 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 a444cc135b..6dd85cefe4 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -403,6 +403,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.Countdown = value; } + public int CountdownOffset + { + get => baseBeatmap.CountdownOffset; + set => baseBeatmap.CountdownOffset = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index a86d1fbaef..deb46c3d2e 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -250,6 +250,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.Countdown = value; } + public int CountdownOffset + { + get => PlayableBeatmap.CountdownOffset; + set => PlayableBeatmap.CountdownOffset = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index b40f1bea72..3ed0a78175 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Edit.Setup CountdownOffset = new LabelledNumberBox { Label = EditorSetupStrings.CountdownOffset, - Current = { Value = Beatmap.BeatmapInfo.CountdownOffset.ToString() }, + Current = { Value = Beatmap.CountdownOffset.ToString() }, Description = EditorSetupStrings.CountdownOffsetDescription, } } @@ -113,13 +113,13 @@ namespace osu.Game.Screens.Edit.Setup { updateBeatmap(); // update displayed text to ensure parsed value matches display (i.e. if empty string was provided). - CountdownOffset.Current.Value = Beatmap.BeatmapInfo.CountdownOffset.ToString(CultureInfo.InvariantCulture); + CountdownOffset.Current.Value = Beatmap.CountdownOffset.ToString(CultureInfo.InvariantCulture); } private void updateBeatmap() { Beatmap.Countdown = EnableCountdown.Current.Value ? CountdownSpeed.Current.Value : CountdownType.None; - Beatmap.BeatmapInfo.CountdownOffset = int.TryParse(CountdownOffset.Current.Value, NumberStyles.None, CultureInfo.InvariantCulture, out int offset) ? offset : 0; + Beatmap.CountdownOffset = int.TryParse(CountdownOffset.Current.Value, NumberStyles.None, CultureInfo.InvariantCulture, out int offset) ? offset : 0; Beatmap.WidescreenStoryboard = widescreenSupport.Current.Value; Beatmap.EpilepsyWarning = epilepsyWarning.Current.Value; From 9fbf2872e1a63547f44adafc790f8bc57a1c18a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 14:27:35 +0200 Subject: [PATCH 018/223] Remove no longer applicable region marking --- osu.Game/Beatmaps/BeatmapInfo.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3ed15f52fb..0a6719a96a 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -134,8 +134,6 @@ namespace osu.Game.Beatmaps Status = BeatmapOnlineStatus.None; } - #region Properties we may not want persisted (but also maybe no harm?) - /// /// The time at which this beatmap was last played by the local user. /// @@ -148,8 +146,6 @@ namespace osu.Game.Beatmaps /// public double? EditorTimestamp { get; set; } - #endregion - public bool Equals(BeatmapInfo? other) { if (ReferenceEquals(this, other)) return true; From c67e2dc301696654132b2d31f9f07abc4ede47c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 14:51:20 +0200 Subject: [PATCH 019/223] Bump schema version --- osu.Game/Database/RealmAccess.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 1ece81be50..33b06f32b1 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -93,8 +93,9 @@ namespace osu.Game.Database /// 39 2023-12-19 Migrate any EndTimeObjectCount and TotalObjectCount values of 0 to -1 to better identify non-calculated values. /// 40 2023-12-21 Add ScoreInfo.Version to keep track of which build scores were set on. /// 41 2024-04-17 Add ScoreInfo.TotalScoreWithoutMods for future mod multiplier rebalances. + /// 42 2024-06-12 Removed several properties from ScoreInfo which did not need to be persisted to realm. /// - private const int schema_version = 41; + private const int schema_version = 42; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. From 04527f3c9da63b5fc54d9afd3c7304b0634a8434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 13 Jun 2024 09:30:00 +0200 Subject: [PATCH 020/223] Fix `TestBeatmap` not transferring newly migrated properties --- osu.Game/Tests/Beatmaps/TestBeatmap.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index de7bcfcfaa..863badbd4a 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -28,6 +28,17 @@ namespace osu.Game.Tests.Beatmaps ControlPointInfo = baseBeatmap.ControlPointInfo; Breaks = baseBeatmap.Breaks; UnhandledEventLines = baseBeatmap.UnhandledEventLines; + AudioLeadIn = baseBeatmap.AudioLeadIn; + StackLeniency = baseBeatmap.StackLeniency; + SpecialStyle = baseBeatmap.SpecialStyle; + LetterboxInBreaks = baseBeatmap.LetterboxInBreaks; + WidescreenStoryboard = baseBeatmap.WidescreenStoryboard; + EpilepsyWarning = baseBeatmap.EpilepsyWarning; + SamplesMatchPlaybackRate = baseBeatmap.SamplesMatchPlaybackRate; + DistanceSpacing = baseBeatmap.DistanceSpacing; + GridSize = baseBeatmap.GridSize; + TimelineZoom = baseBeatmap.TimelineZoom; + CountdownOffset = baseBeatmap.CountdownOffset; if (withHitObjects) HitObjects = baseBeatmap.HitObjects; From 1d4d8063622dbc552fd695bfb6561fbe14b63e57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jul 2024 12:19:45 +0200 Subject: [PATCH 021/223] Fix `WidescreenStoryboard` breakage after moving out of `BeatmapInfo` --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 4 ++-- .../Beatmaps/Formats/LegacyStoryboardDecoder.cs | 15 +++++++++++++++ osu.Game/Beatmaps/WorkingBeatmap.cs | 7 ++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index f873eaf535..bd81892d95 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -80,7 +80,7 @@ namespace osu.Game.Beatmaps.Formats this.beatmap = beatmap; this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion; - applyLegacyDefaults(this.beatmap); + ApplyLegacyDefaults(this.beatmap); base.ParseStreamInto(stream, beatmap); @@ -186,7 +186,7 @@ namespace osu.Game.Beatmaps.Formats /// This method's intention is to restore those legacy defaults. /// See also: https://osu.ppy.sh/wiki/en/Client/File_formats/Osu_%28file_format%29 /// - private static void applyLegacyDefaults(Beatmap beatmap) + internal static void ApplyLegacyDefaults(Beatmap beatmap) { beatmap.WidescreenStoryboard = false; beatmap.SamplesMatchPlaybackRate = false; diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 2f9a256d31..dc96c2ff82 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -37,6 +37,17 @@ namespace osu.Game.Beatmaps.Formats SetFallbackDecoder(() => new LegacyStoryboardDecoder()); } + protected override Storyboard CreateTemplateObject() + { + var sb = base.CreateTemplateObject(); + + var beatmap = new Beatmap(); + LegacyBeatmapDecoder.ApplyLegacyDefaults(beatmap); + sb.Beatmap = beatmap; + + return sb; + } + protected override void ParseStreamInto(LineBufferedReader stream, Storyboard storyboard) { this.storyboard = storyboard; @@ -72,6 +83,10 @@ namespace osu.Game.Beatmaps.Formats case "UseSkinSprites": storyboard.UseSkinSprites = pair.Value == "1"; break; + + case @"WidescreenStoryboard": + storyboard.Beatmap.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1; + break; } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 25159996f3..8b0d3dda6a 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -62,7 +62,12 @@ namespace osu.Game.Beatmaps #region Resource getters protected virtual Waveform GetWaveform() => new Waveform(null); - protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo }; + + protected virtual Storyboard GetStoryboard() => new Storyboard + { + BeatmapInfo = BeatmapInfo, + Beatmap = Beatmap, + }; protected abstract IBeatmap GetBeatmap(); public abstract Texture GetBackground(); From 2fd495228c21f9bd8e8700aefdb1b93c69027d3e Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Fri, 25 Oct 2024 02:25:32 -0400 Subject: [PATCH 022/223] Fix post-merge errors --- .../Visual/Online/TestSceneUserStatisticsWatcher.cs | 3 ++- osu.Game/Online/API/IAPIProvider.cs | 5 +++++ osu.Game/Online/UserStatisticsWatcher.cs | 4 ++-- osu.Game/OsuGame.cs | 2 +- osu.Game/OsuGameBase.cs | 11 ++++------- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs index 1454f8c183..e5ccad703e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs @@ -25,6 +25,7 @@ namespace osu.Game.Tests.Visual.Online { protected override bool UseOnlineAPI => false; + private LocalUserStatisticsProvider statisticsProvider = null!; private UserStatisticsWatcher watcher = null!; [Resolved] @@ -109,7 +110,7 @@ namespace osu.Game.Tests.Visual.Online { Clear(); Add(statisticsProvider = new LocalUserStatisticsProvider()); - Add(watcher = new SoloStatisticsWatcher(statisticsProvider)); + Add(watcher = new UserStatisticsWatcher(statisticsProvider)); }); } diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index cea2d20d8d..e4d6b07037 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -124,6 +124,11 @@ namespace osu.Game.Online.API /// void Logout(); + /// + /// Schedule a callback to run on the update thread. + /// + internal void Schedule(Action action); + /// /// Constructs a new . May be null if not supported. /// diff --git a/osu.Game/Online/UserStatisticsWatcher.cs b/osu.Game/Online/UserStatisticsWatcher.cs index ea50966ad0..b63bdff17f 100644 --- a/osu.Game/Online/UserStatisticsWatcher.cs +++ b/osu.Game/Online/UserStatisticsWatcher.cs @@ -36,7 +36,7 @@ namespace osu.Game.Online private Dictionary? latestStatistics; - public SoloStatisticsWatcher(LocalUserStatisticsProvider? statisticsProvider = null) + public UserStatisticsWatcher(LocalUserStatisticsProvider? statisticsProvider = null) { this.statisticsProvider = statisticsProvider; } @@ -118,7 +118,7 @@ namespace osu.Game.Online { string rulesetName = scoreInfo.Ruleset.ShortName; - statisticsProvider?.UpdateStatistics(updatedStatistics, callback.Score.Ruleset); + statisticsProvider?.UpdateStatistics(updatedStatistics, scoreInfo.Ruleset); if (latestStatistics == null) return; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index dce24c6ee7..b420578024 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1069,7 +1069,7 @@ namespace osu.Game ScreenStack.Push(CreateLoader().With(l => l.RelativeSizeAxes = Axes.Both)); }); - loadComponentSingleFile(new UserStatisticsWatcher(), Add, true); + loadComponentSingleFile(new UserStatisticsWatcher(LocalUserStatisticsProvider), Add, true); loadComponentSingleFile(Toolbar = new Toolbar { OnHome = delegate diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f4b2f21ea9..7404eb232f 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -212,8 +212,8 @@ namespace osu.Game protected MultiplayerClient MultiplayerClient { get; private set; } private MetadataClient metadataClient; - private SoloStatisticsWatcher soloStatisticsWatcher; - private LocalUserStatisticsProvider localUserStatisticsProvider; + + protected LocalUserStatisticsProvider LocalUserStatisticsProvider { get; private set; } private RealmAccess realm; @@ -330,9 +330,7 @@ namespace osu.Game dependencies.CacheAs(SpectatorClient = new OnlineSpectatorClient(endpoints)); dependencies.CacheAs(MultiplayerClient = new OnlineMultiplayerClient(endpoints)); dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); - - dependencies.CacheAs(localUserStatisticsProvider = new LocalUserStatisticsProvider()); - dependencies.CacheAs(soloStatisticsWatcher = new SoloStatisticsWatcher(localUserStatisticsProvider)); + dependencies.CacheAs(LocalUserStatisticsProvider = new LocalUserStatisticsProvider()); base.Content.Add(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient)); @@ -375,8 +373,7 @@ namespace osu.Game base.Content.Add(SpectatorClient); base.Content.Add(MultiplayerClient); base.Content.Add(metadataClient); - base.Content.Add(localUserStatisticsProvider); - base.Content.Add(soloStatisticsWatcher); + base.Content.Add(LocalUserStatisticsProvider); base.Content.Add(rulesetConfigCache); From 3a57b21c89e6917a25ba9a748a7be7e1d86985e2 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Fri, 25 Oct 2024 02:38:41 -0400 Subject: [PATCH 023/223] Move `LocalUserStatisticsProvider` to non-base game class and make dependency optional --- osu.Game/OsuGame.cs | 5 ++++- osu.Game/OsuGameBase.cs | 4 ---- osu.Game/Users/UserRankPanel.cs | 17 ++++++++++------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b420578024..f7e6184dac 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1069,7 +1069,10 @@ namespace osu.Game ScreenStack.Push(CreateLoader().With(l => l.RelativeSizeAxes = Axes.Both)); }); - loadComponentSingleFile(new UserStatisticsWatcher(LocalUserStatisticsProvider), Add, true); + LocalUserStatisticsProvider statisticsProvider; + + loadComponentSingleFile(statisticsProvider = new LocalUserStatisticsProvider(), Add, true); + loadComponentSingleFile(new UserStatisticsWatcher(statisticsProvider), Add, true); loadComponentSingleFile(Toolbar = new Toolbar { OnHome = delegate diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7404eb232f..d4704d1c72 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -213,8 +213,6 @@ namespace osu.Game private MetadataClient metadataClient; - protected LocalUserStatisticsProvider LocalUserStatisticsProvider { get; private set; } - private RealmAccess realm; protected SafeAreaContainer SafeAreaContainer { get; private set; } @@ -330,7 +328,6 @@ namespace osu.Game dependencies.CacheAs(SpectatorClient = new OnlineSpectatorClient(endpoints)); dependencies.CacheAs(MultiplayerClient = new OnlineMultiplayerClient(endpoints)); dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); - dependencies.CacheAs(LocalUserStatisticsProvider = new LocalUserStatisticsProvider()); base.Content.Add(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient)); @@ -373,7 +370,6 @@ namespace osu.Game base.Content.Add(SpectatorClient); base.Content.Add(MultiplayerClient); base.Content.Add(metadataClient); - base.Content.Add(LocalUserStatisticsProvider); base.Content.Add(rulesetConfigCache); diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index 70885940e1..a761ddaea7 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -45,19 +45,22 @@ namespace osu.Game.Users } [Resolved] - private LocalUserStatisticsProvider statisticsProvider { get; set; } = null!; + private LocalUserStatisticsProvider? statisticsProvider { get; set; } protected override void LoadComplete() { base.LoadComplete(); - statistics.BindTo(statisticsProvider.Statistics); - statistics.BindValueChanged(stats => + if (statisticsProvider != null) { - loadingLayer.State.Value = stats.NewValue == null ? Visibility.Visible : Visibility.Hidden; - globalRankDisplay.Content = stats.NewValue?.GlobalRank?.ToLocalisableString("\\##,##0") ?? "-"; - countryRankDisplay.Content = stats.NewValue?.CountryRank?.ToLocalisableString("\\##,##0") ?? "-"; - }, true); + statistics.BindTo(statisticsProvider.Statistics); + statistics.BindValueChanged(stats => + { + loadingLayer.State.Value = stats.NewValue == null ? Visibility.Visible : Visibility.Hidden; + globalRankDisplay.Content = stats.NewValue?.GlobalRank?.ToLocalisableString("\\##,##0") ?? "-"; + countryRankDisplay.Content = stats.NewValue?.CountryRank?.ToLocalisableString("\\##,##0") ?? "-"; + }, true); + } } protected override Drawable CreateLayout() From 44dd81363ac98fc6648e048928f42431bf0464dc Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Fri, 25 Oct 2024 03:06:41 -0400 Subject: [PATCH 024/223] Make `UserStatisticsWatcher` fully rely on `LocalUserStatisticsProvider` --- .../Online/LocalUserStatisticsProvider.cs | 7 +++ osu.Game/Online/UserStatisticsWatcher.cs | 49 ++----------------- 2 files changed, 12 insertions(+), 44 deletions(-) diff --git a/osu.Game/Online/LocalUserStatisticsProvider.cs b/osu.Game/Online/LocalUserStatisticsProvider.cs index e2f016b336..372bb090d6 100644 --- a/osu.Game/Online/LocalUserStatisticsProvider.cs +++ b/osu.Game/Online/LocalUserStatisticsProvider.cs @@ -33,6 +33,13 @@ namespace osu.Game.Online private readonly Dictionary allStatistics = new Dictionary(); + /// + /// Returns the currently available for the given ruleset. + /// This may return null if the requested statistics has not been fetched yet. + /// + /// The ruleset to return the corresponding for. + internal UserStatistics? GetStatisticsFor(RulesetInfo ruleset) => allStatistics.GetValueOrDefault(ruleset.ShortName); + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Online/UserStatisticsWatcher.cs b/osu.Game/Online/UserStatisticsWatcher.cs index b63bdff17f..162204e4e8 100644 --- a/osu.Game/Online/UserStatisticsWatcher.cs +++ b/osu.Game/Online/UserStatisticsWatcher.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; @@ -10,7 +9,6 @@ using osu.Framework.Graphics; using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.API.Requests; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Spectator; using osu.Game.Scoring; using osu.Game.Users; @@ -34,8 +32,6 @@ namespace osu.Game.Online private readonly Dictionary watchedScores = new Dictionary(); - private Dictionary? latestStatistics; - public UserStatisticsWatcher(LocalUserStatisticsProvider? statisticsProvider = null) { this.statisticsProvider = statisticsProvider; @@ -44,8 +40,6 @@ namespace osu.Game.Online protected override void LoadComplete() { base.LoadComplete(); - - api.LocalUser.BindValueChanged(user => onUserChanged(user.NewValue), true); spectatorClient.OnUserScoreProcessed += userScoreProcessed; } @@ -67,35 +61,6 @@ namespace osu.Game.Online }); } - private void onUserChanged(APIUser? localUser) => Schedule(() => - { - latestStatistics = null; - - if (localUser == null || localUser.OnlineID <= 1) - return; - - var userRequest = new GetUsersRequest(new[] { localUser.OnlineID }); - userRequest.Success += initialiseUserStatistics; - api.Queue(userRequest); - }); - - private void initialiseUserStatistics(GetUsersResponse response) => Schedule(() => - { - var user = response.Users.SingleOrDefault(); - - // possible if the user is restricted or similar. - if (user == null) - return; - - latestStatistics = new Dictionary(); - - if (user.RulesetsStatistics != null) - { - foreach (var rulesetStats in user.RulesetsStatistics) - latestStatistics.Add(rulesetStats.Key, rulesetStats.Value); - } - }); - private void userScoreProcessed(int userId, long scoreId) { if (userId != api.LocalUser.Value?.OnlineID) @@ -116,18 +81,14 @@ namespace osu.Game.Online private void dispatchStatisticsUpdate(ScoreInfo scoreInfo, UserStatistics updatedStatistics) { - string rulesetName = scoreInfo.Ruleset.ShortName; - - statisticsProvider?.UpdateStatistics(updatedStatistics, scoreInfo.Ruleset); - - if (latestStatistics == null) + if (statisticsProvider == null) return; - latestStatistics.TryGetValue(rulesetName, out UserStatistics? latestRulesetStatistics); - latestRulesetStatistics ??= new UserStatistics(); + var latestRulesetStatistics = statisticsProvider.GetStatisticsFor(scoreInfo.Ruleset); + statisticsProvider.UpdateStatistics(updatedStatistics, scoreInfo.Ruleset); - latestUpdate.Value = new UserStatisticsUpdate(scoreInfo, latestRulesetStatistics, updatedStatistics); - latestStatistics[rulesetName] = updatedStatistics; + if (latestRulesetStatistics != null) + latestUpdate.Value = new UserStatisticsUpdate(scoreInfo, latestRulesetStatistics, updatedStatistics); } protected override void Dispose(bool isDisposing) From 663b769c710644cd12479b4a690cc24503f538bc Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Fri, 25 Oct 2024 03:30:43 -0400 Subject: [PATCH 025/223] Update `DiscordRichPresence` to use new statistics provider component --- osu.Desktop/DiscordRichPresence.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 5a7a01df1b..3ad4112733 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -15,6 +15,7 @@ using osu.Framework.Threading; using osu.Game; using osu.Game.Configuration; using osu.Game.Extensions; +using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; @@ -47,6 +48,9 @@ namespace osu.Desktop [Resolved] private MultiplayerClient multiplayerClient { get; set; } = null!; + [Resolved] + private LocalUserStatisticsProvider statisticsProvider { get; set; } = null!; + [Resolved] private OsuConfigManager config { get; set; } = null!; @@ -65,6 +69,7 @@ namespace osu.Desktop }; private IBindable? user; + private IBindable? localStatistics; [BackgroundDependencyLoader] private void load() @@ -117,6 +122,10 @@ namespace osu.Desktop status.BindValueChanged(_ => schedulePresenceUpdate()); activity.BindValueChanged(_ => schedulePresenceUpdate()); privacyMode.BindValueChanged(_ => schedulePresenceUpdate()); + + localStatistics = statisticsProvider.Statistics.GetBoundCopy(); + localStatistics.BindValueChanged(_ => schedulePresenceUpdate()); + multiplayerClient.RoomUpdated += onRoomUpdated; } @@ -158,7 +167,7 @@ namespace osu.Desktop private void updatePresence(bool hideIdentifiableInformation) { - if (user == null) + if (user == null || localStatistics == null) return; // user activity @@ -228,12 +237,7 @@ namespace osu.Desktop if (privacyMode.Value == DiscordRichPresenceMode.Limited) presence.Assets.LargeImageText = string.Empty; else - { - if (user.Value.RulesetsStatistics != null && user.Value.RulesetsStatistics.TryGetValue(ruleset.Value.ShortName, out UserStatistics? statistics)) - presence.Assets.LargeImageText = $"{user.Value.Username}" + (statistics.GlobalRank > 0 ? $" (rank #{statistics.GlobalRank:N0})" : string.Empty); - else - presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty); - } + presence.Assets.LargeImageText = $"{user.Value.Username}" + (localStatistics.Value?.GlobalRank > 0 ? $" (rank #{localStatistics.Value?.GlobalRank:N0})" : string.Empty); // small image presence.Assets.SmallImageKey = ruleset.Value.IsLegacyRuleset() ? $"mode_{ruleset.Value.OnlineID}" : "mode_custom"; From 979065c4212a425c9438c157089895c2eeac48de Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sat, 26 Oct 2024 23:09:16 -0400 Subject: [PATCH 026/223] Reorder code slightly --- .../Online/LocalUserStatisticsProvider.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game/Online/LocalUserStatisticsProvider.cs b/osu.Game/Online/LocalUserStatisticsProvider.cs index 372bb090d6..e64f88759e 100644 --- a/osu.Game/Online/LocalUserStatisticsProvider.cs +++ b/osu.Game/Online/LocalUserStatisticsProvider.cs @@ -14,31 +14,24 @@ using osu.Game.Users; namespace osu.Game.Online { /// - /// A component that is responsible for providing the latest statistics of the logged-in user for the game-wide selected ruleset. + /// A component that keeps track of the latest statistics for the local user. /// public partial class LocalUserStatisticsProvider : Component { - /// - /// The statistics of the logged-in user for the game-wide selected ruleset. - /// - public IBindable Statistics => statistics; - - private readonly Bindable statistics = new Bindable(); - [Resolved] private IBindable ruleset { get; set; } = null!; [Resolved] private IAPIProvider api { get; set; } = null!; - private readonly Dictionary allStatistics = new Dictionary(); - /// - /// Returns the currently available for the given ruleset. - /// This may return null if the requested statistics has not been fetched yet. + /// The statistics of the local user for the game-wide selected ruleset. /// - /// The ruleset to return the corresponding for. - internal UserStatistics? GetStatisticsFor(RulesetInfo ruleset) => allStatistics.GetValueOrDefault(ruleset.ShortName); + public IBindable Statistics => statistics; + + private readonly Bindable statistics = new Bindable(); + + private readonly Dictionary allStatistics = new Dictionary(); protected override void LoadComplete() { @@ -90,6 +83,13 @@ namespace osu.Game.Online api.Queue(currentRequest); } + /// + /// Returns the currently available for the given ruleset. + /// This may return null if the requested statistics has not been fetched yet. + /// + /// The ruleset to return the corresponding for. + internal UserStatistics? GetStatisticsFor(RulesetInfo ruleset) => allStatistics.GetValueOrDefault(ruleset.ShortName); + internal void UpdateStatistics(UserStatistics statistics, RulesetInfo statisticsRuleset) { allStatistics[statisticsRuleset.ShortName] = statistics; From 4a628287e260e0120adc2dd102fcfc8c81930a78 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 17 Nov 2024 18:13:37 -0500 Subject: [PATCH 027/223] Decouple game-wide ruleset bindable and refactor `LocalUserStatisticsProvider` This also throws away the logic of updating `API.LocalUser.Value.Statistics`. Components should rely on `LocalUserStatisticsProvider` instead for proper behaviour and ability to update on statistics updates. --- osu.Desktop/DiscordRichPresence.cs | 13 ++- .../Menus/TestSceneToolbarUserButton.cs | 12 +-- .../TestSceneLocalUserStatisticsProvider.cs | 88 +++++++++++------ .../Visual/Online/TestSceneUserPanel.cs | 10 +- .../Online/TestSceneUserStatisticsWatcher.cs | 19 ++-- .../Visual/Ranking/TestSceneOverallRanking.cs | 2 +- .../Ranking/TestSceneStatisticsPanel.cs | 6 +- .../Online/API/Requests/Responses/APIUser.cs | 12 ++- .../Online/LocalUserStatisticsProvider.cs | 98 ++++++++----------- ...e.cs => ScoreBasedUserStatisticsUpdate.cs} | 6 +- osu.Game/Online/UserStatisticsWatcher.cs | 38 ++++--- .../TransientUserStatisticsUpdateDisplay.cs | 4 +- .../Ranking/Statistics/User/OverallRanking.cs | 4 +- .../Statistics/User/RankingChangeRow.cs | 4 +- .../Ranking/Statistics/UserStatisticsPanel.cs | 4 +- osu.Game/Users/UserRankPanel.cs | 26 +++-- 16 files changed, 188 insertions(+), 158 deletions(-) rename osu.Game/Online/{UserStatisticsUpdate.cs => ScoreBasedUserStatisticsUpdate.cs} (84%) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 3ad4112733..c9529d2f5e 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -69,7 +69,7 @@ namespace osu.Desktop }; private IBindable? user; - private IBindable? localStatistics; + private IBindable? statisticsUpdate; [BackgroundDependencyLoader] private void load() @@ -123,8 +123,8 @@ namespace osu.Desktop activity.BindValueChanged(_ => schedulePresenceUpdate()); privacyMode.BindValueChanged(_ => schedulePresenceUpdate()); - localStatistics = statisticsProvider.Statistics.GetBoundCopy(); - localStatistics.BindValueChanged(_ => schedulePresenceUpdate()); + statisticsUpdate = statisticsProvider.StatisticsUpdate.GetBoundCopy(); + statisticsUpdate.BindValueChanged(_ => schedulePresenceUpdate()); multiplayerClient.RoomUpdated += onRoomUpdated; } @@ -167,7 +167,7 @@ namespace osu.Desktop private void updatePresence(bool hideIdentifiableInformation) { - if (user == null || localStatistics == null) + if (user == null) return; // user activity @@ -237,7 +237,10 @@ namespace osu.Desktop if (privacyMode.Value == DiscordRichPresenceMode.Limited) presence.Assets.LargeImageText = string.Empty; else - presence.Assets.LargeImageText = $"{user.Value.Username}" + (localStatistics.Value?.GlobalRank > 0 ? $" (rank #{localStatistics.Value?.GlobalRank:N0})" : string.Empty); + { + var statistics = statisticsProvider.GetStatisticsFor(ruleset.Value); + presence.Assets.LargeImageText = $"{user.Value.Username}" + (statistics?.GlobalRank > 0 ? $" (rank #{statistics.GlobalRank:N0})" : string.Empty); + } // small image presence.Assets.SmallImageKey = ruleset.Value.IsLegacyRuleset() ? $"mode_{ruleset.Value.OnlineID}" : "mode_custom"; diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs index 71a45e2398..1af4af8f6b 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbarUserButton.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("Gain", () => { var transientUpdateDisplay = this.ChildrenOfType().Single(); - transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate( + transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate( new ScoreInfo(), new UserStatistics { @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("Loss", () => { var transientUpdateDisplay = this.ChildrenOfType().Single(); - transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate( + transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate( new ScoreInfo(), new UserStatistics { @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("Tiny increase in PP", () => { var transientUpdateDisplay = this.ChildrenOfType().Single(); - transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate( + transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate( new ScoreInfo(), new UserStatistics { @@ -153,7 +153,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("No change 1", () => { var transientUpdateDisplay = this.ChildrenOfType().Single(); - transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate( + transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate( new ScoreInfo(), new UserStatistics { @@ -170,7 +170,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("Was null", () => { var transientUpdateDisplay = this.ChildrenOfType().Single(); - transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate( + transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate( new ScoreInfo(), new UserStatistics { @@ -187,7 +187,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("Became null", () => { var transientUpdateDisplay = this.ChildrenOfType().Single(); - transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate( + transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate( new ScoreInfo(), new UserStatistics { diff --git a/osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs b/osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs index 1a27fd1de5..342d805be4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs @@ -3,14 +3,15 @@ using System.Collections.Generic; using NUnit.Framework; -using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.Containers; 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.Catch; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; @@ -34,7 +35,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("setup provider", () => { - OsuSpriteText text; + OsuTextFlowContainer text; ((DummyAPIAccess)API).HandleRequest = r => { @@ -59,17 +60,31 @@ namespace osu.Game.Tests.Visual.Online Clear(); Add(statisticsProvider = new LocalUserStatisticsProvider()); - Add(text = new OsuSpriteText + Add(text = new OsuTextFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, }); - statisticsProvider.Statistics.BindValueChanged(s => + statisticsProvider.StatisticsUpdate.BindValueChanged(s => { - text.Text = s.NewValue == null - ? "Statistics: (null)" - : $"Statistics: (total score: {s.NewValue.TotalScore:N0})"; + text.Clear(); + + foreach (var ruleset in Dependencies.Get().AvailableRulesets) + { + text.AddText(statisticsProvider.GetStatisticsFor(ruleset) is UserStatistics statistics + ? $"{ruleset.Name} statistics: (total score: {statistics.TotalScore})" + : $"{ruleset.Name} statistics: (null)"); + text.NewLine(); + } + + if (s.NewValue == null) + text.AddText("latest update: (null)"); + else + { + text.AddText($"latest update: {s.NewValue.Ruleset}" + + $" ({(s.NewValue.OldStatistics?.TotalScore.ToString() ?? "null")} -> {s.NewValue.NewStatistics.TotalScore})"); + } }); Ruleset.Value = new OsuRuleset().RulesetInfo; @@ -79,19 +94,10 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestInitialStatistics() { - AddAssert("initial statistics populated", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(4_000_000)); - } - - [Test] - public void TestRulesetChanges() - { - AddAssert("statistics from osu", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(4_000_000)); - AddStep("change ruleset to taiko", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); - AddAssert("statistics from taiko", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(3_000_000)); - AddStep("change ruleset to catch", () => Ruleset.Value = new CatchRuleset().RulesetInfo); - AddAssert("statistics from catch", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(2_000_000)); - AddStep("change ruleset to mania", () => Ruleset.Value = new ManiaRuleset().RulesetInfo); - AddAssert("statistics from mania", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(1_000_000)); + AddAssert("osu statistics populated", () => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(4_000_000)); + AddAssert("taiko statistics populated", () => statisticsProvider.GetStatisticsFor(new TaikoRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(3_000_000)); + AddAssert("catch statistics populated", () => statisticsProvider.GetStatisticsFor(new CatchRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(2_000_000)); + AddAssert("mania statistics populated", () => statisticsProvider.GetStatisticsFor(new ManiaRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(1_000_000)); } [Test] @@ -105,18 +111,44 @@ namespace osu.Game.Tests.Visual.Online serverSideStatistics[(1000, "taiko")] = new UserStatistics { TotalScore = 6_000_000 }; }); - AddAssert("statistics matches user 1001 from osu", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(4_000_000)); + AddAssert("statistics matches user 1001 in osu", + () => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore, + () => Is.EqualTo(4_000_000)); - AddStep("change ruleset to taiko", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); - AddAssert("statistics matches user 1001 from taiko", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(3_000_000)); + AddAssert("statistics matches user 1001 in taiko", + () => statisticsProvider.GetStatisticsFor(new TaikoRuleset().RulesetInfo)!.TotalScore, + () => Is.EqualTo(3_000_000)); - AddStep("change ruleset to osu", () => Ruleset.Value = new OsuRuleset().RulesetInfo); setUser(1000, false); - AddAssert("statistics matches user 1000 from osu", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(5_000_000)); + AddAssert("statistics matches user 1000 in osu", + () => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore, + () => Is.EqualTo(5_000_000)); - AddStep("change ruleset to osu", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); - AddAssert("statistics matches user 1000 from taiko", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(6_000_000)); + AddAssert("statistics matches user 1000 in taiko", + () => statisticsProvider.GetStatisticsFor(new TaikoRuleset().RulesetInfo)!.TotalScore, + () => Is.EqualTo(6_000_000)); + } + + [Test] + public void TestRefetchStatistics() + { + setUser(1001); + + AddStep("update statistics server side", + () => serverSideStatistics[(1001, "osu")] = new UserStatistics { TotalScore = 9_000_000 }); + + AddAssert("statistics match old score", + () => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore, + () => Is.EqualTo(4_000_000)); + + AddStep("request refetch", () => statisticsProvider.RefetchStatistics(new OsuRuleset().RulesetInfo)); + AddUntilStep("statistics update raised", + () => statisticsProvider.StatisticsUpdate.Value.NewStatistics.TotalScore, + () => Is.EqualTo(9_000_000)); + AddAssert("statistics match new score", + () => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore, + () => Is.EqualTo(9_000_000)); } private UserStatistics tryGetStatistics(int userId, string rulesetName) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index 365dce551c..e291b90361 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -34,8 +34,8 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - [Cached] - private readonly LocalUserStatisticsProvider statisticsProvider = new LocalUserStatisticsProvider(); + [Cached(typeof(LocalUserStatisticsProvider))] + private readonly TestUserStatisticsProvider statisticsProvider = new TestUserStatisticsProvider(); [Resolved] private IRulesetStore rulesetStore { get; set; } @@ -206,5 +206,11 @@ namespace osu.Game.Tests.Visual.Online public new TextFlowContainer LastVisitMessage => base.LastVisitMessage; } + + private partial class TestUserStatisticsProvider : LocalUserStatisticsProvider + { + public new void UpdateStatistics(UserStatistics newStatistics, RulesetInfo ruleset) + => base.UpdateStatistics(newStatistics, ruleset); + } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs index e5ccad703e..c91dfe9eb7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.Online var ruleset = new OsuRuleset().RulesetInfo; - UserStatisticsUpdate? update = null; + ScoreBasedUserStatisticsUpdate? update = null; registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); feignScoreProcessing(userId, ruleset, 5_000_000); @@ -149,7 +149,7 @@ namespace osu.Game.Tests.Visual.Online // note ordering - in this test processing completes *before* the registration is added. feignScoreProcessing(userId, ruleset, 5_000_000); - UserStatisticsUpdate? update = null; + ScoreBasedUserStatisticsUpdate? update = null; registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Online long scoreId = getScoreId(); var ruleset = new OsuRuleset().RulesetInfo; - UserStatisticsUpdate? update = null; + ScoreBasedUserStatisticsUpdate? update = null; registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); feignScoreProcessing(userId, ruleset, 5_000_000); @@ -194,7 +194,7 @@ namespace osu.Game.Tests.Visual.Online long scoreId = getScoreId(); var ruleset = new OsuRuleset().RulesetInfo; - UserStatisticsUpdate? update = null; + ScoreBasedUserStatisticsUpdate? update = null; registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); feignScoreProcessing(userId, ruleset, 5_000_000); @@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.Online long scoreId = getScoreId(); var ruleset = new OsuRuleset().RulesetInfo; - UserStatisticsUpdate? update = null; + ScoreBasedUserStatisticsUpdate? update = null; registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); feignScoreProcessing(userId, ruleset, 5_000_000); @@ -244,7 +244,7 @@ namespace osu.Game.Tests.Visual.Online feignScoreProcessing(userId, ruleset, 6_000_000); - UserStatisticsUpdate? update = null; + ScoreBasedUserStatisticsUpdate? update = null; registerForUpdates(secondScoreId, ruleset, receivedUpdate => update = receivedUpdate); AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, secondScoreId)); @@ -262,15 +262,14 @@ namespace osu.Game.Tests.Visual.Online var ruleset = new OsuRuleset().RulesetInfo; - UserStatisticsUpdate? update = null; + ScoreBasedUserStatisticsUpdate? update = null; registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); feignScoreProcessing(userId, ruleset, 5_000_000); AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); AddUntilStep("update received", () => update != null); - AddAssert("local user values are correct", () => dummyAPI.LocalUser.Value.Statistics.TotalScore, () => Is.EqualTo(5_000_000)); - AddAssert("statistics values are correct", () => statisticsProvider.Statistics.Value!.TotalScore, () => Is.EqualTo(5_000_000)); + AddAssert("statistics values are correct", () => dummyAPI.LocalUser.Value.Statistics.TotalScore, () => Is.EqualTo(5_000_000)); } private int nextUserId = 2000; @@ -292,7 +291,7 @@ namespace osu.Game.Tests.Visual.Online }); } - private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action onUpdateReady) => + private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action onUpdateReady) => AddStep("register for updates", () => { watcher.RegisterForStatisticsUpdateAfter( diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs index ffc7d88a34..b406ea369f 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs @@ -112,6 +112,6 @@ namespace osu.Game.Tests.Visual.Ranking }); private void displayUpdate(UserStatistics before, UserStatistics after) => - AddStep("display update", () => overallRanking.StatisticsUpdate.Value = new UserStatisticsUpdate(new ScoreInfo(), before, after)); + AddStep("display update", () => overallRanking.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 f46f76cbb8..c12b9d29bc 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -91,12 +91,12 @@ namespace osu.Game.Tests.Visual.Ranking UserStatisticsWatcher userStatisticsWatcher = null!; ScoreInfo score = null!; - AddStep("create user statistics watcher", () => Add(userStatisticsWatcher = new UserStatisticsWatcher())); + AddStep("create user statistics watcher", () => Add(userStatisticsWatcher = new UserStatisticsWatcher(new LocalUserStatisticsProvider()))); AddStep("set user statistics update", () => { score = TestResources.CreateTestScoreInfo(); score.OnlineID = 1234; - ((Bindable)userStatisticsWatcher.LatestUpdate).Value = new UserStatisticsUpdate(score, + ((Bindable)userStatisticsWatcher.LatestUpdate).Value = new ScoreBasedUserStatisticsUpdate(score, new UserStatistics { Level = new UserStatistics.LevelInfo @@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.Ranking Score = { Value = score }, DisplayedUserStatisticsUpdate = { - Value = new UserStatisticsUpdate(score, new UserStatistics + Value = new ScoreBasedUserStatisticsUpdate(score, new UserStatistics { Level = new UserStatistics.LevelInfo { diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 5d80fde515..452b5f7654 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -223,12 +223,14 @@ namespace osu.Game.Online.API.Requests.Responses /// /// User statistics for the requested ruleset (in the case of a or response). - /// Otherwise empty. /// + /// + /// This returns null when accessed from . Use instead. + /// [JsonProperty(@"statistics")] public UserStatistics Statistics { - get => statistics ??= new UserStatistics(); + get => statistics; set { if (statistics != null) @@ -242,7 +244,11 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"rank_history")] private APIRankHistory rankHistory { - set => Statistics.RankHistory = value; + set + { + statistics ??= new UserStatistics(); + statistics.RankHistory = value; + } } [JsonProperty(@"active_tournament_banners")] diff --git a/osu.Game/Online/LocalUserStatisticsProvider.cs b/osu.Game/Online/LocalUserStatisticsProvider.cs index e64f88759e..ea4688a307 100644 --- a/osu.Game/Online/LocalUserStatisticsProvider.cs +++ b/osu.Game/Online/LocalUserStatisticsProvider.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -18,84 +19,63 @@ namespace osu.Game.Online /// public partial class LocalUserStatisticsProvider : Component { + private readonly Bindable statisticsUpdate = new Bindable(); + + /// + /// A bindable communicating updates to the local user's statistics on any ruleset. + /// This does not guarantee the presence of old statistics, as it is invoked on initial population of statistics. + /// + public IBindable StatisticsUpdate => statisticsUpdate; + [Resolved] - private IBindable ruleset { get; set; } = null!; + private RulesetStore rulesets { get; set; } = null!; [Resolved] private IAPIProvider api { get; set; } = null!; + private readonly Dictionary statisticsCache = new Dictionary(); + private readonly Dictionary statisticsRequests = new Dictionary(); + /// - /// The statistics of the local user for the game-wide selected ruleset. + /// Returns the currently available for the given ruleset. + /// This may return null if the requested statistics has not been fetched before yet. /// - public IBindable Statistics => statistics; - - private readonly Bindable statistics = new Bindable(); - - private readonly Dictionary allStatistics = new Dictionary(); + /// The ruleset to return the corresponding for. + public UserStatistics? GetStatisticsFor(RulesetInfo ruleset) => statisticsCache.GetValueOrDefault(ruleset.ShortName); protected override void LoadComplete() { base.LoadComplete(); - - statistics.BindValueChanged(v => - { - if (api.LocalUser.Value != null && v.NewValue != null) - api.LocalUser.Value.Statistics = v.NewValue; - }); - - ruleset.BindValueChanged(_ => updateStatisticsBindable()); - - api.LocalUser.BindValueChanged(_ => - { - allStatistics.Clear(); - updateStatisticsBindable(); - }, true); + api.LocalUser.BindValueChanged(_ => initialiseStatistics(), true); } - private GetUserRequest? currentRequest; - - private void updateStatisticsBindable() => Schedule(() => + private void initialiseStatistics() { - statistics.Value = null; + statisticsCache.Clear(); - if (api.LocalUser.Value == null || api.LocalUser.Value.OnlineID <= 1 || !ruleset.Value.IsLegacyRuleset()) - { - statistics.Value = new UserStatistics(); - return; - } - - if (currentRequest?.CompletionState == APIRequestCompletionState.Waiting) - { - currentRequest.Cancel(); - currentRequest = null; - } - - if (allStatistics.TryGetValue(ruleset.Value.ShortName, out var existing)) - statistics.Value = existing; - else - requestStatistics(ruleset.Value); - }); - - private void requestStatistics(RulesetInfo ruleset) - { - currentRequest = new GetUserRequest(api.LocalUser.Value.OnlineID, ruleset); - currentRequest.Success += u => statistics.Value = allStatistics[ruleset.ShortName] = u.Statistics; - api.Queue(currentRequest); + foreach (var ruleset in rulesets.AvailableRulesets.Where(r => r.IsLegacyRuleset())) + RefetchStatistics(ruleset); } - /// - /// Returns the currently available for the given ruleset. - /// This may return null if the requested statistics has not been fetched yet. - /// - /// The ruleset to return the corresponding for. - internal UserStatistics? GetStatisticsFor(RulesetInfo ruleset) => allStatistics.GetValueOrDefault(ruleset.ShortName); - - internal void UpdateStatistics(UserStatistics statistics, RulesetInfo statisticsRuleset) + public void RefetchStatistics(RulesetInfo ruleset) { - allStatistics[statisticsRuleset.ShortName] = statistics; + if (statisticsRequests.TryGetValue(ruleset.ShortName, out var previousRequest)) + previousRequest.Cancel(); - if (statisticsRuleset.ShortName == ruleset.Value.ShortName) - updateStatisticsBindable(); + var request = statisticsRequests[ruleset.ShortName] = new GetUserRequest(api.LocalUser.Value.Id, ruleset); + request.Success += u => UpdateStatistics(u.Statistics, ruleset); + api.Queue(request); + } + + protected void UpdateStatistics(UserStatistics newStatistics, RulesetInfo ruleset) + { + var oldStatistics = statisticsCache.GetValueOrDefault(ruleset.ShortName); + + statisticsRequests.Remove(ruleset.ShortName); + statisticsCache[ruleset.ShortName] = newStatistics; + statisticsUpdate.Value = new UserStatisticsUpdate(ruleset, oldStatistics, newStatistics); } } + + public record UserStatisticsUpdate(RulesetInfo Ruleset, UserStatistics? OldStatistics, UserStatistics NewStatistics); } diff --git a/osu.Game/Online/UserStatisticsUpdate.cs b/osu.Game/Online/ScoreBasedUserStatisticsUpdate.cs similarity index 84% rename from osu.Game/Online/UserStatisticsUpdate.cs rename to osu.Game/Online/ScoreBasedUserStatisticsUpdate.cs index f85b219ef0..dc55c57c68 100644 --- a/osu.Game/Online/UserStatisticsUpdate.cs +++ b/osu.Game/Online/ScoreBasedUserStatisticsUpdate.cs @@ -9,7 +9,7 @@ namespace osu.Game.Online /// /// Contains data about the change in a user's profile statistics after completing a score. /// - public class UserStatisticsUpdate + public class ScoreBasedUserStatisticsUpdate { /// /// The score set by the user that triggered the update. @@ -27,12 +27,12 @@ namespace osu.Game.Online public UserStatistics After { get; } /// - /// Creates a new . + /// Creates a new . /// /// The score set by the user that triggered the update. /// The user's profile statistics prior to the score being set. /// The user's profile statistics after the score was set. - public UserStatisticsUpdate(ScoreInfo score, UserStatistics before, UserStatistics after) + public ScoreBasedUserStatisticsUpdate(ScoreInfo score, UserStatistics before, UserStatistics after) { Score = score; Before = before; diff --git a/osu.Game/Online/UserStatisticsWatcher.cs b/osu.Game/Online/UserStatisticsWatcher.cs index 162204e4e8..bd3c4b819f 100644 --- a/osu.Game/Online/UserStatisticsWatcher.cs +++ b/osu.Game/Online/UserStatisticsWatcher.cs @@ -8,10 +8,8 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Game.Extensions; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Online.Spectator; using osu.Game.Scoring; -using osu.Game.Users; namespace osu.Game.Online { @@ -20,9 +18,12 @@ namespace osu.Game.Online /// public partial class UserStatisticsWatcher : Component { - private readonly LocalUserStatisticsProvider? statisticsProvider; - public IBindable LatestUpdate => latestUpdate; - private readonly Bindable latestUpdate = new Bindable(); + private readonly LocalUserStatisticsProvider statisticsProvider; + + public IBindable LatestUpdate => latestUpdate; + private readonly Bindable latestUpdate = new Bindable(); + + private ScoreInfo? scorePendingUpdate; [Resolved] private SpectatorClient spectatorClient { get; set; } = null!; @@ -32,7 +33,7 @@ namespace osu.Game.Online private readonly Dictionary watchedScores = new Dictionary(); - public UserStatisticsWatcher(LocalUserStatisticsProvider? statisticsProvider = null) + public UserStatisticsWatcher(LocalUserStatisticsProvider statisticsProvider) { this.statisticsProvider = statisticsProvider; } @@ -40,7 +41,9 @@ namespace osu.Game.Online protected override void LoadComplete() { base.LoadComplete(); + spectatorClient.OnUserScoreProcessed += userScoreProcessed; + statisticsProvider.StatisticsUpdate.ValueChanged += onStatisticsUpdated; } /// @@ -69,27 +72,20 @@ namespace osu.Game.Online if (!watchedScores.Remove(scoreId, out var scoreInfo)) return; - requestStatisticsUpdate(userId, scoreInfo); + scorePendingUpdate = scoreInfo; + statisticsProvider.RefetchStatistics(scoreInfo.Ruleset); } - private void requestStatisticsUpdate(int userId, ScoreInfo scoreInfo) + private void onStatisticsUpdated(ValueChangedEvent update) => Schedule(() => { - var request = new GetUserRequest(userId, scoreInfo.Ruleset); - request.Success += user => Schedule(() => dispatchStatisticsUpdate(scoreInfo, user.Statistics)); - api.Queue(request); - } - - private void dispatchStatisticsUpdate(ScoreInfo scoreInfo, UserStatistics updatedStatistics) - { - if (statisticsProvider == null) + if (scorePendingUpdate == null || !update.NewValue.Ruleset.Equals(scorePendingUpdate.Ruleset)) return; - var latestRulesetStatistics = statisticsProvider.GetStatisticsFor(scoreInfo.Ruleset); - statisticsProvider.UpdateStatistics(updatedStatistics, scoreInfo.Ruleset); + if (update.NewValue.OldStatistics != null) + latestUpdate.Value = new ScoreBasedUserStatisticsUpdate(scorePendingUpdate, update.NewValue.OldStatistics, update.NewValue.NewStatistics); - if (latestRulesetStatistics != null) - latestUpdate.Value = new UserStatisticsUpdate(scoreInfo, latestRulesetStatistics, updatedStatistics); - } + scorePendingUpdate = null; + }); protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs index 07c2e72774..d5891da936 100644 --- a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs +++ b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Toolbar { public partial class TransientUserStatisticsUpdateDisplay : CompositeDrawable { - public Bindable LatestUpdate { get; } = new Bindable(); + public Bindable LatestUpdate { get; } = new Bindable(); private Statistic globalRank = null!; private Statistic pp = null!; @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Toolbar }; if (userStatisticsWatcher != null) - ((IBindable)LatestUpdate).BindTo(userStatisticsWatcher.LatestUpdate); + ((IBindable)LatestUpdate).BindTo(userStatisticsWatcher.LatestUpdate); } protected override void LoadComplete() diff --git a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs index 1e60e09486..171a3f0f65 100644 --- a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs +++ b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User { private const float transition_duration = 300; - public Bindable StatisticsUpdate { get; } = new Bindable(); + public Bindable StatisticsUpdate { get; } = new Bindable(); private LoadingLayer loadingLayer = null!; private GridContainer content = null!; @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User FinishTransforms(true); } - private void onUpdateReceived(ValueChangedEvent update) + private void onUpdateReceived(ValueChangedEvent update) { if (update.NewValue == null) { diff --git a/osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs index e5f07d9891..e6a6530345 100644 --- a/osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs +++ b/osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User { public abstract partial class RankingChangeRow : CompositeDrawable { - public Bindable StatisticsUpdate { get; } = new Bindable(); + public Bindable StatisticsUpdate { get; } = new Bindable(); private readonly Func accessor; @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User StatisticsUpdate.BindValueChanged(onStatisticsUpdate, true); } - private void onStatisticsUpdate(ValueChangedEvent statisticsUpdate) + private void onStatisticsUpdate(ValueChangedEvent statisticsUpdate) { var update = statisticsUpdate.NewValue; diff --git a/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs index 4e9c07ab7b..86fed4a9bb 100644 --- a/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/UserStatisticsPanel.cs @@ -18,9 +18,9 @@ namespace osu.Game.Screens.Ranking.Statistics { private readonly ScoreInfo achievedScore; - internal readonly Bindable DisplayedUserStatisticsUpdate = new Bindable(); + internal readonly Bindable DisplayedUserStatisticsUpdate = new Bindable(); - private IBindable latestGlobalStatisticsUpdate = null!; + private IBindable latestGlobalStatisticsUpdate = null!; public UserStatisticsPanel(ScoreInfo achievedScore) { diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index 5804fce4c1..6fa926998e 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -12,6 +12,7 @@ using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Resources.Localisation.Web; +using osu.Game.Rulesets; using osuTK; namespace osu.Game.Users @@ -29,8 +30,6 @@ namespace osu.Game.Users private ProfileValueDisplay countryRankDisplay = null!; private LoadingLayer loadingLayer = null!; - private readonly IBindable statistics = new Bindable(); - public UserRankPanel(APIUser user) : base(user) { @@ -47,22 +46,31 @@ namespace osu.Game.Users [Resolved] private LocalUserStatisticsProvider? statisticsProvider { get; set; } + [Resolved] + private IBindable ruleset { get; set; } = null!; + + private IBindable statisticsUpdate = null!; + protected override void LoadComplete() { base.LoadComplete(); if (statisticsProvider != null) { - statistics.BindTo(statisticsProvider.Statistics); - statistics.BindValueChanged(stats => - { - loadingLayer.State.Value = stats.NewValue == null ? Visibility.Visible : Visibility.Hidden; - globalRankDisplay.Content = stats.NewValue?.GlobalRank?.ToLocalisableString("\\##,##0") ?? "-"; - countryRankDisplay.Content = stats.NewValue?.CountryRank?.ToLocalisableString("\\##,##0") ?? "-"; - }, true); + statisticsUpdate = statisticsProvider.StatisticsUpdate.GetBoundCopy(); + statisticsUpdate.BindValueChanged(_ => updateDisplay(), true); } } + private void updateDisplay() + { + var statistics = statisticsProvider?.GetStatisticsFor(ruleset.Value); + + loadingLayer.State.Value = statistics == null ? Visibility.Visible : Visibility.Hidden; + globalRankDisplay.Content = statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? "-"; + countryRankDisplay.Content = statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? "-"; + } + protected override Drawable CreateLayout() { FillFlowContainer details; From 28f87407f6abb1075de854a10cb110fa8d0be3d3 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 17 Nov 2024 18:15:38 -0500 Subject: [PATCH 028/223] Make `DifficultyRecommender` rely on the statistics provider --- osu.Game/Beatmaps/DifficultyRecommender.cs | 74 +++++++++++----------- osu.Game/OsuGame.cs | 6 +- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index ec00756fd9..4d883e5327 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -10,7 +10,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Online.API; +using osu.Game.Online; using osu.Game.Rulesets; namespace osu.Game.Beatmaps @@ -21,18 +21,49 @@ namespace osu.Game.Beatmaps /// public partial class DifficultyRecommender : Component { - [Resolved] - private IAPIProvider api { get; set; } + private readonly LocalUserStatisticsProvider statisticsProvider; [Resolved] private Bindable ruleset { get; set; } private readonly Dictionary recommendedDifficultyMapping = new Dictionary(); - [BackgroundDependencyLoader] - private void load() + /// + /// Rulesets ordered descending by their respective recommended difficulties. + /// The currently selected ruleset will always be first. + /// + private IEnumerable orderedRulesets { - api.LocalUser.BindValueChanged(_ => populateValues(), true); + get + { + if (LoadState < LoadState.Ready || ruleset.Value == null) + return Enumerable.Empty(); + + return recommendedDifficultyMapping + .OrderByDescending(pair => pair.Value) + .Select(pair => pair.Key) + .Where(r => !r.Equals(ruleset.Value.ShortName, StringComparison.Ordinal)) + .Prepend(ruleset.Value.ShortName); + } + } + + private IBindable statisticsUpdate = null!; + + public DifficultyRecommender(LocalUserStatisticsProvider statisticsProvider) + { + this.statisticsProvider = statisticsProvider; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + statisticsUpdate = statisticsProvider.StatisticsUpdate.GetBoundCopy(); + statisticsUpdate.BindValueChanged(u => + { + // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 + recommendedDifficultyMapping[u.NewValue.Ruleset.ShortName] = Math.Pow((double)(u.NewValue.NewStatistics.PP ?? 0), 0.4) * 0.195; + }, true); } /// @@ -63,36 +94,5 @@ namespace osu.Game.Beatmaps return null; } - - private void populateValues() - { - if (api.LocalUser.Value.RulesetsStatistics == null) - return; - - foreach (var kvp in api.LocalUser.Value.RulesetsStatistics) - { - // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 - recommendedDifficultyMapping[kvp.Key] = Math.Pow((double)(kvp.Value.PP ?? 0), 0.4) * 0.195; - } - } - - /// - /// Rulesets ordered descending by their respective recommended difficulties. - /// The currently selected ruleset will always be first. - /// - private IEnumerable orderedRulesets - { - get - { - if (LoadState < LoadState.Ready || ruleset.Value == null) - return Enumerable.Empty(); - - return recommendedDifficultyMapping - .OrderByDescending(pair => pair.Value) - .Select(pair => pair.Key) - .Where(r => !r.Equals(ruleset.Value.ShortName, StringComparison.Ordinal)) - .Prepend(ruleset.Value.ShortName); - } - } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index f7e6184dac..b87ad33902 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -148,8 +148,7 @@ namespace osu.Game [Resolved] private FrameworkConfigManager frameworkConfig { get; set; } - [Cached] - private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender(); + private DifficultyRecommender difficultyRecommender; [Cached] private readonly LegacyImportManager legacyImportManager = new LegacyImportManager(); @@ -1142,7 +1141,8 @@ namespace osu.Game loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add); loadComponentSingleFile(new DetachedBeatmapStore(), Add, true); - Add(difficultyRecommender); + loadComponentSingleFile(difficultyRecommender = new DifficultyRecommender(statisticsProvider), Add, true); + Add(externalLinkOpener = new ExternalLinkOpener()); Add(new MusicKeyBindingHandler()); Add(new OnlineStatusNotifier(() => ScreenStack.CurrentScreen)); From 07609b62677d24bfc17e9a1b387775186566c26a Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 17 Nov 2024 18:32:12 -0500 Subject: [PATCH 029/223] Fix `UserRankPanel` not updating on ruleset change --- osu.Game/Users/UserRankPanel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index 6fa926998e..4f2a252539 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -58,7 +58,8 @@ namespace osu.Game.Users if (statisticsProvider != null) { statisticsUpdate = statisticsProvider.StatisticsUpdate.GetBoundCopy(); - statisticsUpdate.BindValueChanged(_ => updateDisplay(), true); + statisticsUpdate.BindValueChanged(_ => updateDisplay()); + ruleset.BindValueChanged(_ => updateDisplay(), true); } } From 1847b679dbfcc1279c82e6e04f73d3b3ce3a9e0a Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 17 Nov 2024 18:45:05 -0500 Subject: [PATCH 030/223] Only update user rank panel display when ruleset matches Nothing behaviourally different, just reduce number of redundant calls. --- osu.Game/Users/UserRankPanel.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index 4f2a252539..c66dd8ef49 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -58,7 +58,12 @@ namespace osu.Game.Users if (statisticsProvider != null) { statisticsUpdate = statisticsProvider.StatisticsUpdate.GetBoundCopy(); - statisticsUpdate.BindValueChanged(_ => updateDisplay()); + statisticsUpdate.BindValueChanged(u => + { + if (u.NewValue.Ruleset.Equals(ruleset.Value)) + updateDisplay(); + }); + ruleset.BindValueChanged(_ => updateDisplay(), true); } } From caf56afba6bb12383ae5d2695c5c8136c6d479cc Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 17 Nov 2024 19:13:23 -0500 Subject: [PATCH 031/223] Fix various test failures --- .../Online/TestSceneUserStatisticsWatcher.cs | 2 +- .../TestSceneBeatmapRecommendations.cs | 32 +++++++++++-------- osu.Game/Beatmaps/DifficultyRecommender.cs | 3 ++ .../Online/API/Requests/Responses/APIUser.cs | 8 ++--- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs index c91dfe9eb7..d410b7f3a4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs @@ -269,7 +269,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); AddUntilStep("update received", () => update != null); - AddAssert("statistics values are correct", () => dummyAPI.LocalUser.Value.Statistics.TotalScore, () => Is.EqualTo(5_000_000)); + AddAssert("statistics values are correct", () => statisticsProvider.GetStatisticsFor(ruleset)!.TotalScore, () => Is.EqualTo(5_000_000)); } private int nextUserId = 2000; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 66862e1b78..d5b0b3b1b0 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -8,14 +8,14 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Extensions; 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.Catch; using osu.Game.Rulesets.Mania; @@ -28,25 +28,31 @@ namespace osu.Game.Tests.Visual.SongSelect { public partial class TestSceneBeatmapRecommendations : OsuGameTestScene { - [Resolved] - private IRulesetStore rulesetStore { get; set; } - [SetUpSteps] public override void SetUpSteps() { AddStep("populate ruleset statistics", () => { - Dictionary rulesetStatistics = new Dictionary(); - - rulesetStore.AvailableRulesets.Where(ruleset => ruleset.IsLegacyRuleset()).ForEach(rulesetInfo => + ((DummyAPIAccess)API).HandleRequest += r => { - rulesetStatistics[rulesetInfo.ShortName] = new UserStatistics + switch (r) { - PP = getNecessaryPP(rulesetInfo.OnlineID) - }; - }); + case GetUserRequest userRequest: + userRequest.TriggerSuccess(new APIUser + { + Id = 99, + Statistics = new UserStatistics + { + PP = getNecessaryPP(userRequest.Ruleset?.OnlineID ?? 0) + } + }); - API.LocalUser.Value.RulesetsStatistics = rulesetStatistics; + return true; + + default: + return false; + } + }; }); decimal getNecessaryPP(int? rulesetID) diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index 4d883e5327..0c75f19658 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -61,6 +61,9 @@ namespace osu.Game.Beatmaps statisticsUpdate = statisticsProvider.StatisticsUpdate.GetBoundCopy(); statisticsUpdate.BindValueChanged(u => { + if (u.NewValue == null) + return; + // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 recommendedDifficultyMapping[u.NewValue.Ruleset.ShortName] = Math.Pow((double)(u.NewValue.NewStatistics.PP ?? 0), 0.4) * 0.195; }, true); diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 452b5f7654..a829484506 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -230,7 +230,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"statistics")] public UserStatistics Statistics { - get => statistics; + get => statistics ??= new UserStatistics(); set { if (statistics != null) @@ -244,11 +244,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"rank_history")] private APIRankHistory rankHistory { - set - { - statistics ??= new UserStatistics(); - statistics.RankHistory = value; - } + set => Statistics.RankHistory = value; } [JsonProperty(@"active_tournament_banners")] From b1068336638f426231b39aeeba0b27b515c1d1a5 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 17 Nov 2024 20:35:59 -0500 Subject: [PATCH 032/223] Fix more test / component breakage --- .../TestSceneBeatmapRecommendations.cs | 2 +- osu.Game/Beatmaps/DifficultyRecommender.cs | 35 +++++++++++++------ osu.Game/OsuGame.cs | 3 +- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index d5b0b3b1b0..bd5c43d242 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.SongSelect { AddStep("populate ruleset statistics", () => { - ((DummyAPIAccess)API).HandleRequest += r => + ((DummyAPIAccess)API).HandleRequest = r => { switch (r) { diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index 0c75f19658..bf81356407 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -12,6 +12,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Online; using osu.Game.Rulesets; +using osu.Game.Users; namespace osu.Game.Beatmaps { @@ -24,7 +25,10 @@ namespace osu.Game.Beatmaps private readonly LocalUserStatisticsProvider statisticsProvider; [Resolved] - private Bindable ruleset { get; set; } + private Bindable gameRuleset { get; set; } + + [Resolved] + private RulesetStore rulesets { get; set; } = null!; private readonly Dictionary recommendedDifficultyMapping = new Dictionary(); @@ -36,14 +40,14 @@ namespace osu.Game.Beatmaps { get { - if (LoadState < LoadState.Ready || ruleset.Value == null) + if (LoadState < LoadState.Ready || gameRuleset.Value == null) return Enumerable.Empty(); return recommendedDifficultyMapping .OrderByDescending(pair => pair.Value) .Select(pair => pair.Key) - .Where(r => !r.Equals(ruleset.Value.ShortName, StringComparison.Ordinal)) - .Prepend(ruleset.Value.ShortName); + .Where(r => !r.Equals(gameRuleset.Value.ShortName, StringComparison.Ordinal)) + .Prepend(gameRuleset.Value.ShortName); } } @@ -54,19 +58,28 @@ namespace osu.Game.Beatmaps this.statisticsProvider = statisticsProvider; } + [BackgroundDependencyLoader] + private void load() + { + foreach (var ruleset in rulesets.AvailableRulesets) + { + if (statisticsProvider.GetStatisticsFor(ruleset) is UserStatistics statistics) + updateMapping(ruleset, statistics); + } + } + protected override void LoadComplete() { base.LoadComplete(); statisticsUpdate = statisticsProvider.StatisticsUpdate.GetBoundCopy(); - statisticsUpdate.BindValueChanged(u => - { - if (u.NewValue == null) - return; + statisticsUpdate.ValueChanged += u => updateMapping(u.NewValue.Ruleset, u.NewValue.NewStatistics); + } - // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 - recommendedDifficultyMapping[u.NewValue.Ruleset.ShortName] = Math.Pow((double)(u.NewValue.NewStatistics.PP ?? 0), 0.4) * 0.195; - }, true); + private void updateMapping(RulesetInfo ruleset, UserStatistics statistics) + { + // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 + recommendedDifficultyMapping[ruleset.ShortName] = Math.Pow((double)(statistics.PP ?? 0), 0.4) * 0.195; } /// diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b87ad33902..a92b1f4d36 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1071,6 +1071,7 @@ namespace osu.Game LocalUserStatisticsProvider statisticsProvider; loadComponentSingleFile(statisticsProvider = new LocalUserStatisticsProvider(), Add, true); + loadComponentSingleFile(difficultyRecommender = new DifficultyRecommender(statisticsProvider), Add, true); loadComponentSingleFile(new UserStatisticsWatcher(statisticsProvider), Add, true); loadComponentSingleFile(Toolbar = new Toolbar { @@ -1141,8 +1142,6 @@ namespace osu.Game loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add); loadComponentSingleFile(new DetachedBeatmapStore(), Add, true); - loadComponentSingleFile(difficultyRecommender = new DifficultyRecommender(statisticsProvider), Add, true); - Add(externalLinkOpener = new ExternalLinkOpener()); Add(new MusicKeyBindingHandler()); Add(new OnlineStatusNotifier(() => ScreenStack.CurrentScreen)); From 29e7adcd3b195bc466cb7c434ce493c32e3e94c5 Mon Sep 17 00:00:00 2001 From: Sheppsu <49356627+Sheppsu@users.noreply.github.com> Date: Mon, 18 Nov 2024 03:57:50 -0500 Subject: [PATCH 033/223] add player settings to multi spectator screen --- .../Spectate/MultiSpectatorScreen.cs | 3 +- .../Spectate/MultiSpectatorSettings.cs | 81 +++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index cb00763e6b..841aaf7a45 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -126,7 +126,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate syncManager = new SpectatorSyncManager(masterClockContainer) { ReadyToStart = performInitialSeek, - } + }, + new MultiSpectatorSettings() }; for (int i = 0; i < Users.Count; i++) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs new file mode 100644 index 0000000000..7ed6b95110 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs @@ -0,0 +1,81 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Play.HUD; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public partial class MultiSpectatorSettings : CompositeDrawable + { + private const double slide_duration = 200; + + private readonly PlayerSettingsOverlay playerSettingsOverlay; + private readonly Container slidingContainer; + + private readonly BindableBool opened = new BindableBool(); + + public MultiSpectatorSettings() + { + Origin = Anchor.TopLeft; + Anchor = Anchor.TopRight; + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + slidingContainer = new Container + { + Origin = Anchor.TopLeft, + Anchor = Anchor.TopLeft, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new IconButton + { + Icon = FontAwesome.Solid.Cog, + Origin = Anchor.TopLeft, + Anchor = Anchor.TopLeft, + Scale = new Vector2(1f), + Position = new Vector2(-30, 0), + Action = () => opened.Toggle() + }, + playerSettingsOverlay = new PlayerSettingsOverlay() + } + } + }; + + playerSettingsOverlay.Show(); + + opened.BindValueChanged(value => + { + if (value.NewValue) + open(); + else + close(); + }); + } + + private void open() + { + slidingContainer.MoveToOffset(new Vector2(-playerSettingsOverlay.Width, 0), slide_duration, Easing.Out).Then().OnComplete(c => + { + c.Origin = Anchor.TopRight; + c.Position = Vector2.Zero; + }); + } + + private void close() + { + slidingContainer.MoveToOffset(new Vector2(playerSettingsOverlay.Width, 0), slide_duration, Easing.Out).Then().OnComplete(c => + { + c.Origin = Anchor.TopLeft; + c.Position = Vector2.Zero; + }); + } + } +} From dcf4674c6c6ac567d80ecdd0145fb8080012e89e Mon Sep 17 00:00:00 2001 From: StanR Date: Mon, 18 Nov 2024 14:01:17 +0500 Subject: [PATCH 034/223] Scale down beatmap cards in profile overlay --- .../Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index b237a0ee05..8a47ae6830 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -72,6 +72,7 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, + Scale = new Vector2(0.8f) } : null; } From 7d4062d2adba94f38805f9b59ad047ce3822f260 Mon Sep 17 00:00:00 2001 From: Sheppsu <49356627+Sheppsu@users.noreply.github.com> Date: Mon, 18 Nov 2024 04:04:28 -0500 Subject: [PATCH 035/223] remove redundant Scale attribute --- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs index 7ed6b95110..64c798b092 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs @@ -40,7 +40,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Icon = FontAwesome.Solid.Cog, Origin = Anchor.TopLeft, Anchor = Anchor.TopLeft, - Scale = new Vector2(1f), Position = new Vector2(-30, 0), Action = () => opened.Toggle() }, From 4066186b24efaf90333b63298c8b23856b419711 Mon Sep 17 00:00:00 2001 From: StanR Date: Mon, 18 Nov 2024 14:48:51 +0500 Subject: [PATCH 036/223] Scale beatmap cards down by ~0.8 --- .../Online/TestSceneUserProfileOverlay.cs | 11 ++++++ .../Drawables/BeatmapSetOnlineStatusPill.cs | 2 +- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 4 +-- .../Drawables/Cards/BeatmapCardExtra.cs | 34 +++++++++---------- .../Cards/BeatmapCardExtraInfoRow.cs | 7 ++-- .../Drawables/Cards/BeatmapCardNormal.cs | 32 ++++++++--------- .../Cards/Statistics/BeatmapCardStatistic.cs | 6 ++-- osu.Game/Overlays/BeatmapListingOverlay.cs | 1 - .../Beatmaps/PaginatedBeatmapContainer.cs | 3 +- 9 files changed, 55 insertions(+), 45 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 006610dccd..d16ed46bd2 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -58,6 +59,16 @@ namespace osu.Game.Tests.Visual.Online return true; } + if (req is GetUserBeatmapsRequest getUserBeatmapsRequest) + { + getUserBeatmapsRequest.TriggerSuccess(new List + { + CreateAPIBeatmapSet(), + CreateAPIBeatmapSet() + }); + return true; + } + return false; }; }); diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs index f18355505a..599d1b380a 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs @@ -86,7 +86,7 @@ namespace osu.Game.Beatmaps.Drawables }; Status = BeatmapOnlineStatus.None; - TextPadding = new MarginPadding { Horizontal = 5, Bottom = 1 }; + TextPadding = new MarginPadding { Horizontal = 4, Bottom = 1 }; } protected override void LoadComplete() diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 25e42bcbf7..56103c1d6d 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -20,9 +20,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards public abstract partial class BeatmapCard : OsuClickableContainer, IHasContextMenu { public const float TRANSITION_DURATION = 340; - public const float CORNER_RADIUS = 10; + public const float CORNER_RADIUS = 8; - protected const float WIDTH = 430; + protected const float WIDTH = 345; public IBindable Expanded { get; } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs index 2c2761ff0c..ebd0113379 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs @@ -22,7 +22,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards protected override Drawable IdleContent => idleBottomContent; protected override Drawable DownloadInProgressContent => downloadProgressBar; - private const float height = 140; + private const float height = 112; [Cached] private readonly BeatmapCardContent content; @@ -68,7 +68,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards Padding = new MarginPadding { Right = CORNER_RADIUS }, Child = leftIconArea = new FillFlowContainer { - Margin = new MarginPadding(5), + Margin = new MarginPadding(4), AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(1) @@ -80,7 +80,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards Width = WIDTH - height + CORNER_RADIUS, FavouriteState = { BindTarget = FavouriteState }, ButtonsCollapsedWidth = CORNER_RADIUS, - ButtonsExpandedWidth = 30, + ButtonsExpandedWidth = 24, Children = new Drawable[] { new FillFlowContainer @@ -109,7 +109,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards new TruncatingSpriteText { Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title), - Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold), + Font = OsuFont.Default.With(size: 18f, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, }, titleBadgeArea = new FillFlowContainer @@ -142,7 +142,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards new TruncatingSpriteText { Text = createArtistText(), - Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold), + Font = OsuFont.Default.With(size: 14f, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, }, Empty() @@ -154,7 +154,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.X, Text = BeatmapSet.Source, Shadow = false, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), + Font = OsuFont.GetFont(size: 11f, weight: FontWeight.SemiBold), Colour = colourProvider.Content2 }, } @@ -173,18 +173,18 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 3), + Spacing = new Vector2(0, 2), AlwaysPresent = true, Children = new Drawable[] { new LinkFlowContainer(s => { s.Shadow = false; - s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold); + s.Font = OsuFont.GetFont(size: 11f, weight: FontWeight.SemiBold); }).With(d => { d.AutoSizeAxes = Axes.Both; - d.Margin = new MarginPadding { Top = 2 }; + d.Margin = new MarginPadding { Top = 1 }; d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); d.AddUserLink(BeatmapSet.Author); }), @@ -215,7 +215,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards downloadProgressBar = new BeatmapCardDownloadProgressBar { RelativeSizeAxes = Axes.X, - Height = 6, + Height = 5, Anchor = Anchor.Centre, Origin = Anchor.Centre, State = { BindTarget = DownloadTracker.State }, @@ -231,17 +231,17 @@ namespace osu.Game.Beatmaps.Drawables.Cards { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 10, Vertical = 13 }, + Padding = new MarginPadding { Horizontal = 8, Vertical = 10 }, Child = new BeatmapCardDifficultyList(BeatmapSet) }; c.Expanded.BindTarget = Expanded; }); if (BeatmapSet.HasVideo) - leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(20) }); + leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(16) }); if (BeatmapSet.HasStoryboard) - leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(20) }); + leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(16) }); if (BeatmapSet.FeaturedInSpotlight) { @@ -249,7 +249,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Margin = new MarginPadding { Left = 5 } + Margin = new MarginPadding { Left = 4 } }); } @@ -259,7 +259,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Margin = new MarginPadding { Left = 5 } + Margin = new MarginPadding { Left = 4 } }); } @@ -269,7 +269,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Margin = new MarginPadding { Left = 5 } + Margin = new MarginPadding { Left = 4 } }; } @@ -288,7 +288,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { BeatmapCardStatistic withMargin(BeatmapCardStatistic original) { - original.Margin = new MarginPadding { Right = 10 }; + original.Margin = new MarginPadding { Right = 8 }; return original; } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs index 3a1b8f7e86..a11ef0f95c 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs @@ -25,7 +25,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Horizontal, - Spacing = new Vector2(4, 0), + Spacing = new Vector2(3, 0), Children = new Drawable[] { new BeatmapSetOnlineStatusPill @@ -33,13 +33,14 @@ namespace osu.Game.Beatmaps.Drawables.Cards AutoSizeAxes = Axes.Both, Status = beatmapSet.Status, Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft + Origin = Anchor.CentreLeft, + TextSize = 13f }, new DifficultySpectrumDisplay(beatmapSet) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - DotSize = new Vector2(6, 12) + DotSize = new Vector2(5, 10) } } }; diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs index 46ab7ec5f6..724919f3bd 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs @@ -23,7 +23,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards protected override Drawable IdleContent => idleBottomContent; protected override Drawable DownloadInProgressContent => downloadProgressBar; - public const float HEIGHT = 100; + public const float HEIGHT = 80; [Cached] private readonly BeatmapCardContent content; @@ -69,7 +69,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards Padding = new MarginPadding { Right = CORNER_RADIUS }, Child = leftIconArea = new FillFlowContainer { - Margin = new MarginPadding(5), + Margin = new MarginPadding(4), AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(1) @@ -81,7 +81,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards Width = WIDTH - HEIGHT + CORNER_RADIUS, FavouriteState = { BindTarget = FavouriteState }, ButtonsCollapsedWidth = CORNER_RADIUS, - ButtonsExpandedWidth = 30, + ButtonsExpandedWidth = 24, Children = new Drawable[] { new FillFlowContainer @@ -110,7 +110,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards new TruncatingSpriteText { Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title), - Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold), + Font = OsuFont.Default.With(size: 18f, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, }, titleBadgeArea = new FillFlowContainer @@ -143,7 +143,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards new TruncatingSpriteText { Text = createArtistText(), - Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold), + Font = OsuFont.Default.With(size: 14f, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, }, Empty() @@ -153,11 +153,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards new LinkFlowContainer(s => { s.Shadow = false; - s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold); + s.Font = OsuFont.GetFont(size: 11f, weight: FontWeight.SemiBold); }).With(d => { d.AutoSizeAxes = Axes.Both; - d.Margin = new MarginPadding { Top = 2 }; + d.Margin = new MarginPadding { Top = 1 }; d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); d.AddUserLink(BeatmapSet.Author); }), @@ -177,7 +177,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 3), + Spacing = new Vector2(0, 2), AlwaysPresent = true, Children = new Drawable[] { @@ -186,7 +186,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), + Spacing = new Vector2(8, 0), Alpha = 0, AlwaysPresent = true, ChildrenEnumerable = createStatistics() @@ -197,7 +197,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards downloadProgressBar = new BeatmapCardDownloadProgressBar { RelativeSizeAxes = Axes.X, - Height = 6, + Height = 5, Anchor = Anchor.Centre, Origin = Anchor.Centre, State = { BindTarget = DownloadTracker.State }, @@ -213,17 +213,17 @@ namespace osu.Game.Beatmaps.Drawables.Cards { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 10, Vertical = 13 }, + Padding = new MarginPadding { Horizontal = 8, Vertical = 10 }, Child = new BeatmapCardDifficultyList(BeatmapSet) }; c.Expanded.BindTarget = Expanded; }); if (BeatmapSet.HasVideo) - leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(20) }); + leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(16) }); if (BeatmapSet.HasStoryboard) - leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(20) }); + leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(16) }); if (BeatmapSet.FeaturedInSpotlight) { @@ -231,7 +231,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Margin = new MarginPadding { Left = 5 } + Margin = new MarginPadding { Left = 4 } }); } @@ -241,7 +241,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Margin = new MarginPadding { Left = 5 } + Margin = new MarginPadding { Left = 4 } }); } @@ -251,7 +251,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Margin = new MarginPadding { Left = 5 } + Margin = new MarginPadding { Left = 4 } }; } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs b/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs index 6fd7142c05..ece52d0fa9 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs @@ -46,21 +46,21 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), + Spacing = new Vector2(4, 0), Children = new Drawable[] { spriteIcon = new SpriteIcon { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Size = new Vector2(10), + Size = new Vector2(8), Margin = new MarginPadding { Top = 1 } }, spriteText = new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.Default.With(size: 14) + Font = OsuFont.Default.With(size: 11) } } }; diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index b47e2b82c0..f83368fa41 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -198,7 +198,6 @@ namespace osu.Game.Overlays { c.Anchor = Anchor.TopCentre; c.Origin = Anchor.TopCentre; - c.Scale = new Vector2(0.8f); })).ToArray(); private static ReverseChildIDFillFlowContainer createCardContainerFor(IEnumerable newCards) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 8a47ae6830..df657aa55b 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -71,8 +71,7 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps ? new BeatmapCardNormal(model) { Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Scale = new Vector2(0.8f) + Origin = Anchor.TopCentre } : null; } From 74daf85e489bb5402504729303b0f2dd1fa14e2f Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 18 Nov 2024 06:40:14 -0500 Subject: [PATCH 037/223] Replace bindable with an event --- osu.Desktop/DiscordRichPresence.cs | 10 ++++--- .../TestSceneLocalUserStatisticsProvider.cs | 26 ++++++++++------- osu.Game/Beatmaps/DifficultyRecommender.cs | 16 ++++++++--- .../Online/LocalUserStatisticsProvider.cs | 16 ++++++----- osu.Game/Online/UserStatisticsWatcher.cs | 10 +++---- osu.Game/Users/UserRankPanel.cs | 28 +++++++++++-------- 6 files changed, 65 insertions(+), 41 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index c9529d2f5e..c08185ddbe 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -69,7 +69,6 @@ namespace osu.Desktop }; private IBindable? user; - private IBindable? statisticsUpdate; [BackgroundDependencyLoader] private void load() @@ -123,10 +122,8 @@ namespace osu.Desktop activity.BindValueChanged(_ => schedulePresenceUpdate()); privacyMode.BindValueChanged(_ => schedulePresenceUpdate()); - statisticsUpdate = statisticsProvider.StatisticsUpdate.GetBoundCopy(); - statisticsUpdate.BindValueChanged(_ => schedulePresenceUpdate()); - multiplayerClient.RoomUpdated += onRoomUpdated; + statisticsProvider.StatisticsUpdated += onStatisticsUpdated; } private void onReady(object _, ReadyMessage __) @@ -142,6 +139,8 @@ namespace osu.Desktop private void onRoomUpdated() => schedulePresenceUpdate(); + private void onStatisticsUpdated(UserStatisticsUpdate _) => schedulePresenceUpdate(); + private ScheduledDelegate? presenceUpdateDelegate; private void schedulePresenceUpdate() @@ -353,6 +352,9 @@ namespace osu.Desktop if (multiplayerClient.IsNotNull()) multiplayerClient.RoomUpdated -= onRoomUpdated; + if (statisticsProvider.IsNotNull()) + statisticsProvider.StatisticsUpdated -= onStatisticsUpdated; + client.Dispose(); base.Dispose(isDisposing); } diff --git a/osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs b/osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs index 342d805be4..f24a9333c1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Online Origin = Anchor.Centre, }); - statisticsProvider.StatisticsUpdate.BindValueChanged(s => + statisticsProvider.StatisticsUpdated += update => { text.Clear(); @@ -78,14 +78,9 @@ namespace osu.Game.Tests.Visual.Online text.NewLine(); } - if (s.NewValue == null) - text.AddText("latest update: (null)"); - else - { - text.AddText($"latest update: {s.NewValue.Ruleset}" - + $" ({(s.NewValue.OldStatistics?.TotalScore.ToString() ?? "null")} -> {s.NewValue.NewStatistics.TotalScore})"); - } - }); + text.AddText($"latest update: {update.Ruleset}" + + $" ({(update.OldStatistics?.TotalScore.ToString() ?? "null")} -> {update.NewStatistics.TotalScore})"); + }; Ruleset.Value = new OsuRuleset().RulesetInfo; }); @@ -133,6 +128,8 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestRefetchStatistics() { + UserStatisticsUpdate? update = null; + setUser(1001); AddStep("update statistics server side", @@ -142,13 +139,22 @@ namespace osu.Game.Tests.Visual.Online () => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(4_000_000)); + AddStep("setup event", () => + { + update = null; + statisticsProvider.StatisticsUpdated -= onStatisticsUpdated; + statisticsProvider.StatisticsUpdated += onStatisticsUpdated; + }); + AddStep("request refetch", () => statisticsProvider.RefetchStatistics(new OsuRuleset().RulesetInfo)); AddUntilStep("statistics update raised", - () => statisticsProvider.StatisticsUpdate.Value.NewStatistics.TotalScore, + () => update?.NewStatistics.TotalScore, () => Is.EqualTo(9_000_000)); AddAssert("statistics match new score", () => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(9_000_000)); + + void onStatisticsUpdated(UserStatisticsUpdate u) => update = u; } private UserStatistics tryGetStatistics(int userId, string rulesetName) diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index bf81356407..d132b86052 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -9,6 +9,7 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Game.Online; using osu.Game.Rulesets; @@ -51,8 +52,6 @@ namespace osu.Game.Beatmaps } } - private IBindable statisticsUpdate = null!; - public DifficultyRecommender(LocalUserStatisticsProvider statisticsProvider) { this.statisticsProvider = statisticsProvider; @@ -72,10 +71,11 @@ namespace osu.Game.Beatmaps { base.LoadComplete(); - statisticsUpdate = statisticsProvider.StatisticsUpdate.GetBoundCopy(); - statisticsUpdate.ValueChanged += u => updateMapping(u.NewValue.Ruleset, u.NewValue.NewStatistics); + statisticsProvider.StatisticsUpdated += onStatisticsUpdated; } + private void onStatisticsUpdated(UserStatisticsUpdate update) => updateMapping(update.Ruleset, update.NewStatistics); + private void updateMapping(RulesetInfo ruleset, UserStatistics statistics) { // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 @@ -110,5 +110,13 @@ namespace osu.Game.Beatmaps return null; } + + protected override void Dispose(bool isDisposing) + { + if (statisticsProvider.IsNotNull()) + statisticsProvider.StatisticsUpdated -= onStatisticsUpdated; + + base.Dispose(isDisposing); + } } } diff --git a/osu.Game/Online/LocalUserStatisticsProvider.cs b/osu.Game/Online/LocalUserStatisticsProvider.cs index ea4688a307..a17041c996 100644 --- a/osu.Game/Online/LocalUserStatisticsProvider.cs +++ b/osu.Game/Online/LocalUserStatisticsProvider.cs @@ -1,10 +1,10 @@ // 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; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Extensions; using osu.Game.Online.API; @@ -19,13 +19,15 @@ namespace osu.Game.Online /// public partial class LocalUserStatisticsProvider : Component { - private readonly Bindable statisticsUpdate = new Bindable(); - /// - /// A bindable communicating updates to the local user's statistics on any ruleset. - /// This does not guarantee the presence of old statistics, as it is invoked on initial population of statistics. + /// Invoked whenever a change occured to the statistics of any ruleset, + /// either due to change in local user (log out and log in) or as a result of score submission. /// - public IBindable StatisticsUpdate => statisticsUpdate; + /// + /// This does not guarantee the presence of the old statistics, + /// specifically in the case of initial population or change in local user. + /// + public event Action? StatisticsUpdated; [Resolved] private RulesetStore rulesets { get; set; } = null!; @@ -73,7 +75,7 @@ namespace osu.Game.Online statisticsRequests.Remove(ruleset.ShortName); statisticsCache[ruleset.ShortName] = newStatistics; - statisticsUpdate.Value = new UserStatisticsUpdate(ruleset, oldStatistics, newStatistics); + StatisticsUpdated?.Invoke(new UserStatisticsUpdate(ruleset, oldStatistics, newStatistics)); } } diff --git a/osu.Game/Online/UserStatisticsWatcher.cs b/osu.Game/Online/UserStatisticsWatcher.cs index bd3c4b819f..8ed1ff594d 100644 --- a/osu.Game/Online/UserStatisticsWatcher.cs +++ b/osu.Game/Online/UserStatisticsWatcher.cs @@ -43,7 +43,7 @@ namespace osu.Game.Online base.LoadComplete(); spectatorClient.OnUserScoreProcessed += userScoreProcessed; - statisticsProvider.StatisticsUpdate.ValueChanged += onStatisticsUpdated; + statisticsProvider.StatisticsUpdated += onStatisticsUpdated; } /// @@ -76,13 +76,13 @@ namespace osu.Game.Online statisticsProvider.RefetchStatistics(scoreInfo.Ruleset); } - private void onStatisticsUpdated(ValueChangedEvent update) => Schedule(() => + private void onStatisticsUpdated(UserStatisticsUpdate update) => Schedule(() => { - if (scorePendingUpdate == null || !update.NewValue.Ruleset.Equals(scorePendingUpdate.Ruleset)) + if (scorePendingUpdate == null || !update.Ruleset.Equals(scorePendingUpdate.Ruleset)) return; - if (update.NewValue.OldStatistics != null) - latestUpdate.Value = new ScoreBasedUserStatisticsUpdate(scorePendingUpdate, update.NewValue.OldStatistics, update.NewValue.NewStatistics); + if (update.OldStatistics != null) + latestUpdate.Value = new ScoreBasedUserStatisticsUpdate(scorePendingUpdate, update.OldStatistics, update.NewStatistics); scorePendingUpdate = null; }); diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index c66dd8ef49..5e3ae172be 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -49,23 +50,20 @@ namespace osu.Game.Users [Resolved] private IBindable ruleset { get; set; } = null!; - private IBindable statisticsUpdate = null!; - protected override void LoadComplete() { base.LoadComplete(); if (statisticsProvider != null) - { - statisticsUpdate = statisticsProvider.StatisticsUpdate.GetBoundCopy(); - statisticsUpdate.BindValueChanged(u => - { - if (u.NewValue.Ruleset.Equals(ruleset.Value)) - updateDisplay(); - }); + statisticsProvider.StatisticsUpdated += onStatisticsUpdated; - ruleset.BindValueChanged(_ => updateDisplay(), true); - } + ruleset.BindValueChanged(_ => updateDisplay(), true); + } + + private void onStatisticsUpdated(UserStatisticsUpdate update) + { + if (update.Ruleset.Equals(ruleset.Value)) + updateDisplay(); } private void updateDisplay() @@ -231,5 +229,13 @@ namespace osu.Game.Users } protected override Drawable? CreateBackground() => null; + + protected override void Dispose(bool isDisposing) + { + if (statisticsProvider.IsNotNull()) + statisticsProvider.StatisticsUpdated -= onStatisticsUpdated; + + base.Dispose(isDisposing); + } } } From 0b52080a52d29e022a70de6fc1a4c3417411969f Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 18 Nov 2024 06:46:57 -0500 Subject: [PATCH 038/223] Handle logged out user --- osu.Game/Online/LocalUserStatisticsProvider.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/LocalUserStatisticsProvider.cs b/osu.Game/Online/LocalUserStatisticsProvider.cs index a17041c996..a25f5b05aa 100644 --- a/osu.Game/Online/LocalUserStatisticsProvider.cs +++ b/osu.Game/Online/LocalUserStatisticsProvider.cs @@ -55,6 +55,9 @@ namespace osu.Game.Online { statisticsCache.Clear(); + if (api.LocalUser.Value == null || api.LocalUser.Value.Id <= 1) + return; + foreach (var ruleset in rulesets.AvailableRulesets.Where(r => r.IsLegacyRuleset())) RefetchStatistics(ruleset); } From 5ac3bb73ee2569b62f5a928606b16bfc2b969a9e Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Mon, 18 Nov 2024 22:19:08 +0100 Subject: [PATCH 039/223] Adds an option to the catch editor to convert sliders to fruits --- .../JuiceStreamSelectionBlueprint.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index 3eb8d6c018..2f2ccae38b 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -10,10 +10,12 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit; using osuTK; using osuTK.Input; @@ -54,6 +56,12 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints [Resolved] private EditorBeatmap? editorBeatmap { get; set; } + [Resolved] + private IEditorChangeHandler? changeHandler { get; set; } + + [Resolved] + private BindableBeatDivisor? beatDivisor { get; set; } + public JuiceStreamSelectionBlueprint(JuiceStream hitObject) : base(hitObject) { @@ -119,6 +127,20 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints return base.OnMouseDown(e); } + protected override bool OnKeyDown(KeyDownEvent e) + { + if (!IsSelected) + return false; + + if (e.Key == Key.F && e.ControlPressed && e.ShiftPressed) + { + convertToFruits(); + return true; + } + + return false; + } + private void onDefaultsApplied(HitObject _) { computeObjectBounds(); @@ -168,6 +190,48 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints lastSliderPathVersion = HitObject.Path.Version.Value; } + private void convertToFruits() + { + if (editorBeatmap == null || beatDivisor == null) + return; + + var timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(HitObject.StartTime); + double streamSpacing = timingPoint.BeatLength / beatDivisor.Value; + + changeHandler?.BeginChange(); + + int i = 0; + double time = HitObject.StartTime; + + while (!Precision.DefinitelyBigger(time, HitObject.GetEndTime(), 1)) + { + // positionWithRepeats is a fractional number in the range of [0, HitObject.SpanCount()] + // and indicates how many fractional spans of a slider have passed up to time. + double positionWithRepeats = (time - HitObject.StartTime) / HitObject.Duration * HitObject.SpanCount(); + double pathPosition = positionWithRepeats - (int)positionWithRepeats; + // every second span is in the reverse direction - need to reverse the path position. + if (positionWithRepeats % 2 >= 1) + pathPosition = 1 - pathPosition; + + float fruitXValue = HitObject.OriginalX + HitObject.Path.PositionAt(pathPosition).X; + + editorBeatmap.Add(new Fruit + { + StartTime = time, + OriginalX = fruitXValue, + NewCombo = i == 0 && HitObject.NewCombo, + Samples = HitObject.Samples.Select(s => s.With()).ToList() + }); + + i += 1; + time = HitObject.StartTime + i * streamSpacing; + } + + editorBeatmap.Remove(HitObject); + + changeHandler?.EndChange(); + } + private IEnumerable getContextMenuItems() { yield return new OsuMenuItem("Add vertex", MenuItemType.Standard, () => @@ -177,6 +241,11 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints { Hotkey = new Hotkey(new KeyCombination(InputKey.Control, InputKey.MouseLeft)) }; + + yield return new OsuMenuItem("Convert to fruits", MenuItemType.Destructive, convertToFruits) + { + Hotkey = new Hotkey(new KeyCombination(InputKey.Control, InputKey.Shift, InputKey.F)) + }; } protected override void Dispose(bool isDisposing) From 00e3b20ff0bb3d6f001fc375034cef8406fab799 Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Mon, 18 Nov 2024 22:30:15 +0100 Subject: [PATCH 040/223] Change text to stream instead of fruits as that is the term by catch mappers --- .../Edit/Blueprints/JuiceStreamSelectionBlueprint.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index 2f2ccae38b..a61478f5d5 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints if (e.Key == Key.F && e.ControlPressed && e.ShiftPressed) { - convertToFruits(); + convertToStream(); return true; } @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints lastSliderPathVersion = HitObject.Path.Version.Value; } - private void convertToFruits() + private void convertToStream() { if (editorBeatmap == null || beatDivisor == null) return; @@ -242,7 +242,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints Hotkey = new Hotkey(new KeyCombination(InputKey.Control, InputKey.MouseLeft)) }; - yield return new OsuMenuItem("Convert to fruits", MenuItemType.Destructive, convertToFruits) + yield return new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream) { Hotkey = new Hotkey(new KeyCombination(InputKey.Control, InputKey.Shift, InputKey.F)) }; From a679f0736ed05367d2ab471d364914c024bc9d90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 7 Nov 2024 14:58:06 +0100 Subject: [PATCH 041/223] Add ability to close playlists within grace period after creation --- .../API/Requests/ClosePlaylistRequest.cs | 27 ++++++++++++ .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 42 ++++++++++++++++--- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 2 + .../Playlists/ClosePlaylistDialog.cs | 19 +++++++++ 4 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Online/API/Requests/ClosePlaylistRequest.cs create mode 100644 osu.Game/Screens/OnlinePlay/Playlists/ClosePlaylistDialog.cs diff --git a/osu.Game/Online/API/Requests/ClosePlaylistRequest.cs b/osu.Game/Online/API/Requests/ClosePlaylistRequest.cs new file mode 100644 index 0000000000..545266491e --- /dev/null +++ b/osu.Game/Online/API/Requests/ClosePlaylistRequest.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Net.Http; +using osu.Framework.IO.Network; + +namespace osu.Game.Online.API.Requests +{ + public class ClosePlaylistRequest : APIRequest + { + private readonly long roomId; + + public ClosePlaylistRequest(long roomId) + { + this.roomId = roomId; + } + + protected override WebRequest CreateWebRequest() + { + var request = base.CreateWebRequest(); + request.Method = HttpMethod.Delete; + return request; + } + + protected override string Target => $@"rooms/{roomId}"; + } +} diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index d396d18b4f..76de649ef8 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.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.ComponentModel; using osu.Framework.Allocation; @@ -22,9 +23,13 @@ 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.Rooms; +using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge.Components; +using osu.Game.Screens.OnlinePlay.Playlists; using osuTK; using osuTK.Graphics; using Container = osu.Framework.Graphics.Containers.Container; @@ -48,6 +53,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge [Resolved(canBeNull: true)] private LoungeSubScreen? lounge { get; set; } + [Resolved] + private IDialogOverlay? dialogOverlay { get; set; } + + [Resolved] + private IAPIProvider api { get; set; } = null!; + private readonly BindableWithCurrent selectedRoom = new BindableWithCurrent(); private Sample? sampleSelect; private Sample? sampleJoin; @@ -144,13 +155,34 @@ namespace osu.Game.Screens.OnlinePlay.Lounge public Popover GetPopover() => new PasswordEntryPopover(Room); - public MenuItem[] ContextMenuItems => new MenuItem[] + public MenuItem[] ContextMenuItems { - new OsuMenuItem("Create copy", MenuItemType.Standard, () => + get { - lounge?.OpenCopy(Room); - }) - }; + var items = new List + { + new OsuMenuItem("Create copy", MenuItemType.Standard, () => + { + lounge?.OpenCopy(Room); + }) + }; + + if (Room.Type == MatchType.Playlists && Room.Host?.Id == api.LocalUser.Value.Id && Room.StartDate?.AddMinutes(5) >= DateTimeOffset.Now) + { + items.Add(new OsuMenuItem("Close playlist", MenuItemType.Destructive, () => + { + dialogOverlay?.Push(new ClosePlaylistDialog(Room, () => + { + var request = new ClosePlaylistRequest(Room.RoomID!.Value); + request.Success += () => lounge?.RefreshRooms(); + api.Queue(request); + })); + })); + } + + return items.ToArray(); + } + } public bool OnPressed(KeyBindingPressEvent e) { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index 90288a1067..e3ec97e157 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -382,6 +382,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge this.Push(CreateRoomSubScreen(room)); } + public void RefreshRooms() => ListingPollingComponent.PollImmediately(); + private void updateLoadingLayer() { if (operationInProgress.Value || !ListingPollingComponent.InitialRoomsReceived.Value) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/ClosePlaylistDialog.cs b/osu.Game/Screens/OnlinePlay/Playlists/ClosePlaylistDialog.cs new file mode 100644 index 0000000000..08fed037d3 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Playlists/ClosePlaylistDialog.cs @@ -0,0 +1,19 @@ +// 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.Game.Online.Rooms; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.OnlinePlay.Playlists +{ + public partial class ClosePlaylistDialog : DeletionDialog + { + public ClosePlaylistDialog(Room room, Action closeAction) + { + HeaderText = "Are you sure you want to close the following playlist:"; + BodyText = room.Name; + DangerousAction = closeAction; + } + } +} From a76b4418b9159ad25aaa67ec9b1ced2c7e588c46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2024 16:55:37 +0900 Subject: [PATCH 042/223] Change some beatmap default settings to match stable - Countdown should [be off by default](https://github.com/peppy/osu-stable-reference/blob/9a0748563812b5085a0ef5f8600b997408330eab/osu!/GameplayElements/Beatmaps/Beatmap.cs#L372) - Samples match playback rate [also](https://github.com/peppy/osu-stable-reference/blob/9a0748563812b5085a0ef5f8600b997408330eab/osu!/GameplayElements/Beatmaps/Beatmap.cs#L210) --- osu.Game/Beatmaps/BeatmapInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index f1463eb632..d94c09d40f 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -150,7 +150,7 @@ namespace osu.Game.Beatmaps public bool EpilepsyWarning { get; set; } - public bool SamplesMatchPlaybackRate { get; set; } = true; + public bool SamplesMatchPlaybackRate { get; set; } /// /// The time at which this beatmap was last played by the local user. @@ -181,7 +181,7 @@ namespace osu.Game.Beatmaps public double? EditorTimestamp { get; set; } [Ignored] - public CountdownType Countdown { get; set; } = CountdownType.Normal; + public CountdownType Countdown { get; set; } = CountdownType.None; /// /// The number of beats to move the countdown backwards (compared to its default location). From 29757ffdf2260a22f736e11dd1d2100a26ac11ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2024 17:36:28 +0900 Subject: [PATCH 043/223] Allow setting osu!mania scroll speed to single decimal precision Addresses https://github.com/ppy/osu/discussions/30663. --- .../Configuration/ManiaRulesetConfigManager.cs | 6 +++--- .../Edit/DrawableManiaEditorRuleset.cs | 2 +- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 8 ++++---- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 4 ++-- osu.Game/Localisation/RulesetSettingsStrings.cs | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index f975c7f1d4..d9cc224ad1 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Configuration { base.InitialiseDefaults(); - SetDefault(ManiaRulesetSetting.ScrollSpeed, 8, 1, 40); + SetDefault(ManiaRulesetSetting.ScrollSpeed, 8.0, 1.0, 40.0, 0.1); SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); SetDefault(ManiaRulesetSetting.TimingBasedNoteColouring, false); @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Configuration if (Get(ManiaRulesetSetting.ScrollTime) is double scrollTime) { - SetValue(ManiaRulesetSetting.ScrollSpeed, (int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime)); + SetValue(ManiaRulesetSetting.ScrollSpeed, Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime)); SetValue(ManiaRulesetSetting.ScrollTime, null); } #pragma warning restore CS0618 @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Configuration public override TrackedSettings CreateTrackedSettings() => new TrackedSettings { - new TrackedSetting(ManiaRulesetSetting.ScrollSpeed, + new TrackedSetting(ManiaRulesetSetting.ScrollSpeed, speed => new SettingDescription( rawValue: speed, name: RulesetSettingsStrings.ScrollSpeed, diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs index 4c4cf519ce..181bc7341c 100644 --- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs +++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Mania.Edit protected override void Update() { - TargetTimeRange = TimelineTimeRange == null || ShowSpeedChanges.Value ? ComputeScrollTime(Config.Get(ManiaRulesetSetting.ScrollSpeed)) : TimelineTimeRange.Value; + TargetTimeRange = TimelineTimeRange == null || ShowSpeedChanges.Value ? ComputeScrollTime(Config.Get(ManiaRulesetSetting.ScrollSpeed)) : TimelineTimeRange.Value; base.Update(); } } diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 30eca0636c..17add32513 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Mania LabelText = RulesetSettingsStrings.ScrollingDirection, Current = config.GetBindable(ManiaRulesetSetting.ScrollDirection) }, - new SettingsSlider + new SettingsSlider { LabelText = RulesetSettingsStrings.ScrollSpeed, - Current = config.GetBindable(ManiaRulesetSetting.ScrollSpeed), - KeyboardStep = 5 + Current = config.GetBindable(ManiaRulesetSetting.ScrollSpeed), + KeyboardStep = 1 }, new SettingsCheckbox { @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania }; } - private partial class ManiaScrollSlider : RoundedSliderBar + private partial class ManiaScrollSlider : RoundedSliderBar { public override LocalisableString TooltipText => RulesetSettingsStrings.ScrollSpeedTooltip((int)DrawableManiaRuleset.ComputeScrollTime(Current.Value), Current.Value); } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index aed53e157a..d173ae4143 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.UI protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; private readonly Bindable configDirection = new Bindable(); - private readonly BindableInt configScrollSpeed = new BindableInt(); + private readonly BindableDouble configScrollSpeed = new BindableDouble(); private double currentTimeRange; protected double TargetTimeRange; @@ -160,7 +160,7 @@ namespace osu.Game.Rulesets.Mania.UI /// /// The scroll speed. /// The scroll time. - public static double ComputeScrollTime(int scrollSpeed) => MAX_TIME_RANGE / scrollSpeed; + public static double ComputeScrollTime(double scrollSpeed) => MAX_TIME_RANGE / scrollSpeed; public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer(); diff --git a/osu.Game/Localisation/RulesetSettingsStrings.cs b/osu.Game/Localisation/RulesetSettingsStrings.cs index e3d51f1124..9434cd53de 100644 --- a/osu.Game/Localisation/RulesetSettingsStrings.cs +++ b/osu.Game/Localisation/RulesetSettingsStrings.cs @@ -80,9 +80,9 @@ namespace osu.Game.Localisation public static LocalisableString TimingBasedColouring => new TranslatableString(getKey(@"Timing_based_colouring"), @"Timing-based note colouring"); /// - /// "{0}ms (speed {1})" + /// "{0}ms (speed {1:N1})" /// - public static LocalisableString ScrollSpeedTooltip(int scrollTime, int scrollSpeed) => new TranslatableString(getKey(@"ruleset"), @"{0}ms (speed {1})", scrollTime, scrollSpeed); + public static LocalisableString ScrollSpeedTooltip(int scrollTime, double scrollSpeed) => new TranslatableString(getKey(@"ruleset"), @"{0}ms (speed {1:N1})", scrollTime, scrollSpeed); /// /// "Touch control scheme" From 69c2c988a1e1d4c7ff3cbbbd70a6a8836ecdab00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Nov 2024 09:54:56 +0100 Subject: [PATCH 044/223] Add extra check to ensure closed rooms can't be closed harder --- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 76de649ef8..7d36cec7ba 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -26,6 +26,7 @@ using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Rooms; +using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge.Components; @@ -167,7 +168,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge }) }; - if (Room.Type == MatchType.Playlists && Room.Host?.Id == api.LocalUser.Value.Id && Room.StartDate?.AddMinutes(5) >= DateTimeOffset.Now) + if (Room.Type == MatchType.Playlists && Room.Host?.Id == api.LocalUser.Value.Id && Room.StartDate?.AddMinutes(5) >= DateTimeOffset.Now && Room.Status is not RoomStatusEnded) { items.Add(new OsuMenuItem("Close playlist", MenuItemType.Destructive, () => { From cfc38df88940eb1ea0cbb0e87fea36bd1476a3ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Nov 2024 09:55:28 +0100 Subject: [PATCH 045/223] Add close button to playlists footer --- .../TestScenePlaylistsRoomSubScreen.cs | 32 +++++++ .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 12 +-- .../Playlists/PlaylistsRoomFooter.cs | 95 +++++++++++++++++-- .../Playlists/PlaylistsRoomSubScreen.cs | 15 ++- 4 files changed, 140 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index 4306fc1e6a..5f9e06fda5 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -2,8 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; @@ -14,6 +19,9 @@ namespace osu.Game.Tests.Visual.Playlists { public partial class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene { + [Resolved] + private IAPIProvider api { get; set; } = null!; + protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; [Test] @@ -37,5 +45,29 @@ namespace osu.Game.Tests.Visual.Playlists AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen()); AddAssert("status is still ended", () => roomScreen.Room.Status, Is.TypeOf); } + + [Test] + public void TestCloseButtonGoesAwayAfterGracePeriod() + { + Room room = null!; + PlaylistsRoomSubScreen roomScreen = null!; + + AddStep("create room", () => + { + RoomManager.AddRoom(room = new Room + { + Name = @"Test Room", + Host = api.LocalUser.Value, + Category = RoomCategory.Normal, + StartDate = DateTimeOffset.Now.AddMinutes(-5).AddSeconds(3), + EndDate = DateTimeOffset.Now.AddMinutes(30) + }); + }); + + AddStep("push screen", () => LoadScreen(roomScreen = new PlaylistsRoomSubScreen(room))); + AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen()); + AddAssert("close button present", () => roomScreen.ChildrenOfType().Any()); + AddUntilStep("wait for close button to disappear", () => !roomScreen.ChildrenOfType().Any()); + } } } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index ffea3878fa..4ef31c02c3 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -71,7 +71,7 @@ namespace osu.Game.Screens.OnlinePlay.Match protected RulesetStore Rulesets { get; private set; } = null!; [Resolved] - private IAPIProvider api { get; set; } = null!; + protected IAPIProvider API { get; private set; } = null!; [Resolved(canBeNull: true)] protected OnlinePlayScreen? ParentScreen { get; private set; } @@ -80,7 +80,7 @@ namespace osu.Game.Screens.OnlinePlay.Match private PreviewTrackManager previewTrackManager { get; set; } = null!; [Resolved(canBeNull: true)] - private IDialogOverlay? dialogOverlay { get; set; } + protected IDialogOverlay? DialogOverlay { get; private set; } [Cached] private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); @@ -282,7 +282,7 @@ namespace osu.Game.Screens.OnlinePlay.Match } } - protected virtual bool IsConnected => api.State.Value == APIState.Online; + protected virtual bool IsConnected => API.State.Value == APIState.Online; public override bool OnBackButton() { @@ -361,17 +361,17 @@ namespace osu.Game.Screens.OnlinePlay.Match bool hasUnsavedChanges = Room.RoomID == null && Room.Playlist.Count > 0; - if (dialogOverlay == null || !hasUnsavedChanges) + if (DialogOverlay == null || !hasUnsavedChanges) return true; // if the dialog is already displayed, block exiting until the user explicitly makes a decision. - if (dialogOverlay.CurrentDialog is ConfirmDiscardChangesDialog discardChangesDialog) + if (DialogOverlay.CurrentDialog is ConfirmDiscardChangesDialog discardChangesDialog) { discardChangesDialog.Flash(); return false; } - dialogOverlay.Push(new ConfirmDiscardChangesDialog(() => + DialogOverlay.Push(new ConfirmDiscardChangesDialog(() => { ExitConfirmed = true; settingsOverlay.Hide(); diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs index 0d837423a6..7838bd2fc8 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs @@ -2,9 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.ComponentModel; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Online.Rooms; +using osu.Game.Online.Rooms.RoomStatuses; using osuTK; namespace osu.Game.Screens.OnlinePlay.Playlists @@ -12,22 +17,98 @@ namespace osu.Game.Screens.OnlinePlay.Playlists public partial class PlaylistsRoomFooter : CompositeDrawable { public Action? OnStart; + public Action? OnClose; + + private readonly Room room; + private DangerousRoundedButton closeButton = null!; + + [Resolved] + private IAPIProvider api { get; set; } = null!; public PlaylistsRoomFooter(Room room) + { + this.room = room; + } + + [BackgroundDependencyLoader] + private void load() { RelativeSizeAxes = Axes.Both; - InternalChildren = new[] + InternalChild = new FillFlowContainer { - new PlaylistsReadyButton(room) + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10), + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Size = new Vector2(600, 1), - Action = () => OnStart?.Invoke() + new PlaylistsReadyButton(room) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Size = new Vector2(600, 1), + Action = () => OnStart?.Invoke() + }, + closeButton = new DangerousRoundedButton + { + Text = "Close", + Action = () => OnClose?.Invoke(), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 1), + Alpha = 0, + RelativeSizeAxes = Axes.Y, + } } }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + room.PropertyChanged += onRoomChanged; + updateState(); + } + + private void hideCloseButton() + { + closeButton?.ResizeWidthTo(0, 100, Easing.OutQuint) + .Then().FadeOut().Expire(); + } + + private void onRoomChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Status) || e.PropertyName == nameof(Room.Host) || e.PropertyName == nameof(Room.StartDate)) + updateState(); + } + + private void updateState() + { + TimeSpan? deletionGracePeriodRemaining = room.StartDate?.AddMinutes(5) - DateTimeOffset.Now; + + if (room.Host?.Id == api.LocalUser.Value.Id) + { + if (deletionGracePeriodRemaining > TimeSpan.Zero && room.Status is not RoomStatusEnded) + { + closeButton.FadeIn(); + using (BeginDelayedSequence(deletionGracePeriodRemaining.Value.TotalMilliseconds)) + hideCloseButton(); + } + else if (closeButton.Alpha > 0) + hideCloseButton(); + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + room.PropertyChanged -= onRoomChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 44d1841fb8..bac99123d5 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -12,7 +12,9 @@ using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Graphics.Cursor; using osu.Game.Input; +using osu.Game.Online.API.Requests; using osu.Game.Online.Rooms; +using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Match.Components; @@ -255,7 +257,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected override Drawable CreateFooter() => new PlaylistsRoomFooter(Room) { - OnStart = StartPlay + OnStart = StartPlay, + OnClose = closePlaylist, }; protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new PlaylistsRoomSettingsOverlay(room) @@ -273,6 +276,16 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Logger.Log($"Polling adjusted (selection: {selectionPollingComponent.TimeBetweenPolls.Value})"); } + private void closePlaylist() + { + DialogOverlay?.Push(new ClosePlaylistDialog(Room, () => + { + var request = new ClosePlaylistRequest(Room.RoomID!.Value); + request.Success += () => Room.Status = new RoomStatusEnded(); + API.Queue(request); + })); + } + protected override Screen CreateGameplayScreen(PlaylistItem selectedItem) { return new PlayerLoader(() => new PlaylistsPlayer(Room, selectedItem) From 8b68859d9d2ef11ee48a425a818a7f4f390b13c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Nov 2024 09:55:40 +0100 Subject: [PATCH 046/223] Fix `Room.CopyFrom()` skipping a field Was making the close button not display when creating a room anew. --- osu.Game/Online/Rooms/Room.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 486f70c0ed..094fe4ce56 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -379,6 +379,7 @@ namespace osu.Game.Online.Rooms Type = other.Type; MaxParticipants = other.MaxParticipants; ParticipantCount = other.ParticipantCount; + StartDate = other.StartDate; EndDate = other.EndDate; UserScore = other.UserScore; QueueMode = other.QueueMode; From c590bef4c3ce5550e0b1af2ad6ab1625348e58d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2024 19:05:29 +0900 Subject: [PATCH 047/223] Remove legacy default setter for `SamplesMatchPlaybackRate` now that it's the default --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 3d8c8a6e7a..4d7ac355e0 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -192,7 +192,6 @@ namespace osu.Game.Beatmaps.Formats private static void applyLegacyDefaults(BeatmapInfo beatmapInfo) { beatmapInfo.WidescreenStoryboard = false; - beatmapInfo.SamplesMatchPlaybackRate = false; } protected override void ParseLine(Beatmap beatmap, Section section, string line) From ead7e99c591aa037110635a304d9368a8ea2435a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Nov 2024 11:06:36 +0100 Subject: [PATCH 048/223] Fix incorrect comment --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 698acc9822..a520040ad1 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -94,7 +94,7 @@ namespace osu.Game.Database /// 41 2024-04-17 Add ScoreInfo.TotalScoreWithoutMods for future mod multiplier rebalances. /// 42 2024-08-07 Update mania key bindings to reflect changes to ManiaAction /// 43 2024-10-14 Reset keybind for toggling FPS display to avoid conflict with "convert to stream" in the editor, if not already changed by user. - /// 44 2024-11-22 Removed several properties from ScoreInfo which did not need to be persisted to realm. + /// 44 2024-11-22 Removed several properties from BeatmapInfo which did not need to be persisted to realm. /// private const int schema_version = 44; From c844d65a81da4606eee240ad58774d38953cedb4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2024 19:11:14 +0900 Subject: [PATCH 049/223] Use `TryGetValue` wherever possible Rider says so. --- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 4 ++-- osu.Game/Overlays/Chat/ChannelList/ChannelList.cs | 7 +++---- osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index cb42b2b62a..8f425edc44 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -164,10 +164,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy private Drawable getResult(HitResult result) { - if (!hit_result_mapping.ContainsKey(result)) + if (!hit_result_mapping.TryGetValue(result, out var value)) return null; - string filename = this.GetManiaSkinConfig(hit_result_mapping[result])?.Value + string filename = this.GetManiaSkinConfig(value)?.Value ?? default_hit_result_skin_filenames[result]; var animation = this.GetAnimation(filename, true, true, frameLength: 1000 / 20d); diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index fc0060d86a..3b657e7056 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -120,10 +120,9 @@ namespace osu.Game.Overlays.Chat.ChannelList public void RemoveChannel(Channel channel) { - if (!channelMap.ContainsKey(channel)) + if (!channelMap.TryGetValue(channel, out var item)) return; - ChannelListItem item = channelMap[channel]; FillFlowContainer flow = getFlowForChannel(channel); channelMap.Remove(channel); @@ -134,10 +133,10 @@ namespace osu.Game.Overlays.Chat.ChannelList public ChannelListItem GetItem(Channel channel) { - if (!channelMap.ContainsKey(channel)) + if (!channelMap.TryGetValue(channel, out var item)) throw new ArgumentOutOfRangeException(); - return channelMap[channel]; + return item; } public void ScrollChannelIntoView(Channel channel) => scroll.ScrollIntoView(GetItem(channel)); diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index c27e7f15ca..a311531088 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -78,12 +78,12 @@ namespace osu.Game.Tests.Visual.Spectator /// The spectator state to end play with. public void SendEndPlay(int userId, SpectatedUserState state = SpectatedUserState.Quit) { - if (!userBeatmapDictionary.ContainsKey(userId)) + if (!userBeatmapDictionary.TryGetValue(userId, out int value)) return; ((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState { - BeatmapID = userBeatmapDictionary[userId], + BeatmapID = value, RulesetID = 0, Mods = userModsDictionary[userId], State = state From 9930922769f092d5edd3968404bdd266ad8367fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2024 19:51:55 +0900 Subject: [PATCH 050/223] Fix chat channel listing not being ordered to expectations - Public channels (and announcements) are now alphabetically ordered. - Private message channels are now ordered by most recent activity. Closes https://github.com/ppy/osu/issues/30835. --- .../Overlays/Chat/ChannelList/ChannelList.cs | 66 ++++++++++++++----- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index 3b657e7056..a2ec385a7e 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -77,10 +77,10 @@ namespace osu.Game.Overlays.Chat.ChannelList RelativeSizeAxes = Axes.X, } }, - announceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper()), - publicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper()), + announceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper(), false), + publicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper(), false), selector = new ChannelListItem(ChannelListingChannel), - privateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper()), + privateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper(), true), }, }, }, @@ -111,9 +111,9 @@ namespace osu.Game.Overlays.Chat.ChannelList item.OnRequestSelect += chan => OnRequestSelect?.Invoke(chan); item.OnRequestLeave += chan => OnRequestLeave?.Invoke(chan); - FillFlowContainer flow = getFlowForChannel(channel); + ChannelGroup group = getGroupFromChannel(channel); channelMap.Add(channel, item); - flow.Add(item); + group.AddChannel(item); updateVisibility(); } @@ -123,10 +123,10 @@ namespace osu.Game.Overlays.Chat.ChannelList if (!channelMap.TryGetValue(channel, out var item)) return; - FillFlowContainer flow = getFlowForChannel(channel); + ChannelGroup group = getGroupFromChannel(channel); channelMap.Remove(channel); - flow.Remove(item, true); + group.RemoveChannel(item); updateVisibility(); } @@ -141,21 +141,21 @@ namespace osu.Game.Overlays.Chat.ChannelList public void ScrollChannelIntoView(Channel channel) => scroll.ScrollIntoView(GetItem(channel)); - private FillFlowContainer getFlowForChannel(Channel channel) + private ChannelGroup getGroupFromChannel(Channel channel) { switch (channel.Type) { case ChannelType.Public: - return publicChannelGroup.ItemFlow; + return publicChannelGroup; case ChannelType.PM: - return privateChannelGroup.ItemFlow; + return privateChannelGroup; case ChannelType.Announce: - return announceChannelGroup.ItemFlow; + return announceChannelGroup; default: - return publicChannelGroup.ItemFlow; + return publicChannelGroup; } } @@ -169,9 +169,9 @@ namespace osu.Game.Overlays.Chat.ChannelList private partial class ChannelGroup : FillFlowContainer { - public readonly FillFlowContainer ItemFlow; + public readonly ChannelListItemFlow ItemFlow; - public ChannelGroup(LocalisableString label) + public ChannelGroup(LocalisableString label, bool sortByRecent) { Direction = FillDirection.Vertical; RelativeSizeAxes = Axes.X; @@ -186,7 +186,7 @@ namespace osu.Game.Overlays.Chat.ChannelList Margin = new MarginPadding { Left = 18, Bottom = 5 }, Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), }, - ItemFlow = new FillFlowContainer + ItemFlow = new ChannelListItemFlow(sortByRecent) { Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.X, @@ -194,6 +194,42 @@ namespace osu.Game.Overlays.Chat.ChannelList }, }; } + + public partial class ChannelListItemFlow : FillFlowContainer + { + private readonly bool sortByRecent; + + public ChannelListItemFlow(bool sortByRecent) + { + this.sortByRecent = sortByRecent; + } + + public void Reflow() => InvalidateLayout(); + + public override IEnumerable FlowingChildren => sortByRecent + ? base.FlowingChildren.OfType().OrderByDescending(i => i.Channel.LastMessageId) + : base.FlowingChildren.OfType().OrderBy(i => i.Channel.Name); + } + + public void AddChannel(ChannelListItem item) + { + ItemFlow.Add(item); + + item.Channel.NewMessagesArrived += newMessagesArrived; + item.Channel.PendingMessageResolved += pendingMessageResolved; + + ItemFlow.Reflow(); + } + + public void RemoveChannel(ChannelListItem item) + { + item.Channel.NewMessagesArrived -= newMessagesArrived; + item.Channel.PendingMessageResolved -= pendingMessageResolved; + ItemFlow.Remove(item, true); + } + + private void pendingMessageResolved(LocalEchoMessage _, Message __) => ItemFlow.Reflow(); + private void newMessagesArrived(IEnumerable _) => ItemFlow.Reflow(); } private partial class ChannelSearchTextBox : BasicSearchTextBox From 62837c7e53de8e9130c216b3b6c3158ef2504d78 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 9 Nov 2024 10:27:23 -0800 Subject: [PATCH 051/223] Fix discord "view beatmap" button being shown when editing and hide identifiable information is set --- osu.Desktop/DiscordRichPresence.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 5a7a01df1b..ba61f4be34 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -167,7 +167,9 @@ namespace osu.Desktop presence.State = clampLength(activity.Value.GetStatus(hideIdentifiableInformation)); presence.Details = clampLength(activity.Value.GetDetails(hideIdentifiableInformation) ?? string.Empty); - if (getBeatmapID(activity.Value) is int beatmapId && beatmapId > 0) + if (getBeatmapID(activity.Value) is int beatmapId + && beatmapId > 0 + && !(activity.Value is UserActivity.EditingBeatmap && hideIdentifiableInformation)) { presence.Buttons = new[] { From 3713bb48b775c00da2fbbb6d86b98fc7b8ddafa5 Mon Sep 17 00:00:00 2001 From: Sheppsu <49356627+Sheppsu@users.noreply.github.com> Date: Sat, 23 Nov 2024 01:09:58 -0500 Subject: [PATCH 052/223] expand and contract settings from hover --- .../Spectate/MultiSpectatorSettings.cs | 78 +++++++------------ 1 file changed, 29 insertions(+), 49 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs index 64c798b092..dfb26d104a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs @@ -1,80 +1,60 @@ // 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.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play.HUD; -using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { - public partial class MultiSpectatorSettings : CompositeDrawable + public partial class MultiSpectatorSettings : ExpandingContainer { - private const double slide_duration = 200; - - private readonly PlayerSettingsOverlay playerSettingsOverlay; - private readonly Container slidingContainer; - - private readonly BindableBool opened = new BindableBool(); + public const float CONTRACTED_WIDTH = 30; + public const int EXPANDED_WIDTH = 300; public MultiSpectatorSettings() + : base(CONTRACTED_WIDTH, EXPANDED_WIDTH) { - Origin = Anchor.TopLeft; + Origin = Anchor.TopRight; Anchor = Anchor.TopRight; - AutoSizeAxes = Axes.Both; - InternalChildren = new Drawable[] + PlayerSettingsOverlay playerSettingsOverlay; + + InternalChild = new FillFlowContainer { - slidingContainer = new Container + Origin = Anchor.TopLeft, + Anchor = Anchor.TopLeft, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { - Origin = Anchor.TopLeft, - Anchor = Anchor.TopLeft, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] + new IconButton { - new IconButton - { - Icon = FontAwesome.Solid.Cog, - Origin = Anchor.TopLeft, - Anchor = Anchor.TopLeft, - Position = new Vector2(-30, 0), - Action = () => opened.Toggle() - }, - playerSettingsOverlay = new PlayerSettingsOverlay() + Icon = FontAwesome.Solid.Cog, + Origin = Anchor.TopLeft, + Anchor = Anchor.TopLeft, + Action = () => Expanded.Toggle() + }, + playerSettingsOverlay = new PlayerSettingsOverlay + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft } } }; playerSettingsOverlay.Show(); - - opened.BindValueChanged(value => - { - if (value.NewValue) - open(); - else - close(); - }); } - private void open() + protected override void OnHoverLost(HoverLostEvent e) { - slidingContainer.MoveToOffset(new Vector2(-playerSettingsOverlay.Width, 0), slide_duration, Easing.Out).Then().OnComplete(c => - { - c.Origin = Anchor.TopRight; - c.Position = Vector2.Zero; - }); - } - - private void close() - { - slidingContainer.MoveToOffset(new Vector2(playerSettingsOverlay.Width, 0), slide_duration, Easing.Out).Then().OnComplete(c => - { - c.Origin = Anchor.TopLeft; - c.Position = Vector2.Zero; - }); + // Prevent unexpanding when hovering player settings + if (!Contains(e.ScreenSpaceMousePosition)) + base.OnHoverLost(e); } } } From eed02c2ab143b0fd1973906aa5a3d629f581eb49 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sat, 23 Nov 2024 15:45:29 -0500 Subject: [PATCH 053/223] Fix daily challenge results screen beginning score fetch from user highest --- .../DailyChallenge/DailyChallenge.cs | 2 +- .../DailyChallenge/DailyChallengePlayer.cs | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengePlayer.cs diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 0dc7e7930a..6cb8a87a2a 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -532,7 +532,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private void startPlay() { sampleStart?.Play(); - this.Push(new PlayerLoader(() => new PlaylistsPlayer(room, playlistItem) + this.Push(new PlayerLoader(() => new DailyChallengePlayer(room, playlistItem) { Exited = () => Scheduler.AddOnce(() => leaderboard.RefetchScores()) })); diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengePlayer.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengePlayer.cs new file mode 100644 index 0000000000..cfc0898e5a --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengePlayer.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics; +using osu.Game.Online.Rooms; +using osu.Game.Scoring; +using osu.Game.Screens.OnlinePlay.Playlists; +using osu.Game.Screens.Play; +using osu.Game.Screens.Ranking; + +namespace osu.Game.Screens.OnlinePlay.DailyChallenge +{ + public partial class DailyChallengePlayer : PlaylistsPlayer + { + public DailyChallengePlayer(Room room, PlaylistItem playlistItem, PlayerConfiguration? configuration = null) + : base(room, playlistItem, configuration) + { + } + + protected override ResultsScreen CreateResults(ScoreInfo score) + { + Debug.Assert(Room.RoomID != null); + + if (score.OnlineID >= 0) + { + return new PlaylistItemScoreResultsScreen(Room.RoomID.Value, PlaylistItem, score.OnlineID) + { + AllowRetry = true, + ShowUserStatistics = true, + }; + } + + // If the score has failed submission, fall back to displaying scores from user's highest. + return new PlaylistItemUserResultsScreen(score, Room.RoomID.Value, PlaylistItem) + { + AllowRetry = true, + ShowUserStatistics = true, + }; + } + } +} From cab26c70c1f2fca14b3feaa2abff5385963a401b Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sat, 23 Nov 2024 22:27:56 -0500 Subject: [PATCH 054/223] Fix editor grid settings not displaying decimal portion in slider tooltips --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 768a764ad1..2fe0d51034 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit { MinValue = 0f, MaxValue = OsuPlayfield.BASE_SIZE.X, + Precision = 0.01f, }; /// @@ -47,6 +48,7 @@ namespace osu.Game.Rulesets.Osu.Edit { MinValue = 0f, MaxValue = OsuPlayfield.BASE_SIZE.Y, + Precision = 0.01f, }; /// @@ -56,6 +58,7 @@ namespace osu.Game.Rulesets.Osu.Edit { MinValue = 4f, MaxValue = 128f, + Precision = 0.01f, }; /// @@ -65,6 +68,7 @@ namespace osu.Game.Rulesets.Osu.Edit { MinValue = -180f, MaxValue = 180f, + Precision = 0.01f, }; /// From 259ad8ae0f55f7802d252529b0cc80373c5504a5 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sat, 23 Nov 2024 23:28:22 -0500 Subject: [PATCH 055/223] Add failing test cases --- .../Editing/TestSceneEditorBeatmapCreation.cs | 131 +++++++++++++++--- 1 file changed, 113 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index db87987815..b15ee0cab8 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -104,28 +104,12 @@ namespace osu.Game.Tests.Visual.Editing { var setup = Editor.ChildrenOfType().First(); - string temp = TestResources.GetTestBeatmapForImport(); - - string extractedFolder = $"{temp}_extracted"; - Directory.CreateDirectory(extractedFolder); - - try + return setFile(TestResources.GetTestBeatmapForImport(), extractedFolder => { - using (var zip = ZipArchive.Open(temp)) - zip.WriteToDirectory(extractedFolder); - bool success = setup.ChildrenOfType().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"))); - - // ensure audio file is copied to beatmap as "audio.mp3" rather than original filename. Assert.That(Beatmap.Value.Metadata.AudioFile == "audio.mp3"); - return success; - } - finally - { - File.Delete(temp); - Directory.Delete(extractedFolder, true); - } + }); }); AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual); @@ -530,5 +514,116 @@ namespace osu.Game.Tests.Visual.Editing return set != null && set.PerformRead(s => s.Beatmaps.Count == 3 && s.Files.Count == 3); }); } + + [Test] + public void TestMultipleBackgroundFiles() + { + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddAssert("set background", () => setBackground(expected: "bg.jpg")); + + AddStep("save", () => Editor.Save()); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty"; + }); + + AddAssert("new difficulty uses same background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("set background", () => setBackground(expected: "bg (1).jpg")); + AddAssert("new difficulty uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); + + AddStep("save", () => Editor.Save()); + AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + AddAssert("old difficulty uses old background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); + AddAssert("old background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg.jpg")); + + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddStep("set background", () => setBackground(expected: "bg.jpg")); + AddAssert("other background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); + + bool setBackground(string expected) + { + var setup = Editor.ChildrenOfType().First(); + + return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder => + { + bool success = setup.ChildrenOfType().First().ChangeBackgroundImage(new FileInfo(Path.Combine(extractedFolder, "machinetop_background.jpg"))); + Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected)); + return success; + }); + } + } + + [Test] + public void TestMultipleAudioFiles() + { + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddAssert("set audio", () => setAudio(expected: "audio.mp3")); + + AddStep("save", () => Editor.Save()); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty"; + }); + + AddAssert("new difficulty uses same audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("set audio", () => setAudio(expected: "audio (1).mp3")); + AddAssert("new difficulty uses new audio", () => Beatmap.Value.Metadata.AudioFile == "audio (1).mp3"); + + AddStep("save", () => Editor.Save()); + AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + AddAssert("old difficulty uses old audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); + AddAssert("old audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio.mp3")); + + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddStep("set audio", () => setAudio(expected: "audio.mp3")); + AddAssert("other audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); + + bool setAudio(string expected) + { + var setup = Editor.ChildrenOfType().First(); + + return setFile(TestResources.GetTestBeatmapForImport(), extractedFolder => + { + bool success = setup.ChildrenOfType().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"))); + Assert.That(Beatmap.Value.Metadata.AudioFile, Is.EqualTo(expected)); + return success; + }); + } + } + + private bool setFile(string archivePath, Func func) + { + string temp = archivePath; + + string extractedFolder = $"{temp}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); + + return func(extractedFolder); + } + finally + { + File.Delete(temp); + Directory.Delete(extractedFolder, true); + } + } } } From 871c365fd8d0ff1525f07462bf2173e3848c7de9 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 00:32:27 -0500 Subject: [PATCH 056/223] Preserve existing beatmap background/audio files if used elsewhere --- .../Screens/Edit/Setup/ResourcesSection.cs | 58 ++++++++++++------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 845c21b598..8ab26a74a2 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -77,27 +77,35 @@ namespace osu.Game.Screens.Edit.Setup if (!source.Exists) return false; + var beatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; - var destination = new FileInfo($@"bg{source.Extension}"); + string[] filenames = set.Files.Select(f => f.Filename).Where(f => + f.StartsWith(@"bg", StringComparison.OrdinalIgnoreCase) && + f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - // remove the previous background for now. - // in the future we probably want to check if this is being used elsewhere (other difficulties?) - var oldFile = set.GetFile(working.Value.Metadata.BackgroundFile); + string currentFilename = working.Value.Metadata.BackgroundFile; + string? newFilename = null; + + var oldFile = set.GetFile(currentFilename); + + if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.BackgroundFile != currentFilename)) + { + beatmaps.DeleteFile(set, oldFile); + newFilename = currentFilename; + } + + newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"bg{source.Extension}"); using (var stream = source.OpenRead()) - { - if (oldFile != null) - beatmaps.DeleteFile(set, oldFile); + beatmaps.AddFile(set, stream, newFilename); - beatmaps.AddFile(set, stream, destination.Name); - } + working.Value.Metadata.BackgroundFile = newFilename; + updateAllDifficultiesButton.Enabled.Value = true; editorBeatmap.SaveState(); - working.Value.Metadata.BackgroundFile = destination.Name; headerBackground.UpdateBackground(); - editor?.ApplyToBackground(bg => bg.RefreshBackground()); return true; @@ -108,23 +116,31 @@ namespace osu.Game.Screens.Edit.Setup if (!source.Exists) return false; + var beatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; - var destination = new FileInfo($@"audio{source.Extension}"); + string[] filenames = set.Files.Select(f => f.Filename).Where(f => + f.StartsWith(@"audio", StringComparison.OrdinalIgnoreCase) && + f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - // remove the previous audio track for now. - // in the future we probably want to check if this is being used elsewhere (other difficulties?) - var oldFile = set.GetFile(working.Value.Metadata.AudioFile); + string currentFilename = working.Value.Metadata.AudioFile; + string? newFilename = null; - using (var stream = source.OpenRead()) + var oldFile = set.GetFile(currentFilename); + + if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.AudioFile != currentFilename)) { - if (oldFile != null) - beatmaps.DeleteFile(set, oldFile); - - beatmaps.AddFile(set, stream, destination.Name); + beatmaps.DeleteFile(set, oldFile); + newFilename = currentFilename; } - working.Value.Metadata.AudioFile = destination.Name; + newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"audio{source.Extension}"); + + using (var stream = source.OpenRead()) + beatmaps.AddFile(set, stream, newFilename); + + working.Value.Metadata.AudioFile = newFilename; + updateAllDifficultiesButton.Enabled.Value = true; editorBeatmap.SaveState(); music.ReloadCurrentTrack(); From 8e20dc7e9def53de9cc45706fec3e9d77a2c00d4 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 00:32:50 -0500 Subject: [PATCH 057/223] Add option to update all difficulties with new background/audio file --- .../Screens/Edit/Setup/ResourcesSection.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 8ab26a74a2..50c7072f84 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -1,7 +1,9 @@ // 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.IO; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -10,6 +12,8 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osu.Game.Localisation; +using osu.Game.Models; +using osu.Game.Utils; namespace osu.Game.Screens.Edit.Setup { @@ -36,6 +40,7 @@ namespace osu.Game.Screens.Edit.Setup private Editor? editor { get; set; } private SetupScreenHeaderBackground headerBackground = null!; + private RoundedButton updateAllDifficultiesButton = null!; [BackgroundDependencyLoader] private void load() @@ -58,6 +63,13 @@ namespace osu.Game.Screens.Edit.Setup Caption = EditorSetupStrings.AudioTrack, PlaceholderText = EditorSetupStrings.ClickToSelectTrack, }, + updateAllDifficultiesButton = new RoundedButton + { + RelativeSizeAxes = Axes.X, + Text = "Update all difficulties", + Action = updateAllDifficulties, + Enabled = { Value = false }, + } }; backgroundChooser.PreviewContainer.Add(headerBackground); @@ -148,6 +160,41 @@ namespace osu.Game.Screens.Edit.Setup return true; } + private void updateAllDifficulties() + { + var beatmap = working.Value.BeatmapInfo; + var set = working.Value.BeatmapSetInfo; + + string backgroundFile = working.Value.Metadata.BackgroundFile; + string audioFile = working.Value.Metadata.AudioFile; + + foreach (var otherBeatmap in set.Beatmaps.Where(b => !b.Equals(beatmap))) + { + var otherWorking = beatmaps.GetWorkingBeatmap(otherBeatmap); + + if (!string.Equals(otherBeatmap.Metadata.BackgroundFile, backgroundFile, StringComparison.OrdinalIgnoreCase)) + { + if (set.GetFile(otherBeatmap.Metadata.BackgroundFile) is RealmNamedFileUsage file) + beatmaps.DeleteFile(set, file); + + otherBeatmap.Metadata.BackgroundFile = backgroundFile; + } + + if (!string.Equals(otherBeatmap.Metadata.AudioFile, audioFile, StringComparison.OrdinalIgnoreCase)) + { + if (set.GetFile(otherBeatmap.Metadata.AudioFile) is RealmNamedFileUsage file) + beatmaps.DeleteFile(set, file); + + otherBeatmap.Metadata.AudioFile = audioFile; + } + + beatmaps.Save(otherBeatmap, otherWorking.Beatmap); + } + + editorBeatmap.SaveState(); + updateAllDifficultiesButton.Enabled.Value = false; + } + private void backgroundChanged(ValueChangedEvent file) { if (file.NewValue == null || !ChangeBackgroundImage(file.NewValue)) From e348b3a7aa929b31116751e665c2b1bf402a3df9 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 00:34:03 -0500 Subject: [PATCH 058/223] Only enable button if there are multiple difficulties --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 50c7072f84..5c904f6ce1 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Edit.Setup beatmaps.AddFile(set, stream, newFilename); working.Value.Metadata.BackgroundFile = newFilename; - updateAllDifficultiesButton.Enabled.Value = true; + updateAllDifficultiesButton.Enabled.Value = set.Beatmaps.Count > 1; editorBeatmap.SaveState(); @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Edit.Setup beatmaps.AddFile(set, stream, newFilename); working.Value.Metadata.AudioFile = newFilename; - updateAllDifficultiesButton.Enabled.Value = true; + updateAllDifficultiesButton.Enabled.Value = set.Beatmaps.Count > 1; editorBeatmap.SaveState(); music.ReloadCurrentTrack(); From dc210d59b5a4b4954cd87194c941857d20637d9b Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 00:45:11 -0500 Subject: [PATCH 059/223] Add test coverage for sync button --- .../Editing/TestSceneEditorBeatmapCreation.cs | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index b15ee0cab8..9fabed346b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -15,6 +15,8 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Collections; using osu.Game.Database; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Localisation; using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; @@ -605,6 +607,60 @@ namespace osu.Game.Tests.Visual.Editing } } + [Test] + public void TestUpdateBackgroundOnAllDifficulties() + { + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddAssert("button disabled", () => !getButton().Enabled.Value); + AddAssert("set background", () => setBackground(expected: "bg.jpg")); + + // there is only one diff so this should still be disabled. + AddAssert("button still disabled", () => !getButton().Enabled.Value); + + AddStep("save", () => Editor.Save()); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty"; + }); + + AddAssert("new difficulty uses same background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("button disabled", () => !getButton().Enabled.Value); + AddAssert("set background", () => setBackground(expected: "bg (1).jpg")); + AddAssert("new background added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); + AddAssert("new difficulty uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); + + AddAssert("button enabled", () => getButton().Enabled.Value); + AddStep("press button", () => getButton().TriggerClick()); + + AddAssert("new difficulty still uses new background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps[1].Metadata.BackgroundFile == "bg (1).jpg"); + AddAssert("old difficulty uses new background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps[0].Metadata.BackgroundFile == "bg (1).jpg"); + AddAssert("old background removed", () => Beatmap.Value.BeatmapSetInfo.Files.All(f => f.Filename != "bg.jpg")); + + AddStep("save", () => Editor.Save()); + AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + AddAssert("old difficulty still uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); + + bool setBackground(string expected) + { + var setup = Editor.ChildrenOfType().First(); + + return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder => + { + bool success = setup.ChildrenOfType().First().ChangeBackgroundImage(new FileInfo(Path.Combine(extractedFolder, "machinetop_background.jpg"))); + Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected)); + return success; + }); + } + + RoundedButton getButton() => Editor.ChildrenOfType().Single(b => b.Text == EditorSetupStrings.ResourcesUpdateAllDifficulties); + } + private bool setFile(string archivePath, Func func) { string temp = archivePath; From c8b13b726dc23feebffa628deab6fa0f2252813a Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 00:45:17 -0500 Subject: [PATCH 060/223] Add localisation support --- osu.Game/Localisation/EditorSetupStrings.cs | 5 +++++ osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Localisation/EditorSetupStrings.cs b/osu.Game/Localisation/EditorSetupStrings.cs index 350517734f..60e677757e 100644 --- a/osu.Game/Localisation/EditorSetupStrings.cs +++ b/osu.Game/Localisation/EditorSetupStrings.cs @@ -188,6 +188,11 @@ namespace osu.Game.Localisation /// public static LocalisableString AudioTrack => new TranslatableString(getKey(@"audio_track"), @"Audio Track"); + /// + /// "Update all difficulties" + /// + public static LocalisableString ResourcesUpdateAllDifficulties => new TranslatableString(getKey(@"resources_update_all_difficulties"), @"Update all difficulties"); + /// /// "Click to select a track" /// diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 5c904f6ce1..aa28e56218 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Edit.Setup updateAllDifficultiesButton = new RoundedButton { RelativeSizeAxes = Axes.X, - Text = "Update all difficulties", + Text = EditorSetupStrings.ResourcesUpdateAllDifficulties, Action = updateAllDifficulties, Enabled = { Value = false }, } From a872f749740b8f1bcf755add2cbe0d7298fa489e Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 01:02:34 -0500 Subject: [PATCH 061/223] Make sync button only affect changed resource type --- .../Screens/Edit/Setup/ResourcesSection.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index aa28e56218..8c9b9796ed 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -84,6 +84,9 @@ namespace osu.Game.Screens.Edit.Setup audioTrackChooser.Current.BindValueChanged(audioTrackChanged); } + private string? newBackgroundFile; + private string? newAudioFile; + public bool ChangeBackgroundImage(FileInfo source) { if (!source.Exists) @@ -112,7 +115,7 @@ namespace osu.Game.Screens.Edit.Setup using (var stream = source.OpenRead()) beatmaps.AddFile(set, stream, newFilename); - working.Value.Metadata.BackgroundFile = newFilename; + working.Value.Metadata.BackgroundFile = newBackgroundFile = newFilename; updateAllDifficultiesButton.Enabled.Value = set.Beatmaps.Count > 1; editorBeatmap.SaveState(); @@ -151,7 +154,7 @@ namespace osu.Game.Screens.Edit.Setup using (var stream = source.OpenRead()) beatmaps.AddFile(set, stream, newFilename); - working.Value.Metadata.AudioFile = newFilename; + working.Value.Metadata.AudioFile = newAudioFile = newFilename; updateAllDifficultiesButton.Enabled.Value = set.Beatmaps.Count > 1; editorBeatmap.SaveState(); @@ -165,27 +168,24 @@ namespace osu.Game.Screens.Edit.Setup var beatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; - string backgroundFile = working.Value.Metadata.BackgroundFile; - string audioFile = working.Value.Metadata.AudioFile; - foreach (var otherBeatmap in set.Beatmaps.Where(b => !b.Equals(beatmap))) { var otherWorking = beatmaps.GetWorkingBeatmap(otherBeatmap); - if (!string.Equals(otherBeatmap.Metadata.BackgroundFile, backgroundFile, StringComparison.OrdinalIgnoreCase)) + if (newBackgroundFile != null && !string.Equals(otherBeatmap.Metadata.BackgroundFile, newBackgroundFile, StringComparison.OrdinalIgnoreCase)) { if (set.GetFile(otherBeatmap.Metadata.BackgroundFile) is RealmNamedFileUsage file) beatmaps.DeleteFile(set, file); - otherBeatmap.Metadata.BackgroundFile = backgroundFile; + otherBeatmap.Metadata.BackgroundFile = newBackgroundFile; } - if (!string.Equals(otherBeatmap.Metadata.AudioFile, audioFile, StringComparison.OrdinalIgnoreCase)) + if (newAudioFile != null && !string.Equals(otherBeatmap.Metadata.AudioFile, newAudioFile, StringComparison.OrdinalIgnoreCase)) { if (set.GetFile(otherBeatmap.Metadata.AudioFile) is RealmNamedFileUsage file) beatmaps.DeleteFile(set, file); - otherBeatmap.Metadata.AudioFile = audioFile; + otherBeatmap.Metadata.AudioFile = newAudioFile; } beatmaps.Save(otherBeatmap, otherWorking.Beatmap); From 95a6226413a29f82b64040ebd081939a354d7a06 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 01:11:28 -0500 Subject: [PATCH 062/223] Only enable button if there are different filenames --- .../Screens/Edit/Setup/ResourcesSection.cs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 8c9b9796ed..863cf9f241 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Edit.Setup private Editor? editor { get; set; } private SetupScreenHeaderBackground headerBackground = null!; - private RoundedButton updateAllDifficultiesButton = null!; + private RoundedButton syncResourcesButton = null!; [BackgroundDependencyLoader] private void load() @@ -63,11 +63,11 @@ namespace osu.Game.Screens.Edit.Setup Caption = EditorSetupStrings.AudioTrack, PlaceholderText = EditorSetupStrings.ClickToSelectTrack, }, - updateAllDifficultiesButton = new RoundedButton + syncResourcesButton = new RoundedButton { RelativeSizeAxes = Axes.X, Text = EditorSetupStrings.ResourcesUpdateAllDifficulties, - Action = updateAllDifficulties, + Action = syncResources, Enabled = { Value = false }, } }; @@ -116,7 +116,7 @@ namespace osu.Game.Screens.Edit.Setup beatmaps.AddFile(set, stream, newFilename); working.Value.Metadata.BackgroundFile = newBackgroundFile = newFilename; - updateAllDifficultiesButton.Enabled.Value = set.Beatmaps.Count > 1; + syncResourcesButton.Enabled.Value = set.Beatmaps.Count > 1; editorBeatmap.SaveState(); @@ -155,7 +155,7 @@ namespace osu.Game.Screens.Edit.Setup beatmaps.AddFile(set, stream, newFilename); working.Value.Metadata.AudioFile = newAudioFile = newFilename; - updateAllDifficultiesButton.Enabled.Value = set.Beatmaps.Count > 1; + updateSyncResourcesButton(); editorBeatmap.SaveState(); music.ReloadCurrentTrack(); @@ -163,7 +163,16 @@ namespace osu.Game.Screens.Edit.Setup return true; } - private void updateAllDifficulties() + private void updateSyncResourcesButton() + { + var set = working.Value.BeatmapSetInfo; + + syncResourcesButton.Enabled.Value = + (newBackgroundFile != null && set.Beatmaps.DistinctBy(b => b.Metadata.BackgroundFile, StringComparer.OrdinalIgnoreCase).Count() > 1) || + (newAudioFile != null && set.Beatmaps.DistinctBy(b => b.Metadata.AudioFile, StringComparer.OrdinalIgnoreCase).Count() > 1); + } + + private void syncResources() { var beatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; @@ -192,7 +201,7 @@ namespace osu.Game.Screens.Edit.Setup } editorBeatmap.SaveState(); - updateAllDifficultiesButton.Enabled.Value = false; + syncResourcesButton.Enabled.Value = false; } private void backgroundChanged(ValueChangedEvent file) From 3480da22d2dcde78dd23cb20d8131784ac9d5522 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 01:13:39 -0500 Subject: [PATCH 063/223] Remove no-op `SaveState` call --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 863cf9f241..a52e42c7c0 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -200,7 +200,6 @@ namespace osu.Game.Screens.Edit.Setup beatmaps.Save(otherBeatmap, otherWorking.Beatmap); } - editorBeatmap.SaveState(); syncResourcesButton.Enabled.Value = false; } From 631bfadd68f2a58ef51f7d17c10271bfddc34de5 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 04:10:01 -0500 Subject: [PATCH 064/223] Replace event subscription with callback in `UserStatisticsWatcher` Also no longer cancels previous API requests as there's no actual need to do it. --- .../Online/LocalUserStatisticsProvider.cs | 19 +++++++---------- osu.Game/Online/UserStatisticsWatcher.cs | 21 +++++-------------- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/osu.Game/Online/LocalUserStatisticsProvider.cs b/osu.Game/Online/LocalUserStatisticsProvider.cs index a25f5b05aa..5fa2b40715 100644 --- a/osu.Game/Online/LocalUserStatisticsProvider.cs +++ b/osu.Game/Online/LocalUserStatisticsProvider.cs @@ -36,7 +36,6 @@ namespace osu.Game.Online private IAPIProvider api { get; set; } = null!; private readonly Dictionary statisticsCache = new Dictionary(); - private readonly Dictionary statisticsRequests = new Dictionary(); /// /// Returns the currently available for the given ruleset. @@ -62,23 +61,21 @@ namespace osu.Game.Online RefetchStatistics(ruleset); } - public void RefetchStatistics(RulesetInfo ruleset) + public void RefetchStatistics(RulesetInfo ruleset, Action? callback = null) { - if (statisticsRequests.TryGetValue(ruleset.ShortName, out var previousRequest)) - previousRequest.Cancel(); - - var request = statisticsRequests[ruleset.ShortName] = new GetUserRequest(api.LocalUser.Value.Id, ruleset); - request.Success += u => UpdateStatistics(u.Statistics, ruleset); + var request = new GetUserRequest(api.LocalUser.Value.Id, ruleset); + request.Success += u => UpdateStatistics(u.Statistics, ruleset, callback); api.Queue(request); } - protected void UpdateStatistics(UserStatistics newStatistics, RulesetInfo ruleset) + protected void UpdateStatistics(UserStatistics newStatistics, RulesetInfo ruleset, Action? callback = null) { var oldStatistics = statisticsCache.GetValueOrDefault(ruleset.ShortName); - - statisticsRequests.Remove(ruleset.ShortName); statisticsCache[ruleset.ShortName] = newStatistics; - StatisticsUpdated?.Invoke(new UserStatisticsUpdate(ruleset, oldStatistics, newStatistics)); + + var update = new UserStatisticsUpdate(ruleset, oldStatistics, newStatistics); + callback?.Invoke(update); + StatisticsUpdated?.Invoke(update); } } diff --git a/osu.Game/Online/UserStatisticsWatcher.cs b/osu.Game/Online/UserStatisticsWatcher.cs index 8ed1ff594d..73ca3c9f53 100644 --- a/osu.Game/Online/UserStatisticsWatcher.cs +++ b/osu.Game/Online/UserStatisticsWatcher.cs @@ -23,8 +23,6 @@ namespace osu.Game.Online public IBindable LatestUpdate => latestUpdate; private readonly Bindable latestUpdate = new Bindable(); - private ScoreInfo? scorePendingUpdate; - [Resolved] private SpectatorClient spectatorClient { get; set; } = null!; @@ -43,7 +41,6 @@ namespace osu.Game.Online base.LoadComplete(); spectatorClient.OnUserScoreProcessed += userScoreProcessed; - statisticsProvider.StatisticsUpdated += onStatisticsUpdated; } /// @@ -72,21 +69,13 @@ namespace osu.Game.Online if (!watchedScores.Remove(scoreId, out var scoreInfo)) return; - scorePendingUpdate = scoreInfo; - statisticsProvider.RefetchStatistics(scoreInfo.Ruleset); + statisticsProvider.RefetchStatistics(scoreInfo.Ruleset, u => Schedule(() => + { + if (u.OldStatistics != null) + latestUpdate.Value = new ScoreBasedUserStatisticsUpdate(scoreInfo, u.OldStatistics, u.NewStatistics); + })); } - private void onStatisticsUpdated(UserStatisticsUpdate update) => Schedule(() => - { - if (scorePendingUpdate == null || !update.Ruleset.Equals(scorePendingUpdate.Ruleset)) - return; - - if (update.OldStatistics != null) - latestUpdate.Value = new ScoreBasedUserStatisticsUpdate(scorePendingUpdate, update.OldStatistics, update.NewStatistics); - - scorePendingUpdate = null; - }); - protected override void Dispose(bool isDisposing) { if (spectatorClient.IsNotNull()) From aa1358b2b4576b2d9d08365503cb610328068255 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 04:33:03 -0500 Subject: [PATCH 065/223] Enable NRT and fix code --- osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index e291b90361..dfb8213acf 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using NUnit.Framework; using osu.Framework.Allocation; @@ -25,11 +23,11 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public partial class TestSceneUserPanel : OsuTestScene { - private readonly Bindable activity = new Bindable(); + private readonly Bindable activity = new Bindable(); private readonly Bindable status = new Bindable(); - private UserGridPanel boundPanel1; - private TestUserListPanel boundPanel2; + private UserGridPanel boundPanel1 = null!; + private TestUserListPanel boundPanel2 = null!; [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); @@ -38,7 +36,7 @@ namespace osu.Game.Tests.Visual.Online private readonly TestUserStatisticsProvider statisticsProvider = new TestUserStatisticsProvider(); [Resolved] - private IRulesetStore rulesetStore { get; set; } + private IRulesetStore rulesetStore { get; set; } = null!; [SetUp] public void SetUp() => Schedule(() => @@ -209,8 +207,8 @@ namespace osu.Game.Tests.Visual.Online private partial class TestUserStatisticsProvider : LocalUserStatisticsProvider { - public new void UpdateStatistics(UserStatistics newStatistics, RulesetInfo ruleset) - => base.UpdateStatistics(newStatistics, ruleset); + public new void UpdateStatistics(UserStatistics newStatistics, RulesetInfo ruleset, Action? callback = null) + => base.UpdateStatistics(newStatistics, ruleset, callback); } } } From 242079346661b3bb5734ef6db066627addea2681 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 05:39:42 -0500 Subject: [PATCH 066/223] Allow controlling back button visibility state from screens --- osu.Game/OsuGame.cs | 13 ++++++++----- osu.Game/Screens/IOsuScreen.cs | 12 ++++++++++++ osu.Game/Screens/OsuScreen.cs | 5 +++++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index dce24c6ee7..4d6bc4fd14 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1581,12 +1581,20 @@ namespace osu.Game if (current is IOsuScreen currentOsuScreen) { + if (currentOsuScreen.AllowBackButton) + BackButton.State.UnbindFrom(currentOsuScreen.BackButtonState); + OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode); API.Activity.UnbindFrom(currentOsuScreen.Activity); } if (newScreen is IOsuScreen newOsuScreen) { + if (newOsuScreen.AllowBackButton) + ((IBindable)BackButton.State).BindTo(newOsuScreen.BackButtonState); + else + BackButton.Hide(); + OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode); API.Activity.BindTo(newOsuScreen.Activity); @@ -1597,11 +1605,6 @@ namespace osu.Game else Toolbar.Show(); - if (newOsuScreen.AllowBackButton) - BackButton.Show(); - else - BackButton.Hide(); - if (newOsuScreen.ShowFooter) { BackButton.Hide(); diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index b80c1f87a4..7025460daa 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -3,8 +3,11 @@ using System.Collections.Generic; using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Screens.Footer; @@ -59,6 +62,15 @@ namespace osu.Game.Screens /// IBindable OverlayActivationMode { get; } + /// + /// Controls the visibility state of to better work with screen-specific transitions (i.e. quick restart in player). + /// The back button can still be triggered by the action even while hidden. + /// + /// + /// This is ignored when is set to false. + /// + IBindable BackButtonState { get; } + /// /// The current for this screen. /// diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 695a074907..2c5c889154 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -11,6 +11,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Overlays; @@ -56,6 +57,10 @@ namespace osu.Game.Screens IBindable IOsuScreen.OverlayActivationMode => OverlayActivationMode; + public readonly Bindable BackButtonState = new Bindable(Visibility.Visible); + + IBindable IOsuScreen.BackButtonState => BackButtonState; + public virtual bool CursorVisible => true; protected new OsuGameBase Game => base.Game as OsuGameBase; From ae9119eef044c348805e4b2488fb768fc342e621 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 05:40:06 -0500 Subject: [PATCH 067/223] Hide back button when quick-restarting unless load time takes long --- osu.Game/Screens/Play/PlayerLoader.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 3e36c630db..a6e171ba02 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -478,6 +478,8 @@ namespace osu.Game.Screens.Play if (quickRestart) { + BackButtonState.Value = Visibility.Hidden; + // A quick restart starts by triggering a fade to black AddInternal(quickRestartBlackLayer = new Box { @@ -496,6 +498,8 @@ namespace osu.Game.Screens.Play .Delay(quick_restart_initial_delay) .ScaleTo(1) .FadeInFromZero(500, Easing.OutQuint); + + this.Delay(quick_restart_initial_delay).Schedule(() => BackButtonState.Value = Visibility.Visible); } else { From 53b390667a844ca98dd0d7f1d1be0ebffd6c2133 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 06:04:36 -0500 Subject: [PATCH 068/223] Fix failing test --- osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs | 6 ++++++ osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs index 460d7814e0..609bc6e166 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginOverlay.cs @@ -10,11 +10,13 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; +using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays; using osu.Game.Overlays.Login; using osu.Game.Overlays.Settings; +using osu.Game.Tests.Visual.Online; using osu.Game.Users; using osu.Game.Users.Drawables; using osuTK.Input; @@ -31,6 +33,9 @@ namespace osu.Game.Tests.Visual.Menus [Resolved] private OsuConfigManager configManager { get; set; } = null!; + [Cached(typeof(LocalUserStatisticsProvider))] + private readonly TestSceneUserPanel.TestUserStatisticsProvider statisticsProvider = new TestSceneUserPanel.TestUserStatisticsProvider(); + [BackgroundDependencyLoader] private void load() { @@ -170,6 +175,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("enter code", () => loginOverlay.ChildrenOfType().First().Text = "88800088"); assertAPIState(APIState.Online); + AddStep("feed statistics", () => statisticsProvider.UpdateStatistics(new UserStatistics(), Ruleset.Value)); AddStep("click on flag", () => { InputManager.MoveMouseTo(loginOverlay.ChildrenOfType().First()); diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index dfb8213acf..3f1d961588 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -205,7 +205,7 @@ namespace osu.Game.Tests.Visual.Online public new TextFlowContainer LastVisitMessage => base.LastVisitMessage; } - private partial class TestUserStatisticsProvider : LocalUserStatisticsProvider + public partial class TestUserStatisticsProvider : LocalUserStatisticsProvider { public new void UpdateStatistics(UserStatistics newStatistics, RulesetInfo ruleset, Action? callback = null) => base.UpdateStatistics(newStatistics, ruleset, callback); From 8611ed31c2dce59da516b20fa73bd547add2991f Mon Sep 17 00:00:00 2001 From: "tsrk." Date: Sun, 24 Nov 2024 14:22:56 +0100 Subject: [PATCH 069/223] refactor(MenuTip): add localisation support Signed-off-by: tsrk. --- osu.Game/Localisation/MenuTipStrings.cs | 154 ++++++++++++++++++++++++ osu.Game/Screens/Menu/MenuTip.cs | 66 +++++----- 2 files changed, 188 insertions(+), 32 deletions(-) create mode 100644 osu.Game/Localisation/MenuTipStrings.cs diff --git a/osu.Game/Localisation/MenuTipStrings.cs b/osu.Game/Localisation/MenuTipStrings.cs new file mode 100644 index 0000000000..e955040f37 --- /dev/null +++ b/osu.Game/Localisation/MenuTipStrings.cs @@ -0,0 +1,154 @@ +// 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.Localisation; + +namespace osu.Game.Localisation +{ + public static class MenuTipStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.MenuTip"; + + /// + /// "Press Ctrl-T anywhere in the game to toggle the toolbar!" + /// + public static LocalisableString ToggleToolbarShortcut => new TranslatableString(getKey(@"toggle_toolbar_shortcut"), @"Press Ctrl-T anywhere in the game to toggle the toolbar!"); + + /// + /// "Press Ctrl-O anywhere in the game to access options!" + /// + public static LocalisableString GameSettingsShortcut => new TranslatableString(getKey(@"game_settings_shortcut"), @"Press Ctrl-O anywhere in the game to access options!"); + + /// + /// "All settings are dynamic and take effect in real-time. Try changing the skin while watching autoplay!" + /// + public static LocalisableString DynamicSettings => new TranslatableString(getKey(@"dynamic_settings"), @"All settings are dynamic and take effect in real-time. Try changing the skin while watching autoplay!"); + + /// + /// "New features are coming online every update. Make sure to stay up-to-date!" + /// + public static LocalisableString NewFeaturesAreComingOnline => new TranslatableString(getKey(@"new_features_are_coming_online"), @"New features are coming online every update. Make sure to stay up-to-date!"); + + /// + /// "If you find the UI too large or small, try adjusting UI scale in settings!" + /// + public static LocalisableString UIScalingSettings => new TranslatableString(getKey(@"ui_scaling_settings"), @"If you find the UI too large or small, try adjusting UI scale in settings!"); + + /// + /// "Try adjusting the "Screen Scaling" mode to change your gameplay or UI area, even in fullscreen!" + /// + public static LocalisableString ScreenScalingSettings => new TranslatableString(getKey(@"screen_scaling_settings"), @"Try adjusting the ""Screen Scaling"" mode to change your gameplay or UI area, even in fullscreen!"); + + /// + /// "What used to be "osu!direct" is available to all users just like on the website. You can access it anywhere using Ctrl-B!" + /// + public static LocalisableString FreeOsuDirect => new TranslatableString(getKey(@"free_osu_direct"), @"What used to be ""osu!direct"" is available to all users just like on the website. You can access it anywhere using Ctrl-B!"); + + /// + /// "Seeking in replays is available by dragging on the progress bar at the bottom of the screen or by using the left and right arrow keys!" + /// + public static LocalisableString ReplaySeeking => new TranslatableString(getKey(@"replay_seeking"), @"Seeking in replays is available by dragging on the progress bar at the bottom of the screen or by using the left and right arrow keys!"); + + /// + /// "Try scrolling right in mod select to find a bunch of new fun mods!" + /// + public static LocalisableString TryNewMods => new TranslatableString(getKey(@"try_new_mods"), @"Try scrolling right in mod select to find a bunch of new fun mods!"); + + /// + /// "Most of the web content (profiles, rankings, etc.) are available natively in-game from the icons on the toolbar!" + /// + public static LocalisableString EmbeddedWebContent => new TranslatableString(getKey(@"embedded_web_content"), @"Most of the web content (profiles, rankings, etc.) are available natively in-game from the icons on the toolbar!"); + + /// + /// "Get more details, hide or delete a beatmap by right-clicking on its panel at song select!" + /// + public static LocalisableString BeatmapRightClick => new TranslatableString(getKey(@"beatmap_right_click"), @"Get more details, hide or delete a beatmap by right-clicking on its panel at song select!"); + + /// + /// "Check out the "playlists" system, which lets users create their own custom and permanent leaderboards!" + /// + public static LocalisableString DiscoverPlaylists => new TranslatableString(getKey(@"discover_playlists"), @"Check out the ""playlists"" system, which lets users create their own custom and permanent leaderboards!"); + + /// + /// "Toggle advanced frame / thread statistics with Ctrl-F11!" + /// + public static LocalisableString ToggleAdvancedFPSCounter => new TranslatableString(getKey(@"toggle_advanced_fps_counter"), @"Toggle advanced frame / thread statistics with Ctrl-F11!"); + + /// + /// "You can pause during a replay by pressing Space!" + /// + public static LocalisableString ReplayPausing => new TranslatableString(getKey(@"replay_pausing"), @"You can pause during a replay by pressing Space!"); + + /// + /// "Most of the hotkeys in the game are configurable and can be changed to anything you want. Check the bindings panel under input settings!" + /// + public static LocalisableString ConfigurableHotkeys => new TranslatableString(getKey(@"configurable_hotkeys"), @"Most of the hotkeys in the game are configurable and can be changed to anything you want. Check the bindings panel under input settings!"); + + /// + /// "Your gameplay HUD can be customized by using the skin layout editor. Open it at any time via Ctrl-Shift-S!" + /// + public static LocalisableString SkinEditor => new TranslatableString(getKey(@"skin_editor"), @"Your gameplay HUD can be customized by using the skin layout editor. Open it at any time via Ctrl-Shift-S!"); + + /// + /// "You can create mod presets to make toggling your favorite mod combinations easier!" + /// + public static LocalisableString ModPresets => new TranslatableString(getKey(@"mod_presets"), @"You can create mod presets to make toggling your favorite mod combinations easier!"); + + /// + /// "Many mods have customisation settings that drastically change how they function. Click the Mod Customisation button in mod select to view settings!" + /// + public static LocalisableString ModCustomisationSettings => new TranslatableString(getKey(@"mod_customisation_settings"), @"Many mods have customisation settings that drastically change how they function. Click the Mod Customisation button in mod select to view settings!"); + + /// + /// "Press Ctrl-Shift-R to switch to a random skin!" + /// + public static LocalisableString RandomSkinShortcut => new TranslatableString(getKey(@"random_skin_shortcut"), @"Press Ctrl-Shift-R to switch to a random skin!"); + + /// + /// "While watching a replay, press Ctrl-H to toggle replay settings!" + /// + public static LocalisableString ToggleReplaySettingsShortcut => new TranslatableString(getKey(@"toggle_replay_settings_shortcut"), @"While watching a replay, press Ctrl-H to toggle replay settings!"); + + /// + /// "You can easily copy the mods from scores on a leaderboard by right-clicking on them!" + /// + public static LocalisableString CopyModsFromScore => new TranslatableString(getKey(@"copy_mods_from_score"), @"You can easily copy the mods from scores on a leaderboard by right-clicking on them!"); + + /// + /// "Ctrl-Enter at song select will start a beatmap in autoplay mode!" + /// + public static LocalisableString AutoplayBeatmapShortcut => new TranslatableString(getKey(@"autoplay_beatmap_shortcut"), @"Ctrl-Enter at song select will start a beatmap in autoplay mode!"); + + /// + /// "Multithreading support means that even with low "FPS" your input and judgements will be accurate!" + /// + public static LocalisableString MultithreadingSupport => new TranslatableString(getKey(@"multithreading_support"), @"Multithreading support means that even with low ""FPS"" your input and judgements will be accurate!"); + + /// + /// "All delete operations are temporary until exiting. Restore accidentally deleted content from the maintenance settings!" + /// + public static LocalisableString TemporaryDeleteOperations => new TranslatableString(getKey(@"temporary_delete_operations"), @"All delete operations are temporary until exiting. Restore accidentally deleted content from the maintenance settings!"); + + /// + /// "Take a look under the hood at performance counters and enable verbose performance logging with Ctrl-F2!" + /// + public static LocalisableString GlobalStatisticsShortcut => new TranslatableString(getKey(@"global_statistics_shortcut"), @"Take a look under the hood at performance counters and enable verbose performance logging with Ctrl-F2!"); + + /// + /// "When your gameplay HUD is hidden, you can press and hold Ctrl to view it temporarily!" + /// + public static LocalisableString PeekHUDWhenHidden => new TranslatableString(getKey(@"peek_hud_when_hidden"), @"When your gameplay HUD is hidden, you can press and hold Ctrl to view it temporarily!"); + + /// + /// "Drag and drop any image into the skin editor to load it in quickly!" + /// + public static LocalisableString DragAndDropImageInSkinEditor => new TranslatableString(getKey(@"drag_and_drop_image_in_skin_editor"), @"Drag and drop any image into the skin editor to load it in quickly!"); + + /// + /// "a tip for you:" + /// + public static LocalisableString MenuTipTitle => new TranslatableString(getKey(@"menu_tip_title"), @"a tip for you:"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Screens/Menu/MenuTip.cs b/osu.Game/Screens/Menu/MenuTip.cs index 58eeb7e82d..3fc5fe57fb 100644 --- a/osu.Game/Screens/Menu/MenuTip.cs +++ b/osu.Game/Screens/Menu/MenuTip.cs @@ -7,12 +7,14 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; using osuTK.Graphics; +using osu.Game.Localisation; namespace osu.Game.Screens.Menu { @@ -78,49 +80,49 @@ namespace osu.Game.Screens.Menu static void formatRegular(SpriteText t) => t.Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular); static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold); - string tip = getRandomTip(); + var tip = getRandomTip(); textFlow.Clear(); - textFlow.AddParagraph("a tip for you:", formatSemiBold); + textFlow.AddParagraph(MenuTipStrings.MenuTipTitle, formatSemiBold); textFlow.AddParagraph(tip, formatRegular); this.FadeInFromZero(200, Easing.OutQuint) - .Delay(1000 + 80 * tip.Length) + .Delay(1000 + 80 * tip.ToString().Length) .Then() .FadeOutFromOne(2000, Easing.OutQuint); } - private string getRandomTip() + private LocalisableString getRandomTip() { - string[] tips = + LocalisableString[] tips = { - "Press Ctrl-T anywhere in the game to toggle the toolbar!", - "Press Ctrl-O anywhere in the game to access options!", - "All settings are dynamic and take effect in real-time. Try changing the skin while watching autoplay!", - "New features are coming online every update. Make sure to stay up-to-date!", - "If you find the UI too large or small, try adjusting UI scale in settings!", - "Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!", - "What used to be \"osu!direct\" is available to all users just like on the website. You can access it anywhere using Ctrl-B!", - "Seeking in replays is available by dragging on the progress bar at the bottom of the screen or by using the left and right arrow keys!", - "Multithreading support means that even with low \"FPS\" your input and judgements will be accurate!", - "Try scrolling right in mod select to find a bunch of new fun mods!", - "Most of the web content (profiles, rankings, etc.) are available natively in-game from the icons on the toolbar!", - "Get more details, hide or delete a beatmap by right-clicking on its panel at song select!", - "All delete operations are temporary until exiting. Restore accidentally deleted content from the maintenance settings!", - "Check out the \"playlists\" system, which lets users create their own custom and permanent leaderboards!", - "Toggle advanced frame / thread statistics with Ctrl-F11!", - "Take a look under the hood at performance counters and enable verbose performance logging with Ctrl-F2!", - "You can pause during a replay by pressing Space!", - "Most of the hotkeys in the game are configurable and can be changed to anything you want. Check the bindings panel under input settings!", - "When your gameplay HUD is hidden, you can press and hold Ctrl to view it temporarily!", - "Your gameplay HUD can be customized by using the skin layout editor. Open it at any time via Ctrl-Shift-S!", - "Drag and drop any image into the skin editor to load it in quickly!", - "You can create mod presets to make toggling your favorite mod combinations easier!", - "Many mods have customisation settings that drastically change how they function. Click the Mod Customisation button in mod select to view settings!", - "Press Ctrl-Shift-R to switch to a random skin!", - "While watching a replay, press Ctrl-H to toggle replay settings!", - "You can easily copy the mods from scores on a leaderboard by right-clicking on them!", - "Ctrl-Enter at song select will start a beatmap in autoplay mode!" + MenuTipStrings.ToggleToolbarShortcut, + MenuTipStrings.GameSettingsShortcut, + MenuTipStrings.DynamicSettings, + MenuTipStrings.NewFeaturesAreComingOnline, + MenuTipStrings.UIScalingSettings, + MenuTipStrings.ScreenScalingSettings, + MenuTipStrings.FreeOsuDirect, + MenuTipStrings.ReplaySeeking, + MenuTipStrings.MultithreadingSupport, + MenuTipStrings.TryNewMods, + MenuTipStrings.EmbeddedWebContent, + MenuTipStrings.BeatmapRightClick, + MenuTipStrings.TemporaryDeleteOperations, + MenuTipStrings.DiscoverPlaylists, + MenuTipStrings.ToggleAdvancedFPSCounter, + MenuTipStrings.GlobalStatisticsShortcut, + MenuTipStrings.ReplayPausing, + MenuTipStrings.ConfigurableHotkeys, + MenuTipStrings.PeekHUDWhenHidden, + MenuTipStrings.SkinEditor, + MenuTipStrings.DragAndDropImageInSkinEditor, + MenuTipStrings.ModPresets, + MenuTipStrings.ModCustomisationSettings, + MenuTipStrings.RandomSkinShortcut, + MenuTipStrings.ToggleReplaySettingsShortcut, + MenuTipStrings.CopyModsFromScore, + MenuTipStrings.AutoplayBeatmapShortcut }; return tips[RNG.Next(0, tips.Length)]; From 146838555999a69386aebb3d67f6af4bc860e1ba Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 22:38:48 -0500 Subject: [PATCH 070/223] Reset new file states after syncing --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index a52e42c7c0..90603a6366 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -200,6 +200,8 @@ namespace osu.Game.Screens.Edit.Setup beatmaps.Save(otherBeatmap, otherWorking.Beatmap); } + newAudioFile = null; + newBackgroundFile = null; syncResourcesButton.Enabled.Value = false; } From a1916d12db3be7e55fd7ac1d184a1725cca4266d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Nov 2024 18:41:50 +0900 Subject: [PATCH 071/223] Ensure UR benchmark has hitwindows populated --- osu.Game.Benchmarks/BenchmarkUnstableRate.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkUnstableRate.cs b/osu.Game.Benchmarks/BenchmarkUnstableRate.cs index aa229c7d06..7b6c839648 100644 --- a/osu.Game.Benchmarks/BenchmarkUnstableRate.cs +++ b/osu.Game.Benchmarks/BenchmarkUnstableRate.cs @@ -4,7 +4,9 @@ using System.Collections.Generic; using BenchmarkDotNet.Attributes; using osu.Framework.Utils; -using osu.Game.Rulesets.Objects; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; namespace osu.Game.Benchmarks @@ -18,8 +20,14 @@ namespace osu.Game.Benchmarks base.SetUp(); events = new List(); - for (int i = 0; i < 1000; i++) - events.Add(new HitEvent(RNG.NextDouble(-200.0, 200.0), RNG.NextDouble(1.0, 2.0), HitResult.Great, new HitObject(), null, null)); + for (int i = 0; i < 2048; i++) + { + // Ensure the object has hit windows populated. + var hitObject = new HitCircle(); + hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + events.Add(new HitEvent(RNG.NextDouble(-200.0, 200.0), RNG.NextDouble(1.0, 2.0), HitResult.Great, hitObject, null, null)); + } } [Benchmark] From 605fe71f46eaf2944b659389cca3cfa03011e5de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Nov 2024 19:17:32 +0900 Subject: [PATCH 072/223] Make empty hitwindows readonly static and slightly improve comparison performance --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 2 +- osu.Game/Rulesets/Scoring/HitEventExtensions.cs | 2 +- osu.Game/Rulesets/Scoring/HitWindows.cs | 4 ++-- osu.Game/Screens/Play/HUD/UnstableRateCounter.cs | 2 +- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 19554b6504..4ca937bf86 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -205,7 +205,7 @@ namespace osu.Game.Rulesets.Mods { foreach (var hitObject in hitObjects) { - if (!(hitObject.HitWindows is HitWindows.EmptyHitWindows)) + if (hitObject.HitWindows != HitWindows.Empty) yield return hitObject; foreach (HitObject nested in getAllApplicableHitObjects(hitObject.NestedHitObjects)) diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index fc4eef13ba..672c229875 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -65,6 +65,6 @@ namespace osu.Game.Rulesets.Scoring return timeOffsets.Average(); } - public static bool AffectsUnstableRate(HitEvent e) => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit(); + public static bool AffectsUnstableRate(HitEvent e) => e.HitObject.HitWindows != HitWindows.Empty && e.Result.IsHit(); } } diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs index 2d008b58ba..a6a268fc78 100644 --- a/osu.Game/Rulesets/Scoring/HitWindows.cs +++ b/osu.Game/Rulesets/Scoring/HitWindows.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Scoring /// An empty with only and . /// No time values are provided (meaning instantaneous hit or miss). /// - public static HitWindows Empty => new EmptyHitWindows(); + public static HitWindows Empty { get; } = new EmptyHitWindows(); public HitWindows() { @@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Scoring /// protected virtual DifficultyRange[] GetRanges() => base_ranges; - public class EmptyHitWindows : HitWindows + private class EmptyHitWindows : HitWindows { private static readonly DifficultyRange[] ranges = { diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index ab7ab6b3a0..6fe5e818c4 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Play.HUD } private bool changesUnstableRate(JudgementResult judgement) - => !(judgement.HitObject.HitWindows is HitWindows.EmptyHitWindows) && judgement.IsHit; + => judgement.HitObject.HitWindows != HitWindows.Empty && judgement.IsHit; protected override void LoadComplete() { diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index a9b93e0ffc..a80aeaa5dd 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// The s to display the timing distribution of. public HitEventTimingDistributionGraph(IReadOnlyList hitEvents) { - this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsBasic() && e.Result.IsHit()).ToList(); + this.hitEvents = hitEvents.Where(e => e.HitObject.HitWindows != HitWindows.Empty && e.Result.IsBasic() && e.Result.IsHit()).ToList(); bins = Enumerable.Range(0, total_timing_distribution_bins).Select(_ => new Dictionary()).ToArray>(); } From 33d725e889481843601fe1f647a88ba866a8c6ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Nov 2024 19:24:58 +0900 Subject: [PATCH 073/223] Address unstable rate calculations as a list for marginal gains --- osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs | 3 ++- osu.Game/Rulesets/Scoring/HitEventExtensions.cs | 2 +- osu.Game/Screens/Play/HUD/UnstableRateCounter.cs | 3 --- osu.Game/Screens/Ranking/Statistics/UnstableRate.cs | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs index 5a416d05d7..94a0e34d0d 100644 --- a/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs +++ b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs @@ -20,7 +20,8 @@ namespace osu.Game.Tests.NonVisual.Ranking public void TestDistributedHits() { var events = Enumerable.Range(-5, 11) - .Select(t => new HitEvent(t - 5, 1.0, HitResult.Great, new HitObject(), null, null)); + .Select(t => new HitEvent(t - 5, 1.0, HitResult.Great, new HitObject(), null, null)) + .ToList(); var unstableRate = new UnstableRate(events); diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index 672c229875..e79504d1ec 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Scoring /// A non-null value if unstable rate could be calculated, /// and if unstable rate cannot be calculated due to being empty. /// - public static double? CalculateUnstableRate(this IEnumerable hitEvents) + public static double? CalculateUnstableRate(this IReadOnlyList hitEvents) { Debug.Assert(hitEvents.All(ev => ev.GameplayRate != null)); diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index 6fe5e818c4..3c9ab87022 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -44,9 +44,6 @@ namespace osu.Game.Screens.Play.HUD DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint)); } - private bool changesUnstableRate(JudgementResult judgement) - => judgement.HitObject.HitWindows != HitWindows.Empty && judgement.IsHit; - protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs index cc3535a426..10b18d09c9 100644 --- a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs +++ b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// Creates and computes an statistic. /// /// Sequence of s to calculate the unstable rate based on. - public UnstableRate(IEnumerable hitEvents) + public UnstableRate(IReadOnlyList hitEvents) : base("Unstable Rate") { Value = hitEvents.CalculateUnstableRate(); From c8847e8da86bbfdbc2801c8e5aef1c9a95389c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 Nov 2024 12:53:40 +0100 Subject: [PATCH 074/223] Fix incorrect unit test --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 54ebebeb7b..b5c299ed9d 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -1000,7 +1000,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False); Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False); Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False); - Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.Normal)); + Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.None)); Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0)); Assert.That(decoded.BeatmapInfo.Metadata.PreviewTime, Is.EqualTo(-1)); Assert.That(decoded.BeatmapInfo.Ruleset.OnlineID, Is.EqualTo(0)); From 5668258182537bdfaa6d6419e75a3d6f497e1d86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Nov 2024 19:43:42 +0900 Subject: [PATCH 075/223] Add incremental processing --- osu.Game.Benchmarks/BenchmarkUnstableRate.cs | 26 ++++++++++-- .../NonVisual/Ranking/UnstableRateTest.cs | 42 ++++++++++++++++++- .../Rulesets/Scoring/HitEventExtensions.cs | 14 ++++--- .../Screens/Play/HUD/UnstableRateCounter.cs | 2 +- .../Ranking/Statistics/UnstableRate.cs | 2 +- 5 files changed, 74 insertions(+), 12 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkUnstableRate.cs b/osu.Game.Benchmarks/BenchmarkUnstableRate.cs index 7b6c839648..4d3023e92e 100644 --- a/osu.Game.Benchmarks/BenchmarkUnstableRate.cs +++ b/osu.Game.Benchmarks/BenchmarkUnstableRate.cs @@ -13,27 +13,45 @@ namespace osu.Game.Benchmarks { public class BenchmarkUnstableRate : BenchmarkTest { - private List events = null!; + private readonly List> incrementalEventLists = new List>(); public override void SetUp() { base.SetUp(); - events = new List(); + + var events = new List(); for (int i = 0; i < 2048; i++) { // Ensure the object has hit windows populated. var hitObject = new HitCircle(); hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - events.Add(new HitEvent(RNG.NextDouble(-200.0, 200.0), RNG.NextDouble(1.0, 2.0), HitResult.Great, hitObject, null, null)); + + incrementalEventLists.Add(new List(events)); } } [Benchmark] public void CalculateUnstableRate() { - _ = events.CalculateUnstableRate(); + for (int i = 0; i < 2048; i++) + { + var events = incrementalEventLists[i]; + _ = events.CalculateUnstableRate(); + } + } + + [Benchmark] + public void CalculateUnstableRateUsingIncrementalCalculation() + { + HitEventExtensions.UnstableRateCalculationResult? last = null; + + for (int i = 0; i < 2048; i++) + { + var events = incrementalEventLists[i]; + last = events.CalculateUnstableRate(last); + } } } } diff --git a/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs index 94a0e34d0d..03dc91b5d4 100644 --- a/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs +++ b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs @@ -26,7 +26,47 @@ namespace osu.Game.Tests.NonVisual.Ranking var unstableRate = new UnstableRate(events); Assert.IsNotNull(unstableRate.Value); - Assert.IsTrue(Precision.AlmostEquals(unstableRate.Value.Value, 10 * Math.Sqrt(10))); + Assert.AreEqual(unstableRate.Value.Value, 10 * Math.Sqrt(10), Precision.DOUBLE_EPSILON); + } + + [Test] + public void TestDistributedHitsIncrementalRewind() + { + var events = Enumerable.Range(-5, 11) + .Select(t => new HitEvent(t - 5, 1.0, HitResult.Great, new HitObject(), null, null)) + .ToList(); + + HitEventExtensions.UnstableRateCalculationResult result = null; + + for (int i = 0; i < events.Count; i++) + { + result = events.GetRange(0, i + 1) + .CalculateUnstableRate(result); + } + + result = events.GetRange(0, 2).CalculateUnstableRate(result); + + Assert.IsNotNull(result!.Result); + Assert.AreEqual(5, result.Result, Precision.DOUBLE_EPSILON); + } + + [Test] + public void TestDistributedHitsIncremental() + { + var events = Enumerable.Range(-5, 11) + .Select(t => new HitEvent(t - 5, 1.0, HitResult.Great, new HitObject(), null, null)) + .ToList(); + + HitEventExtensions.UnstableRateCalculationResult result = null; + + for (int i = 0; i < events.Count; i++) + { + result = events.GetRange(0, i + 1) + .CalculateUnstableRate(result); + } + + Assert.IsNotNull(result!.Result); + Assert.AreEqual(10 * Math.Sqrt(10), result.Result, Precision.DOUBLE_EPSILON); } [Test] diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index e79504d1ec..115ffb67f7 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -20,16 +20,18 @@ namespace osu.Game.Rulesets.Scoring /// A non-null value if unstable rate could be calculated, /// and if unstable rate cannot be calculated due to being empty. /// - public static double? CalculateUnstableRate(this IReadOnlyList hitEvents) + public static UnstableRateCalculationResult? CalculateUnstableRate(this IReadOnlyList hitEvents, UnstableRateCalculationResult? previousResult = null) { Debug.Assert(hitEvents.All(ev => ev.GameplayRate != null)); int count = 0; - double mean = 0; - double sumOfSquares = 0; + double mean = previousResult?.Mean ?? 0; + double sumOfSquares = previousResult?.SumOfSquares ?? 0; - foreach (var e in hitEvents) + for (int i = previousResult?.CalculatedHitEventsCount - 1 ?? 0; i < hitEvents.Count; i++) { + HitEvent e = hitEvents[i]; + if (!AffectsUnstableRate(e)) continue; @@ -45,7 +47,7 @@ namespace osu.Game.Rulesets.Scoring if (count == 0) return null; - return 10.0 * Math.Sqrt(sumOfSquares / count); + return new UnstableRateCalculationResult(hitEvents.Count, sumOfSquares, mean, 10.0 * Math.Sqrt(sumOfSquares / count)); } /// @@ -66,5 +68,7 @@ namespace osu.Game.Rulesets.Scoring } public static bool AffectsUnstableRate(HitEvent e) => e.HitObject.HitWindows != HitWindows.Empty && e.Result.IsHit(); + + public record UnstableRateCalculationResult(int CalculatedHitEventsCount, double SumOfSquares, double Mean, double Result); } } diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index 3c9ab87022..db271a21c5 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Play.HUD private void updateDisplay() { - double? unstableRate = scoreProcessor.HitEvents.CalculateUnstableRate(); + double? unstableRate = scoreProcessor.HitEvents.CalculateUnstableRate()?.Result; valid.Value = unstableRate != null; if (unstableRate != null) diff --git a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs index 10b18d09c9..d114bed156 100644 --- a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs +++ b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Ranking.Statistics public UnstableRate(IReadOnlyList hitEvents) : base("Unstable Rate") { - Value = hitEvents.CalculateUnstableRate(); + Value = hitEvents.CalculateUnstableRate()?.Result; } protected override string DisplayValue(double? value) => value == null ? "(not available)" : value.Value.ToString(@"N2"); From ea68d4b33abbd920e267001fb4785ae000031f54 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Nov 2024 20:36:43 +0900 Subject: [PATCH 076/223] Use class instead of record for lower allocations --- .../Rulesets/Scoring/HitEventExtensions.cs | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index 115ffb67f7..d9eb8b0c37 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -20,34 +20,36 @@ namespace osu.Game.Rulesets.Scoring /// A non-null value if unstable rate could be calculated, /// and if unstable rate cannot be calculated due to being empty. /// - public static UnstableRateCalculationResult? CalculateUnstableRate(this IReadOnlyList hitEvents, UnstableRateCalculationResult? previousResult = null) + public static UnstableRateCalculationResult? CalculateUnstableRate(this IReadOnlyList hitEvents, UnstableRateCalculationResult? result = null) { Debug.Assert(hitEvents.All(ev => ev.GameplayRate != null)); - int count = 0; - double mean = previousResult?.Mean ?? 0; - double sumOfSquares = previousResult?.SumOfSquares ?? 0; + result ??= new UnstableRateCalculationResult(); - for (int i = previousResult?.CalculatedHitEventsCount - 1 ?? 0; i < hitEvents.Count; i++) + // Handle rewinding in the simplest way possible. + if (hitEvents.Count < result.NextProcessableIndex + 1) + result = new UnstableRateCalculationResult(); + + for (int i = result.NextProcessableIndex; i < hitEvents.Count; i++) { HitEvent e = hitEvents[i]; if (!AffectsUnstableRate(e)) continue; - count++; + result.NextProcessableIndex++; // Division by gameplay rate is to account for TimeOffset scaling with gameplay rate. double currentValue = e.TimeOffset / e.GameplayRate!.Value; - double nextMean = mean + (currentValue - mean) / count; - sumOfSquares += (currentValue - mean) * (currentValue - nextMean); - mean = nextMean; + double nextMean = result.Mean + (currentValue - result.Mean) / result.NextProcessableIndex; + result.SumOfSquares += (currentValue - result.Mean) * (currentValue - nextMean); + result.Mean = nextMean; } - if (count == 0) + if (result.NextProcessableIndex == 0) return null; - return new UnstableRateCalculationResult(hitEvents.Count, sumOfSquares, mean, 10.0 * Math.Sqrt(sumOfSquares / count)); + return result; } /// @@ -69,6 +71,13 @@ namespace osu.Game.Rulesets.Scoring public static bool AffectsUnstableRate(HitEvent e) => e.HitObject.HitWindows != HitWindows.Empty && e.Result.IsHit(); - public record UnstableRateCalculationResult(int CalculatedHitEventsCount, double SumOfSquares, double Mean, double Result); + public class UnstableRateCalculationResult + { + public int NextProcessableIndex; + public double SumOfSquares; + public double Mean; + + public double Result => 10.0 * Math.Sqrt(SumOfSquares / NextProcessableIndex); + } } } From bbe8f2ec44cf7f2fa76c23dddbfcc33bea7045ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Nov 2024 20:49:30 +0900 Subject: [PATCH 077/223] Only update unstable rate counter when an applicable hitobject is reached --- osu.Game/Rulesets/Scoring/HitEventExtensions.cs | 4 +++- osu.Game/Screens/Play/HUD/UnstableRateCounter.cs | 13 +++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index d9eb8b0c37..3236ce83dd 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Scoring { @@ -69,7 +70,8 @@ namespace osu.Game.Rulesets.Scoring return timeOffsets.Average(); } - public static bool AffectsUnstableRate(HitEvent e) => e.HitObject.HitWindows != HitWindows.Empty && e.Result.IsHit(); + public static bool AffectsUnstableRate(HitEvent e) => AffectsUnstableRate(e.HitObject, e.Result); + public static bool AffectsUnstableRate(HitObject hitObject, HitResult result) => hitObject.HitWindows != HitWindows.Empty && result.IsHit(); public class UnstableRateCalculationResult { diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index db271a21c5..a856a09388 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -28,6 +28,8 @@ namespace osu.Game.Screens.Play.HUD private const float alpha_when_invalid = 0.3f; private readonly Bindable valid = new Bindable(); + private HitEventExtensions.UnstableRateCalculationResult? unstableRateResult; + [Resolved] private ScoreProcessor scoreProcessor { get; set; } = null!; @@ -53,13 +55,20 @@ namespace osu.Game.Screens.Play.HUD updateDisplay(); } - private void updateDisplay(JudgementResult _) => Scheduler.AddOnce(updateDisplay); + private void updateDisplay(JudgementResult result) + { + if (HitEventExtensions.AffectsUnstableRate(result.HitObject, result.Type)) + Scheduler.AddOnce(updateDisplay); + } private void updateDisplay() { - double? unstableRate = scoreProcessor.HitEvents.CalculateUnstableRate()?.Result; + unstableRateResult = scoreProcessor.HitEvents.CalculateUnstableRate(unstableRateResult); + + double? unstableRate = unstableRateResult?.Result; valid.Value = unstableRate != null; + if (unstableRate != null) Current.Value = (int)Math.Round(unstableRate.Value); } From 0a3f3c3210dd50f5a50c7fb0df875299abc9ffe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 Nov 2024 13:14:22 +0100 Subject: [PATCH 078/223] Add guard against fetching statistics for non-legacy rulesets --- osu.Game/Online/LocalUserStatisticsProvider.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/LocalUserStatisticsProvider.cs b/osu.Game/Online/LocalUserStatisticsProvider.cs index 5fa2b40715..79122b4186 100644 --- a/osu.Game/Online/LocalUserStatisticsProvider.cs +++ b/osu.Game/Online/LocalUserStatisticsProvider.cs @@ -63,6 +63,9 @@ namespace osu.Game.Online public void RefetchStatistics(RulesetInfo ruleset, Action? callback = null) { + if (!ruleset.IsLegacyRuleset()) + throw new InvalidOperationException($@"Retrieving statistics is not supported for ruleset {ruleset.ShortName}"); + var request = new GetUserRequest(api.LocalUser.Value.Id, ruleset); request.Success += u => UpdateStatistics(u.Statistics, ruleset, callback); api.Queue(request); From d903d381d50d792d3452f07c117f7069866c66fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 12:10:34 +0900 Subject: [PATCH 079/223] Rename `NextProcessableIndex` to `EventCount` in line with actual functionality --- osu.Game/Rulesets/Scoring/HitEventExtensions.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index 3236ce83dd..7442c6ccc3 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -28,26 +28,26 @@ namespace osu.Game.Rulesets.Scoring result ??= new UnstableRateCalculationResult(); // Handle rewinding in the simplest way possible. - if (hitEvents.Count < result.NextProcessableIndex + 1) + if (hitEvents.Count < result.EventCount + 1) result = new UnstableRateCalculationResult(); - for (int i = result.NextProcessableIndex; i < hitEvents.Count; i++) + for (int i = result.EventCount; i < hitEvents.Count; i++) { HitEvent e = hitEvents[i]; if (!AffectsUnstableRate(e)) continue; - result.NextProcessableIndex++; + result.EventCount++; // Division by gameplay rate is to account for TimeOffset scaling with gameplay rate. double currentValue = e.TimeOffset / e.GameplayRate!.Value; - double nextMean = result.Mean + (currentValue - result.Mean) / result.NextProcessableIndex; + double nextMean = result.Mean + (currentValue - result.Mean) / result.EventCount; result.SumOfSquares += (currentValue - result.Mean) * (currentValue - nextMean); result.Mean = nextMean; } - if (result.NextProcessableIndex == 0) + if (result.EventCount == 0) return null; return result; @@ -75,11 +75,11 @@ namespace osu.Game.Rulesets.Scoring public class UnstableRateCalculationResult { - public int NextProcessableIndex; + public int EventCount; public double SumOfSquares; public double Mean; - public double Result => 10.0 * Math.Sqrt(SumOfSquares / NextProcessableIndex); + public double Result => 10.0 * Math.Sqrt(SumOfSquares / EventCount); } } } From d6cf1db0f5e7af63a73c8a7814dd52fd723b0ada Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 12:16:26 +0900 Subject: [PATCH 080/223] Add basic xmldoc to results class --- .../Rulesets/Scoring/HitEventExtensions.cs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index 7442c6ccc3..269342460f 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -73,13 +73,36 @@ namespace osu.Game.Rulesets.Scoring public static bool AffectsUnstableRate(HitEvent e) => AffectsUnstableRate(e.HitObject, e.Result); public static bool AffectsUnstableRate(HitObject hitObject, HitResult result) => hitObject.HitWindows != HitWindows.Empty && result.IsHit(); + /// + /// Data type returned by which allows efficient incremental processing. + /// + /// + /// This should be passed back into future calls as a parameter. + /// + /// The optimisations used here rely on hit events being a consecutive sequence from a single gameplay session. + /// When a new gameplay session is started, any existing results should be disposed. + /// public class UnstableRateCalculationResult { + /// + /// Total events processed. For internal incremental calculation use. + /// public int EventCount; + + /// + /// Last sum-of-squares value. For internal incremental calculation use. + /// public double SumOfSquares; + + /// + /// Last mean value. For internal incremental calculation use. + /// public double Mean; - public double Result => 10.0 * Math.Sqrt(SumOfSquares / EventCount); + /// + /// The unstable rate. + /// + public double Result => EventCount == 0 ? 0 : 10.0 * Math.Sqrt(SumOfSquares / EventCount); } } } From f708466a9bc8593f18700426b8b40d23865b3899 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 Nov 2024 23:43:31 +0900 Subject: [PATCH 081/223] Add test coverage --- .../Visual/Online/TestSceneChatOverlay.cs | 55 +++++++++++++++++++ .../Overlays/Chat/ChannelList/ChannelList.cs | 32 ++++++----- 2 files changed, 72 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 3d6fe50d34..ab9ee1d8cc 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -457,6 +457,61 @@ namespace osu.Game.Tests.Visual.Online waitForChannel1Visible(); } + [Test] + public void TestPublicChannelsSortedByName() + { + // Intentionally join back to front. + AddStep("Show overlay with channel 2", () => + { + channelManager.CurrentChannel.Value = channelManager.JoinChannel(testChannel2); + chatOverlay.Show(); + }); + AddUntilStep("second channel is at top of list", () => getFirstVisiblePublicChannel().Channel == testChannel2); + + AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + AddUntilStep("first channel is at top of list", () => getFirstVisiblePublicChannel().Channel == testChannel1); + + AddStep("message in channel 2", () => + { + testChannel2.AddNewMessages(new Message(1) { Content = "hi!", Sender = new APIUser { Username = "person" } }); + }); + AddUntilStep("first channel still at top of list", () => getFirstVisiblePublicChannel().Channel == testChannel1); + + ChannelListItem getFirstVisiblePublicChannel() => + chatOverlay.ChildrenOfType().Single().PublicChannelGroup.ItemFlow.FlowingChildren.OfType().First(item => item.Channel.Type == ChannelType.Public); + } + + [Test] + public void TestPrivateChannelsSortedByRecent() + { + Channel pmChannel1 = createPrivateChannel(); + Channel pmChannel2 = createPrivateChannel(); + + joinChannel(pmChannel1); + joinChannel(pmChannel2); + + AddStep("Show overlay", () => chatOverlay.Show()); + + AddUntilStep("first channel is at top of list", () => getFirstVisiblePMChannel().Channel == pmChannel1); + + AddStep("message in channel 2", () => + { + pmChannel2.AddNewMessages(new Message(1) { Content = "hi!", Sender = new APIUser { Username = "person" } }); + }); + + AddUntilStep("wait for first channel raised to top of list", () => getFirstVisiblePMChannel().Channel == pmChannel2); + + AddStep("message in channel 1", () => + { + pmChannel1.AddNewMessages(new Message(1) { Content = "hi!", Sender = new APIUser { Username = "person" } }); + }); + + AddUntilStep("wait for first channel raised to top of list", () => getFirstVisiblePMChannel().Channel == pmChannel1); + + ChannelListItem getFirstVisiblePMChannel() => + chatOverlay.ChildrenOfType().Single().PrivateChannelGroup.ItemFlow.FlowingChildren.OfType().First(item => item.Channel.Type == ChannelType.PM); + } + [Test] public void TestKeyboardNewChannel() { diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index a2ec385a7e..3e8c71e645 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -37,11 +37,13 @@ namespace osu.Game.Overlays.Chat.ChannelList private readonly Dictionary channelMap = new Dictionary(); + public ChannelGroup AnnounceChannelGroup { get; private set; } = null!; + public ChannelGroup PublicChannelGroup { get; private set; } = null!; + public ChannelGroup PrivateChannelGroup { get; private set; } = null!; + private OsuScrollContainer scroll = null!; private SearchContainer groupFlow = null!; - private ChannelGroup announceChannelGroup = null!; - private ChannelGroup publicChannelGroup = null!; - private ChannelGroup privateChannelGroup = null!; + private ChannelListItem selector = null!; private TextBox searchTextBox = null!; @@ -77,10 +79,10 @@ namespace osu.Game.Overlays.Chat.ChannelList RelativeSizeAxes = Axes.X, } }, - announceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper(), false), - publicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper(), false), + AnnounceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper(), false), + PublicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper(), false), selector = new ChannelListItem(ChannelListingChannel), - privateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper(), true), + PrivateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper(), true), }, }, }, @@ -146,28 +148,28 @@ namespace osu.Game.Overlays.Chat.ChannelList switch (channel.Type) { case ChannelType.Public: - return publicChannelGroup; + return PublicChannelGroup; case ChannelType.PM: - return privateChannelGroup; + return PrivateChannelGroup; case ChannelType.Announce: - return announceChannelGroup; + return AnnounceChannelGroup; default: - return publicChannelGroup; + return PublicChannelGroup; } } private void updateVisibility() { - if (announceChannelGroup.ItemFlow.Children.Count == 0) - announceChannelGroup.Hide(); + if (AnnounceChannelGroup.ItemFlow.Children.Count == 0) + AnnounceChannelGroup.Hide(); else - announceChannelGroup.Show(); + AnnounceChannelGroup.Show(); } - private partial class ChannelGroup : FillFlowContainer + public partial class ChannelGroup : FillFlowContainer { public readonly ChannelListItemFlow ItemFlow; @@ -207,7 +209,7 @@ namespace osu.Game.Overlays.Chat.ChannelList public void Reflow() => InvalidateLayout(); public override IEnumerable FlowingChildren => sortByRecent - ? base.FlowingChildren.OfType().OrderByDescending(i => i.Channel.LastMessageId) + ? base.FlowingChildren.OfType().OrderByDescending(i => i.Channel.LastMessageId ?? long.MinValue) : base.FlowingChildren.OfType().OrderBy(i => i.Channel.Name); } From 17347563ee5c139288f50b990a15d630c7681ea4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 13:06:47 +0900 Subject: [PATCH 082/223] Fix incorrect null handling --- osu.Game/Online/Chat/Channel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 15ce926039..9de77237b4 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -161,7 +161,7 @@ namespace osu.Game.Online.Chat Messages.AddRange(messages); long? maxMessageId = messages.Max(m => m.Id); - if (maxMessageId > LastMessageId) + if (LastMessageId == null || maxMessageId > LastMessageId) LastMessageId = maxMessageId; purgeOldMessages(); From 8585327858a00b6c15612bf29e446ccb733773d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 14:08:53 +0900 Subject: [PATCH 083/223] Ensure `DrawableMedal` loading doesn't ever block on online resources --- .../Overlays/MedalSplash/DrawableMedal.cs | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 2beed6645a..adad540c34 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Logging; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -28,16 +29,14 @@ namespace osu.Game.Overlays.MedalSplash [CanBeNull] public event Action StateChanged; - private readonly Medal medal; private readonly Container medalContainer; - private readonly Sprite medalSprite, medalGlow; + private readonly Sprite medalGlow; private readonly OsuSpriteText unlocked, name; private readonly TextFlowContainer description; private DisplayState state; public DrawableMedal(Medal medal) { - this.medal = medal; Position = new Vector2(0f, MedalAnimation.DISC_SIZE / 2); FillFlowContainer infoFlow; @@ -51,7 +50,7 @@ namespace osu.Game.Overlays.MedalSplash Alpha = 0f, Children = new Drawable[] { - medalSprite = new Sprite + new DelayedLoadWrapper(() => new MedalOnlineSprite(medal), 0) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -122,11 +121,12 @@ namespace osu.Game.Overlays.MedalSplash } [BackgroundDependencyLoader] - private void load(OsuColour colours, TextureStore textures, LargeTextureStore largeTextures) + private void load(OsuColour colours, TextureStore textures) { - medalSprite.Texture = largeTextures.Get(medal.ImageUrl); medalGlow.Texture = textures.Get(@"MedalSplash/medal-glow"); description.Colour = colours.BlueLight; + + Logger.Log("loaded"); } protected override void LoadComplete() @@ -191,6 +191,31 @@ namespace osu.Game.Overlays.MedalSplash break; } } + + private partial class MedalOnlineSprite : Sprite + { + private readonly Medal medal; + + public MedalOnlineSprite(Medal medal) + { + this.medal = medal; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, TextureStore textures, LargeTextureStore largeTextures) + { + Texture = largeTextures.Get(medal.ImageUrl); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + this.FadeInFromZero(150, Easing.OutQuint); + } + } } public enum DisplayState From d057dc9a95cf76f6888e6e0d8f8a60dca3705343 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 14:13:07 +0900 Subject: [PATCH 084/223] Refactor `MedalOverlay` to be more readable Shouldn't really have any functionality changes, just fixing some old code that I can't easily parse these days. --- osu.Game/Overlays/MedalOverlay.cs | 78 ++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 28 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 19f61cb910..7303a57cd0 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays private IAPIProvider api { get; set; } = null!; private Container medalContainer = null!; - private MedalAnimation? lastAnimation; + private MedalAnimation? currentMedalDisplay; [BackgroundDependencyLoader] private void load() @@ -54,11 +54,7 @@ namespace osu.Game.Overlays { base.LoadComplete(); - OverlayActivationMode.BindValueChanged(val => - { - if (val.NewValue == OverlayActivation.All && (queuedMedals.Any() || medalContainer.Any() || lastAnimation?.IsLoaded == false)) - Show(); - }, true); + OverlayActivationMode.BindValueChanged(_ => displayIfReady(), true); } private void handleMedalMessages(SocketMessage obj) @@ -86,31 +82,13 @@ namespace osu.Game.Overlays queuedMedals.Enqueue(medalAnimation); Logger.Log($"Queueing medal unlock for \"{medal.Name}\" ({queuedMedals.Count} to display)"); - if (OverlayActivationMode.Value == OverlayActivation.All) - Scheduler.AddOnce(Show); - } - - protected override void Update() - { - base.Update(); - - if (medalContainer.Any() || lastAnimation?.IsLoaded == false) - return; - - if (!queuedMedals.TryDequeue(out lastAnimation)) - { - Logger.Log("All queued medals have been displayed!"); - Hide(); - return; - } - - Logger.Log($"Preparing to display \"{lastAnimation.Medal.Name}\""); - LoadComponentAsync(lastAnimation, medalContainer.Add); + Schedule(displayIfReady); } protected override bool OnClick(ClickEvent e) { - lastAnimation?.Dismiss(); + dismissDisplayedMedal(); + loadNextMedal(); return true; } @@ -118,13 +96,57 @@ namespace osu.Game.Overlays { if (e.Action == GlobalAction.Back) { - lastAnimation?.Dismiss(); + dismissDisplayedMedal(); + loadNextMedal(); return true; } return base.OnPressed(e); } + private void dismissDisplayedMedal() + { + if (currentMedalDisplay?.IsLoaded == false) + return; + + currentMedalDisplay?.Dismiss(); + currentMedalDisplay = null; + } + + private void displayIfReady() + { + if (OverlayActivationMode.Value != OverlayActivation.All) + return; + + if (currentMedalDisplay != null) + { + Show(); + return; + } + + if (queuedMedals.Any()) + { + Show(); + loadNextMedal(); + } + } + + private void loadNextMedal() + { + if (currentMedalDisplay != null) + return; + + if (!queuedMedals.TryDequeue(out currentMedalDisplay)) + { + Logger.Log("All queued medals have been displayed!"); + Hide(); + return; + } + + Logger.Log($"Preparing to display \"{currentMedalDisplay.Medal.Name}\""); + LoadComponentAsync(currentMedalDisplay, m => medalContainer.Add(m)); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 672dbe6e03bd95971f46ace7685d91cd75729bb8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 14:42:30 +0900 Subject: [PATCH 085/223] Better control of show/hide of overlay --- osu.Game/Overlays/MedalOverlay.cs | 55 +++++++++++++++++-------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 7303a57cd0..b7e68fd557 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -57,6 +57,11 @@ namespace osu.Game.Overlays OverlayActivationMode.BindValueChanged(_ => displayIfReady(), true); } + public override void Hide() + { + // don't allow hiding the overlay via any method other than our own. + } + private void handleMedalMessages(SocketMessage obj) { if (obj.Event != @"new") @@ -87,8 +92,7 @@ namespace osu.Game.Overlays protected override bool OnClick(ClickEvent e) { - dismissDisplayedMedal(); - loadNextMedal(); + progressDisplayByUser(); return true; } @@ -96,21 +100,31 @@ namespace osu.Game.Overlays { if (e.Action == GlobalAction.Back) { - dismissDisplayedMedal(); - loadNextMedal(); + progressDisplayByUser(); return true; } return base.OnPressed(e); } - private void dismissDisplayedMedal() + private void progressDisplayByUser() { + // For now, we want to make sure that medals are definitely seen by the user. + // So we block exiting the overlay until the load of the active medal completes. if (currentMedalDisplay?.IsLoaded == false) return; currentMedalDisplay?.Dismiss(); currentMedalDisplay = null; + + if (!queuedMedals.Any()) + { + Logger.Log("All queued medals have been displayed, hiding overlay!"); + base.Hide(); + return; + } + + showNextMedal(); } private void displayIfReady() @@ -118,33 +132,26 @@ namespace osu.Game.Overlays if (OverlayActivationMode.Value != OverlayActivation.All) return; - if (currentMedalDisplay != null) - { - Show(); - return; - } - - if (queuedMedals.Any()) - { - Show(); - loadNextMedal(); - } + if (currentMedalDisplay != null || queuedMedals.Any()) + showNextMedal(); } - private void loadNextMedal() + private void showNextMedal() { + // A medal is already loading / loaded, so just ensure the overlay is visible. if (currentMedalDisplay != null) - return; - - if (!queuedMedals.TryDequeue(out currentMedalDisplay)) { - Logger.Log("All queued medals have been displayed!"); - Hide(); + Show(); return; } - Logger.Log($"Preparing to display \"{currentMedalDisplay.Medal.Name}\""); - LoadComponentAsync(currentMedalDisplay, m => medalContainer.Add(m)); + if (queuedMedals.TryDequeue(out currentMedalDisplay)) + { + Logger.Log($"Preparing to display \"{currentMedalDisplay.Medal.Name}\""); + + Show(); + LoadComponentAsync(currentMedalDisplay, m => medalContainer.Add(m)); + } } protected override void Dispose(bool isDisposing) From e8fae85e8d5b0077b9825a650180b89511c38d87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 14:45:40 +0900 Subject: [PATCH 086/223] Fix hidden dissmissing logic --- osu.Game/Overlays/MedalAnimation.cs | 5 +++-- osu.Game/Overlays/MedalOverlay.cs | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/MedalAnimation.cs b/osu.Game/Overlays/MedalAnimation.cs index daceeedf47..fdca0b2cc7 100644 --- a/osu.Game/Overlays/MedalAnimation.cs +++ b/osu.Game/Overlays/MedalAnimation.cs @@ -245,18 +245,19 @@ namespace osu.Game.Overlays this.FadeOut(200); } - public void Dismiss() + public bool Dismiss() { if (drawableMedal != null && drawableMedal.State != DisplayState.Full) { // if we haven't yet, play out the animation fully drawableMedal.State = DisplayState.Full; FinishTransforms(true); - return; + return false; } Hide(); Expire(); + return true; } private partial class BackgroundStrip : Container diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index b7e68fd557..736f744429 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -114,7 +114,10 @@ namespace osu.Game.Overlays if (currentMedalDisplay?.IsLoaded == false) return; - currentMedalDisplay?.Dismiss(); + // Dismissing may sometimes play out the medal animation rather than immediately dismissing. + if (currentMedalDisplay?.Dismiss() == false) + return; + currentMedalDisplay = null; if (!queuedMedals.Any()) From d150aeef2b019ecf1cad5f4e3f5b16ea1473297b Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Tue, 26 Nov 2024 01:01:59 -0500 Subject: [PATCH 087/223] Use score-based endpoint everywhere --- .../TestScenePlaylistsResultsScreen.cs | 4 ++-- .../Rooms/ShowPlaylistUserScoreRequest.cs | 23 ------------------- .../PlaylistItemUserResultsScreen.cs | 4 ++-- 3 files changed, 4 insertions(+), 27 deletions(-) delete mode 100644 osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 5977e67b0e..6ccbcd2859 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -231,7 +231,7 @@ namespace osu.Game.Tests.Visual.Playlists // pre-check for requests we should be handling (as they are scheduled below). switch (request) { - case ShowPlaylistUserScoreRequest: + case ShowPlaylistScoreRequest: case IndexPlaylistScoresRequest: break; @@ -253,7 +253,7 @@ namespace osu.Game.Tests.Visual.Playlists switch (request) { - case ShowPlaylistUserScoreRequest s: + case ShowPlaylistScoreRequest s: if (userScore == null) triggerFail(s); else diff --git a/osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs b/osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs deleted file mode 100644 index 8e6a1ac7c7..0000000000 --- a/osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs +++ /dev/null @@ -1,23 +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.Game.Online.API; - -namespace osu.Game.Online.Rooms -{ - public class ShowPlaylistUserScoreRequest : APIRequest - { - private readonly long roomId; - private readonly long playlistItemId; - private readonly long userId; - - public ShowPlaylistUserScoreRequest(long roomId, long playlistItemId, long userId) - { - this.roomId = roomId; - this.playlistItemId = playlistItemId; - this.userId = userId; - } - - protected override string Target => $"rooms/{roomId}/playlist/{playlistItemId}/scores/users/{userId}"; - } -} diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs index e038cf3288..988331e213 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs @@ -11,7 +11,7 @@ using osu.Game.Scoring; namespace osu.Game.Screens.OnlinePlay.Playlists { /// - /// Shows the user's best score for a given playlist item, with scores around included. + /// Shows the user's submitted score in a given playlist item, with scores around included. /// public partial class PlaylistItemUserResultsScreen : PlaylistItemResultsScreen { @@ -20,7 +20,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { } - protected override APIRequest CreateScoreRequest() => new ShowPlaylistUserScoreRequest(RoomId, PlaylistItem.ID, API.LocalUser.Value.Id); + protected override APIRequest CreateScoreRequest() => new ShowPlaylistScoreRequest(RoomId, PlaylistItem.ID, Score?.OnlineID ?? -1); protected override ScoreInfo[] PerformSuccessCallback(Action> callback, List scores, MultiplayerScores? pivot = null) { From c1416f9920e454812bf78e8e9d770dade16de1d9 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Tue, 26 Nov 2024 01:10:12 -0500 Subject: [PATCH 088/223] Bring back user-based endpoint for viewing result screen from playlists lounge --- .../Rooms/ShowPlaylistUserScoreRequest.cs | 23 +++++++++++++++++++ .../PlaylistItemUserResultsScreen.cs | 4 +++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs diff --git a/osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs b/osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs new file mode 100644 index 0000000000..8e6a1ac7c7 --- /dev/null +++ b/osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Online.API; + +namespace osu.Game.Online.Rooms +{ + public class ShowPlaylistUserScoreRequest : APIRequest + { + private readonly long roomId; + private readonly long playlistItemId; + private readonly long userId; + + public ShowPlaylistUserScoreRequest(long roomId, long playlistItemId, long userId) + { + this.roomId = roomId; + this.playlistItemId = playlistItemId; + this.userId = userId; + } + + protected override string Target => $"rooms/{roomId}/playlist/{playlistItemId}/scores/users/{userId}"; + } +} diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs index 988331e213..b659a98802 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs @@ -20,7 +20,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { } - protected override APIRequest CreateScoreRequest() => new ShowPlaylistScoreRequest(RoomId, PlaylistItem.ID, Score?.OnlineID ?? -1); + protected override APIRequest CreateScoreRequest() => Score == null + ? new ShowPlaylistScoreRequest(RoomId, PlaylistItem.ID, Score?.OnlineID ?? -1) + : new ShowPlaylistUserScoreRequest(RoomId, PlaylistItem.ID, API.LocalUser.Value.Id); protected override ScoreInfo[] PerformSuccessCallback(Action> callback, List scores, MultiplayerScores? pivot = null) { From 7201bac60d88b33177a16a596177f431e9b06192 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Tue, 26 Nov 2024 01:10:19 -0500 Subject: [PATCH 089/223] Remove `DailyChallengePlayer` --- .../DailyChallenge/DailyChallenge.cs | 2 +- .../DailyChallenge/DailyChallengePlayer.cs | 41 ------------------- 2 files changed, 1 insertion(+), 42 deletions(-) delete mode 100644 osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengePlayer.cs diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 6cb8a87a2a..0dc7e7930a 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -532,7 +532,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private void startPlay() { sampleStart?.Play(); - this.Push(new PlayerLoader(() => new DailyChallengePlayer(room, playlistItem) + this.Push(new PlayerLoader(() => new PlaylistsPlayer(room, playlistItem) { Exited = () => Scheduler.AddOnce(() => leaderboard.RefetchScores()) })); diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengePlayer.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengePlayer.cs deleted file mode 100644 index cfc0898e5a..0000000000 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengePlayer.cs +++ /dev/null @@ -1,41 +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.Diagnostics; -using osu.Game.Online.Rooms; -using osu.Game.Scoring; -using osu.Game.Screens.OnlinePlay.Playlists; -using osu.Game.Screens.Play; -using osu.Game.Screens.Ranking; - -namespace osu.Game.Screens.OnlinePlay.DailyChallenge -{ - public partial class DailyChallengePlayer : PlaylistsPlayer - { - public DailyChallengePlayer(Room room, PlaylistItem playlistItem, PlayerConfiguration? configuration = null) - : base(room, playlistItem, configuration) - { - } - - protected override ResultsScreen CreateResults(ScoreInfo score) - { - Debug.Assert(Room.RoomID != null); - - if (score.OnlineID >= 0) - { - return new PlaylistItemScoreResultsScreen(Room.RoomID.Value, PlaylistItem, score.OnlineID) - { - AllowRetry = true, - ShowUserStatistics = true, - }; - } - - // If the score has failed submission, fall back to displaying scores from user's highest. - return new PlaylistItemUserResultsScreen(score, Room.RoomID.Value, PlaylistItem) - { - AllowRetry = true, - ShowUserStatistics = true, - }; - } - } -} From e3ea38a366601df01f045f4f111765c34d041145 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 15:12:38 +0900 Subject: [PATCH 090/223] Add setting to allow hold-for-pause to still exist Users have asked for this multiple times since last release. Not sure on the best default value, but I'm going with the stable/classic one, at least for the initial release to avoid needing migrations. In the future we may reconsider this for new users. --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Localisation/GameplaySettingsStrings.cs | 5 +++++ .../Overlays/Settings/Sections/Gameplay/HUDSettings.cs | 5 +++++ osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 10 +++++++--- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 362c06849d..33d99e9b0f 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -214,6 +214,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorContractSidebars, false); SetDefault(OsuSetting.AlwaysShowHoldForMenuButton, false); + SetDefault(OsuSetting.AlwaysRequireHoldingForPause, false); } protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup) @@ -444,5 +445,6 @@ namespace osu.Game.Configuration EditorRotationOrigin, EditorTimelineShowBreaks, EditorAdjustExistingObjectsOnTimingChanges, + AlwaysRequireHoldingForPause } } diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs index 6de61f7ebe..ff6a6102a7 100644 --- a/osu.Game/Localisation/GameplaySettingsStrings.cs +++ b/osu.Game/Localisation/GameplaySettingsStrings.cs @@ -89,6 +89,11 @@ namespace osu.Game.Localisation /// public static LocalisableString AlwaysShowHoldForMenuButton => new TranslatableString(getKey(@"always_show_hold_for_menu_button"), @"Always show hold for menu button"); + /// + /// "Require holding key to pause gameplay" + /// + public static LocalisableString AlwaysRequireHoldForMenu => new TranslatableString(getKey(@"require_holding_key_to_pause_gameplay"), @"Require holding key to pause gameplay"); + /// /// "Always play first combo break sound" /// diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs index f4dd319152..b4caaf7983 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs @@ -41,6 +41,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay Current = config.GetBindable(OsuSetting.GameplayLeaderboard), }, new SettingsCheckbox + { + LabelText = GameplaySettingsStrings.AlwaysRequireHoldForMenu, + Current = config.GetBindable(OsuSetting.AlwaysRequireHoldingForPause), + }, + new SettingsCheckbox { LabelText = GameplaySettingsStrings.AlwaysShowHoldForMenuButton, Current = config.GetBindable(OsuSetting.AlwaysShowHoldForMenuButton), diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 5d3d5774d0..96e937fda7 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -162,14 +162,18 @@ namespace osu.Game.Screens.Play.HUD private bool pendingAnimation; private ScheduledDelegate shakeOperation; + private Bindable alwaysRequireHold; + public HoldButton(bool isDangerousAction) : base(isDangerousAction) { } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, OsuConfigManager config) { + alwaysRequireHold = config.GetBindable(OsuSetting.AlwaysRequireHoldingForPause); + Size = new Vector2(60); Child = new CircularContainer @@ -300,7 +304,7 @@ namespace osu.Game.Screens.Play.HUD case GlobalAction.Back: if (!pendingAnimation) { - if (IsDangerousAction) + if (IsDangerousAction || alwaysRequireHold.Value) BeginConfirm(); else Confirm(); @@ -314,7 +318,7 @@ namespace osu.Game.Screens.Play.HUD if (!pendingAnimation) { - if (IsDangerousAction) + if (IsDangerousAction || alwaysRequireHold.Value) BeginConfirm(); else Confirm(); From b76460f1003a269d2893d2466a71dd8e8a4a3201 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Tue, 26 Nov 2024 01:26:44 -0500 Subject: [PATCH 091/223] Schedule the thing Queuing up requests on change to `api.LocalUser` is bad because the API state is updated after `LocalUser` is updated, therefore we have to schhhhhedullllllllleeeeeeeeeeeeeeee. --- osu.Game/Online/LocalUserStatisticsProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/LocalUserStatisticsProvider.cs b/osu.Game/Online/LocalUserStatisticsProvider.cs index 79122b4186..312b80e18a 100644 --- a/osu.Game/Online/LocalUserStatisticsProvider.cs +++ b/osu.Game/Online/LocalUserStatisticsProvider.cs @@ -50,7 +50,7 @@ namespace osu.Game.Online api.LocalUser.BindValueChanged(_ => initialiseStatistics(), true); } - private void initialiseStatistics() + private void initialiseStatistics() => Schedule(() => { statisticsCache.Clear(); @@ -59,7 +59,7 @@ namespace osu.Game.Online foreach (var ruleset in rulesets.AvailableRulesets.Where(r => r.IsLegacyRuleset())) RefetchStatistics(ruleset); - } + }); public void RefetchStatistics(RulesetInfo ruleset, Action? callback = null) { From 42c68ba43ee1f4a0964406425c0497d603d124cc Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Tue, 26 Nov 2024 01:28:58 -0500 Subject: [PATCH 092/223] Add inline comment --- osu.Game/Online/LocalUserStatisticsProvider.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/LocalUserStatisticsProvider.cs b/osu.Game/Online/LocalUserStatisticsProvider.cs index 312b80e18a..22d5788c87 100644 --- a/osu.Game/Online/LocalUserStatisticsProvider.cs +++ b/osu.Game/Online/LocalUserStatisticsProvider.cs @@ -47,10 +47,16 @@ namespace osu.Game.Online protected override void LoadComplete() { base.LoadComplete(); - api.LocalUser.BindValueChanged(_ => initialiseStatistics(), true); + + api.LocalUser.BindValueChanged(_ => + { + // queuing up requests directly on user change is unsafe, as the API status may have not been updated yet. + // schedule a frame to allow the API to be in its correct state sending requests. + Schedule(initialiseStatistics); + }, true); } - private void initialiseStatistics() => Schedule(() => + private void initialiseStatistics() { statisticsCache.Clear(); @@ -59,7 +65,7 @@ namespace osu.Game.Online foreach (var ruleset in rulesets.AvailableRulesets.Where(r => r.IsLegacyRuleset())) RefetchStatistics(ruleset); - }); + } public void RefetchStatistics(RulesetInfo ruleset, Action? callback = null) { From 1e6c04e98b092e52a35b20d07e9a5a67e61de1b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 16:05:04 +0900 Subject: [PATCH 093/223] Remove debug logging --- osu.Game/Overlays/MedalSplash/DrawableMedal.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index adad540c34..460239f620 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Logging; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -125,8 +124,6 @@ namespace osu.Game.Overlays.MedalSplash { medalGlow.Texture = textures.Get(@"MedalSplash/medal-glow"); description.Colour = colours.BlueLight; - - Logger.Log("loaded"); } protected override void LoadComplete() From e0199386a38ef6581a8e78168e7080b1b34f9b0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 17:33:39 +0900 Subject: [PATCH 094/223] Add failing test case showing changing selection in editor affects samples --- .../TestSceneHitObjectSampleAdjustments.cs | 101 ++++++++++++------ 1 file changed, 67 insertions(+), 34 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index 5cc1e64197..ae814173a1 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -527,8 +527,11 @@ namespace osu.Game.Tests.Visual.Editing checkPlacementSampleBank(HitSampleInfo.BANK_NORMAL); checkPlacementSampleAdditionBank(HitSampleInfo.BANK_NORMAL); - void checkPlacementSampleBank(string expected) => AddAssert($"Placement sample is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected)); - void checkPlacementSampleAdditionBank(string expected) => AddAssert($"Placement sample addition is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected)); + void checkPlacementSampleBank(string expected) => AddAssert($"Placement sample is {expected}", + () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected)); + + void checkPlacementSampleAdditionBank(string expected) => AddAssert($"Placement sample addition is {expected}", + () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected)); } [Test] @@ -774,6 +777,7 @@ namespace osu.Game.Tests.Visual.Editing } [Test] + [Solo] public void TestSelectingObjectDoesNotMutateSamples() { clickSamplePiece(0); @@ -781,15 +785,39 @@ namespace osu.Game.Tests.Visual.Editing setAdditionBankViaPopover(HitSampleInfo.BANK_SOFT); dismissPopover(); - hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH); - hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL); - hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT); + assertNoChanges(); - AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0])); + AddStep("select first object", () => + { + EditorBeatmap.SelectedHitObjects.Clear(); + EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]); + }); + assertNoChanges(); - hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH); - hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL); - hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT); + AddStep("select second object", () => + { + EditorBeatmap.SelectedHitObjects.Clear(); + EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[1]); + }); + assertNoChanges(); + + AddStep("select first object", () => + { + EditorBeatmap.SelectedHitObjects.Clear(); + EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]); + }); + assertNoChanges(); + + void assertNoChanges() + { + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH); + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL); + hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT); + + hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL); + hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT); + } } private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} sample piece", () => @@ -883,11 +911,12 @@ namespace osu.Game.Tests.Visual.Editing return h.Samples.All(o => o.Volume == volume); }); - private void hitObjectNodeHasSampleVolume(int objectIndex, int nodeIndex, int volume) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has volume {volume}", () => - { - var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats; - return h is not null && h.NodeSamples[nodeIndex].All(o => o.Volume == volume); - }); + private void hitObjectNodeHasSampleVolume(int objectIndex, int nodeIndex, int volume) => AddAssert( + $"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has volume {volume}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats; + return h is not null && h.NodeSamples[nodeIndex].All(o => o.Volume == volume); + }); private void setBankViaPopover(string bank) => AddStep($"set bank {bank} via popover", () => { @@ -944,29 +973,33 @@ namespace osu.Game.Tests.Visual.Editing return h.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank); }); - private void hitObjectNodeHasSamples(int objectIndex, int nodeIndex, params string[] samples) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has samples {string.Join(',', samples)}", () => - { - var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats; - return h is not null && h.NodeSamples[nodeIndex].Select(s => s.Name).SequenceEqual(samples); - }); + private void hitObjectNodeHasSamples(int objectIndex, int nodeIndex, params string[] samples) => AddAssert( + $"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has samples {string.Join(',', samples)}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats; + return h is not null && h.NodeSamples[nodeIndex].Select(s => s.Name).SequenceEqual(samples); + }); - private void hitObjectNodeHasSampleBank(int objectIndex, int nodeIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has bank {bank}", () => - { - var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats; - return h is not null && h.NodeSamples[nodeIndex].All(o => o.Bank == bank); - }); + private void hitObjectNodeHasSampleBank(int objectIndex, int nodeIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has bank {bank}", + () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats; + return h is not null && h.NodeSamples[nodeIndex].All(o => o.Bank == bank); + }); - private void hitObjectNodeHasSampleNormalBank(int objectIndex, int nodeIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has normal bank {bank}", () => - { - var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats; - return h is not null && h.NodeSamples[nodeIndex].Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank); - }); + private void hitObjectNodeHasSampleNormalBank(int objectIndex, int nodeIndex, string bank) => AddAssert( + $"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has normal bank {bank}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats; + return h is not null && h.NodeSamples[nodeIndex].Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank); + }); - private void hitObjectNodeHasSampleAdditionBank(int objectIndex, int nodeIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has addition bank {bank}", () => - { - var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats; - return h is not null && h.NodeSamples[nodeIndex].Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank); - }); + private void hitObjectNodeHasSampleAdditionBank(int objectIndex, int nodeIndex, string bank) => AddAssert( + $"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has addition bank {bank}", () => + { + var h = EditorBeatmap.HitObjects.ElementAt(objectIndex) as IHasRepeats; + return h is not null && h.NodeSamples[nodeIndex].Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank); + }); private void editorTimeIs(double time) => AddAssert($"editor time is {time}", () => Precision.AlmostEquals(EditorClock.CurrentTimeAccurate, time, 1)); } From 3ecb3b674d5e519b110546802ff14d6a3d248dc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 16:04:37 +0900 Subject: [PATCH 095/223] Don't reset state when changing from one selection to another in the editor This was causing state pollution in the new selection. I can't see why this needs to happen when a selection changes to another. This fixes https://github.com/ppy/osu/issues/30839 and also the same issue happening for the new combo toggle. Tests all seem to pass, and I can't immediately find anything broken, but YMMV. --- .../Screens/Edit/Compose/Components/EditorSelectionHandler.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 6724a1dc4d..78cee2c1cf 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -258,6 +258,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private void resetTernaryStates() { + if (SelectedItems.Count > 0) + return; + SelectionNewComboState.Value = TernaryState.False; AutoSelectionBankEnabled.Value = true; SelectionAdditionBanksEnabled.Value = true; From 41c309fb7220c830ab646c5a2d63c71c063e5cc8 Mon Sep 17 00:00:00 2001 From: "tsrk." Date: Tue, 26 Nov 2024 09:35:18 +0100 Subject: [PATCH 096/223] chore(MenuTip): update text according to recent changes Signed-off-by: tsrk. --- osu.Game/Localisation/MenuTipStrings.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Localisation/MenuTipStrings.cs b/osu.Game/Localisation/MenuTipStrings.cs index e955040f37..b8a00a1c17 100644 --- a/osu.Game/Localisation/MenuTipStrings.cs +++ b/osu.Game/Localisation/MenuTipStrings.cs @@ -15,9 +15,9 @@ namespace osu.Game.Localisation public static LocalisableString ToggleToolbarShortcut => new TranslatableString(getKey(@"toggle_toolbar_shortcut"), @"Press Ctrl-T anywhere in the game to toggle the toolbar!"); /// - /// "Press Ctrl-O anywhere in the game to access options!" + /// "Press Ctrl-O anywhere in the game to access settings!" /// - public static LocalisableString GameSettingsShortcut => new TranslatableString(getKey(@"game_settings_shortcut"), @"Press Ctrl-O anywhere in the game to access options!"); + public static LocalisableString GameSettingsShortcut => new TranslatableString(getKey(@"game_settings_shortcut"), @"Press Ctrl-O anywhere in the game to access settings!"); /// /// "All settings are dynamic and take effect in real-time. Try changing the skin while watching autoplay!" @@ -85,9 +85,9 @@ namespace osu.Game.Localisation public static LocalisableString ConfigurableHotkeys => new TranslatableString(getKey(@"configurable_hotkeys"), @"Most of the hotkeys in the game are configurable and can be changed to anything you want. Check the bindings panel under input settings!"); /// - /// "Your gameplay HUD can be customized by using the skin layout editor. Open it at any time via Ctrl-Shift-S!" + /// "Your gameplay HUD can be customised by using the skin layout editor. Open it at any time via Ctrl-Shift-S!" /// - public static LocalisableString SkinEditor => new TranslatableString(getKey(@"skin_editor"), @"Your gameplay HUD can be customized by using the skin layout editor. Open it at any time via Ctrl-Shift-S!"); + public static LocalisableString SkinEditor => new TranslatableString(getKey(@"skin_editor"), @"Your gameplay HUD can be customised by using the skin layout editor. Open it at any time via Ctrl-Shift-S!"); /// /// "You can create mod presets to make toggling your favorite mod combinations easier!" @@ -95,9 +95,9 @@ namespace osu.Game.Localisation public static LocalisableString ModPresets => new TranslatableString(getKey(@"mod_presets"), @"You can create mod presets to make toggling your favorite mod combinations easier!"); /// - /// "Many mods have customisation settings that drastically change how they function. Click the Mod Customisation button in mod select to view settings!" + /// "Many mods have customisation settings that drastically change how they function. Click the Customise button in mod select to view settings!" /// - public static LocalisableString ModCustomisationSettings => new TranslatableString(getKey(@"mod_customisation_settings"), @"Many mods have customisation settings that drastically change how they function. Click the Mod Customisation button in mod select to view settings!"); + public static LocalisableString ModCustomisationSettings => new TranslatableString(getKey(@"mod_customisation_settings"), @"Many mods have customisation settings that drastically change how they function. Click the Customise button in mod select to view settings!"); /// /// "Press Ctrl-Shift-R to switch to a random skin!" From 98044c108e7f7e9e8723c61ff1ca3823a95feaeb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 17:41:00 +0900 Subject: [PATCH 097/223] Revert "Ensure `DrawableMedal` loading doesn't ever block on online resources" This reverts commit 8585327858a00b6c15612bf29e446ccb733773d9. --- .../Overlays/MedalSplash/DrawableMedal.cs | 34 ++++--------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 460239f620..2beed6645a 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -28,14 +28,16 @@ namespace osu.Game.Overlays.MedalSplash [CanBeNull] public event Action StateChanged; + private readonly Medal medal; private readonly Container medalContainer; - private readonly Sprite medalGlow; + private readonly Sprite medalSprite, medalGlow; private readonly OsuSpriteText unlocked, name; private readonly TextFlowContainer description; private DisplayState state; public DrawableMedal(Medal medal) { + this.medal = medal; Position = new Vector2(0f, MedalAnimation.DISC_SIZE / 2); FillFlowContainer infoFlow; @@ -49,7 +51,7 @@ namespace osu.Game.Overlays.MedalSplash Alpha = 0f, Children = new Drawable[] { - new DelayedLoadWrapper(() => new MedalOnlineSprite(medal), 0) + medalSprite = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -120,8 +122,9 @@ namespace osu.Game.Overlays.MedalSplash } [BackgroundDependencyLoader] - private void load(OsuColour colours, TextureStore textures) + private void load(OsuColour colours, TextureStore textures, LargeTextureStore largeTextures) { + medalSprite.Texture = largeTextures.Get(medal.ImageUrl); medalGlow.Texture = textures.Get(@"MedalSplash/medal-glow"); description.Colour = colours.BlueLight; } @@ -188,31 +191,6 @@ namespace osu.Game.Overlays.MedalSplash break; } } - - private partial class MedalOnlineSprite : Sprite - { - private readonly Medal medal; - - public MedalOnlineSprite(Medal medal) - { - this.medal = medal; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, TextureStore textures, LargeTextureStore largeTextures) - { - Texture = largeTextures.Get(medal.ImageUrl); - - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - this.FadeInFromZero(150, Easing.OutQuint); - } - } } public enum DisplayState From 71294c312b1a29d2ca73c1f335140e4f350754d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 17:58:50 +0900 Subject: [PATCH 098/223] Change point of queueing to avoid loading-from-in-queue --- osu.Game/Overlays/MedalOverlay.cs | 34 +++++++++++++------------------ 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 736f744429..c24b209b3a 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays { base.LoadComplete(); - OverlayActivationMode.BindValueChanged(_ => displayIfReady(), true); + OverlayActivationMode.BindValueChanged(_ => showNextMedal(), true); } public override void Hide() @@ -84,10 +84,13 @@ namespace osu.Game.Overlays var medalAnimation = new MedalAnimation(medal); - queuedMedals.Enqueue(medalAnimation); Logger.Log($"Queueing medal unlock for \"{medal.Name}\" ({queuedMedals.Count} to display)"); - Schedule(displayIfReady); + LoadComponentAsync(medalAnimation, m => + { + queuedMedals.Enqueue(m); + showNextMedal(); + }); } protected override bool OnClick(ClickEvent e) @@ -130,30 +133,21 @@ namespace osu.Game.Overlays showNextMedal(); } - private void displayIfReady() - { - if (OverlayActivationMode.Value != OverlayActivation.All) - return; - - if (currentMedalDisplay != null || queuedMedals.Any()) - showNextMedal(); - } - private void showNextMedal() { - // A medal is already loading / loaded, so just ensure the overlay is visible. - if (currentMedalDisplay != null) - { - Show(); + // If already displayed, keep displaying medals regardless of activation mode changes. + if (OverlayActivationMode.Value != OverlayActivation.All && State.Value == Visibility.Hidden) + return; + + // A medal is already displaying. + if (currentMedalDisplay != null) return; - } if (queuedMedals.TryDequeue(out currentMedalDisplay)) { - Logger.Log($"Preparing to display \"{currentMedalDisplay.Medal.Name}\""); - + Logger.Log($"Displaying \"{currentMedalDisplay.Medal.Name}\""); + medalContainer.Add(currentMedalDisplay); Show(); - LoadComponentAsync(currentMedalDisplay, m => medalContainer.Add(m)); } } From bf29e3ae718373f16ff1edc5e35c412fa89e9902 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 18:00:32 +0900 Subject: [PATCH 099/223] Simplify hide code by moving to common method --- osu.Game/Overlays/MedalOverlay.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index c24b209b3a..512cb697dd 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; @@ -112,24 +111,11 @@ namespace osu.Game.Overlays private void progressDisplayByUser() { - // For now, we want to make sure that medals are definitely seen by the user. - // So we block exiting the overlay until the load of the active medal completes. - if (currentMedalDisplay?.IsLoaded == false) - return; - // Dismissing may sometimes play out the medal animation rather than immediately dismissing. if (currentMedalDisplay?.Dismiss() == false) return; currentMedalDisplay = null; - - if (!queuedMedals.Any()) - { - Logger.Log("All queued medals have been displayed, hiding overlay!"); - base.Hide(); - return; - } - showNextMedal(); } @@ -149,6 +135,11 @@ namespace osu.Game.Overlays medalContainer.Add(currentMedalDisplay); Show(); } + else if (State.Value == Visibility.Visible) + { + Logger.Log("All queued medals have been displayed, hiding overlay!"); + base.Hide(); + } } protected override void Dispose(bool isDisposing) From 312336de24e730247b64af3b601bbe114d2d563f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 18:12:26 +0900 Subject: [PATCH 100/223] Fix classic skin spinner's middle pieces displaying in the wrong order Closes https://github.com/ppy/osu/issues/30873. See [stable reference](https://github.com/peppy/osu-stable-reference/blob/3ea48705eb67172c430371dcfc8a16a002ed0d3d/osu!/GameplayElements/HitObjects/Osu/SpinnerOsu.cs#L148-L158). --- .../Skinning/Legacy/LegacyNewStyleSpinner.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs index d4a0f243e4..5d09267c21 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs @@ -63,18 +63,18 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Origin = Anchor.Centre, Texture = source.GetTexture("spinner-top"), }, - fixedMiddle = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Texture = source.GetTexture("spinner-middle"), - }, spinningMiddle = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, Texture = source.GetTexture("spinner-middle2"), }, + fixedMiddle = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-middle"), + }, } }); From 46d1f005907f83c769840974eda24630136e9701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Nov 2024 11:39:03 +0100 Subject: [PATCH 101/223] Fix `Beatmap.Countdown` not being copied on conversion --- osu.Game/Beatmaps/BeatmapConverter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index c0066cc637..82b40c0318 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -83,6 +83,7 @@ namespace osu.Game.Beatmaps beatmap.DistanceSpacing = original.DistanceSpacing; beatmap.GridSize = original.GridSize; beatmap.TimelineZoom = original.TimelineZoom; + beatmap.Countdown = original.Countdown; beatmap.CountdownOffset = original.CountdownOffset; return beatmap; From c69d36dc96fc39e13fe8980837c94dc31f633dbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Nov 2024 12:40:49 +0100 Subject: [PATCH 102/223] Remove leftover `[Solo]` attribute --- .../Visual/Editing/TestSceneHitObjectSampleAdjustments.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index ae814173a1..765fe1ecf6 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -777,7 +777,6 @@ namespace osu.Game.Tests.Visual.Editing } [Test] - [Solo] public void TestSelectingObjectDoesNotMutateSamples() { clickSamplePiece(0); From bd1f978138c2dc6d02f084f49be91c99b3902366 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 26 Nov 2024 21:35:10 +0900 Subject: [PATCH 103/223] Empty commit to fix CI From f04862ea7417349a8dd32895cfa80d4477d0cadd Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 26 Nov 2024 12:11:29 -0800 Subject: [PATCH 104/223] Edit one more word not using british english --- osu.Game/Localisation/MenuTipStrings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/MenuTipStrings.cs b/osu.Game/Localisation/MenuTipStrings.cs index b8a00a1c17..f97ad5fa2c 100644 --- a/osu.Game/Localisation/MenuTipStrings.cs +++ b/osu.Game/Localisation/MenuTipStrings.cs @@ -90,9 +90,9 @@ namespace osu.Game.Localisation public static LocalisableString SkinEditor => new TranslatableString(getKey(@"skin_editor"), @"Your gameplay HUD can be customised by using the skin layout editor. Open it at any time via Ctrl-Shift-S!"); /// - /// "You can create mod presets to make toggling your favorite mod combinations easier!" + /// "You can create mod presets to make toggling your favourite mod combinations easier!" /// - public static LocalisableString ModPresets => new TranslatableString(getKey(@"mod_presets"), @"You can create mod presets to make toggling your favorite mod combinations easier!"); + public static LocalisableString ModPresets => new TranslatableString(getKey(@"mod_presets"), @"You can create mod presets to make toggling your favourite mod combinations easier!"); /// /// "Many mods have customisation settings that drastically change how they function. Click the Customise button in mod select to view settings!" From df74a177ae8ce195d79e20b6903b7477d201db10 Mon Sep 17 00:00:00 2001 From: HenintsoaSky Date: Wed, 27 Nov 2024 00:13:32 +0300 Subject: [PATCH 105/223] Add option to disable star fountain in gameplay --- .../Visual/Menus/TestSceneStarFountain.cs | 54 +++++++++++++++++++ osu.Game/Configuration/OsuConfigManager.cs | 2 + .../Localisation/GameplaySettingsStrings.cs | 5 ++ .../Sections/Gameplay/GeneralSettings.cs | 5 ++ .../Screens/Play/KiaiGameplayFountains.cs | 16 +++++- 5 files changed, 81 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs index 29fa7287d2..64a0f1f821 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs @@ -3,8 +3,10 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; @@ -73,5 +75,57 @@ namespace osu.Game.Tests.Visual.Menus ((StarFountain)Children[1]).Shoot(-1); }); } + + [Test] + public void TestGameplayKiaiStarToggle() + { + Bindable kiaiStarEffectsEnabled = null!; + + AddStep("load configuration", () => + { + var config = new OsuConfigManager(LocalStorage); + kiaiStarEffectsEnabled = config.GetBindable(OsuSetting.KiaiStarFountain); + }); + + AddStep("make fountains", () => + { + Children = new Drawable[] + { + new KiaiGameplayFountains.GameplayStarFountain + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + X = 75, + }, + new KiaiGameplayFountains.GameplayStarFountain + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + X = -75, + }, + }; + }); + + AddStep("enable KiaiStarEffects", () => kiaiStarEffectsEnabled.Value = true); + AddRepeatStep("activate fountains (enabled)", () => + { + ((KiaiGameplayFountains.GameplayStarFountain)Children[0]).Shoot(1); + ((KiaiGameplayFountains.GameplayStarFountain)Children[1]).Shoot(-1); + }, 100); + + AddStep("disable KiaiStarEffects", () => kiaiStarEffectsEnabled.Value = false); + AddRepeatStep("attempt to activate fountains (disabled)", () => + { + ((KiaiGameplayFountains.GameplayStarFountain)Children[0]).Shoot(1); + ((KiaiGameplayFountains.GameplayStarFountain)Children[1]).Shoot(-1); + }, 100); + + AddStep("re-enable KiaiStarEffects", () => kiaiStarEffectsEnabled.Value = true); + AddRepeatStep("activate fountains (re-enabled)", () => + { + ((KiaiGameplayFountains.GameplayStarFountain)Children[0]).Shoot(1); + ((KiaiGameplayFountains.GameplayStarFountain)Children[1]).Shoot(-1); + }, 100); + } } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 33d99e9b0f..36a5328756 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -138,6 +138,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.LightenDuringBreaks, true); SetDefault(OsuSetting.HitLighting, true); + SetDefault(OsuSetting.KiaiStarFountain, true); SetDefault(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always); SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true); @@ -414,6 +415,7 @@ namespace osu.Game.Configuration NotifyOnPrivateMessage, UIHoldActivationDelay, HitLighting, + KiaiStarFountain, MenuBackgroundSource, GameplayDisableWinKey, SeasonalBackgroundMode, diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs index ff6a6102a7..3d18eacf9d 100644 --- a/osu.Game/Localisation/GameplaySettingsStrings.cs +++ b/osu.Game/Localisation/GameplaySettingsStrings.cs @@ -74,6 +74,11 @@ namespace osu.Game.Localisation /// public static LocalisableString FadePlayfieldWhenHealthLow => new TranslatableString(getKey(@"fade_playfield_when_health_low"), @"Fade playfield to red when health is low"); + /// + /// "Star fountain during kiai time" + /// + public static LocalisableString KiaiStarFountain => new TranslatableString(getKey(@"star_fountain_during_kiai_time"), @"Star fountain during kiai time"); + /// /// "Always show key overlay" /// diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 83e9140b33..136832a75b 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -31,6 +31,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = GraphicsSettingsStrings.HitLighting, Current = config.GetBindable(OsuSetting.HitLighting) }, + new SettingsCheckbox + { + LabelText = GameplaySettingsStrings.KiaiStarFountain, + Current = config.GetBindable(OsuSetting.KiaiStarFountain) + }, }; } } diff --git a/osu.Game/Screens/Play/KiaiGameplayFountains.cs b/osu.Game/Screens/Play/KiaiGameplayFountains.cs index 7659c61123..4d1d247f87 100644 --- a/osu.Game/Screens/Play/KiaiGameplayFountains.cs +++ b/osu.Game/Screens/Play/KiaiGameplayFountains.cs @@ -5,8 +5,10 @@ using System; using osu.Framework.Allocation; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; using osu.Game.Screens.Menu; @@ -18,9 +20,13 @@ namespace osu.Game.Screens.Play private StarFountain leftFountain = null!; private StarFountain rightFountain = null!; + private Bindable kiaiStarEffectsEnabled = null!; + [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { + kiaiStarEffectsEnabled = config.GetBindable(OsuSetting.KiaiStarFountain); + RelativeSizeAxes = Axes.Both; Children = new[] @@ -48,6 +54,12 @@ namespace osu.Game.Screens.Play { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + if (!kiaiStarEffectsEnabled.Value) + return; + + if (!kiaiStarEffectsEnabled.Value) + return; + if (effectPoint.KiaiMode && !isTriggered) { bool isNearEffectPoint = Math.Abs(BeatSyncSource.Clock.CurrentTime - effectPoint.Time) < 500; @@ -76,6 +88,8 @@ namespace osu.Game.Screens.Play { protected override double ShootDuration => 400; + private readonly Bindable kiaiStarEffectsEnabled = new Bindable(); + public GameplayStarFountainSpewer() : base(perSecond: 180) { From 460471e73fc17c50cd67073de1e6eb05d0e75179 Mon Sep 17 00:00:00 2001 From: HenintsoaSky Date: Wed, 27 Nov 2024 00:27:22 +0300 Subject: [PATCH 106/223] Rename of the setting --- osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs | 2 +- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- osu.Game/Localisation/GameplaySettingsStrings.cs | 4 ++-- .../Overlays/Settings/Sections/Gameplay/GeneralSettings.cs | 4 ++-- osu.Game/Screens/Play/KiaiGameplayFountains.cs | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs index 64a0f1f821..6f73979e58 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("load configuration", () => { var config = new OsuConfigManager(LocalStorage); - kiaiStarEffectsEnabled = config.GetBindable(OsuSetting.KiaiStarFountain); + kiaiStarEffectsEnabled = config.GetBindable(OsuSetting.StarFountains); }); AddStep("make fountains", () => diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 36a5328756..4f62db8cf7 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -138,7 +138,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.LightenDuringBreaks, true); SetDefault(OsuSetting.HitLighting, true); - SetDefault(OsuSetting.KiaiStarFountain, true); + SetDefault(OsuSetting.StarFountains, true); SetDefault(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always); SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true); @@ -415,7 +415,7 @@ namespace osu.Game.Configuration NotifyOnPrivateMessage, UIHoldActivationDelay, HitLighting, - KiaiStarFountain, + StarFountains, MenuBackgroundSource, GameplayDisableWinKey, SeasonalBackgroundMode, diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs index 3d18eacf9d..2715f0b8cf 100644 --- a/osu.Game/Localisation/GameplaySettingsStrings.cs +++ b/osu.Game/Localisation/GameplaySettingsStrings.cs @@ -75,9 +75,9 @@ namespace osu.Game.Localisation public static LocalisableString FadePlayfieldWhenHealthLow => new TranslatableString(getKey(@"fade_playfield_when_health_low"), @"Fade playfield to red when health is low"); /// - /// "Star fountain during kiai time" + /// "Star fountains" /// - public static LocalisableString KiaiStarFountain => new TranslatableString(getKey(@"star_fountain_during_kiai_time"), @"Star fountain during kiai time"); + public static LocalisableString StarFountains => new TranslatableString(getKey(@"star_fountains"), @"Star fountains"); /// /// "Always show key overlay" diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 136832a75b..779d5cdf00 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -33,8 +33,8 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay }, new SettingsCheckbox { - LabelText = GameplaySettingsStrings.KiaiStarFountain, - Current = config.GetBindable(OsuSetting.KiaiStarFountain) + LabelText = GameplaySettingsStrings.StarFountains, + Current = config.GetBindable(OsuSetting.StarFountains) }, }; } diff --git a/osu.Game/Screens/Play/KiaiGameplayFountains.cs b/osu.Game/Screens/Play/KiaiGameplayFountains.cs index 4d1d247f87..011de52b2a 100644 --- a/osu.Game/Screens/Play/KiaiGameplayFountains.cs +++ b/osu.Game/Screens/Play/KiaiGameplayFountains.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - kiaiStarEffectsEnabled = config.GetBindable(OsuSetting.KiaiStarFountain); + kiaiStarEffectsEnabled = config.GetBindable(OsuSetting.StarFountains); RelativeSizeAxes = Axes.Both; From 80a66085a9497b97e6c7312a34ca52abe8e632a4 Mon Sep 17 00:00:00 2001 From: HenintsoaSky Date: Wed, 27 Nov 2024 00:41:02 +0300 Subject: [PATCH 107/223] rename and remove again --- osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs | 12 ++++++------ osu.Game/Screens/Play/KiaiGameplayFountains.cs | 10 ++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs index 6f73979e58..0d981014b8 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs @@ -77,14 +77,14 @@ namespace osu.Game.Tests.Visual.Menus } [Test] - public void TestGameplayKiaiStarToggle() + public void TestGameplayStarFountainsSetting() { - Bindable kiaiStarEffectsEnabled = null!; + Bindable starFountainsEnabled = null!; AddStep("load configuration", () => { var config = new OsuConfigManager(LocalStorage); - kiaiStarEffectsEnabled = config.GetBindable(OsuSetting.StarFountains); + starFountainsEnabled = config.GetBindable(OsuSetting.StarFountains); }); AddStep("make fountains", () => @@ -106,21 +106,21 @@ namespace osu.Game.Tests.Visual.Menus }; }); - AddStep("enable KiaiStarEffects", () => kiaiStarEffectsEnabled.Value = true); + AddStep("enable KiaiStarEffects", () => starFountainsEnabled.Value = true); AddRepeatStep("activate fountains (enabled)", () => { ((KiaiGameplayFountains.GameplayStarFountain)Children[0]).Shoot(1); ((KiaiGameplayFountains.GameplayStarFountain)Children[1]).Shoot(-1); }, 100); - AddStep("disable KiaiStarEffects", () => kiaiStarEffectsEnabled.Value = false); + AddStep("disable KiaiStarEffects", () => starFountainsEnabled.Value = false); AddRepeatStep("attempt to activate fountains (disabled)", () => { ((KiaiGameplayFountains.GameplayStarFountain)Children[0]).Shoot(1); ((KiaiGameplayFountains.GameplayStarFountain)Children[1]).Shoot(-1); }, 100); - AddStep("re-enable KiaiStarEffects", () => kiaiStarEffectsEnabled.Value = true); + AddStep("re-enable KiaiStarEffects", () => starFountainsEnabled.Value = true); AddRepeatStep("activate fountains (re-enabled)", () => { ((KiaiGameplayFountains.GameplayStarFountain)Children[0]).Shoot(1); diff --git a/osu.Game/Screens/Play/KiaiGameplayFountains.cs b/osu.Game/Screens/Play/KiaiGameplayFountains.cs index 011de52b2a..a6b2cd6fdb 100644 --- a/osu.Game/Screens/Play/KiaiGameplayFountains.cs +++ b/osu.Game/Screens/Play/KiaiGameplayFountains.cs @@ -20,12 +20,12 @@ namespace osu.Game.Screens.Play private StarFountain leftFountain = null!; private StarFountain rightFountain = null!; - private Bindable kiaiStarEffectsEnabled = null!; + private Bindable kiaiStarFountainsEnabled = null!; [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - kiaiStarEffectsEnabled = config.GetBindable(OsuSetting.StarFountains); + kiaiStarFountainsEnabled = config.GetBindable(OsuSetting.StarFountains); RelativeSizeAxes = Axes.Both; @@ -54,10 +54,10 @@ namespace osu.Game.Screens.Play { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - if (!kiaiStarEffectsEnabled.Value) + if (!kiaiStarFountainsEnabled.Value) return; - if (!kiaiStarEffectsEnabled.Value) + if (!kiaiStarFountainsEnabled.Value) return; if (effectPoint.KiaiMode && !isTriggered) @@ -88,8 +88,6 @@ namespace osu.Game.Screens.Play { protected override double ShootDuration => 400; - private readonly Bindable kiaiStarEffectsEnabled = new Bindable(); - public GameplayStarFountainSpewer() : base(perSecond: 180) { From 3e1b4f4ac564a1b69b2d8111b19d3908f99980e4 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Tue, 26 Nov 2024 16:50:23 -0500 Subject: [PATCH 108/223] Rename `AllowBackButton` to `AllowUserExit` and rewrite visibility flow structure Co-authored-by: Dean Herbert --- osu.Game/OsuGame.cs | 25 ++++++++++++------- .../Maintenance/MigrationRunScreen.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Edit/EditorLoader.cs | 2 +- osu.Game/Screens/IOsuScreen.cs | 21 ++++++++-------- osu.Game/Screens/Menu/MainMenu.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayScreen.cs | 2 +- osu.Game/Screens/OsuScreen.cs | 13 +++++++--- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Play/PlayerLoader.cs | 4 +-- osu.Game/Screens/StartupScreen.cs | 2 +- 11 files changed, 44 insertions(+), 33 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4d6bc4fd14..514209524e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -175,6 +175,11 @@ namespace osu.Game /// public readonly IBindable OverlayActivationMode = new Bindable(); + /// + /// Whether the back button is currently displayed. + /// + public readonly IBindable BackButtonVisibility = new Bindable(); + IBindable ILocalUserPlayInfo.PlayingState => playingState; private readonly Bindable playingState = new Bindable(); @@ -1019,7 +1024,7 @@ namespace osu.Game if (!(ScreenStack.CurrentScreen is IOsuScreen currentScreen)) return; - if (!((Drawable)currentScreen).IsLoaded || (currentScreen.AllowBackButton && !currentScreen.OnBackButton())) + if (!((Drawable)currentScreen).IsLoaded || (currentScreen.AllowUserExit && !currentScreen.OnBackButton())) ScreenStack.Exit(); } }, @@ -1189,6 +1194,14 @@ namespace osu.Game if (mode.NewValue != OverlayActivation.All) CloseAllOverlays(); }; + BackButtonVisibility.ValueChanged += visible => + { + if (visible.NewValue) + BackButton.Show(); + else + BackButton.Hide(); + }; + // Importantly, this should be run after binding PostNotification to the import handlers so they can present the import after game startup. handleStartupImport(); } @@ -1581,20 +1594,14 @@ namespace osu.Game if (current is IOsuScreen currentOsuScreen) { - if (currentOsuScreen.AllowBackButton) - BackButton.State.UnbindFrom(currentOsuScreen.BackButtonState); - + BackButtonVisibility.UnbindFrom(currentOsuScreen.BackButtonVisibility); OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode); API.Activity.UnbindFrom(currentOsuScreen.Activity); } if (newScreen is IOsuScreen newOsuScreen) { - if (newOsuScreen.AllowBackButton) - ((IBindable)BackButton.State).BindTo(newOsuScreen.BackButtonState); - else - BackButton.Hide(); - + BackButtonVisibility.BindTo(newOsuScreen.BackButtonVisibility); OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode); API.Activity.BindTo(newOsuScreen.Activity); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs index 3bba480aaa..c0363851ef 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance [Resolved(canBeNull: true)] private OsuGame game { get; set; } - public override bool AllowBackButton => false; + public override bool AllowUserExit => false; public override bool AllowExternalScreenChange => false; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 644e1afb3b..13e5791605 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Edit public override float BackgroundParallaxAmount => 0.1f; - public override bool AllowBackButton => false; + public override bool AllowUserExit => false; public override bool HideOverlaysOnEnter => true; diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 0e0fb9f795..7c6ee10840 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Edit public override float BackgroundParallaxAmount => 0.1f; - public override bool AllowBackButton => false; + public override bool AllowUserExit => false; public override bool HideOverlaysOnEnter => true; diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index 7025460daa..46dfbfb1ac 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -3,10 +3,8 @@ using System.Collections.Generic; using osu.Framework.Bindables; -using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; -using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -24,15 +22,20 @@ namespace osu.Game.Screens bool DisallowExternalBeatmapRulesetChanges { get; } /// - /// Whether the user can exit this by pressing the back button. + /// Whether the user can exit this . /// - bool AllowBackButton { get; } + /// + /// When overriden to false, + /// the user is blocked from exiting the screen via the action, + /// and the back button is hidden from this screen by the initial state of being set to hidden. + /// + bool AllowUserExit { get; } /// /// Whether a footer (and a back button) should be displayed underneath the screen. /// /// - /// Temporarily, the back button is shown regardless of whether is true. + /// Temporarily, the back button is shown regardless of whether is true. /// bool ShowFooter { get; } @@ -63,13 +66,9 @@ namespace osu.Game.Screens IBindable OverlayActivationMode { get; } /// - /// Controls the visibility state of to better work with screen-specific transitions (i.e. quick restart in player). - /// The back button can still be triggered by the action even while hidden. + /// Whether the back button should be displayed in this screen. /// - /// - /// This is ignored when is set to false. - /// - IBindable BackButtonState { get; } + IBindable BackButtonVisibility { get; } /// /// The current for this screen. diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 35c6bab81b..6b94d4bdfb 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Menu public override bool HideOverlaysOnEnter => Buttons == null || Buttons.State == ButtonSystemState.Initial; - public override bool AllowBackButton => false; + public override bool AllowUserExit => false; public override bool AllowExternalScreenChange => true; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index cc6a4e09e1..17fb667e14 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -180,7 +180,7 @@ namespace osu.Game.Screens.OnlinePlay if (!(screenStack.CurrentScreen is IOnlinePlaySubScreen onlineSubScreen)) return false; - if (((Drawable)onlineSubScreen).IsLoaded && onlineSubScreen.AllowBackButton && onlineSubScreen.OnBackButton()) + if (((Drawable)onlineSubScreen).IsLoaded && onlineSubScreen.AllowUserExit && onlineSubScreen.OnBackButton()) return true; if (screenStack.CurrentScreen != null && !(screenStack.CurrentScreen is LoungeSubScreen)) diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 2c5c889154..ab66241a77 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -11,7 +11,6 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Overlays; @@ -38,7 +37,7 @@ namespace osu.Game.Screens public string Description => Title; - public virtual bool AllowBackButton => true; + public virtual bool AllowUserExit => true; public virtual bool ShowFooter => false; @@ -57,9 +56,14 @@ namespace osu.Game.Screens IBindable IOsuScreen.OverlayActivationMode => OverlayActivationMode; - public readonly Bindable BackButtonState = new Bindable(Visibility.Visible); + /// + /// The initial visibility state of the back button when this screen is entered for the first time. + /// + protected virtual bool InitialBackButtonVisibility => AllowUserExit; - IBindable IOsuScreen.BackButtonState => BackButtonState; + public readonly Bindable BackButtonVisibility; + + IBindable IOsuScreen.BackButtonVisibility => BackButtonVisibility; public virtual bool CursorVisible => true; @@ -159,6 +163,7 @@ namespace osu.Game.Screens Origin = Anchor.Centre; OverlayActivationMode = new Bindable(InitialOverlayActivationMode); + BackButtonVisibility = new Bindable(InitialBackButtonVisibility); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e9722350bd..f4e3e6f434 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Play /// public event Action OnGameplayStarted; - public override bool AllowBackButton => false; // handled by HoldForMenuButton + public override bool AllowUserExit => false; // handled by HoldForMenuButton protected override bool PlayExitSound => !isRestarting; diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index a6e171ba02..49db0f05bd 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -478,7 +478,7 @@ namespace osu.Game.Screens.Play if (quickRestart) { - BackButtonState.Value = Visibility.Hidden; + BackButtonVisibility.Value = false; // A quick restart starts by triggering a fade to black AddInternal(quickRestartBlackLayer = new Box @@ -499,7 +499,7 @@ namespace osu.Game.Screens.Play .ScaleTo(1) .FadeInFromZero(500, Easing.OutQuint); - this.Delay(quick_restart_initial_delay).Schedule(() => BackButtonState.Value = Visibility.Visible); + this.Delay(quick_restart_initial_delay).Schedule(() => BackButtonVisibility.Value = true); } else { diff --git a/osu.Game/Screens/StartupScreen.cs b/osu.Game/Screens/StartupScreen.cs index 9e04a238eb..0724327a9f 100644 --- a/osu.Game/Screens/StartupScreen.cs +++ b/osu.Game/Screens/StartupScreen.cs @@ -10,7 +10,7 @@ namespace osu.Game.Screens /// public abstract partial class StartupScreen : OsuScreen { - public override bool AllowBackButton => false; + public override bool AllowUserExit => false; public override bool HideOverlaysOnEnter => true; From 16d8b1138562d4350726ea0b3ed3053d73f805f0 Mon Sep 17 00:00:00 2001 From: HenintsoaSky Date: Wed, 27 Nov 2024 00:53:22 +0300 Subject: [PATCH 109/223] A toggle for star fountains --- changes.patch | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 changes.patch diff --git a/changes.patch b/changes.patch new file mode 100644 index 0000000000..e69de29bb2 From 9083daf3630ae230cccab5f469369906d9cc5d48 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Tue, 26 Nov 2024 20:04:35 -0500 Subject: [PATCH 110/223] Fix epic code failure I wasn't feeling well last night. --- .../OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs index b659a98802..22bab7eb93 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs @@ -20,8 +20,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { } - protected override APIRequest CreateScoreRequest() => Score == null - ? new ShowPlaylistScoreRequest(RoomId, PlaylistItem.ID, Score?.OnlineID ?? -1) + protected override APIRequest CreateScoreRequest() => Score != null + ? new ShowPlaylistScoreRequest(RoomId, PlaylistItem.ID, Score.OnlineID) : new ShowPlaylistUserScoreRequest(RoomId, PlaylistItem.ID, API.LocalUser.Value.Id); protected override ScoreInfo[] PerformSuccessCallback(Action> callback, List scores, MultiplayerScores? pivot = null) From a477bb7bfec1422b18c17c88f8012807838dc7e2 Mon Sep 17 00:00:00 2001 From: HenintsoaSky Date: Wed, 27 Nov 2024 07:38:33 +0300 Subject: [PATCH 111/223] Renaming of 'StarFountainEnabled' --- osu.Game/Screens/Play/KiaiGameplayFountains.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/KiaiGameplayFountains.cs b/osu.Game/Screens/Play/KiaiGameplayFountains.cs index a6b2cd6fdb..fd9596c838 100644 --- a/osu.Game/Screens/Play/KiaiGameplayFountains.cs +++ b/osu.Game/Screens/Play/KiaiGameplayFountains.cs @@ -20,12 +20,12 @@ namespace osu.Game.Screens.Play private StarFountain leftFountain = null!; private StarFountain rightFountain = null!; - private Bindable kiaiStarFountainsEnabled = null!; + private Bindable kiaiStarFountains = null!; [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - kiaiStarFountainsEnabled = config.GetBindable(OsuSetting.StarFountains); + kiaiStarFountains = config.GetBindable(OsuSetting.StarFountains); RelativeSizeAxes = Axes.Both; @@ -54,10 +54,7 @@ namespace osu.Game.Screens.Play { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - if (!kiaiStarFountainsEnabled.Value) - return; - - if (!kiaiStarFountainsEnabled.Value) + if (!kiaiStarFountains.Value) return; if (effectPoint.KiaiMode && !isTriggered) From aa3d3a6344dd9428840236119132aba023874d63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Nov 2024 14:24:57 +0900 Subject: [PATCH 112/223] Remove unnecessary local subscription in `BeatmapCarousel` Not sure why I left this around during the refactor. This is 100% handled by the `DetachedBeatmapStore`. Removing this subscription reduces overheads by a huge amount for users with large beatmap databases. My hypothesis is that subscriptions are more expensive based on **the number of results matching**. This one matches almost every beatmap so removing it is a large win. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 46 ---------------------- 1 file changed, 46 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5e1e0ce615..fc7c7989e2 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -29,7 +29,6 @@ using osu.Game.Input.Bindings; using osu.Game.Screens.Select.Carousel; using osuTK; using osuTK.Input; -using Realms; namespace osu.Game.Screens.Select { @@ -207,8 +206,6 @@ namespace osu.Game.Screens.Select private CarouselRoot root; - private IDisposable? subscriptionBeatmaps; - private readonly DrawablePool setPool = new DrawablePool(100); private Sample? spinSample; @@ -258,13 +255,6 @@ namespace osu.Game.Screens.Select } } - protected override void LoadComplete() - { - base.LoadComplete(); - - subscriptionBeatmaps = realm.RegisterForNotifications(r => r.All().Where(b => !b.Hidden), beatmapsChanged); - } - private readonly HashSet setsRequiringUpdate = new HashSet(); private readonly HashSet setsRequiringRemoval = new HashSet(); @@ -366,35 +356,6 @@ namespace osu.Game.Screens.Select BeatmapSetInfo? fetchFromID(Guid id) => realm.Realm.Find(id); } - private void beatmapsChanged(IRealmCollection sender, ChangeSet? changes) - { - // we only care about actual changes in hidden status. - if (changes == null) - return; - - bool changed = false; - - foreach (int i in changes.InsertedIndices) - { - var beatmapInfo = sender[i]; - var beatmapSet = beatmapInfo.BeatmapSet; - - Debug.Assert(beatmapSet != null); - - // Only require to action here if the beatmap is missing. - // This avoids processing these events unnecessarily when new beatmaps are imported, for example. - if (root.BeatmapSetsByID.TryGetValue(beatmapSet.ID, out var existingSets) - && existingSets.SelectMany(s => s.Beatmaps).All(b => b.BeatmapInfo.ID != beatmapInfo.ID)) - { - updateBeatmapSet(beatmapSet.Detach()); - changed = true; - } - } - - if (changed) - invalidateAfterChange(); - } - public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => { removeBeatmapSet(beatmapSet.ID); @@ -1292,12 +1253,5 @@ namespace osu.Game.Screens.Select return ScrollableExtent * ((scrollbarPosition - top_padding) / (ScrollbarMovementExtent - (top_padding + bottom_padding))); } } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - subscriptionBeatmaps?.Dispose(); - } } } From dfbccc2144cfe509d1e49bdaaeaa0e3f4e62d334 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 00:55:02 -0500 Subject: [PATCH 113/223] Knock some sense into the playlists results screen implementation As we're moving towards using the `/playlist//scores/` endpoint, the existing playlists results screen classes needed some restructuring. --- .../TestScenePlaylistsResultsScreen.cs | 136 +++++++++++++----- .../DailyChallenge/DailyChallenge.cs | 2 +- .../Multiplayer/MultiplayerResultsScreen.cs | 2 +- .../Playlists/PlaylistItemResultsScreen.cs | 4 +- .../PlaylistItemScoreResultsScreen.cs | 14 +- .../PlaylistItemUserBestResultsScreen.cs | 41 ++++++ .../PlaylistItemUserResultsScreen.cs | 48 ------- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 2 +- .../Playlists/PlaylistsRoomSubScreen.cs | 6 +- 9 files changed, 161 insertions(+), 94 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserBestResultsScreen.cs delete mode 100644 osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 6ccbcd2859..c288b04da2 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Net; using Newtonsoft.Json.Linq; using NUnit.Framework; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Graphics.Containers; @@ -31,7 +32,7 @@ namespace osu.Game.Tests.Visual.Playlists private const int scores_per_result = 10; private const int real_user_position = 200; - private TestResultsScreen resultsScreen = null!; + private ResultsScreen resultsScreen = null!; private int lowestScoreId; // Score ID of the lowest score in the list. private int highestScoreId; // Score ID of the highest score in the list. @@ -68,11 +69,11 @@ namespace osu.Game.Tests.Visual.Playlists } [Test] - public void TestShowWithUserScore() + public void TestShowUserScore() { AddStep("bind user score info handler", () => bindHandler(userScore: userScore)); - createResults(() => userScore); + createResultsWithScore(() => userScore); waitForDisplay(); AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineID).State == PanelState.Expanded); @@ -81,11 +82,24 @@ namespace osu.Game.Tests.Visual.Playlists } [Test] - public void TestShowNullUserScore() + public void TestShowUserBest() + { + AddStep("bind user score info handler", () => bindHandler(userScore: userScore)); + + createUserBestResults(); + waitForDisplay(); + + AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.UserID == userScore.UserID).State == PanelState.Expanded); + AddAssert($"score panel position is {real_user_position}", + () => this.ChildrenOfType().Single(p => p.Score.UserID == userScore.UserID).ScorePosition.Value == real_user_position); + } + + [Test] + public void TestShowNonUserScores() { AddStep("bind user score info handler", () => bindHandler()); - createResults(); + createUserBestResults(); waitForDisplay(); AddAssert("top score selected", () => this.ChildrenOfType().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded); @@ -96,7 +110,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("bind user score info handler", () => bindHandler(true, userScore)); - createResults(() => userScore); + createResultsWithScore(() => userScore); waitForDisplay(); AddAssert("more than 1 panel displayed", () => this.ChildrenOfType().Count() > 1); @@ -104,11 +118,11 @@ namespace osu.Game.Tests.Visual.Playlists } [Test] - public void TestShowNullUserScoreWithDelay() + public void TestShowNonUserScoresWithDelay() { AddStep("bind delayed handler", () => bindHandler(true)); - createResults(); + createUserBestResults(); waitForDisplay(); AddAssert("top score selected", () => this.ChildrenOfType().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded); @@ -119,7 +133,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("bind delayed handler", () => bindHandler(true)); - createResults(); + createUserBestResults(); waitForDisplay(); for (int i = 0; i < 2; i++) @@ -127,13 +141,16 @@ namespace osu.Game.Tests.Visual.Playlists int beforePanelCount = 0; AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType().Count()); - AddStep("scroll to right", () => resultsScreen.ScorePanelList.ChildrenOfType().Single().ScrollToEnd(false)); + AddStep("scroll to right", () => resultsScreen.ChildrenOfType().Single().ChildrenOfType().Single().ScrollToEnd(false)); + + AddAssert("right loading spinner shown", () => + resultsScreen.ChildrenOfType().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Visible); - AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible); waitForDisplay(); AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() == beforePanelCount + scores_per_result); - AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden); + AddAssert("right loading spinner hidden", () => + resultsScreen.ChildrenOfType().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Hidden); } } @@ -142,29 +159,36 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("bind delayed handler with scores", () => bindHandler(delayed: true)); - createResults(); + createUserBestResults(); waitForDisplay(); int beforePanelCount = 0; AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType().Count()); - AddStep("scroll to right", () => resultsScreen.ScorePanelList.ChildrenOfType().Single().ScrollToEnd(false)); + AddStep("scroll to right", () => resultsScreen.ChildrenOfType().Single().ChildrenOfType().Single().ScrollToEnd(false)); + + AddAssert("right loading spinner shown", () => + resultsScreen.ChildrenOfType().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Visible); - AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible); waitForDisplay(); AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() == beforePanelCount + scores_per_result); - AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden); + AddAssert("right loading spinner hidden", () => + resultsScreen.ChildrenOfType().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Hidden); AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType().Count()); AddStep("bind delayed handler with no scores", () => bindHandler(delayed: true, noScores: true)); - AddStep("scroll to right", () => resultsScreen.ScorePanelList.ChildrenOfType().Single().ScrollToEnd(false)); + AddStep("scroll to right", () => resultsScreen.ChildrenOfType().Single().ChildrenOfType().Single().ScrollToEnd(false)); + + AddAssert("right loading spinner shown", () => + resultsScreen.ChildrenOfType().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Visible); - AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible); waitForDisplay(); AddAssert("count not increased", () => this.ChildrenOfType().Count() == beforePanelCount); - AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden); + AddAssert("right loading spinner hidden", () => + resultsScreen.ChildrenOfType().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Hidden); + AddAssert("no placeholders shown", () => this.ChildrenOfType().Count(), () => Is.Zero); } @@ -173,7 +197,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("bind user score info handler", () => bindHandler(userScore: userScore)); - createResults(() => userScore); + createResultsWithScore(() => userScore); waitForDisplay(); AddStep("bind delayed handler", () => bindHandler(true)); @@ -183,30 +207,36 @@ namespace osu.Game.Tests.Visual.Playlists int beforePanelCount = 0; AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType().Count()); - AddStep("scroll to left", () => resultsScreen.ScorePanelList.ChildrenOfType().Single().ScrollToStart(false)); + AddStep("scroll to left", () => resultsScreen.ChildrenOfType().Single().ChildrenOfType().Single().ScrollToStart(false)); + + AddAssert("left loading spinner shown", () => + resultsScreen.ChildrenOfType().Single(l => l.Anchor == Anchor.CentreLeft).State.Value == Visibility.Visible); - AddAssert("left loading spinner shown", () => resultsScreen.LeftSpinner.State.Value == Visibility.Visible); waitForDisplay(); AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() == beforePanelCount + scores_per_result); - AddAssert("left loading spinner hidden", () => resultsScreen.LeftSpinner.State.Value == Visibility.Hidden); + AddAssert("left loading spinner hidden", () => + resultsScreen.ChildrenOfType().Single(l => l.Anchor == Anchor.CentreLeft).State.Value == Visibility.Hidden); } } + /// + /// Shows the with no scores provided by the API. + /// [Test] - public void TestShowWithNoScores() + public void TestShowUserBestWithNoScoresPresent() { AddStep("bind user score info handler", () => bindHandler(noScores: true)); - createResults(); - AddAssert("no scores visible", () => !resultsScreen.ScorePanelList.GetScorePanels().Any()); + createUserBestResults(); + AddAssert("no scores visible", () => !resultsScreen.ChildrenOfType().Single().GetScorePanels().Any()); AddAssert("placeholder shown", () => this.ChildrenOfType().Count(), () => Is.EqualTo(1)); } - private void createResults(Func? getScore = null) + private void createResultsWithScore(Func getScore) { AddStep("load results", () => { - LoadScreen(resultsScreen = new TestResultsScreen(getScore?.Invoke(), 1, new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + LoadScreen(resultsScreen = new TestScoreResultsScreen(getScore(), 1, new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID })); @@ -215,14 +245,27 @@ namespace osu.Game.Tests.Visual.Playlists AddUntilStep("wait for screen to load", () => resultsScreen.IsLoaded); } + private void createUserBestResults() + { + AddStep("load results", () => + { + LoadScreen(resultsScreen = new TestUserBestResultsScreen(1, new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + }, 2)); + }); + + AddUntilStep("wait for screen to load", () => resultsScreen.IsLoaded); + } + private void waitForDisplay() { AddUntilStep("wait for scores loaded", () => requestComplete // request handler may need to fire more than once to get scores. && totalCount > 0 - && resultsScreen.ScorePanelList.GetScorePanels().Count() == totalCount - && resultsScreen.ScorePanelList.AllPanelsVisible); + && resultsScreen.ChildrenOfType().Single().GetScorePanels().Count() == totalCount + && resultsScreen.ChildrenOfType().Single().AllPanelsVisible); AddWaitStep("wait for display", 5); } @@ -232,6 +275,7 @@ namespace osu.Game.Tests.Visual.Playlists switch (request) { case ShowPlaylistScoreRequest: + case ShowPlaylistUserScoreRequest: case IndexPlaylistScoresRequest: break; @@ -261,6 +305,14 @@ namespace osu.Game.Tests.Visual.Playlists break; + case ShowPlaylistUserScoreRequest u: + if (userScore == null) + triggerFail(u); + else + triggerSuccess(u, createUserResponse(userScore)); + + break; + case IndexPlaylistScoresRequest i: triggerSuccess(i, createIndexResponse(i, noScores)); break; @@ -314,7 +366,7 @@ namespace osu.Game.Tests.Visual.Playlists MaxCombo = userScore.MaxCombo, User = new APIUser { - Id = 2, + Id = 2 + i, Username = $"peppy{i}", CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }, @@ -329,7 +381,7 @@ namespace osu.Game.Tests.Visual.Playlists MaxCombo = userScore.MaxCombo, User = new APIUser { - Id = 2, + Id = 2 + i, Username = $"peppy{i}", CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }, @@ -363,7 +415,7 @@ namespace osu.Game.Tests.Visual.Playlists MaxCombo = 1000, User = new APIUser { - Id = 2, + Id = 2 + i, Username = $"peppy{i}", CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }, @@ -410,18 +462,32 @@ namespace osu.Game.Tests.Visual.Playlists }; } - private partial class TestResultsScreen : PlaylistItemUserResultsScreen + private partial class TestScoreResultsScreen : PlaylistItemScoreResultsScreen { public new LoadingSpinner LeftSpinner => base.LeftSpinner; public new LoadingSpinner CentreSpinner => base.CentreSpinner; public new LoadingSpinner RightSpinner => base.RightSpinner; public new ScorePanelList ScorePanelList => base.ScorePanelList; - public TestResultsScreen(ScoreInfo? score, int roomId, PlaylistItem playlistItem) + public TestScoreResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem) : base(score, roomId, playlistItem) { AllowRetry = true; } } + + private partial class TestUserBestResultsScreen : PlaylistItemUserBestResultsScreen + { + public new LoadingSpinner LeftSpinner => base.LeftSpinner; + public new LoadingSpinner CentreSpinner => base.CentreSpinner; + public new LoadingSpinner RightSpinner => base.RightSpinner; + public new ScorePanelList ScorePanelList => base.ScorePanelList; + + public TestUserBestResultsScreen(int roomId, PlaylistItem playlistItem, int userId) + : base(roomId, playlistItem, userId) + { + AllowRetry = true; + } + } } } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 0dc7e7930a..13a282dd52 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -345,7 +345,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private void presentScore(long id) { if (this.IsCurrentScreen()) - this.Push(new PlaylistItemScoreResultsScreen(room.RoomID!.Value, playlistItem, id)); + this.Push(new PlaylistItemScoreResultsScreen(id, room.RoomID!.Value, playlistItem)); } private void onRoomScoreSet(MultiplayerRoomScoreSetEvent e) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs index c439df82a6..6b3e8fea46 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs @@ -7,7 +7,7 @@ using osu.Game.Screens.OnlinePlay.Playlists; namespace osu.Game.Screens.OnlinePlay.Multiplayer { - public partial class MultiplayerResultsScreen : PlaylistItemUserResultsScreen + public partial class MultiplayerResultsScreen : PlaylistItemScoreResultsScreen { public MultiplayerResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem) : base(score, roomId, playlistItem) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs index dc06b88823..81ae51bd1b 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs @@ -191,8 +191,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { var scoreInfos = scores.Select(s => s.CreateScoreInfo(ScoreManager, Rulesets, PlaylistItem, Beatmap.Value.BeatmapInfo)).OrderByTotalScore().ToArray(); - // Invoke callback to add the scores. - callback.Invoke(scoreInfos); + // Invoke callback to add the scores. Exclude the score provided to this screen since it's added already. + callback.Invoke(scoreInfos.Where(s => s.OnlineID != Score?.OnlineID)); return scoreInfos; } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs index 32be7f21b0..05c03a4b28 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs @@ -11,13 +11,19 @@ using osu.Game.Scoring; namespace osu.Game.Screens.OnlinePlay.Playlists { /// - /// Shows a selected arbitrary score for a playlist item, with scores around included. + /// Shows a given score in a playlist item, with scores around included. /// public partial class PlaylistItemScoreResultsScreen : PlaylistItemResultsScreen { private readonly long scoreId; - public PlaylistItemScoreResultsScreen(long roomId, PlaylistItem playlistItem, long scoreId) + public PlaylistItemScoreResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem) + : base(score, roomId, playlistItem) + { + scoreId = score.OnlineID; + } + + public PlaylistItemScoreResultsScreen(long scoreId, long roomId, PlaylistItem playlistItem) : base(null, roomId, playlistItem) { this.scoreId = scoreId; @@ -28,9 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected override ScoreInfo[] PerformSuccessCallback(Action> callback, List scores, MultiplayerScores? pivot = null) { var scoreInfos = base.PerformSuccessCallback(callback, scores, pivot); - - Schedule(() => SelectedScore.Value ??= scoreInfos.SingleOrDefault(score => score.OnlineID == scoreId)); - + Schedule(() => SelectedScore.Value ??= scoreInfos.SingleOrDefault(s => s.OnlineID == scoreId)); return scoreInfos; } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserBestResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserBestResultsScreen.cs new file mode 100644 index 0000000000..5b20496dba --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserBestResultsScreen.cs @@ -0,0 +1,41 @@ +// 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.Game.Online.API; +using osu.Game.Online.Rooms; +using osu.Game.Scoring; + +namespace osu.Game.Screens.OnlinePlay.Playlists +{ + /// + /// Shows a user's best score in a playlist item, with scores around included. + /// + public partial class PlaylistItemUserBestResultsScreen : PlaylistItemResultsScreen + { + private readonly int userId; + + public PlaylistItemUserBestResultsScreen(long roomId, PlaylistItem playlistItem, int userId) + : base(null, roomId, playlistItem) + { + this.userId = userId; + } + + protected override APIRequest CreateScoreRequest() => new ShowPlaylistUserScoreRequest(RoomId, PlaylistItem.ID, userId); + + protected override ScoreInfo[] PerformSuccessCallback(Action> callback, List scores, MultiplayerScores? pivot = null) + { + var scoreInfos = base.PerformSuccessCallback(callback, scores, pivot); + + Schedule(() => + { + // Prefer selecting the local user's score, or otherwise default to the first visible score. + SelectedScore.Value ??= scoreInfos.FirstOrDefault(s => s.UserID == userId) ?? scoreInfos.FirstOrDefault(); + }); + + return scoreInfos; + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs deleted file mode 100644 index 22bab7eb93..0000000000 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs +++ /dev/null @@ -1,48 +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; -using System.Collections.Generic; -using System.Linq; -using osu.Game.Online.API; -using osu.Game.Online.Rooms; -using osu.Game.Scoring; - -namespace osu.Game.Screens.OnlinePlay.Playlists -{ - /// - /// Shows the user's submitted score in a given playlist item, with scores around included. - /// - public partial class PlaylistItemUserResultsScreen : PlaylistItemResultsScreen - { - public PlaylistItemUserResultsScreen(ScoreInfo? score, long roomId, PlaylistItem playlistItem) - : base(score, roomId, playlistItem) - { - } - - protected override APIRequest CreateScoreRequest() => Score != null - ? new ShowPlaylistScoreRequest(RoomId, PlaylistItem.ID, Score.OnlineID) - : new ShowPlaylistUserScoreRequest(RoomId, PlaylistItem.ID, API.LocalUser.Value.Id); - - protected override ScoreInfo[] PerformSuccessCallback(Action> callback, List scores, MultiplayerScores? pivot = null) - { - var scoreInfos = scores.Select(s => s.CreateScoreInfo(ScoreManager, Rulesets, PlaylistItem, Beatmap.Value.BeatmapInfo)).OrderByTotalScore().ToArray(); - - // Select a score if we don't already have one selected. - // Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll). - if (SelectedScore.Value == null) - { - Schedule(() => - { - // Prefer selecting the local user's score, or otherwise default to the first visible score. - SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.OnlineID == API.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); - }); - } - - // Invoke callback to add the scores. Exclude the user's current score which was added previously. - callback.Invoke(scoreInfos.Where(s => s.OnlineID != Score?.OnlineID)); - - return scoreInfos; - } - } -} diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 7ca09b5563..b82c2404ab 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected override ResultsScreen CreateResults(ScoreInfo score) { Debug.Assert(Room.RoomID != null); - return new PlaylistItemUserResultsScreen(score, Room.RoomID.Value, PlaylistItem) + return new PlaylistItemScoreResultsScreen(score, Room.RoomID.Value, PlaylistItem) { AllowRetry = true, ShowUserStatistics = true, diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 44d1841fb8..1aaae60195 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -12,6 +12,7 @@ using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Graphics.Cursor; using osu.Game.Input; +using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; @@ -32,6 +33,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private readonly IBindable isIdle = new BindableBool(); + [Resolved] + private IAPIProvider api { get; set; } = null!; + [Resolved(CanBeNull = true)] private IdleTracker? idleTracker { get; set; } @@ -143,7 +147,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists RequestResults = item => { Debug.Assert(Room.RoomID != null); - ParentScreen?.Push(new PlaylistItemUserResultsScreen(null, Room.RoomID.Value, item)); + ParentScreen?.Push(new PlaylistItemUserBestResultsScreen(Room.RoomID.Value, item, api.LocalUser.Value.Id)); } } }, From 5260a401d4a96241f4ea21cc88e7a8b840193c61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Nov 2024 15:09:54 +0900 Subject: [PATCH 114/223] Use `RealmLive` in `SaveFailedScoreButton` This also optimises the manager classes to better support `Live` usage where the managed object is already in a good state (ie. doesn't require re-fetching). --- osu.Game/Beatmaps/BeatmapManager.cs | 6 +++++- osu.Game/Scoring/ScoreManager.cs | 12 +++++++++--- osu.Game/Screens/Play/SaveFailedScoreButton.cs | 10 +++++----- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4191771116..f1ce977d96 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -559,7 +559,11 @@ namespace osu.Game.Beatmaps // If we seem to be missing files, now is a good time to re-fetch. bool missingFiles = beatmapInfo.BeatmapSet?.Files.Count == 0; - if (refetch || beatmapInfo.IsManaged || missingFiles) + if (beatmapInfo.IsManaged) + { + beatmapInfo = beatmapInfo.Detach(); + } + else if (refetch || missingFiles) { Guid id = beatmapInfo.ID; beatmapInfo = Realm.Run(r => r.Find(id)?.Detach()) ?? beatmapInfo; diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index e3601fe91e..3177873182 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -78,7 +78,7 @@ namespace osu.Game.Scoring /// Perform a lookup query on available s. /// /// The query. - /// The first result for the provided query, or null if no results were found. + /// The first result for the provided query in its detached form, or null if no results were found. public ScoreInfo? Query(Expression> query) { return Realm.Run(r => r.All().FirstOrDefault(query)?.Detach()); @@ -88,8 +88,14 @@ namespace osu.Game.Scoring { ScoreInfo? databasedScoreInfo = null; - if (originalScoreInfo is ScoreInfo scoreInfo && !string.IsNullOrEmpty(scoreInfo.Hash)) - databasedScoreInfo = Query(s => s.Hash == scoreInfo.Hash); + if (originalScoreInfo is ScoreInfo scoreInfo) + { + if (scoreInfo.IsManaged) + return scoreInfo.Detach(); + + if (!string.IsNullOrEmpty(scoreInfo.Hash)) + databasedScoreInfo = Query(s => s.Hash == scoreInfo.Hash); + } if (originalScoreInfo.OnlineID > 0) databasedScoreInfo ??= Query(s => s.OnlineID == originalScoreInfo.OnlineID); diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs index 4f665b87e8..e5c9e115d1 100644 --- a/osu.Game/Screens/Play/SaveFailedScoreButton.cs +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Play private readonly Func>? importFailedScore; - private ScoreInfo? importedScore; + private Live? importedScore; private DownloadButton button = null!; @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Play switch (state.Value) { case DownloadState.LocallyAvailable: - game?.PresentScore(importedScore, ScorePresentType.Gameplay); + game?.PresentScore(importedScore?.Value, ScorePresentType.Gameplay); break; case DownloadState.NotDownloaded: @@ -65,7 +65,7 @@ namespace osu.Game.Screens.Play { Task.Run(importFailedScore).ContinueWith(t => { - importedScore = realm.Run(r => r.Find(t.GetResultSafely().ID)?.Detach()); + importedScore = realm.Run?>(r => r.Find(t.GetResultSafely().ID)?.ToLive(realm)); Schedule(() => state.Value = importedScore != null ? DownloadState.LocallyAvailable : DownloadState.NotDownloaded); }).FireAndForget(); } @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Play if (player != null) { - importedScore = realm.Run(r => r.Find(player.Score.ScoreInfo.ID)?.Detach()); + importedScore = realm.Run(r => r.Find(player.Score.ScoreInfo.ID)?.ToLive(realm)); state.Value = importedScore != null ? DownloadState.LocallyAvailable : DownloadState.NotDownloaded; } @@ -137,7 +137,7 @@ namespace osu.Game.Screens.Play { if (state.NewValue != DownloadState.LocallyAvailable) return; - if (importedScore != null) scoreManager.Export(importedScore); + if (importedScore != null) scoreManager.Export(importedScore.Value); this.state.ValueChanged -= exportWhenReady; } From 4fcc76270a276421c998f3e9b668b110bd69e207 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Nov 2024 15:46:55 +0900 Subject: [PATCH 115/223] Ensure events are unbound on disposal as a safety --- .../Overlays/Chat/ChannelList/ChannelList.cs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index 3e8c71e645..f027888962 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -171,10 +171,12 @@ namespace osu.Game.Overlays.Chat.ChannelList public partial class ChannelGroup : FillFlowContainer { + private readonly bool sortByRecent; public readonly ChannelListItemFlow ItemFlow; public ChannelGroup(LocalisableString label, bool sortByRecent) { + this.sortByRecent = sortByRecent; Direction = FillDirection.Vertical; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -217,21 +219,39 @@ namespace osu.Game.Overlays.Chat.ChannelList { ItemFlow.Add(item); - item.Channel.NewMessagesArrived += newMessagesArrived; - item.Channel.PendingMessageResolved += pendingMessageResolved; + if (sortByRecent) + { + item.Channel.NewMessagesArrived += newMessagesArrived; + item.Channel.PendingMessageResolved += pendingMessageResolved; + } ItemFlow.Reflow(); } public void RemoveChannel(ChannelListItem item) { - item.Channel.NewMessagesArrived -= newMessagesArrived; - item.Channel.PendingMessageResolved -= pendingMessageResolved; + if (sortByRecent) + { + item.Channel.NewMessagesArrived -= newMessagesArrived; + item.Channel.PendingMessageResolved -= pendingMessageResolved; + } + ItemFlow.Remove(item, true); } private void pendingMessageResolved(LocalEchoMessage _, Message __) => ItemFlow.Reflow(); private void newMessagesArrived(IEnumerable _) => ItemFlow.Reflow(); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + foreach (var item in ItemFlow) + { + item.Channel.NewMessagesArrived -= newMessagesArrived; + item.Channel.PendingMessageResolved -= pendingMessageResolved; + } + } } private partial class ChannelSearchTextBox : BasicSearchTextBox From c3ac6d7fe5a89888e62ebe88a55ac5c21f0efa33 Mon Sep 17 00:00:00 2001 From: HenintsoaSky Date: Wed, 27 Nov 2024 10:22:30 +0300 Subject: [PATCH 116/223] Delete changes.patch oops --- changes.patch | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 changes.patch diff --git a/changes.patch b/changes.patch deleted file mode 100644 index e69de29bb2..0000000000 From 9c707ed3418b5bde88dd76d5d5f790fb69decb1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Nov 2024 16:47:54 +0900 Subject: [PATCH 117/223] Rename class and fix padding considerations --- ...ings.cs => ExpandingPlayerSettingsOverlay.cs} | 16 ++++++++++++---- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/{MultiSpectatorSettings.cs => ExpandingPlayerSettingsOverlay.cs} (75%) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ExpandingPlayerSettingsOverlay.cs similarity index 75% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ExpandingPlayerSettingsOverlay.cs index dfb26d104a..6ad53d4aeb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ExpandingPlayerSettingsOverlay.cs @@ -8,15 +8,21 @@ using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play.HUD; +using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { - public partial class MultiSpectatorSettings : ExpandingContainer + public partial class ExpandingPlayerSettingsOverlay : ExpandingContainer { - public const float CONTRACTED_WIDTH = 30; - public const int EXPANDED_WIDTH = 300; + private const float padding = 10; - public MultiSpectatorSettings() + public const float CONTRACTED_WIDTH = button_size + padding * 2; + public const float EXPANDED_WIDTH = player_settings_width + button_size + padding * 3; + + private const float player_settings_width = 270; + private const float button_size = IconButton.DEFAULT_BUTTON_SIZE; + + public ExpandingPlayerSettingsOverlay() : base(CONTRACTED_WIDTH, EXPANDED_WIDTH) { Origin = Anchor.TopRight; @@ -30,6 +36,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Anchor = Anchor.TopLeft, Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, + Margin = new MarginPadding(padding), + Spacing = new Vector2(padding), Children = new Drawable[] { new IconButton diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 841aaf7a45..44d26a6dd0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -127,7 +127,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { ReadyToStart = performInitialSeek, }, - new MultiSpectatorSettings() + new ExpandingPlayerSettingsOverlay() }; for (int i = 0; i < Users.Count; i++) From 782ce24ca67c08525ff8c4f3d4382c5627b6af8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Nov 2024 17:09:13 +0900 Subject: [PATCH 118/223] Move player settings out of right flow --- osu.Game/Screens/Play/HUDOverlay.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index a6c2405eb6..62d9686aad 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -146,7 +146,6 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { ModDisplay = CreateModsContainer(), - PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), } }, bottomRightElements = new FillFlowContainer @@ -164,6 +163,7 @@ namespace osu.Game.Screens.Play HoldToQuit = CreateHoldForMenuButton(), } }, + PlayerSettingsOverlay = new PlayerSettingsOverlay(), LeaderboardFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -173,7 +173,7 @@ namespace osu.Game.Screens.Play }, }; - hideTargets = new List { mainComponents, topRightElements }; + hideTargets = new List { mainComponents, topRightElements, PlayerSettingsOverlay }; if (rulesetComponents != null) hideTargets.Add(rulesetComponents); @@ -389,8 +389,6 @@ namespace osu.Game.Screens.Play Origin = Anchor.TopRight, }; - protected PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); - public bool OnPressed(KeyBindingPressEvent e) { if (e.Repeat) From 7fdf13911b7500a4cc9b08259655aabc49142142 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Nov 2024 17:47:27 +0900 Subject: [PATCH 119/223] Adjust the colour of non-pinned settings groups' headers to be more legible --- osu.Game/Overlays/SettingsToolboxGroup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index 53849fa53c..f8cf218564 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -184,7 +184,7 @@ namespace osu.Game.Overlays content.ResizeHeightTo(0, animate ? transition_duration : 0, Easing.OutQuint); } - headerContent.FadeColour(Expanded.Value ? Color4.White : OsuColour.Gray(0.5f), 200, Easing.OutQuint); + headerContent.FadeColour(Expanded.Value ? Color4.White : OsuColour.Gray(0.7f), 200, Easing.OutQuint); } private void updateFadeState() From 0f739418084d99925a0e91691ed459122aec23d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Nov 2024 17:47:42 +0900 Subject: [PATCH 120/223] Combine new implementation back into the old one and use everywhere --- .../ExpandingPlayerSettingsOverlay.cs | 68 -------------- .../Spectate/MultiSpectatorScreen.cs | 2 +- .../Screens/Play/HUD/PlayerSettingsOverlay.cs | 88 +++++++++++++++---- 3 files changed, 74 insertions(+), 84 deletions(-) delete mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ExpandingPlayerSettingsOverlay.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ExpandingPlayerSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ExpandingPlayerSettingsOverlay.cs deleted file mode 100644 index 6ad53d4aeb..0000000000 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ExpandingPlayerSettingsOverlay.cs +++ /dev/null @@ -1,68 +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.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Play.HUD; -using osuTK; - -namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate -{ - public partial class ExpandingPlayerSettingsOverlay : ExpandingContainer - { - private const float padding = 10; - - public const float CONTRACTED_WIDTH = button_size + padding * 2; - public const float EXPANDED_WIDTH = player_settings_width + button_size + padding * 3; - - private const float player_settings_width = 270; - private const float button_size = IconButton.DEFAULT_BUTTON_SIZE; - - public ExpandingPlayerSettingsOverlay() - : base(CONTRACTED_WIDTH, EXPANDED_WIDTH) - { - Origin = Anchor.TopRight; - Anchor = Anchor.TopRight; - - PlayerSettingsOverlay playerSettingsOverlay; - - InternalChild = new FillFlowContainer - { - Origin = Anchor.TopLeft, - Anchor = Anchor.TopLeft, - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding(padding), - Spacing = new Vector2(padding), - Children = new Drawable[] - { - new IconButton - { - Icon = FontAwesome.Solid.Cog, - Origin = Anchor.TopLeft, - Anchor = Anchor.TopLeft, - Action = () => Expanded.Toggle() - }, - playerSettingsOverlay = new PlayerSettingsOverlay - { - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft - } - } - }; - - playerSettingsOverlay.Show(); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - // Prevent unexpanding when hovering player settings - if (!Contains(e.ScreenSpaceMousePosition)) - base.OnHoverLost(e); - } - } -} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 44d26a6dd0..33c3c60ed3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -127,7 +127,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { ReadyToStart = performInitialSeek, }, - new ExpandingPlayerSettingsOverlay() + new PlayerSettingsOverlay() }; for (int i = 0; i < Users.Count; i++) diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index a2b49f6302..e68ca4da7a 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -1,46 +1,104 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osuTK; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play.PlayerSettings; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD { - public partial class PlayerSettingsOverlay : VisibilityContainer + public partial class PlayerSettingsOverlay : ExpandingContainer { + public VisualSettings VisualSettings { get; private set; } + + private const float padding = 10; + + public const float CONTRACTED_WIDTH = button_size + padding * 2; + public const float EXPANDED_WIDTH = player_settings_width + padding * 2; + + private const float player_settings_width = 270; + private const float button_size = IconButton.DEFAULT_BUTTON_SIZE; + + public override void Show() => this.FadeIn(fade_duration); + public override void Hide() => this.FadeOut(fade_duration); + private const int fade_duration = 200; - public readonly VisualSettings VisualSettings; + // we'll handle this ourselves because we have slightly custom logic. + protected override bool ExpandOnHover => false; protected override Container Content => content; private readonly FillFlowContainer content; - public PlayerSettingsOverlay() - { - Anchor = Anchor.TopRight; - Origin = Anchor.TopRight; - AutoSizeAxes = Axes.Both; + private readonly IconButton button; - InternalChild = content = new FillFlowContainer + private InputManager inputManager = null!; + + public PlayerSettingsOverlay() + : base(0, EXPANDED_WIDTH) + { + Origin = Anchor.TopRight; + Anchor = Anchor.TopRight; + + base.Content.Add(content = new FillFlowContainer { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 20), + Margin = new MarginPadding(padding), Children = new PlayerSettingsGroup[] { VisualSettings = new VisualSettings { Expanded = { Value = false } }, new AudioSettings { Expanded = { Value = false } } } - }; + }); + + AddInternal(button = new IconButton + { + Icon = FontAwesome.Solid.Cog, + Origin = Anchor.TopRight, + Anchor = Anchor.TopLeft, + Margin = new MarginPadding(5), + Action = () => Expanded.Toggle() + }); + + AddInternal(new Box + { + Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0), Color4.Black.Opacity(0.8f)), + Depth = float.MaxValue, + RelativeSizeAxes = Axes.Both, + }); } - protected override void PopIn() => this.FadeIn(fade_duration); - protected override void PopOut() => this.FadeOut(fade_duration); + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager()!; + } + + protected override void Update() + { + base.Update(); + + Expanded.Value = inputManager.CurrentState.Mouse.Position.X >= button.ScreenSpaceDrawQuad.TopLeft.X; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + // handle un-expanding manually because our children do weird hover blocking stuff. + } public void AddAtStart(PlayerSettingsGroup drawable) => content.Insert(-1, drawable); } From b70fb4b0fe747977871c04a49feaad1a9b175654 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 05:52:43 -0500 Subject: [PATCH 121/223] Add `FormBeatmapFileSelector` for intermediate user-choice step --- .../UserInterfaceV2/FormFileSelector.cs | 30 +++- .../Edit/Setup/FormBeatmapFileSelector.cs | 161 ++++++++++++++++++ 2 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs diff --git a/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs index 81023417a5..5fdf453fc4 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs @@ -242,20 +242,26 @@ namespace osu.Game.Graphics.UserInterfaceV2 Task ICanAcceptFiles.Import(ImportTask[] tasks, ImportParameters parameters) => throw new NotImplementedException(); + protected virtual FileChooserPopover CreatePopover(string[] handledExtensions, Bindable current, string? chooserPath) => new FileChooserPopover(handledExtensions, current, chooserPath); + public Popover GetPopover() { - var popover = new FileChooserPopover(handledExtensions, Current, initialChooserPath); + var popover = CreatePopover(handledExtensions, Current, initialChooserPath); popoverState.UnbindBindings(); popoverState.BindTo(popover.State); return popover; } - private partial class FileChooserPopover : OsuPopover + protected partial class FileChooserPopover : OsuPopover { protected override string PopInSampleName => "UI/overlay-big-pop-in"; protected override string PopOutSampleName => "UI/overlay-big-pop-out"; - public FileChooserPopover(string[] handledExtensions, Bindable currentFile, string? chooserPath) + private readonly Bindable current = new Bindable(); + + protected OsuFileSelector FileSelector; + + public FileChooserPopover(string[] handledExtensions, Bindable current, string? chooserPath) : base(false) { Child = new Container @@ -264,12 +270,13 @@ namespace osu.Game.Graphics.UserInterfaceV2 // simplest solution to avoid underlying text to bleed through the bottom border // https://github.com/ppy/osu/pull/30005#issuecomment-2378884430 Padding = new MarginPadding { Bottom = 1 }, - Child = new OsuFileSelector(chooserPath, handledExtensions) + Child = FileSelector = new OsuFileSelector(chooserPath, handledExtensions) { RelativeSizeAxes = Axes.Both, - CurrentFile = { BindTarget = currentFile } }, }; + + this.current.BindTo(current); } [BackgroundDependencyLoader] @@ -292,6 +299,19 @@ namespace osu.Game.Graphics.UserInterfaceV2 } }); } + + protected override void LoadComplete() + { + base.LoadComplete(); + + FileSelector.CurrentFile.ValueChanged += f => + { + if (f.NewValue != null) + OnFileSelected(f.NewValue); + }; + } + + protected virtual void OnFileSelected(FileInfo file) => current.Value = file; } } } diff --git a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs new file mode 100644 index 0000000000..317ed1b903 --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs @@ -0,0 +1,161 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics; +using System.IO; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Screens.Edit.Setup +{ + /// + /// A type of dedicated to beatmap resources. + /// + /// + /// This expands on by adding an intermediate step before finalisation + /// to choose whether the selected file should be applied to the current difficulty or all difficulties in the set, + /// the user's choice is saved in before the file selection is finalised and propagated to . + /// + public partial class FormBeatmapFileSelector : FormFileSelector + { + private readonly bool multipleDifficulties; + + public readonly Bindable ApplyToAllDifficulties = new Bindable(true); + + public FormBeatmapFileSelector(bool multipleDifficulties, params string[] handledExtensions) + : base(handledExtensions) + { + this.multipleDifficulties = multipleDifficulties; + } + + protected override FileChooserPopover CreatePopover(string[] handledExtensions, Bindable current, string? chooserPath) + { + var popover = new BeatmapFileChooserPopover(handledExtensions, current, chooserPath, multipleDifficulties); + + popover.ApplyToAllDifficulties.ValueChanged += v => + { + Debug.Assert(v.NewValue != null); + ApplyToAllDifficulties.Value = v.NewValue.Value; + }; + + return popover; + } + + private partial class BeatmapFileChooserPopover : FileChooserPopover + { + private readonly bool multipleDifficulties; + + public readonly Bindable ApplyToAllDifficulties = new Bindable(); + + private Container changeScopeContainer = null!; + + public BeatmapFileChooserPopover(string[] handledExtensions, Bindable current, string? chooserPath, bool multipleDifficulties) + : base(handledExtensions, current, chooserPath) + { + this.multipleDifficulties = multipleDifficulties; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider, OsuColour colours) + { + Add(changeScopeContainer = new InputBlockingContainer + { + Alpha = 0f, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background6.Opacity(0.9f), + RelativeSizeAxes = Axes.Both, + }, + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + CornerRadius = 10f, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background5, + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 10f), + Margin = new MarginPadding(30), + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Apply this change to all difficulties?", + Margin = new MarginPadding { Bottom = 20f }, + }, + new RoundedButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 300f, + Text = "Apply to all difficulties", + Action = () => ApplyToAllDifficulties.Value = true, + BackgroundColour = colours.Red2, + }, + new RoundedButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 300f, + Text = "Only apply to this difficulty", + Action = () => ApplyToAllDifficulties.Value = false, + }, + } + } + } + }, + } + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + ApplyToAllDifficulties.ValueChanged += onChangeScopeSelected; + } + + protected override void OnFileSelected(FileInfo file) + { + if (multipleDifficulties) + changeScopeContainer.FadeIn(200, Easing.InQuint); + else + base.OnFileSelected(file); + } + + private void onChangeScopeSelected(ValueChangedEvent c) + { + if (c.NewValue == null) + return; + + Debug.Assert(FileSelector.CurrentFile.Value != null); + base.OnFileSelected(FileSelector.CurrentFile.Value); + } + } + } +} From efb68e423268a289898c1a5967d20fe73a58b78d Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 05:53:22 -0500 Subject: [PATCH 122/223] Refactor `ResourcesSection` to support new form of selection --- .../Screens/Edit/Setup/ResourcesSection.cs | 188 ++++++++---------- 1 file changed, 88 insertions(+), 100 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 90603a6366..70282878e0 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Beatmaps; -using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osu.Game.Localisation; using osu.Game.Models; @@ -19,8 +18,8 @@ namespace osu.Game.Screens.Edit.Setup { public partial class ResourcesSection : SetupSection { - private FormFileSelector audioTrackChooser = null!; - private FormFileSelector backgroundChooser = null!; + private FormBeatmapFileSelector audioTrackChooser = null!; + private FormBeatmapFileSelector backgroundChooser = null!; public override LocalisableString Title => EditorSetupStrings.ResourcesHeader; @@ -40,7 +39,6 @@ namespace osu.Game.Screens.Edit.Setup private Editor? editor { get; set; } private SetupScreenHeaderBackground headerBackground = null!; - private RoundedButton syncResourcesButton = null!; [BackgroundDependencyLoader] private void load() @@ -51,25 +49,20 @@ namespace osu.Game.Screens.Edit.Setup Height = 110, }; + bool multipleDifficulties = working.Value.BeatmapSetInfo.Beatmaps.Count > 1; + Children = new Drawable[] { - backgroundChooser = new FormFileSelector(".jpg", ".jpeg", ".png") + backgroundChooser = new FormBeatmapFileSelector(multipleDifficulties, ".jpg", ".jpeg", ".png") { Caption = GameplaySettingsStrings.BackgroundHeader, PlaceholderText = EditorSetupStrings.ClickToSelectBackground, }, - audioTrackChooser = new FormFileSelector(".mp3", ".ogg") + audioTrackChooser = new FormBeatmapFileSelector(multipleDifficulties, ".mp3", ".ogg") { Caption = EditorSetupStrings.AudioTrack, PlaceholderText = EditorSetupStrings.ClickToSelectTrack, }, - syncResourcesButton = new RoundedButton - { - RelativeSizeAxes = Axes.X, - Text = EditorSetupStrings.ResourcesUpdateAllDifficulties, - Action = syncResources, - Enabled = { Value = false }, - } }; backgroundChooser.PreviewContainer.Add(headerBackground); @@ -84,39 +77,56 @@ namespace osu.Game.Screens.Edit.Setup audioTrackChooser.Current.BindValueChanged(audioTrackChanged); } - private string? newBackgroundFile; - private string? newAudioFile; - - public bool ChangeBackgroundImage(FileInfo source) + public bool ChangeBackgroundImage(FileInfo source, bool applyToAllDifficulties) { if (!source.Exists) return false; - var beatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; - string[] filenames = set.Files.Select(f => f.Filename).Where(f => - f.StartsWith(@"bg", StringComparison.OrdinalIgnoreCase) && - f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - - string currentFilename = working.Value.Metadata.BackgroundFile; - string? newFilename = null; - - var oldFile = set.GetFile(currentFilename); - - if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.BackgroundFile != currentFilename)) + if (applyToAllDifficulties) { - beatmaps.DeleteFile(set, oldFile); - newFilename = currentFilename; + string newFilename = $@"bg{source.Extension}"; + + foreach (var beatmapInSet in set.Beatmaps) + { + if (set.GetFile(beatmapInSet.Metadata.BackgroundFile) is RealmNamedFileUsage existingFile) + beatmaps.DeleteFile(set, existingFile); + + if (beatmapInSet.Metadata.BackgroundFile != newFilename) + { + beatmapInSet.Metadata.BackgroundFile = newFilename; + + if (!beatmapInSet.Equals(working.Value.BeatmapInfo)) + beatmaps.Save(beatmapInSet, beatmaps.GetWorkingBeatmap(beatmapInSet).Beatmap); + } + } + } + else + { + var beatmap = working.Value.BeatmapInfo; + + string[] filenames = set.Files.Select(f => f.Filename).Where(f => + f.StartsWith(@"bg", StringComparison.OrdinalIgnoreCase) && + f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); + + string currentFilename = working.Value.Metadata.BackgroundFile; + + var oldFile = set.GetFile(currentFilename); + string? newFilename = null; + + if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.BackgroundFile != currentFilename)) + { + beatmaps.DeleteFile(set, oldFile); + newFilename = currentFilename; + } + + newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"bg{source.Extension}"); + working.Value.Metadata.BackgroundFile = newFilename; } - newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"bg{source.Extension}"); - using (var stream = source.OpenRead()) - beatmaps.AddFile(set, stream, newFilename); - - working.Value.Metadata.BackgroundFile = newBackgroundFile = newFilename; - syncResourcesButton.Enabled.Value = set.Beatmaps.Count > 1; + beatmaps.AddFile(set, stream, working.Value.Metadata.BackgroundFile); editorBeatmap.SaveState(); @@ -126,36 +136,56 @@ namespace osu.Game.Screens.Edit.Setup return true; } - public bool ChangeAudioTrack(FileInfo source) + public bool ChangeAudioTrack(FileInfo source, bool applyToAllDifficulties) { if (!source.Exists) return false; - var beatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; - string[] filenames = set.Files.Select(f => f.Filename).Where(f => - f.StartsWith(@"audio", StringComparison.OrdinalIgnoreCase) && - f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - - string currentFilename = working.Value.Metadata.AudioFile; - string? newFilename = null; - - var oldFile = set.GetFile(currentFilename); - - if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.AudioFile != currentFilename)) + if (applyToAllDifficulties) { - beatmaps.DeleteFile(set, oldFile); - newFilename = currentFilename; + string newFilename = $@"audio{source.Extension}"; + + foreach (var beatmapInSet in set.Beatmaps) + { + if (set.GetFile(beatmapInSet.Metadata.AudioFile) is RealmNamedFileUsage existingFile) + beatmaps.DeleteFile(set, existingFile); + + if (beatmapInSet.Metadata.AudioFile != newFilename) + { + beatmapInSet.Metadata.AudioFile = newFilename; + + if (!beatmapInSet.Equals(working.Value.BeatmapInfo)) + beatmaps.Save(beatmapInSet, beatmaps.GetWorkingBeatmap(beatmapInSet).Beatmap); + } + } + } + else + { + var beatmap = working.Value.BeatmapInfo; + + string[] filenames = set.Files.Select(f => f.Filename).Where(f => + f.StartsWith(@"audio", StringComparison.OrdinalIgnoreCase) && + f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); + + string currentFilename = working.Value.Metadata.AudioFile; + + var oldFile = set.GetFile(currentFilename); + string? newFilename = null; + + if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.AudioFile != currentFilename)) + { + beatmaps.DeleteFile(set, oldFile); + newFilename = currentFilename; + } + + newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"audio{source.Extension}"); + working.Value.Metadata.AudioFile = newFilename; } - newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"audio{source.Extension}"); - using (var stream = source.OpenRead()) - beatmaps.AddFile(set, stream, newFilename); - - working.Value.Metadata.AudioFile = newAudioFile = newFilename; - updateSyncResourcesButton(); + beatmaps.AddFile(set, stream, working.Value.Metadata.AudioFile); editorBeatmap.SaveState(); music.ReloadCurrentTrack(); @@ -163,57 +193,15 @@ namespace osu.Game.Screens.Edit.Setup return true; } - private void updateSyncResourcesButton() - { - var set = working.Value.BeatmapSetInfo; - - syncResourcesButton.Enabled.Value = - (newBackgroundFile != null && set.Beatmaps.DistinctBy(b => b.Metadata.BackgroundFile, StringComparer.OrdinalIgnoreCase).Count() > 1) || - (newAudioFile != null && set.Beatmaps.DistinctBy(b => b.Metadata.AudioFile, StringComparer.OrdinalIgnoreCase).Count() > 1); - } - - private void syncResources() - { - var beatmap = working.Value.BeatmapInfo; - var set = working.Value.BeatmapSetInfo; - - foreach (var otherBeatmap in set.Beatmaps.Where(b => !b.Equals(beatmap))) - { - var otherWorking = beatmaps.GetWorkingBeatmap(otherBeatmap); - - if (newBackgroundFile != null && !string.Equals(otherBeatmap.Metadata.BackgroundFile, newBackgroundFile, StringComparison.OrdinalIgnoreCase)) - { - if (set.GetFile(otherBeatmap.Metadata.BackgroundFile) is RealmNamedFileUsage file) - beatmaps.DeleteFile(set, file); - - otherBeatmap.Metadata.BackgroundFile = newBackgroundFile; - } - - if (newAudioFile != null && !string.Equals(otherBeatmap.Metadata.AudioFile, newAudioFile, StringComparison.OrdinalIgnoreCase)) - { - if (set.GetFile(otherBeatmap.Metadata.AudioFile) is RealmNamedFileUsage file) - beatmaps.DeleteFile(set, file); - - otherBeatmap.Metadata.AudioFile = newAudioFile; - } - - beatmaps.Save(otherBeatmap, otherWorking.Beatmap); - } - - newAudioFile = null; - newBackgroundFile = null; - syncResourcesButton.Enabled.Value = false; - } - private void backgroundChanged(ValueChangedEvent file) { - if (file.NewValue == null || !ChangeBackgroundImage(file.NewValue)) + if (file.NewValue == null || !ChangeBackgroundImage(file.NewValue, backgroundChooser.ApplyToAllDifficulties.Value)) backgroundChooser.Current.Value = file.OldValue; } private void audioTrackChanged(ValueChangedEvent file) { - if (file.NewValue == null || !ChangeAudioTrack(file.NewValue)) + if (file.NewValue == null || !ChangeAudioTrack(file.NewValue, audioTrackChooser.ApplyToAllDifficulties.Value)) audioTrackChooser.Current.Value = file.OldValue; } } From 4b8094d0dbc5fd4d9e63511b0f59d62d7e7257d9 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 05:53:30 -0500 Subject: [PATCH 123/223] Update test coverage --- .../Editing/TestSceneEditorBeatmapCreation.cs | 208 ++++++++++-------- .../UserInterface/TestSceneFormControls.cs | 10 +- 2 files changed, 129 insertions(+), 89 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 9fabed346b..2817225f2b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -15,8 +15,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Collections; using osu.Game.Database; -using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Localisation; using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; @@ -102,17 +100,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("enter setup mode", () => InputManager.Key(Key.F4)); AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual); - AddAssert("switch track to real track", () => - { - var setup = Editor.ChildrenOfType().First(); - - return setFile(TestResources.GetTestBeatmapForImport(), extractedFolder => - { - bool success = setup.ChildrenOfType().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"))); - Assert.That(Beatmap.Value.Metadata.AudioFile == "audio.mp3"); - return success; - }); - }); + AddAssert("switch track to real track", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3")); AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual); AddUntilStep("track length changed", () => Beatmap.Value.Track.Length > 60000); @@ -517,11 +505,105 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestSingleBackgroundFile() + { + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddAssert("set background", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg")); + + AddStep("save", () => Editor.Save()); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty"; + }); + + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty (1)"; + }); + + AddStep("switch to second difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(1))); + AddUntilStep("wait for editor load", () => Editor.IsLoaded); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("set background on second diff only", () => setBackground(applyToAllDifficulties: false, expected: "bg (1).jpg")); + AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); + AddStep("save", () => Editor.Save()); + + AddStep("switch to first difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + AddUntilStep("wait for editor load", () => Editor.IsLoaded); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("set background on first diff only", () => setBackground(applyToAllDifficulties: false, expected: "bg (2).jpg")); + AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (2).jpg")); + AddStep("save", () => Editor.Save()); + + AddAssert("set background on all diff", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg")); + AddAssert("all diff uses one background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.BackgroundFile == "bg.jpg")); + AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg.jpg")); + AddAssert("other files removed", () => !Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg" || f.Filename == "bg (2).jpg")); + } + + [Test] + public void TestSingleAudioFile() + { + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddAssert("set audio", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3")); + + AddStep("save", () => Editor.Save()); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty"; + }); + + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty (1)"; + }); + + AddStep("switch to second difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(1))); + AddUntilStep("wait for editor load", () => Editor.IsLoaded); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("set audio on second diff only", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3")); + AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); + AddStep("save", () => Editor.Save()); + + AddStep("switch to first difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + AddUntilStep("wait for editor load", () => Editor.IsLoaded); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("set audio on first diff only", () => setAudio(applyToAllDifficulties: false, expected: "audio (2).mp3")); + AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (2).mp3")); + AddStep("save", () => Editor.Save()); + + AddAssert("set audio on all diff", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3")); + AddAssert("all diff uses one audio", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.AudioFile == "audio.mp3")); + AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio.mp3")); + AddAssert("other files removed", () => !Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3" || f.Filename == "audio (2).mp3")); + } + [Test] public void TestMultipleBackgroundFiles() { AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddAssert("set background", () => setBackground(expected: "bg.jpg")); + AddAssert("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg.jpg")); AddStep("save", () => Editor.Save()); AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); @@ -536,7 +618,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("new difficulty uses same background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); AddStep("enter setup mode", () => InputManager.Key(Key.F4)); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddAssert("set background", () => setBackground(expected: "bg (1).jpg")); + AddAssert("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg (1).jpg")); AddAssert("new difficulty uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); AddStep("save", () => Editor.Save()); @@ -546,27 +628,15 @@ namespace osu.Game.Tests.Visual.Editing AddStep("enter setup mode", () => InputManager.Key(Key.F4)); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddStep("set background", () => setBackground(expected: "bg.jpg")); + AddStep("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg.jpg")); AddAssert("other background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); - - bool setBackground(string expected) - { - var setup = Editor.ChildrenOfType().First(); - - return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder => - { - bool success = setup.ChildrenOfType().First().ChangeBackgroundImage(new FileInfo(Path.Combine(extractedFolder, "machinetop_background.jpg"))); - Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected)); - return success; - }); - } } [Test] public void TestMultipleAudioFiles() { AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddAssert("set audio", () => setAudio(expected: "audio.mp3")); + AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3")); AddStep("save", () => Editor.Save()); AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); @@ -581,7 +651,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("new difficulty uses same audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); AddStep("enter setup mode", () => InputManager.Key(Key.F4)); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddAssert("set audio", () => setAudio(expected: "audio (1).mp3")); + AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3")); AddAssert("new difficulty uses new audio", () => Beatmap.Value.Metadata.AudioFile == "audio (1).mp3"); AddStep("save", () => Editor.Save()); @@ -591,74 +661,38 @@ namespace osu.Game.Tests.Visual.Editing AddStep("enter setup mode", () => InputManager.Key(Key.F4)); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddStep("set audio", () => setAudio(expected: "audio.mp3")); + AddStep("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3")); AddAssert("other audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); - - bool setAudio(string expected) - { - var setup = Editor.ChildrenOfType().First(); - - return setFile(TestResources.GetTestBeatmapForImport(), extractedFolder => - { - bool success = setup.ChildrenOfType().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"))); - Assert.That(Beatmap.Value.Metadata.AudioFile, Is.EqualTo(expected)); - return success; - }); - } } - [Test] - public void TestUpdateBackgroundOnAllDifficulties() + private bool setBackground(bool applyToAllDifficulties, string expected) { - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddAssert("button disabled", () => !getButton().Enabled.Value); - AddAssert("set background", () => setBackground(expected: "bg.jpg")); + var setup = Editor.ChildrenOfType().First(); - // there is only one diff so this should still be disabled. - AddAssert("button still disabled", () => !getButton().Enabled.Value); - - AddStep("save", () => Editor.Save()); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); - AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); - AddUntilStep("wait for created", () => + return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder => { - string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty"; + bool success = setup.ChildrenOfType().First().ChangeBackgroundImage( + new FileInfo(Path.Combine(extractedFolder, @"machinetop_background.jpg")), + applyToAllDifficulties); + + Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected)); + return success; }); + } - AddAssert("new difficulty uses same background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddAssert("button disabled", () => !getButton().Enabled.Value); - AddAssert("set background", () => setBackground(expected: "bg (1).jpg")); - AddAssert("new background added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); - AddAssert("new difficulty uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); + private bool setAudio(bool applyToAllDifficulties, string expected) + { + var setup = Editor.ChildrenOfType().First(); - AddAssert("button enabled", () => getButton().Enabled.Value); - AddStep("press button", () => getButton().TriggerClick()); - - AddAssert("new difficulty still uses new background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps[1].Metadata.BackgroundFile == "bg (1).jpg"); - AddAssert("old difficulty uses new background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps[0].Metadata.BackgroundFile == "bg (1).jpg"); - AddAssert("old background removed", () => Beatmap.Value.BeatmapSetInfo.Files.All(f => f.Filename != "bg.jpg")); - - AddStep("save", () => Editor.Save()); - AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); - AddAssert("old difficulty still uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); - - bool setBackground(string expected) + return setFile(TestResources.GetTestBeatmapForImport(), extractedFolder => { - var setup = Editor.ChildrenOfType().First(); + bool success = setup.ChildrenOfType().First().ChangeAudioTrack( + new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3")), + applyToAllDifficulties); - return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder => - { - bool success = setup.ChildrenOfType().First().ChangeBackgroundImage(new FileInfo(Path.Combine(extractedFolder, "machinetop_background.jpg"))); - Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected)); - return success; - }); - } - - RoundedButton getButton() => Editor.ChildrenOfType().Single(b => b.Text == EditorSetupStrings.ResourcesUpdateAllDifficulties); + Assert.That(Beatmap.Value.Metadata.AudioFile, Is.EqualTo(expected)); + return success; + }); } private bool setFile(string archivePath, Func func) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs index c6fd65b973..b9ff78b49f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs @@ -9,6 +9,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; +using osu.Game.Screens.Edit.Setup; using osuTK; namespace osu.Game.Tests.Visual.UserInterface @@ -89,8 +90,13 @@ namespace osu.Game.Tests.Visual.UserInterface }, new FormFileSelector { - Caption = "Audio file", - PlaceholderText = "Select an audio file", + Caption = "File selector", + PlaceholderText = "Select a file", + }, + new FormBeatmapFileSelector(true) + { + Caption = "File selector with intermediate choice dialog", + PlaceholderText = "Select a file", }, new FormColourPalette { From 4ae3ccfe480bb40ffa3b44bb1fc0879bfb129f98 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 06:05:02 -0500 Subject: [PATCH 124/223] Make `BackButtonVisibility` in game class private --- osu.Game/OsuGame.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 514209524e..c52755197b 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -178,7 +178,7 @@ namespace osu.Game /// /// Whether the back button is currently displayed. /// - public readonly IBindable BackButtonVisibility = new Bindable(); + private readonly IBindable backButtonVisibility = new Bindable(); IBindable ILocalUserPlayInfo.PlayingState => playingState; @@ -1194,7 +1194,7 @@ namespace osu.Game if (mode.NewValue != OverlayActivation.All) CloseAllOverlays(); }; - BackButtonVisibility.ValueChanged += visible => + backButtonVisibility.ValueChanged += visible => { if (visible.NewValue) BackButton.Show(); @@ -1594,14 +1594,14 @@ namespace osu.Game if (current is IOsuScreen currentOsuScreen) { - BackButtonVisibility.UnbindFrom(currentOsuScreen.BackButtonVisibility); + backButtonVisibility.UnbindFrom(currentOsuScreen.BackButtonVisibility); OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode); API.Activity.UnbindFrom(currentOsuScreen.Activity); } if (newScreen is IOsuScreen newOsuScreen) { - BackButtonVisibility.BindTo(newOsuScreen.BackButtonVisibility); + backButtonVisibility.BindTo(newOsuScreen.BackButtonVisibility); OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode); API.Activity.BindTo(newOsuScreen.Activity); From f792b6de002f1e82e004ba2c4c7b3d8de5360a27 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 06:07:10 -0500 Subject: [PATCH 125/223] Fix comment --- osu.Game/Screens/IOsuScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index 46dfbfb1ac..9e474ed0c6 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -35,7 +35,8 @@ namespace osu.Game.Screens /// Whether a footer (and a back button) should be displayed underneath the screen. /// /// - /// Temporarily, the back button is shown regardless of whether is true. + /// Temporarily, the footer's own back button is shown regardless of whether is set to hidden. + /// This will be corrected as the footer becomes used more commonly. /// bool ShowFooter { get; } From 238a1ce284ce0b77fda9823c2255f3525fe6c8e9 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 15:27:18 -0500 Subject: [PATCH 126/223] Fix tests reliability and improve code Shaved off lots of copypasta so the test actually shows what it's testing. --- .../Editing/TestSceneEditorBeatmapCreation.cs | 141 +++++++----------- 1 file changed, 54 insertions(+), 87 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 2817225f2b..c7d745b6e0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("enter compose mode", () => InputManager.Key(Key.F1)); AddUntilStep("wait for timeline load", () => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual); AddAssert("switch track to real track", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3")); @@ -508,43 +508,21 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestSingleBackgroundFile() { - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddAssert("set background", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg")); - AddStep("save", () => Editor.Save()); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); - AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); - AddUntilStep("wait for created", () => - { - string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty"; - }); + createNewDifficulty(); + createNewDifficulty(); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); - AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); - AddUntilStep("wait for created", () => - { - string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty (1)"; - }); + switchToDifficulty(1); - AddStep("switch to second difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(1))); - AddUntilStep("wait for editor load", () => Editor.IsLoaded); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); AddAssert("set background on second diff only", () => setBackground(applyToAllDifficulties: false, expected: "bg (1).jpg")); AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); - AddStep("save", () => Editor.Save()); - AddStep("switch to first difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); - AddUntilStep("wait for editor load", () => Editor.IsLoaded); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + switchToDifficulty(0); + AddAssert("set background on first diff only", () => setBackground(applyToAllDifficulties: false, expected: "bg (2).jpg")); AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (2).jpg")); - AddStep("save", () => Editor.Save()); AddAssert("set background on all diff", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg")); AddAssert("all diff uses one background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.BackgroundFile == "bg.jpg")); @@ -555,43 +533,21 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestSingleAudioFile() { - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddAssert("set audio", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3")); - AddStep("save", () => Editor.Save()); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); - AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); - AddUntilStep("wait for created", () => - { - string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty"; - }); + createNewDifficulty(); + createNewDifficulty(); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); - AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); - AddUntilStep("wait for created", () => - { - string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty (1)"; - }); + switchToDifficulty(1); - AddStep("switch to second difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(1))); - AddUntilStep("wait for editor load", () => Editor.IsLoaded); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); AddAssert("set audio on second diff only", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3")); AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); - AddStep("save", () => Editor.Save()); - AddStep("switch to first difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); - AddUntilStep("wait for editor load", () => Editor.IsLoaded); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + switchToDifficulty(0); + AddAssert("set audio on first diff only", () => setAudio(applyToAllDifficulties: false, expected: "audio (2).mp3")); AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (2).mp3")); - AddStep("save", () => Editor.Save()); AddAssert("set audio on all diff", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3")); AddAssert("all diff uses one audio", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.AudioFile == "audio.mp3")); @@ -602,32 +558,19 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestMultipleBackgroundFiles() { - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddAssert("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg.jpg")); - AddStep("save", () => Editor.Save()); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); - AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); - AddUntilStep("wait for created", () => - { - string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty"; - }); + createNewDifficulty(); AddAssert("new difficulty uses same background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); AddAssert("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg (1).jpg")); AddAssert("new difficulty uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); - AddStep("save", () => Editor.Save()); - AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + switchToDifficulty(0); + AddAssert("old difficulty uses old background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); AddAssert("old background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg.jpg")); - - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); AddStep("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg.jpg")); AddAssert("other background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); } @@ -635,34 +578,58 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestMultipleAudioFiles() { - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3")); + createNewDifficulty(); + + AddAssert("new difficulty uses same audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3")); + AddAssert("new difficulty uses new audio", () => Beatmap.Value.Metadata.AudioFile == "audio (1).mp3"); + + switchToDifficulty(0); + + AddAssert("old difficulty uses old audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); + AddAssert("old audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio.mp3")); + AddStep("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3")); + AddAssert("other audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); + } + + private void createNewDifficulty() + { + string? currentDifficulty = null; + AddStep("save", () => Editor.Save()); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddStep("create new difficulty", () => + { + currentDifficulty = EditorBeatmap.BeatmapInfo.DifficultyName; + Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo); + }); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); AddUntilStep("wait for created", () => { string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty"; + return difficultyName != null && difficultyName != currentDifficulty; }); - AddAssert("new difficulty uses same audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for editor load", () => Editor.IsLoaded); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3")); - AddAssert("new difficulty uses new audio", () => Beatmap.Value.Metadata.AudioFile == "audio (1).mp3"); + } + private void switchToDifficulty(int index) + { AddStep("save", () => Editor.Save()); - AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); - AddAssert("old difficulty uses old audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); - AddAssert("old audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio.mp3")); + AddStep($"switch to difficulty #{index + 1}", () => + Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(index))); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for editor load", () => Editor.IsLoaded); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddStep("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3")); - AddAssert("other audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); } private bool setBackground(bool applyToAllDifficulties, string expected) From 24c0799680c30223bdc1326bc3735807da950178 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 16:54:51 -0500 Subject: [PATCH 127/223] Move beatmap ID lookup to `UesrActivity` --- osu.Desktop/DiscordRichPresence.cs | 18 +----------------- osu.Game/Users/UserActivity.cs | 12 ++++++++++++ 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index ba61f4be34..1fa964d8bc 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -167,9 +167,7 @@ namespace osu.Desktop presence.State = clampLength(activity.Value.GetStatus(hideIdentifiableInformation)); presence.Details = clampLength(activity.Value.GetDetails(hideIdentifiableInformation) ?? string.Empty); - if (getBeatmapID(activity.Value) is int beatmapId - && beatmapId > 0 - && !(activity.Value is UserActivity.EditingBeatmap && hideIdentifiableInformation)) + if (activity.Value.GetBeatmapID(hideIdentifiableInformation) is int beatmapId && beatmapId > 0) { presence.Buttons = new[] { @@ -329,20 +327,6 @@ namespace osu.Desktop return true; } - private static int? getBeatmapID(UserActivity activity) - { - switch (activity) - { - case UserActivity.InGame game: - return game.BeatmapID; - - case UserActivity.EditingBeatmap edit: - return edit.BeatmapID; - } - - return null; - } - protected override void Dispose(bool isDisposing) { if (multiplayerClient.IsNotNull()) diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs index 93812e3f6b..a8e0fc9030 100644 --- a/osu.Game/Users/UserActivity.cs +++ b/osu.Game/Users/UserActivity.cs @@ -41,6 +41,12 @@ namespace osu.Game.Users public virtual Color4 GetAppropriateColour(OsuColour colours) => colours.GreenDarker; + /// + /// Returns the ID of the beatmap involved in this activity, if applicable and/or available. + /// + /// + public virtual int? GetBeatmapID(bool hideIdentifiableInformation = false) => null; + [MessagePackObject] public class ChoosingBeatmap : UserActivity { @@ -76,6 +82,7 @@ namespace osu.Game.Users public override string GetStatus(bool hideIdentifiableInformation = false) => RulesetPlayingVerb; public override string GetDetails(bool hideIdentifiableInformation = false) => BeatmapDisplayTitle; + public override int? GetBeatmapID(bool hideIdentifiableInformation = false) => BeatmapID; } [MessagePackObject] @@ -156,6 +163,11 @@ namespace osu.Game.Users // For now let's assume that showing the beatmap a user is editing could reveal unwanted information. ? string.Empty : BeatmapDisplayTitle; + + public override int? GetBeatmapID(bool hideIdentifiableInformation = false) => hideIdentifiableInformation + // For now let's assume that showing the beatmap a user is editing could reveal unwanted information. + ? null + : BeatmapID; } [MessagePackObject] From 19e396f87886836f41a14b0e739a625e6e663e80 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 23:46:19 -0500 Subject: [PATCH 128/223] Fix android workflow not installing .NET 8 version --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d75f09f184..d8645d728e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,7 +114,10 @@ jobs: dotnet-version: "8.0.x" - name: Install .NET workloads - run: dotnet workload install android + # since windows image 20241113.3.0, not specifying a version here + # installs the .NET 7 version of android workload for very unknown reasons. + # revisit once we upgrade to .NET 9, it's probably fixed there. + run: dotnet workload install android --version (dotnet --version) - name: Compile run: dotnet build -c Debug osu.Android.slnf From 4d9d5adbf441ecc8286ca4dc512f96063ddf19bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Nov 2024 15:13:32 +0900 Subject: [PATCH 129/223] Rename parameter to be more clear --- .../Edit/Setup/FormBeatmapFileSelector.cs | 16 ++++++++-------- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs index 317ed1b903..ae368a7b7e 100644 --- a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs +++ b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs @@ -27,19 +27,19 @@ namespace osu.Game.Screens.Edit.Setup /// public partial class FormBeatmapFileSelector : FormFileSelector { - private readonly bool multipleDifficulties; + private readonly bool beatmapHasMultipleDifficulties; public readonly Bindable ApplyToAllDifficulties = new Bindable(true); - public FormBeatmapFileSelector(bool multipleDifficulties, params string[] handledExtensions) + public FormBeatmapFileSelector(bool beatmapHasMultipleDifficulties, params string[] handledExtensions) : base(handledExtensions) { - this.multipleDifficulties = multipleDifficulties; + this.beatmapHasMultipleDifficulties = beatmapHasMultipleDifficulties; } protected override FileChooserPopover CreatePopover(string[] handledExtensions, Bindable current, string? chooserPath) { - var popover = new BeatmapFileChooserPopover(handledExtensions, current, chooserPath, multipleDifficulties); + var popover = new BeatmapFileChooserPopover(handledExtensions, current, chooserPath, beatmapHasMultipleDifficulties); popover.ApplyToAllDifficulties.ValueChanged += v => { @@ -52,16 +52,16 @@ namespace osu.Game.Screens.Edit.Setup private partial class BeatmapFileChooserPopover : FileChooserPopover { - private readonly bool multipleDifficulties; + private readonly bool beatmapHasMultipleDifficulties; public readonly Bindable ApplyToAllDifficulties = new Bindable(); private Container changeScopeContainer = null!; - public BeatmapFileChooserPopover(string[] handledExtensions, Bindable current, string? chooserPath, bool multipleDifficulties) + public BeatmapFileChooserPopover(string[] handledExtensions, Bindable current, string? chooserPath, bool beatmapHasMultipleDifficulties) : base(handledExtensions, current, chooserPath) { - this.multipleDifficulties = multipleDifficulties; + this.beatmapHasMultipleDifficulties = beatmapHasMultipleDifficulties; } [BackgroundDependencyLoader] @@ -142,7 +142,7 @@ namespace osu.Game.Screens.Edit.Setup protected override void OnFileSelected(FileInfo file) { - if (multipleDifficulties) + if (beatmapHasMultipleDifficulties) changeScopeContainer.FadeIn(200, Easing.InQuint); else base.OnFileSelected(file); diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 70282878e0..1ce944b5a4 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -49,16 +49,16 @@ namespace osu.Game.Screens.Edit.Setup Height = 110, }; - bool multipleDifficulties = working.Value.BeatmapSetInfo.Beatmaps.Count > 1; + bool beatmapHasMultipleDifficulties = working.Value.BeatmapSetInfo.Beatmaps.Count > 1; Children = new Drawable[] { - backgroundChooser = new FormBeatmapFileSelector(multipleDifficulties, ".jpg", ".jpeg", ".png") + backgroundChooser = new FormBeatmapFileSelector(beatmapHasMultipleDifficulties, ".jpg", ".jpeg", ".png") { Caption = GameplaySettingsStrings.BackgroundHeader, PlaceholderText = EditorSetupStrings.ClickToSelectBackground, }, - audioTrackChooser = new FormBeatmapFileSelector(multipleDifficulties, ".mp3", ".ogg") + audioTrackChooser = new FormBeatmapFileSelector(beatmapHasMultipleDifficulties, ".mp3", ".ogg") { Caption = EditorSetupStrings.AudioTrack, PlaceholderText = EditorSetupStrings.ClickToSelectTrack, From 32b34c1967172bab39c5b2f05975e23dee76cdcd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Nov 2024 15:20:51 +0900 Subject: [PATCH 130/223] Rename container to make more sense --- osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs index ae368a7b7e..6af78f24f8 100644 --- a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs +++ b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Edit.Setup public readonly Bindable ApplyToAllDifficulties = new Bindable(); - private Container changeScopeContainer = null!; + private Container selectApplicationScopeContainer = null!; public BeatmapFileChooserPopover(string[] handledExtensions, Bindable current, string? chooserPath, bool beatmapHasMultipleDifficulties) : base(handledExtensions, current, chooserPath) @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Edit.Setup [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuColour colours) { - Add(changeScopeContainer = new InputBlockingContainer + Add(selectApplicationScopeContainer = new InputBlockingContainer { Alpha = 0f, RelativeSizeAxes = Axes.Both, @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Edit.Setup protected override void OnFileSelected(FileInfo file) { if (beatmapHasMultipleDifficulties) - changeScopeContainer.FadeIn(200, Easing.InQuint); + selectApplicationScopeContainer.FadeIn(200, Easing.InQuint); else base.OnFileSelected(file); } From 70eee8882a0ed4df045c0ea8f30764cd93cee88c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Nov 2024 15:42:37 +0900 Subject: [PATCH 131/223] Remove unnecessary null check --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs index 7838bd2fc8..c8e5f0859d 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void hideCloseButton() { - closeButton?.ResizeWidthTo(0, 100, Easing.OutQuint) + closeButton.ResizeWidthTo(0, 100, Easing.OutQuint) .Then().FadeOut().Expire(); } From 4a1401a33df7c3b489c6c0db05b68f6f1fe31079 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 02:37:27 -0500 Subject: [PATCH 132/223] Rewrite bindable flow to make more sense --- .../Edit/Setup/FormBeatmapFileSelector.cs | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs index 6af78f24f8..3e5f0f4306 100644 --- a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs +++ b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs @@ -40,13 +40,7 @@ namespace osu.Game.Screens.Edit.Setup protected override FileChooserPopover CreatePopover(string[] handledExtensions, Bindable current, string? chooserPath) { var popover = new BeatmapFileChooserPopover(handledExtensions, current, chooserPath, beatmapHasMultipleDifficulties); - - popover.ApplyToAllDifficulties.ValueChanged += v => - { - Debug.Assert(v.NewValue != null); - ApplyToAllDifficulties.Value = v.NewValue.Value; - }; - + popover.ApplyToAllDifficulties.BindTo(ApplyToAllDifficulties); return popover; } @@ -54,7 +48,7 @@ namespace osu.Game.Screens.Edit.Setup { private readonly bool beatmapHasMultipleDifficulties; - public readonly Bindable ApplyToAllDifficulties = new Bindable(); + public readonly Bindable ApplyToAllDifficulties = new Bindable(true); private Container selectApplicationScopeContainer = null!; @@ -115,7 +109,11 @@ namespace osu.Game.Screens.Edit.Setup Origin = Anchor.Centre, Width = 300f, Text = "Apply to all difficulties", - Action = () => ApplyToAllDifficulties.Value = true, + Action = () => + { + ApplyToAllDifficulties.Value = true; + updateFileSelection(); + }, BackgroundColour = colours.Red2, }, new RoundedButton @@ -124,7 +122,11 @@ namespace osu.Game.Screens.Edit.Setup Origin = Anchor.Centre, Width = 300f, Text = "Only apply to this difficulty", - Action = () => ApplyToAllDifficulties.Value = false, + Action = () => + { + ApplyToAllDifficulties.Value = false; + updateFileSelection(); + }, }, } } @@ -134,12 +136,6 @@ namespace osu.Game.Screens.Edit.Setup }); } - protected override void LoadComplete() - { - base.LoadComplete(); - ApplyToAllDifficulties.ValueChanged += onChangeScopeSelected; - } - protected override void OnFileSelected(FileInfo file) { if (beatmapHasMultipleDifficulties) @@ -148,11 +144,8 @@ namespace osu.Game.Screens.Edit.Setup base.OnFileSelected(file); } - private void onChangeScopeSelected(ValueChangedEvent c) + private void updateFileSelection() { - if (c.NewValue == null) - return; - Debug.Assert(FileSelector.CurrentFile.Value != null); base.OnFileSelected(FileSelector.CurrentFile.Value); } From b1d0939142f62ea2d43401bb7bd4bc0d32191479 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 02:37:31 -0500 Subject: [PATCH 133/223] Add localisation support --- osu.Game/Localisation/EditorSetupStrings.cs | 20 ++++++++++++++----- .../Edit/Setup/FormBeatmapFileSelector.cs | 7 ++++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/osu.Game/Localisation/EditorSetupStrings.cs b/osu.Game/Localisation/EditorSetupStrings.cs index 60e677757e..8597b7d9a1 100644 --- a/osu.Game/Localisation/EditorSetupStrings.cs +++ b/osu.Game/Localisation/EditorSetupStrings.cs @@ -188,11 +188,6 @@ namespace osu.Game.Localisation /// public static LocalisableString AudioTrack => new TranslatableString(getKey(@"audio_track"), @"Audio Track"); - /// - /// "Update all difficulties" - /// - public static LocalisableString ResourcesUpdateAllDifficulties => new TranslatableString(getKey(@"resources_update_all_difficulties"), @"Update all difficulties"); - /// /// "Click to select a track" /// @@ -203,6 +198,21 @@ namespace osu.Game.Localisation /// public static LocalisableString ClickToSelectBackground => new TranslatableString(getKey(@"click_to_select_background"), @"Click to select a background image"); + /// + /// "Apply this change to all difficulties?" + /// + public static LocalisableString ApplicationScopeSelectionTitle => new TranslatableString(getKey(@"application_scope_selection_title"), @"Apply this change to all difficulties?"); + + /// + /// "Apply to all difficulties" + /// + public static LocalisableString ApplyToAllDifficulties => new TranslatableString(getKey(@"apply_to_all_difficulties"), @"Apply to all difficulties"); + + /// + /// "Only apply to this difficulty" + /// + public static LocalisableString ApplyToThisDifficulty => new TranslatableString(getKey(@"apply_to_this_difficulty"), @"Only apply to this difficulty"); + /// /// "Ruleset ({0})" /// diff --git a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs index 3e5f0f4306..53287383ec 100644 --- a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs +++ b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osuTK; +using osu.Game.Localisation; namespace osu.Game.Screens.Edit.Setup { @@ -100,7 +101,7 @@ namespace osu.Game.Screens.Edit.Setup { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = "Apply this change to all difficulties?", + Text = EditorSetupStrings.ApplicationScopeSelectionTitle, Margin = new MarginPadding { Bottom = 20f }, }, new RoundedButton @@ -108,7 +109,7 @@ namespace osu.Game.Screens.Edit.Setup Anchor = Anchor.Centre, Origin = Anchor.Centre, Width = 300f, - Text = "Apply to all difficulties", + Text = EditorSetupStrings.ApplyToAllDifficulties, Action = () => { ApplyToAllDifficulties.Value = true; @@ -121,7 +122,7 @@ namespace osu.Game.Screens.Edit.Setup Anchor = Anchor.Centre, Origin = Anchor.Centre, Width = 300f, - Text = "Only apply to this difficulty", + Text = EditorSetupStrings.ApplyToThisDifficulty, Action = () => { ApplyToAllDifficulties.Value = false; From 4314f9c0a92ca15635cc317d38b3a56c1bcce9d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Nov 2024 09:22:08 +0100 Subject: [PATCH 134/223] Remove unused accessors --- .../Playlists/TestScenePlaylistsResultsScreen.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index c288b04da2..33bd573617 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -464,11 +464,6 @@ namespace osu.Game.Tests.Visual.Playlists private partial class TestScoreResultsScreen : PlaylistItemScoreResultsScreen { - public new LoadingSpinner LeftSpinner => base.LeftSpinner; - public new LoadingSpinner CentreSpinner => base.CentreSpinner; - public new LoadingSpinner RightSpinner => base.RightSpinner; - public new ScorePanelList ScorePanelList => base.ScorePanelList; - public TestScoreResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem) : base(score, roomId, playlistItem) { @@ -478,11 +473,6 @@ namespace osu.Game.Tests.Visual.Playlists private partial class TestUserBestResultsScreen : PlaylistItemUserBestResultsScreen { - public new LoadingSpinner LeftSpinner => base.LeftSpinner; - public new LoadingSpinner CentreSpinner => base.CentreSpinner; - public new LoadingSpinner RightSpinner => base.RightSpinner; - public new ScorePanelList ScorePanelList => base.ScorePanelList; - public TestUserBestResultsScreen(int roomId, PlaylistItem playlistItem, int userId) : base(roomId, playlistItem, userId) { From ced8dda1a29da0697bf5e47c7ab0734f473b6892 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Nov 2024 17:30:50 +0900 Subject: [PATCH 135/223] Clear previous `LastLocalUserScore` when returning to song select This seems like the lowest friction way of fixing https://github.com/ppy/osu/issues/30885. We could also only null this on application, but this feels worse because - It would require local handling (potentially complex) in `BeatmapOffsetControl` if we want to continue displaying the graph and button after clicking it. - It would make the session static very specific in usage and potentially make future usage not possible due to being nulled in only a very specific scenario. One might argue that it would be nice to have this non-null until the next play, but if such a usage comes up I'd propose we rename this session static and add a new one with that purpose. --- osu.Game/Configuration/SessionStatics.cs | 4 +++- osu.Game/Screens/Play/PlayerLoader.cs | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 225f209380..18631f5d00 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -10,6 +10,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Scoring; +using osu.Game.Screens.Play; namespace osu.Game.Configuration { @@ -77,7 +78,8 @@ namespace osu.Game.Configuration TouchInputActive, /// - /// Stores the local user's last score (can be completed or aborted). + /// Contains the local user's last score (can be completed or aborted) after exiting . + /// Will be cleared to null when leaving . /// LastLocalUserScore, diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 3e36c630db..0db96b71ad 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -28,6 +28,7 @@ using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Performance; +using osu.Game.Scoring; using osu.Game.Screens.Menu; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Skinning; @@ -78,6 +79,8 @@ namespace osu.Game.Screens.Play private FillFlowContainer disclaimers = null!; private OsuScrollContainer settingsScroll = null!; + private Bindable lastScore = null!; + private Bindable showStoryboards = null!; private bool backgroundBrightnessReduction; @@ -179,6 +182,8 @@ namespace osu.Game.Screens.Play { muteWarningShownOnce = sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce); batteryWarningShownOnce = sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce); + lastScore = sessionStatics.GetBindable(Static.LastLocalUserScore); + showStoryboards = config.GetBindable(OsuSetting.ShowStoryboard); const float padding = 25; @@ -347,6 +352,8 @@ namespace osu.Game.Screens.Play highPerformanceSession?.Dispose(); highPerformanceSession = null; + lastScore.Value = null; + return base.OnExiting(e); } From c26c84ba4519ade44fb3196a0e8187dde35605ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Nov 2024 18:03:19 +0900 Subject: [PATCH 136/223] Add test coverage governing new behaviour --- .../Navigation/TestSceneScreenNavigation.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index eda7ce925a..5646649d33 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -354,6 +354,23 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("retry count is 1", () => player.RestartCount == 1); } + [Test] + public void TestLastScoreNullAfterExitingPlayer() + { + AddUntilStep("wait for last play null", getLastPlay, () => Is.Null); + + var getOriginalPlayer = playToCompletion(); + + AddStep("attempt to retry", () => getOriginalPlayer().ChildrenOfType().First().Action()); + AddUntilStep("wait for last play matches player", getLastPlay, () => Is.EqualTo(getOriginalPlayer().Score.ScoreInfo)); + + AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != getOriginalPlayer() && Game.ScreenStack.CurrentScreen is Player); + AddStep("exit player", () => (Game.ScreenStack.CurrentScreen as Player)?.Exit()); + AddUntilStep("wait for last play null", getLastPlay, () => Is.Null); + + ScoreInfo getLastPlay() => Game.Dependencies.Get().Get(Static.LastLocalUserScore); + } + [Test] public void TestRetryImmediatelyAfterCompletion() { From b0958c8d418db28022fe2d12dd0ca2722ddad14c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Nov 2024 10:24:56 +0100 Subject: [PATCH 137/223] Attempt to fix test failures --- osu.Game/Overlays/MedalOverlay.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 512cb697dd..e102feb3e2 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -144,10 +144,12 @@ namespace osu.Game.Overlays protected override void Dispose(bool isDisposing) { - base.Dispose(isDisposing); - + // this event subscription fires async loads, which hard-fail if `CompositeDrawable.disposalCancellationSource` is canceled, which happens in the base call. + // therefore, unsubscribe from this event early to reduce the chances of a stray event firing at an inconvenient spot. if (api.IsNotNull()) api.NotificationsClient.MessageReceived -= handleMedalMessages; + + base.Dispose(isDisposing); } } } From c14fe21219acc24741b7d6b31106763fdd488796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Nov 2024 11:19:00 +0100 Subject: [PATCH 138/223] Fix LCA call crashing in actual usage It's not allowed to call `LoadComponentsAsync()` on a background thread: https://github.com/ppy/osu-framework/blob/fd64f2f0d47f0ee1aaa596bde1e83e527d610340/osu.Framework/Graphics/Containers/CompositeDrawable.cs#L147 and in this case the event that causes the LCA call is dispatched from a websocket client, which is not on the update thread, so scheduling is required. --- osu.Game/Overlays/MedalOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index e102feb3e2..25e22ffbda 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -85,11 +85,11 @@ namespace osu.Game.Overlays Logger.Log($"Queueing medal unlock for \"{medal.Name}\" ({queuedMedals.Count} to display)"); - LoadComponentAsync(medalAnimation, m => + Schedule(() => LoadComponentAsync(medalAnimation, m => { queuedMedals.Enqueue(m); showNextMedal(); - }); + })); } protected override bool OnClick(ClickEvent e) From 8f6e5c475465d826204f5308f4b431c4a52db253 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2024 19:14:18 +0800 Subject: [PATCH 139/223] Convert legacy ruleset to globalconfig --- .globalconfig | 8 +++ CodeAnalysis/osu.ruleset | 58 ------------------- Directory.Build.props | 1 - .../CodeAnalysis.tests.globalconfig | 7 +++ osu.Game.Tests/osu.Game.Tests.csproj | 6 +- osu.Game.Tests/tests.ruleset | 6 -- osu.sln | 2 +- 7 files changed, 19 insertions(+), 69 deletions(-) delete mode 100644 CodeAnalysis/osu.ruleset create mode 100644 osu.Game.Tests/CodeAnalysis.tests.globalconfig delete mode 100644 osu.Game.Tests/tests.ruleset diff --git a/.globalconfig b/.globalconfig index a4d4707f9b..c5723daed6 100644 --- a/.globalconfig +++ b/.globalconfig @@ -46,6 +46,14 @@ dotnet_diagnostic.IDE0130.severity = warning # IDE1006: Naming style dotnet_diagnostic.IDE1006.severity = warning +dotnet_diagnostic.CA1309.severity = warning + +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = warning + +# CA2201: Do not raise reserved exception types +dotnet_diagnostic.CA2201.severity = warning + #Disable operator overloads requiring alternate named methods dotnet_diagnostic.CA2225.severity = none diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset deleted file mode 100644 index 6a99e230d1..0000000000 --- a/CodeAnalysis/osu.ruleset +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 5ba12b845b..e7e5e4e831 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -20,7 +20,6 @@ - $(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset true diff --git a/osu.Game.Tests/CodeAnalysis.tests.globalconfig b/osu.Game.Tests/CodeAnalysis.tests.globalconfig new file mode 100644 index 0000000000..3f039736ec --- /dev/null +++ b/osu.Game.Tests/CodeAnalysis.tests.globalconfig @@ -0,0 +1,7 @@ +# Higher global_level has higher priority, the default global_level +# is 100 for root .globalconfig and 0 for others +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/configuration-files#precedence +is_global = true +global_level = 101 + +dotnet_diagnostic.CA2007.severity = none diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 28a1d4d021..01d2241650 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -12,9 +12,9 @@ WinExe net8.0 - - tests.ruleset - + + + diff --git a/osu.Game.Tests/tests.ruleset b/osu.Game.Tests/tests.ruleset deleted file mode 100644 index a0abb781d3..0000000000 --- a/osu.Game.Tests/tests.ruleset +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/osu.sln b/osu.sln index 829e43fc65..2d9a4e86d0 100644 --- a/osu.sln +++ b/osu.sln @@ -60,7 +60,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Directory.Build.props = Directory.Build.props osu.Android.props = osu.Android.props osu.iOS.props = osu.iOS.props - CodeAnalysis\osu.ruleset = CodeAnalysis\osu.ruleset + global.json = global.json osu.sln.DotSettings = osu.sln.DotSettings osu.TestProject.props = osu.TestProject.props EndProjectSection From 66093872e85d8d08dba3860393fabbe87cf9a660 Mon Sep 17 00:00:00 2001 From: Hiviexd Date: Thu, 28 Nov 2024 12:49:30 +0100 Subject: [PATCH 140/223] Adjust daily challenge tier thresholds to match expectations --- .../Components/DailyChallengeStatsTooltip.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs index 24e531bd87..ea49f9d784 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs @@ -138,34 +138,32 @@ namespace osu.Game.Overlays.Profile.Header.Components topFifty.ValueColour = colourProvider.Content2; } - // reference: https://github.com/ppy/osu-web/blob/adf1e94754ba9625b85eba795f4a310caf169eec/resources/js/profile-page/daily-challenge.tsx#L13-L47 + // reference: https://github.com/ppy/osu-web/blob/a97f156014e00ea1aa315140da60542e798a9f06/resources/js/profile-page/daily-challenge.tsx#L13-L47 - // Rounding up is needed here to ensure the overlay shows the same colour as osu-web for the play count. - // This is because, for example, 31 / 3 > 10 in JavaScript because floats are used, while here it would - // get truncated to 10 with an integer division and show a lower tier. - public static RankingTier TierForPlayCount(int playCount) => TierForDaily((int)Math.Ceiling(playCount / 3.0d)); + // Rounding down is needed here to ensure the overlay shows the same colour as osu-web for the play count. + public static RankingTier TierForPlayCount(int playCount) => TierForDaily((int)Math.Floor(playCount / 3.0d)); public static RankingTier TierForDaily(int daily) { - if (daily > 360) + if (daily >= 360) return RankingTier.Lustrous; - if (daily > 240) + if (daily >= 240) return RankingTier.Radiant; - if (daily > 120) + if (daily >= 120) return RankingTier.Rhodium; - if (daily > 60) + if (daily >= 60) return RankingTier.Platinum; - if (daily > 30) + if (daily >= 30) return RankingTier.Gold; - if (daily > 10) + if (daily >= 10) return RankingTier.Silver; - if (daily > 5) + if (daily >= 5) return RankingTier.Bronze; return RankingTier.Iron; From 6ed21229b7c5c95f7125a1c2f534cbce3b1931b3 Mon Sep 17 00:00:00 2001 From: Hiviexd Date: Thu, 28 Nov 2024 12:49:48 +0100 Subject: [PATCH 141/223] update test --- .../Visual/Online/TestSceneUserProfileDailyChallenge.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs index 9db30380f6..3222e16412 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Online AddSliderStep("weekly best", 0, 250, 1, v => update(s => s.WeeklyStreakBest = v)); AddSliderStep("top 10%", 0, 999, 0, v => update(s => s.Top10PercentPlacements = v)); AddSliderStep("top 50%", 0, 999, 0, v => update(s => s.Top50PercentPlacements = v)); - AddSliderStep("playcount", 0, 999, 0, v => update(s => s.PlayCount = v)); + AddSliderStep("playcount", 0, 1500, 0, v => update(s => s.PlayCount = v)); AddStep("create", () => { Clear(); @@ -66,8 +66,8 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestPlayCountRankingTier() { - AddAssert("1 before silver", () => DailyChallengeStatsTooltip.TierForPlayCount(30) == RankingTier.Bronze); - AddAssert("first silver", () => DailyChallengeStatsTooltip.TierForPlayCount(31) == RankingTier.Silver); + AddAssert("1 before silver", () => DailyChallengeStatsTooltip.TierForPlayCount(29) == RankingTier.Bronze); + AddAssert("first silver", () => DailyChallengeStatsTooltip.TierForPlayCount(30) == RankingTier.Silver); } } } From c57ace0b5f184491890ab566084e4a5b9ec9d790 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2024 19:40:09 +0800 Subject: [PATCH 142/223] Enable recommended rules for documentation --- Directory.Build.props | 3 +++ osu.Game/Database/RealmObjectExtensions.cs | 22 ++++++++++++---------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index e7e5e4e831..60e0334f97 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -20,6 +20,9 @@ + Default + Default + Recommended true diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 2fa3b8a880..df725505fc 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -248,29 +248,30 @@ namespace osu.Game.Database return new RealmLive(realmObject, realm); } +#pragma warning disable RS0030 // mentioning banned symbols in documentation /// - /// Register a callback to be invoked each time this changes. + /// Register a callback to be invoked each time this changes. /// /// /// /// This adds osu! specific thread and managed state safety checks on top of . /// /// - /// The first callback will be invoked with the initial after the asynchronous query completes, + /// The first callback will be invoked with the initial after the asynchronous query completes, /// and then called again after each write transaction which changes either any of the objects in the collection, or /// which objects are in the collection. The changes parameter will /// be null the first time the callback is invoked with the initial results. For each call after that, /// it will contain information about which rows in the results were added, removed or modified. /// /// - /// If a write transaction did not modify any objects in this , the callback is not invoked at all. + /// If a write transaction did not modify any objects in this , the callback is not invoked at all. /// If an error occurs the callback will be invoked with null for the sender parameter and a non-null error. - /// Currently the only errors that can occur are when opening the on the background worker thread. + /// Currently the only errors that can occur are when opening the on the background worker thread. /// /// - /// At the time when the block is called, the object will be fully evaluated + /// At the time when the block is called, the object will be fully evaluated /// and up-to-date, and as long as you do not perform a write transaction on the same thread - /// or explicitly call , accessing it will never perform blocking work. + /// or explicitly call , accessing it will never perform blocking work. /// /// /// Notifications are delivered via the standard event loop, and so can't be delivered while the event loop is blocked by other activity. @@ -279,13 +280,14 @@ namespace osu.Game.Database /// /// /// The to observe for changes. - /// The callback to be invoked with the updated . + /// The callback to be invoked with the updated . /// /// A subscription token. It must be kept alive for as long as you want to receive change notifications. - /// To stop receiving notifications, call . + /// To stop receiving notifications, call . /// - /// - /// + /// + /// +#pragma warning restore RS0030 public static IDisposable QueryAsyncWithNotifications(this IRealmCollection collection, NotificationCallbackDelegate callback) where T : RealmObjectBase { From cd9b5927eba9b19b4b66382aaedd25298a91a4df Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2024 19:46:25 +0800 Subject: [PATCH 143/223] Enable recommended rules for globalization --- .globalconfig | 8 +++++++- CodeAnalysis/BannedSymbols.txt | 4 ---- Directory.Build.props | 2 ++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.globalconfig b/.globalconfig index c5723daed6..d6c657bad0 100644 --- a/.globalconfig +++ b/.globalconfig @@ -46,11 +46,17 @@ dotnet_diagnostic.IDE0130.severity = warning # IDE1006: Naming style dotnet_diagnostic.IDE1006.severity = warning -dotnet_diagnostic.CA1309.severity = warning +# CA1305: Specify IFormatProvider +# Too many noisy warnings for parsing/formatting numbers +dotnet_diagnostic.CA1305.severity = none # CA2007: Consider calling ConfigureAwait on the awaited task dotnet_diagnostic.CA2007.severity = warning +# CA2101: Specify marshaling for P/Invoke string arguments +# Reports warning for all non-UTF16 usages on DllImport; consider migrating to LibraryImport +dotnet_diagnostic.CA2101.severity = none + # CA2201: Do not raise reserved exception types dotnet_diagnostic.CA2201.severity = warning diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index 3c60b28765..550f7c8e11 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -14,10 +14,6 @@ M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Gen M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks. P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks. M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever. -M:System.Char.ToLower(System.Char);char.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture. -M:System.Char.ToUpper(System.Char);char.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture. -M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString. -M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString. M:Humanizer.InflectorExtensions.Pascalize(System.String);Humanizer's .Pascalize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToPascalCase() instead. M:Humanizer.InflectorExtensions.Camelize(System.String);Humanizer's .Camelize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToCamelCase() instead. M:Humanizer.InflectorExtensions.Underscore(System.String);Humanizer's .Underscore() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToSnakeCase() instead. diff --git a/Directory.Build.props b/Directory.Build.props index 60e0334f97..f89696d867 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -23,6 +23,8 @@ Default Default Recommended + Recommended + Recommended true From 13d7c6a2d863b9bae4ae024e4fd59ac2f895c292 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2024 19:55:03 +0800 Subject: [PATCH 144/223] Enable recommended rules for maintainability --- Directory.Build.props | 2 ++ osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs | 2 ++ .../API/Requests/Responses/APIUserMostPlayedBeatmap.cs | 2 ++ .../Notifications/WebSocket/Events/NewChatMessageData.cs | 2 ++ osu.Game/Utils/SpecialFunctions.cs | 5 +---- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index f89696d867..7cd02a72d4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -25,6 +25,8 @@ Recommended Recommended Recommended + Recommended + Default true diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs index 583def8eda..8e4cc387ed 100644 --- a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -45,7 +45,9 @@ namespace osu.Game.Online.API.Requests.Responses public KudosuAction Action; +#pragma warning disable CA1507 // Happens to name the same because of casing preference [JsonProperty("action")] +#pragma warning restore CA1507 private string action { set diff --git a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs index 6d5fd59f9c..38ad2bd02d 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs @@ -15,7 +15,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("count")] public int PlayCount { get; set; } +#pragma warning disable CA1507 // Happens to name the same because of casing preference [JsonProperty("beatmap")] +#pragma warning restore CA1507 private APIBeatmap beatmap { get; set; } public APIBeatmap BeatmapInfo diff --git a/osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs b/osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs index ff9f5ee9f7..677286bb8a 100644 --- a/osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs +++ b/osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs @@ -19,7 +19,9 @@ namespace osu.Game.Online.Notifications.WebSocket.Events [JsonProperty(@"messages")] public List Messages { get; set; } = null!; +#pragma warning disable CA1507 // Happens to name the same because of casing preference [JsonProperty(@"users")] +#pragma warning restore CA1507 private List users { get; set; } = null!; [OnDeserialized] diff --git a/osu.Game/Utils/SpecialFunctions.cs b/osu.Game/Utils/SpecialFunctions.cs index 0b0f0598bb..795a84a973 100644 --- a/osu.Game/Utils/SpecialFunctions.cs +++ b/osu.Game/Utils/SpecialFunctions.cs @@ -666,10 +666,7 @@ namespace osu.Game.Utils { // 2020-10-07 jbialogrodzki #730 Since this is public API we should probably // handle null arguments? It doesn't seem to have been done consistently in this class though. - if (coefficients == null) - { - throw new ArgumentNullException(nameof(coefficients)); - } + ArgumentNullException.ThrowIfNull(coefficients); // 2020-10-07 jbialogrodzki #730 Zero polynomials need explicit handling. // Without this check, we attempted to peek coefficients at negative indices! From f5a77165096fd395a2df7d8e3656bf794a7db2aa Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2024 20:37:53 +0800 Subject: [PATCH 145/223] Apply minor performance rules --- .globalconfig | 16 ++++++++++++++++ Directory.Build.props | 1 + .../TestSceneMultiplayerParticipantsList.cs | 2 +- .../UserInterface/TestSceneDeleteLocalScore.cs | 2 +- .../Extensions/StringDehumanizeExtensions.cs | 2 +- .../Overlays/Chat/ChannelList/ChannelList.cs | 11 ++--------- osu.Game/Overlays/ChatOverlay.cs | 3 +-- osu.Game/Overlays/Comments/CommentsContainer.cs | 2 +- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 4 ++-- .../Objects/Legacy/ConvertHitObjectParser.cs | 2 +- .../Ranking/Statistics/User/OverallRanking.cs | 4 ++-- osu.Game/Skinning/SkinImporter.cs | 2 +- osu.Game/Storyboards/Storyboard.cs | 3 ++- .../Visual/Spectator/TestSpectatorClient.cs | 4 ++-- 14 files changed, 34 insertions(+), 24 deletions(-) diff --git a/.globalconfig b/.globalconfig index d6c657bad0..e218f46cd2 100644 --- a/.globalconfig +++ b/.globalconfig @@ -50,6 +50,22 @@ dotnet_diagnostic.IDE1006.severity = warning # Too many noisy warnings for parsing/formatting numbers dotnet_diagnostic.CA1305.severity = none +# CA1806: Do not ignore method results +# The usages for numeric parsing are explicitly optional +dotnet_diagnostic.CA1806.severity = suggestion + +# CA1822: Mark members as static +# Potential false positive around reflection/too much noise +dotnet_diagnostic.CA1822.severity = none + +# CA1859: Use concrete types when possible for improved performance +# Involves design considerations +dotnet_diagnostic.CA1859.severity = none + +# CA1861: Avoid constant arrays as arguments +# Outdated with collection expressions +dotnet_diagnostic.CA1861.severity = none + # CA2007: Consider calling ConfigureAwait on the awaited task dotnet_diagnostic.CA2007.severity = warning diff --git a/Directory.Build.props b/Directory.Build.props index 7cd02a72d4..0f982d496e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -27,6 +27,7 @@ Recommended Recommended Default + Minimum true diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 95ae4c5e80..d88741ec0c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -198,7 +198,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("make second user host", () => MultiplayerClient.TransferHost(3)); - AddUntilStep("kick buttons not visible", () => this.ChildrenOfType().Count(d => d.IsPresent) == 0); + AddUntilStep("kick buttons not visible", () => !this.ChildrenOfType().Any(d => d.IsPresent)); AddStep("make local user host again", () => MultiplayerClient.TransferHost(API.LocalUser.Value.Id)); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index e2fe10fa74..f7bdda6b57 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -151,7 +151,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("click delete option", () => { - InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType().First(i => i.Item.Text.Value.ToString().ToLowerInvariant() == "delete")); + InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType().First(i => string.Equals(i.Item.Text.Value.ToString(), "delete", System.StringComparison.OrdinalIgnoreCase))); InputManager.Click(MouseButton.Left); }); diff --git a/osu.Game/Extensions/StringDehumanizeExtensions.cs b/osu.Game/Extensions/StringDehumanizeExtensions.cs index 6f0d7622d3..5993f83b55 100644 --- a/osu.Game/Extensions/StringDehumanizeExtensions.cs +++ b/osu.Game/Extensions/StringDehumanizeExtensions.cs @@ -60,7 +60,7 @@ namespace osu.Game.Extensions public static string ToCamelCase(this string input) { string word = input.ToPascalCase(); - return word.Length > 0 ? word.Substring(0, 1).ToLowerInvariant() + word.Substring(1) : word; + return word.Length > 0 ? char.ToLowerInvariant(word[0]) + word.Substring(1) : word; } /// diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index fc0060d86a..39860b5e03 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -120,10 +120,9 @@ namespace osu.Game.Overlays.Chat.ChannelList public void RemoveChannel(Channel channel) { - if (!channelMap.ContainsKey(channel)) + if (!channelMap.TryGetValue(channel, out var item)) return; - ChannelListItem item = channelMap[channel]; FillFlowContainer flow = getFlowForChannel(channel); channelMap.Remove(channel); @@ -132,13 +131,7 @@ namespace osu.Game.Overlays.Chat.ChannelList updateVisibility(); } - public ChannelListItem GetItem(Channel channel) - { - if (!channelMap.ContainsKey(channel)) - throw new ArgumentOutOfRangeException(); - - return channelMap[channel]; - } + public ChannelListItem GetItem(Channel channel) => channelMap[channel]; public void ScrollChannelIntoView(Channel channel) => scroll.ScrollIntoView(GetItem(channel)); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index b11483e678..a00414522d 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -386,9 +386,8 @@ namespace osu.Game.Overlays { channelList.RemoveChannel(channel); - if (loadedChannels.ContainsKey(channel)) + if (loadedChannels.TryGetValue(channel, out var loaded)) { - DrawableChannel loaded = loadedChannels[channel]; loadedChannels.Remove(channel); // DrawableChannel removed from cache must be manually disposed loaded.Dispose(); diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 921c1682f5..5e277357a9 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -244,7 +244,7 @@ namespace osu.Game.Overlays.Comments protected void OnSuccess(CommentBundle response) { commentCounter.Current.Value = response.Total; - newCommentEditor.CommentableMeta.Value = response.CommentableMeta.SingleOrDefault(m => m.Id == id.Value && m.Type == type.Value.ToString().ToSnakeCase().ToLowerInvariant()); + newCommentEditor.CommentableMeta.Value = response.CommentableMeta.SingleOrDefault(m => m.Id == id.Value && string.Equals(m.Type, type.Value.ToString().ToSnakeCase(), StringComparison.OrdinalIgnoreCase)); if (!response.Comments.Any()) { diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 4ca937bf86..fb056b457b 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -173,10 +173,10 @@ namespace osu.Game.Rulesets.Mods }; drawable.OnRevertResult += (_, result) => { - if (!ratesForRewinding.ContainsKey(result.HitObject)) return; + if (!ratesForRewinding.TryGetValue(result.HitObject, out double rewindValue)) return; if (!shouldProcessResult(result)) return; - recentRates.Insert(0, ratesForRewinding[result.HitObject]); + recentRates.Insert(0, rewindValue); ratesForRewinding.Remove(result.HitObject); recentRates.RemoveAt(recentRates.Count - 1); diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index f8bc0ce112..0162c8017b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -243,7 +243,7 @@ namespace osu.Game.Rulesets.Objects.Legacy return PathType.CATMULL; case 'B': - if (input.Length > 1 && int.TryParse(input.Substring(1), out int degree) && degree > 0) + if (input.Length > 1 && int.TryParse(input.AsSpan(1), out int degree) && degree > 0) return PathType.BSpline(degree); return PathType.BEZIER; diff --git a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs index 171a3f0f65..9f5afea6f0 100644 --- a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs +++ b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs @@ -59,14 +59,14 @@ namespace osu.Game.Screens.Ranking.Statistics.User new SimpleStatisticTable.Spacer(), new PerformancePointsChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, }, - new Drawable[] { }, + [], new Drawable[] { new MaximumComboChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, new SimpleStatisticTable.Spacer(), new AccuracyChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, }, - new Drawable[] { }, + [], new Drawable[] { new RankedScoreChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, diff --git a/osu.Game/Skinning/SkinImporter.cs b/osu.Game/Skinning/SkinImporter.cs index 59c7f0ba26..70d3195ecd 100644 --- a/osu.Game/Skinning/SkinImporter.cs +++ b/osu.Game/Skinning/SkinImporter.cs @@ -37,7 +37,7 @@ namespace osu.Game.Skinning protected override string[] HashableFileTypes => new[] { ".ini", ".json" }; - protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == @".osk"; + protected override bool ShouldDeleteArchive(string path) => string.Equals(Path.GetExtension(path), @".osk", StringComparison.OrdinalIgnoreCase); protected override SkinInfo CreateModel(ArchiveReader archive, ImportParameters parameters) => new SkinInfo { Name = archive.Name ?? @"No name" }; diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 8c43b99702..5120757f3d 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.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.IO; using System.Linq; @@ -89,7 +90,7 @@ namespace osu.Game.Storyboards // Importantly, do this after the NullOrEmpty because EF may have stored the non-nullable value as null to the database, bypassing compile-time constraints. backgroundPath = backgroundPath.ToLowerInvariant(); - return GetLayer("Background").Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); + return GetLayer("Background").Elements.Any(e => string.Equals(e.Path, backgroundPath, StringComparison.OrdinalIgnoreCase)); } } diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index c27e7f15ca..5d33afd288 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -78,12 +78,12 @@ namespace osu.Game.Tests.Visual.Spectator /// The spectator state to end play with. public void SendEndPlay(int userId, SpectatedUserState state = SpectatedUserState.Quit) { - if (!userBeatmapDictionary.ContainsKey(userId)) + if (!userBeatmapDictionary.TryGetValue(userId, out int beatmapId)) return; ((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState { - BeatmapID = userBeatmapDictionary[userId], + BeatmapID = beatmapId, RulesetID = 0, Mods = userModsDictionary[userId], State = state From ac2c4e81c77fcee81462adf5b2c8d60dd21036a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Nov 2024 14:04:39 +0100 Subject: [PATCH 146/223] Use switch --- .../OnlinePlay/Playlists/PlaylistsRoomFooter.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs index c8e5f0859d..9ccc8f3cab 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs @@ -83,8 +83,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void onRoomChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.Status) || e.PropertyName == nameof(Room.Host) || e.PropertyName == nameof(Room.StartDate)) - updateState(); + switch (e.PropertyName) + { + case nameof(Room.Status): + case nameof(Room.Host): + case nameof(Room.StartDate): + updateState(); + break; + } } private void updateState() From 9926ffd32627b6d50442dabcea30bb58c8f2ca6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Nov 2024 14:06:12 +0100 Subject: [PATCH 147/223] Make button a little narrower --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs index 9ccc8f3cab..6089b4734e 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Action = () => OnClose?.Invoke(), Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(200, 1), + Size = new Vector2(120, 1), Alpha = 0, RelativeSizeAxes = Axes.Y, } From c93c549b054a7e4ec5935a05e9bb71be807a5182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Nov 2024 14:17:31 +0100 Subject: [PATCH 148/223] Fix ready button not disabling on playlist close --- .../Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 61df331c48..9573155f5a 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.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.ComponentModel; using System.Diagnostics; using System.Linq; @@ -285,7 +286,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists DialogOverlay?.Push(new ClosePlaylistDialog(Room, () => { var request = new ClosePlaylistRequest(Room.RoomID!.Value); - request.Success += () => Room.Status = new RoomStatusEnded(); + request.Success += () => + { + Room.Status = new RoomStatusEnded(); + Room.EndDate = DateTimeOffset.UtcNow; + }; API.Queue(request); })); } From 5b63d725c507b7e12cf111e3b3db9faf20cd0725 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2024 22:13:59 +0800 Subject: [PATCH 149/223] Mark CA1826/CA1860 as suggestion --- .globalconfig | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.globalconfig b/.globalconfig index e218f46cd2..7673e5afb0 100644 --- a/.globalconfig +++ b/.globalconfig @@ -58,13 +58,19 @@ dotnet_diagnostic.CA1806.severity = suggestion # Potential false positive around reflection/too much noise dotnet_diagnostic.CA1822.severity = none +# CA1826: Do not use Enumerable method on indexable collections +dotnet_diagnostic.CA1826.severity = suggestion + # CA1859: Use concrete types when possible for improved performance # Involves design considerations -dotnet_diagnostic.CA1859.severity = none +dotnet_diagnostic.CA1859.severity = suggestion + +# CA1860: Avoid using 'Enumerable.Any()' extension method +dotnet_diagnostic.CA1860.severity = suggestion # CA1861: Avoid constant arrays as arguments # Outdated with collection expressions -dotnet_diagnostic.CA1861.severity = none +dotnet_diagnostic.CA1861.severity = suggestion # CA2007: Consider calling ConfigureAwait on the awaited task dotnet_diagnostic.CA2007.severity = warning From 0a8ec4db2b6fc80cdcf6c9d5dc190b4e31a140d1 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2024 22:20:36 +0800 Subject: [PATCH 150/223] Enable recommended rules for reliability --- .globalconfig | 8 ++++++++ Directory.Build.props | 1 + 2 files changed, 9 insertions(+) diff --git a/.globalconfig b/.globalconfig index 7673e5afb0..7eec612775 100644 --- a/.globalconfig +++ b/.globalconfig @@ -75,6 +75,14 @@ dotnet_diagnostic.CA1861.severity = suggestion # CA2007: Consider calling ConfigureAwait on the awaited task dotnet_diagnostic.CA2007.severity = warning +# CA2016: Forward the 'CancellationToken' parameter to methods +# Some overloads are having special handling for debugger +dotnet_diagnostic.CA2016.severity = suggestion + +# CA2021: Do not call Enumerable.Cast or Enumerable.OfType with incompatible types +# Causing a lot of false positives with generics +dotnet_diagnostic.CA2021.severity = none + # CA2101: Specify marshaling for P/Invoke string arguments # Reports warning for all non-UTF16 usages on DllImport; consider migrating to LibraryImport dotnet_diagnostic.CA2101.severity = none diff --git a/Directory.Build.props b/Directory.Build.props index 0f982d496e..0dcbffd0dc 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -28,6 +28,7 @@ Recommended Default Minimum + Recommended true From fced2545944f401fe6cf7a8429eb97bc774824fc Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2024 22:32:55 +0800 Subject: [PATCH 151/223] Enable selected rules for usage --- .globalconfig | 7 +++++-- Directory.Build.props | 2 ++ .../Argon/ManiaArgonSkinTransformer.cs | 20 +++++++++---------- osu.Game/Online/Leaderboards/Leaderboard.cs | 2 +- osu.Game/Online/OnlineViewContainer.cs | 2 +- osu.Game/Overlays/AccountCreationOverlay.cs | 2 +- .../Overlays/Toolbar/ToolbarUserButton.cs | 2 +- 7 files changed, 21 insertions(+), 16 deletions(-) diff --git a/.globalconfig b/.globalconfig index 7eec612775..e2cd1926f0 100644 --- a/.globalconfig +++ b/.globalconfig @@ -90,8 +90,11 @@ dotnet_diagnostic.CA2101.severity = none # CA2201: Do not raise reserved exception types dotnet_diagnostic.CA2201.severity = warning -#Disable operator overloads requiring alternate named methods -dotnet_diagnostic.CA2225.severity = none +# CA2208: Instantiate argument exceptions correctly +dotnet_diagnostic.CA2208.severity = suggestion + +# CA2242: Test for NaN correctly +dotnet_diagnostic.CA2242.severity = warning # Banned APIs dotnet_diagnostic.RS0030.severity = error diff --git a/Directory.Build.props b/Directory.Build.props index 0dcbffd0dc..0ab41d27a0 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -29,6 +29,8 @@ Default Minimum Recommended + Default + Default true diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index afccb2e568..c37c18081a 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 1: return colour_cyan; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } case 3: @@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 2: return colour_cyan; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } case 4: @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 3: return colour_purple; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } case 5: @@ -206,7 +206,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 4: return colour_cyan; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } case 6: @@ -224,7 +224,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 5: return colour_pink; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } case 7: @@ -244,7 +244,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 6: return colour_pink; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } case 8: @@ -266,7 +266,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 7: return colour_purple; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } case 9: @@ -290,7 +290,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 8: return colour_purple; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } case 10: @@ -316,7 +316,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 9: return colour_purple; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } } @@ -339,7 +339,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 5: return colour_green; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } } } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 0fd9597ac0..d76da54adf 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -363,7 +363,7 @@ namespace osu.Game.Online.Leaderboards return null; default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(state)); } } diff --git a/osu.Game/Online/OnlineViewContainer.cs b/osu.Game/Online/OnlineViewContainer.cs index 824da152b2..ce55b50d94 100644 --- a/osu.Game/Online/OnlineViewContainer.cs +++ b/osu.Game/Online/OnlineViewContainer.cs @@ -87,7 +87,7 @@ namespace osu.Game.Online break; default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(state.NewValue)); } }); diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs index 82fc5508f1..55cba33153 100644 --- a/osu.Game/Overlays/AccountCreationOverlay.cs +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -126,7 +126,7 @@ namespace osu.Game.Overlays break; default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(state.NewValue)); } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 96c0b15c44..787c525566 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -130,7 +130,7 @@ namespace osu.Game.Overlays.Toolbar break; default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(state.NewValue)); } }); } From 58cf1c11e4721c85b554ccd08bf3b64ca62b2395 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 28 Nov 2024 06:41:43 +0900 Subject: [PATCH 152/223] Improve menu/context-menu sample playback --- .../UserInterface/TestSceneContextMenu.cs | 11 ++- .../Cursor/OsuContextMenuContainer.cs | 8 --- .../Graphics/UserInterface/HoverSampleSet.cs | 5 +- .../Graphics/UserInterface/OsuContextMenu.cs | 15 ++-- .../UserInterface/OsuContextMenuSamples.cs | 37 ---------- osu.Game/Graphics/UserInterface/OsuMenu.cs | 19 ++--- .../Graphics/UserInterface/OsuMenuSamples.cs | 70 +++++++++++++++++++ osu.Game/OsuGameBase.cs | 5 ++ .../Edit/Components/Menus/EditorMenuBar.cs | 2 +- 9 files changed, 103 insertions(+), 69 deletions(-) delete mode 100644 osu.Game/Graphics/UserInterface/OsuContextMenuSamples.cs create mode 100644 osu.Game/Graphics/UserInterface/OsuMenuSamples.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs index 2a2f267fc8..118e37dab4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs @@ -101,7 +101,16 @@ namespace osu.Game.Tests.Visual.UserInterface } } } - } + }, + } + }, + new OsuMenuItem(@"Another nested option") + { + Items = new MenuItem[] + { + new OsuMenuItem(@"Sub-One"), + new OsuMenuItem(@"Sub-Two"), + new OsuMenuItem(@"Sub-Three"), } }, new OsuMenuItem(@"Choose me please"), diff --git a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs index 7b21a413f7..3180661b0c 100644 --- a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs @@ -11,16 +11,8 @@ namespace osu.Game.Graphics.Cursor [Cached(typeof(OsuContextMenuContainer))] public partial class OsuContextMenuContainer : ContextMenuContainer { - [Cached] - private OsuContextMenuSamples samples = new OsuContextMenuSamples(); - private OsuContextMenu menu = null!; - public OsuContextMenuContainer() - { - AddInternal(samples); - } - protected override Menu CreateMenu() => menu = new OsuContextMenu(true); public void CloseMenu() diff --git a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs index 5b0fbc693e..62eb765cc8 100644 --- a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs +++ b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs @@ -23,6 +23,9 @@ namespace osu.Game.Graphics.UserInterface DialogCancel, [Description("dialog-ok")] - DialogOk + DialogOk, + + [Description("menu-open")] + MenuOpen, } } diff --git a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs index 96797e5d01..7a5d2c369b 100644 --- a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs @@ -15,7 +15,7 @@ namespace osu.Game.Graphics.UserInterface private const int fade_duration = 250; [Resolved] - private OsuContextMenuSamples samples { get; set; } = null!; + private OsuMenuSamples menuSamples { get; set; } = null!; // todo: this shouldn't be required after https://github.com/ppy/osu-framework/issues/4519 is fixed. private bool wasOpened; @@ -47,15 +47,14 @@ namespace osu.Game.Graphics.UserInterface protected override void AnimateOpen() { + wasOpened = true; this.FadeIn(fade_duration, Easing.OutQuint); - if (playClickSample) - samples.PlayClickSample(); + if (!playClickSample) + return; - if (!wasOpened) - samples.PlayOpenSample(); - - wasOpened = true; + menuSamples?.PlayClickSample(); + menuSamples?.PlayOpenSample(); } protected override void AnimateClose() @@ -63,7 +62,7 @@ namespace osu.Game.Graphics.UserInterface this.FadeOut(fade_duration, Easing.OutQuint); if (wasOpened) - samples.PlayCloseSample(); + menuSamples?.PlayCloseSample(); wasOpened = false; } diff --git a/osu.Game/Graphics/UserInterface/OsuContextMenuSamples.cs b/osu.Game/Graphics/UserInterface/OsuContextMenuSamples.cs deleted file mode 100644 index 6d7543c472..0000000000 --- a/osu.Game/Graphics/UserInterface/OsuContextMenuSamples.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Extensions; -using osu.Framework.Graphics; - -namespace osu.Game.Graphics.UserInterface -{ - public partial class OsuContextMenuSamples : Component - { - private Sample sampleClick; - private Sample sampleOpen; - private Sample sampleClose; - - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - sampleClick = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select"); - sampleOpen = audio.Samples.Get(@"UI/dropdown-open"); - sampleClose = audio.Samples.Get(@"UI/dropdown-close"); - } - - public void PlayClickSample() => Scheduler.AddOnce(playClickSample); - private void playClickSample() => sampleClick.Play(); - - public void PlayOpenSample() => Scheduler.AddOnce(playOpenSample); - private void playOpenSample() => sampleOpen.Play(); - - public void PlayCloseSample() => Scheduler.AddOnce(playCloseSample); - private void playCloseSample() => sampleClose.Play(); - } -} diff --git a/osu.Game/Graphics/UserInterface/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs index 6e7dad2b5f..7cc1bab25f 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs @@ -4,8 +4,6 @@ #nullable disable using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,12 +18,12 @@ namespace osu.Game.Graphics.UserInterface { public partial class OsuMenu : Menu { - private Sample sampleOpen; - private Sample sampleClose; - // todo: this shouldn't be required after https://github.com/ppy/osu-framework/issues/4519 is fixed. private bool wasOpened; + [Resolved] + private OsuMenuSamples menuSamples { get; set; } = null!; + public OsuMenu(Direction direction, bool topLevelMenu = false) : base(direction, topLevelMenu) { @@ -33,13 +31,8 @@ namespace osu.Game.Graphics.UserInterface MaskingContainer.CornerRadius = 4; ItemsContainer.Padding = new MarginPadding(5); - } - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - sampleOpen = audio.Samples.Get(@"UI/dropdown-open"); - sampleClose = audio.Samples.Get(@"UI/dropdown-close"); + OnSubmenuOpen += _ => { menuSamples?.PlaySubOpenSample(); }; } protected override void Update() @@ -64,7 +57,7 @@ namespace osu.Game.Graphics.UserInterface protected override void AnimateOpen() { if (!TopLevelMenu && !wasOpened) - sampleOpen?.Play(); + menuSamples?.PlayOpenSample(); this.FadeIn(300, Easing.OutQuint); wasOpened = true; @@ -73,7 +66,7 @@ namespace osu.Game.Graphics.UserInterface protected override void AnimateClose() { if (!TopLevelMenu && wasOpened) - sampleClose?.Play(); + menuSamples?.PlayCloseSample(); this.FadeOut(300, Easing.OutQuint); wasOpened = false; diff --git a/osu.Game/Graphics/UserInterface/OsuMenuSamples.cs b/osu.Game/Graphics/UserInterface/OsuMenuSamples.cs new file mode 100644 index 0000000000..779671b6ad --- /dev/null +++ b/osu.Game/Graphics/UserInterface/OsuMenuSamples.cs @@ -0,0 +1,70 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; + +namespace osu.Game.Graphics.UserInterface +{ + public partial class OsuMenuSamples : Component + { + private Sample sampleClick; + private Sample sampleOpen; + private Sample sampleSubOpen; + private Sample sampleClose; + + private bool triggerOpen; + private bool triggerSubOpen; + private bool triggerClose; + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleClick = audio.Samples.Get(@"UI/menu-open-select"); + sampleOpen = audio.Samples.Get(@"UI/menu-open"); + sampleSubOpen = audio.Samples.Get(@"UI/menu-sub-open"); + sampleClose = audio.Samples.Get(@"UI/menu-close"); + } + + public void PlayClickSample() + { + Scheduler.AddOnce(playClickSample); + } + + public void PlayOpenSample() + { + triggerOpen = true; + Scheduler.AddOnce(resolvePlayback); + } + + public void PlaySubOpenSample() + { + triggerSubOpen = true; + Scheduler.AddOnce(resolvePlayback); + } + + public void PlayCloseSample() + { + triggerClose = true; + Scheduler.AddOnce(resolvePlayback); + } + + private void playClickSample() => sampleClick.Play(); + + private void resolvePlayback() + { + if (triggerSubOpen) + sampleSubOpen?.Play(); + else if (triggerOpen) + sampleOpen?.Play(); + else if (triggerClose) + sampleClose?.Play(); + + triggerOpen = triggerSubOpen = triggerClose = false; + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index dc13924b4f..0f9848cacc 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -41,6 +41,7 @@ using osu.Game.Database; using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.UserInterface; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.IO; @@ -385,6 +386,10 @@ namespace osu.Game GlobalActionContainer globalBindings; + OsuMenuSamples menuSamples; + dependencies.Cache(menuSamples = new OsuMenuSamples()); + base.Content.Add(menuSamples); + base.Content.Add(SafeAreaContainer = new SafeAreaContainer { SafeAreaOverrideEdges = SafeAreaOverrideEdges, diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index 47a13dcfba..76b8811b89 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -99,7 +99,7 @@ namespace osu.Game.Screens.Edit.Components.Menus ForegroundColourHover = colourProvider.Content1; BackgroundColourHover = colourProvider.Background1; - AddInternal(hoverClickSounds = new HoverClickSounds()); + AddInternal(hoverClickSounds = new HoverClickSounds(HoverSampleSet.MenuOpen)); } protected override void LoadComplete() From 932afcde01469543084467b4699d9774123b8363 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 17:43:32 -0500 Subject: [PATCH 153/223] Make editor make sense --- osu.Game/Screens/Edit/Editor.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 13e5791605..0e4807dc78 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -80,8 +80,6 @@ namespace osu.Game.Screens.Edit public override float BackgroundParallaxAmount => 0.1f; - public override bool AllowUserExit => false; - public override bool HideOverlaysOnEnter => true; public override bool DisallowExternalBeatmapRulesetChanges => true; @@ -194,6 +192,8 @@ namespace osu.Game.Screens.Edit } } + protected override bool InitialBackButtonVisibility => false; + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -760,11 +760,6 @@ namespace osu.Game.Screens.Edit switch (e.Action) { - case GlobalAction.Back: - // as we don't want to display the back button, manual handling of exit action is required. - this.Exit(); - return true; - case GlobalAction.EditorCloneSelection: Clone(); return true; From 078d62fe093fc1ba9587cb6f3cdd4e4fec02e1f7 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 17:54:03 -0500 Subject: [PATCH 154/223] Fix weird default in test scene --- .../Visual/Online/TestSceneUserProfileDailyChallenge.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs index 3222e16412..0477d39193 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Online AddSliderStep("weekly best", 0, 250, 1, v => update(s => s.WeeklyStreakBest = v)); AddSliderStep("top 10%", 0, 999, 0, v => update(s => s.Top10PercentPlacements = v)); AddSliderStep("top 50%", 0, 999, 0, v => update(s => s.Top50PercentPlacements = v)); - AddSliderStep("playcount", 0, 1500, 0, v => update(s => s.PlayCount = v)); + AddSliderStep("playcount", 0, 1500, 1, v => update(s => s.PlayCount = v)); AddStep("create", () => { Clear(); From 51bcde67aae51cf23250109b4256a931dcf074f3 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 17:55:15 -0500 Subject: [PATCH 155/223] Remove no longer required comment --- .../Profile/Header/Components/DailyChallengeStatsTooltip.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs index ea49f9d784..826b40d70c 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs @@ -140,7 +140,6 @@ namespace osu.Game.Overlays.Profile.Header.Components // reference: https://github.com/ppy/osu-web/blob/a97f156014e00ea1aa315140da60542e798a9f06/resources/js/profile-page/daily-challenge.tsx#L13-L47 - // Rounding down is needed here to ensure the overlay shows the same colour as osu-web for the play count. public static RankingTier TierForPlayCount(int playCount) => TierForDaily((int)Math.Floor(playCount / 3.0d)); public static RankingTier TierForDaily(int daily) From 311f0947e41b44aaf5a08397138a8b3d57bc59d7 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 17:57:47 -0500 Subject: [PATCH 156/223] Abstractify resource change logic and share between background and audio --- .../Screens/Edit/Setup/ResourcesSection.cs | 91 ++++++------------- 1 file changed, 29 insertions(+), 62 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 1ce944b5a4..a02900a204 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -82,57 +82,12 @@ namespace osu.Game.Screens.Edit.Setup if (!source.Exists) return false; - var set = working.Value.BeatmapSetInfo; - - if (applyToAllDifficulties) - { - string newFilename = $@"bg{source.Extension}"; - - foreach (var beatmapInSet in set.Beatmaps) - { - if (set.GetFile(beatmapInSet.Metadata.BackgroundFile) is RealmNamedFileUsage existingFile) - beatmaps.DeleteFile(set, existingFile); - - if (beatmapInSet.Metadata.BackgroundFile != newFilename) - { - beatmapInSet.Metadata.BackgroundFile = newFilename; - - if (!beatmapInSet.Equals(working.Value.BeatmapInfo)) - beatmaps.Save(beatmapInSet, beatmaps.GetWorkingBeatmap(beatmapInSet).Beatmap); - } - } - } - else - { - var beatmap = working.Value.BeatmapInfo; - - string[] filenames = set.Files.Select(f => f.Filename).Where(f => - f.StartsWith(@"bg", StringComparison.OrdinalIgnoreCase) && - f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - - string currentFilename = working.Value.Metadata.BackgroundFile; - - var oldFile = set.GetFile(currentFilename); - string? newFilename = null; - - if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.BackgroundFile != currentFilename)) - { - beatmaps.DeleteFile(set, oldFile); - newFilename = currentFilename; - } - - newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"bg{source.Extension}"); - working.Value.Metadata.BackgroundFile = newFilename; - } - - using (var stream = source.OpenRead()) - beatmaps.AddFile(set, stream, working.Value.Metadata.BackgroundFile); - - editorBeatmap.SaveState(); + changeResource(source, applyToAllDifficulties, @"bg", + metadata => metadata.BackgroundFile, + (metadata, name) => metadata.BackgroundFile = name); headerBackground.UpdateBackground(); editor?.ApplyToBackground(bg => bg.RefreshBackground()); - return true; } @@ -141,20 +96,34 @@ namespace osu.Game.Screens.Edit.Setup if (!source.Exists) return false; + changeResource(source, applyToAllDifficulties, @"audio", + metadata => metadata.AudioFile, + (metadata, name) => metadata.AudioFile = name); + + music.ReloadCurrentTrack(); + return true; + } + + private void changeResource(FileInfo source, bool applyToAllDifficulties, string baseFilename, Func readFilename, Action writeFilename) + { var set = working.Value.BeatmapSetInfo; + string newFilename = string.Empty; + if (applyToAllDifficulties) { - string newFilename = $@"audio{source.Extension}"; + newFilename = $"{baseFilename}{source.Extension}"; foreach (var beatmapInSet in set.Beatmaps) { - if (set.GetFile(beatmapInSet.Metadata.AudioFile) is RealmNamedFileUsage existingFile) + string filenameInBeatmap = readFilename(beatmapInSet.Metadata); + + if (set.GetFile(filenameInBeatmap) is RealmNamedFileUsage existingFile) beatmaps.DeleteFile(set, existingFile); - if (beatmapInSet.Metadata.AudioFile != newFilename) + if (filenameInBeatmap != newFilename) { - beatmapInSet.Metadata.AudioFile = newFilename; + writeFilename(beatmapInSet.Metadata, newFilename); if (!beatmapInSet.Equals(working.Value.BeatmapInfo)) beatmaps.Save(beatmapInSet, beatmaps.GetWorkingBeatmap(beatmapInSet).Beatmap); @@ -166,31 +135,29 @@ namespace osu.Game.Screens.Edit.Setup var beatmap = working.Value.BeatmapInfo; string[] filenames = set.Files.Select(f => f.Filename).Where(f => - f.StartsWith(@"audio", StringComparison.OrdinalIgnoreCase) && + f.StartsWith(baseFilename, StringComparison.OrdinalIgnoreCase) && f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - string currentFilename = working.Value.Metadata.AudioFile; + string currentFilename = readFilename(working.Value.Metadata); var oldFile = set.GetFile(currentFilename); - string? newFilename = null; - if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.AudioFile != currentFilename)) + if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => readFilename(b.Metadata) != currentFilename)) { beatmaps.DeleteFile(set, oldFile); newFilename = currentFilename; } - newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"audio{source.Extension}"); - working.Value.Metadata.AudioFile = newFilename; + if (string.IsNullOrEmpty(newFilename)) + newFilename = NamingUtils.GetNextBestFilename(filenames, $@"{baseFilename}{source.Extension}"); + + writeFilename(working.Value.Metadata, newFilename); } using (var stream = source.OpenRead()) - beatmaps.AddFile(set, stream, working.Value.Metadata.AudioFile); + beatmaps.AddFile(set, stream, newFilename); editorBeatmap.SaveState(); - music.ReloadCurrentTrack(); - - return true; } private void backgroundChanged(ValueChangedEvent file) From 489d7a30ec093152cd838cfba9b64c6f235bfe66 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 18:32:03 -0500 Subject: [PATCH 157/223] Perform a single `Save` call rather than doing it in each difficulty --- .../Editing/TestSceneEditorBeatmapCreation.cs | 3 --- .../Screens/Edit/Setup/ResourcesSection.cs | 27 +++++++------------ 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index c7d745b6e0..7a390ac131 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -107,9 +107,6 @@ namespace osu.Game.Tests.Visual.Editing AddStep("test play", () => Editor.TestGameplay()); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog != null); - AddStep("confirm save", () => InputManager.Key(Key.Number1)); - AddUntilStep("wait for return to editor", () => Editor.IsCurrentScreen()); AddAssert("track is still not virtual", () => Beatmap.Value.Track is not TrackVirtual); diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index a02900a204..4d2bbb035e 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -32,9 +32,6 @@ namespace osu.Game.Screens.Edit.Setup [Resolved] private IBindable working { get; set; } = null!; - [Resolved] - private EditorBeatmap editorBeatmap { get; set; } = null!; - [Resolved] private Editor? editor { get; set; } @@ -114,25 +111,17 @@ namespace osu.Game.Screens.Edit.Setup { newFilename = $"{baseFilename}{source.Extension}"; - foreach (var beatmapInSet in set.Beatmaps) + foreach (var beatmap in set.Beatmaps) { - string filenameInBeatmap = readFilename(beatmapInSet.Metadata); + if (set.GetFile(readFilename(beatmap.Metadata)) is RealmNamedFileUsage otherExistingFile) + beatmaps.DeleteFile(set, otherExistingFile); - if (set.GetFile(filenameInBeatmap) is RealmNamedFileUsage existingFile) - beatmaps.DeleteFile(set, existingFile); - - if (filenameInBeatmap != newFilename) - { - writeFilename(beatmapInSet.Metadata, newFilename); - - if (!beatmapInSet.Equals(working.Value.BeatmapInfo)) - beatmaps.Save(beatmapInSet, beatmaps.GetWorkingBeatmap(beatmapInSet).Beatmap); - } + writeFilename(beatmap.Metadata, newFilename); } } else { - var beatmap = working.Value.BeatmapInfo; + var thisBeatmap = working.Value.BeatmapInfo; string[] filenames = set.Files.Select(f => f.Filename).Where(f => f.StartsWith(baseFilename, StringComparison.OrdinalIgnoreCase) && @@ -142,7 +131,7 @@ namespace osu.Game.Screens.Edit.Setup var oldFile = set.GetFile(currentFilename); - if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => readFilename(b.Metadata) != currentFilename)) + if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(thisBeatmap)).All(b => readFilename(b.Metadata) != currentFilename)) { beatmaps.DeleteFile(set, oldFile); newFilename = currentFilename; @@ -157,7 +146,9 @@ namespace osu.Game.Screens.Edit.Setup using (var stream = source.OpenRead()) beatmaps.AddFile(set, stream, newFilename); - editorBeatmap.SaveState(); + // editor change handler cannot be aware of any file changes or other difficulties having their metadata modified. + // for simplicity's sake, trigger a save when changing any resource to ensure the change is correctly saved. + editor?.Save(); } private void backgroundChanged(ValueChangedEvent file) From 276c37bcf77b19762c84b9d4c89251597a492ea8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2024 13:47:56 +0900 Subject: [PATCH 158/223] 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 4699beeac0..02898623a9 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 ccae4a15ee..80e695e5d1 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From b697ddc6db70de3ff8a7f07a9f734de66ea7f694 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2024 15:32:35 +0900 Subject: [PATCH 159/223] Simplify the dev footer display --- osu.Game/OsuGame.cs | 14 ++--- osu.Game/Overlays/DevBuildBanner.cs | 58 ++++++++++++++++++ osu.Game/Overlays/VersionManager.cs | 95 ----------------------------- osu.Game/Screens/Menu/MainMenu.cs | 13 ---- 4 files changed, 64 insertions(+), 116 deletions(-) create mode 100644 osu.Game/Overlays/DevBuildBanner.cs delete mode 100644 osu.Game/Overlays/VersionManager.cs diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a92b1f4d36..2e3b989c4e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -195,7 +195,8 @@ namespace osu.Game private MainMenu menuScreen; - private VersionManager versionManager; + [CanBeNull] + private DevBuildBanner devBuildBanner; [CanBeNull] private IntroScreen introScreen; @@ -1055,10 +1056,7 @@ namespace osu.Game }, topMostOverlayContent.Add); if (!IsDeployedBuild) - { - dependencies.Cache(versionManager = new VersionManager()); - loadComponentSingleFile(versionManager, ScreenContainer.Add); - } + loadComponentSingleFile(devBuildBanner = new DevBuildBanner(), ScreenContainer.Add); loadComponentSingleFile(osuLogo, _ => { @@ -1564,12 +1562,12 @@ namespace osu.Game { case IntroScreen intro: introScreen = intro; - versionManager?.Show(); + devBuildBanner?.Show(); break; case MainMenu menu: menuScreen = menu; - versionManager?.Show(); + devBuildBanner?.Show(); break; case Player player: @@ -1577,7 +1575,7 @@ namespace osu.Game break; default: - versionManager?.Hide(); + devBuildBanner?.Hide(); break; } diff --git a/osu.Game/Overlays/DevBuildBanner.cs b/osu.Game/Overlays/DevBuildBanner.cs new file mode 100644 index 0000000000..f514483e76 --- /dev/null +++ b/osu.Game/Overlays/DevBuildBanner.cs @@ -0,0 +1,58 @@ +// 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.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Overlays +{ + public partial class DevBuildBanner : VisibilityContainer + { + [BackgroundDependencyLoader] + private void load(OsuColour colours, TextureStore textures, OsuGameBase game) + { + AutoSizeAxes = Axes.Both; + + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + + Alpha = 0; + + AddRange(new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Font = OsuFont.Numeric.With(weight: FontWeight.Bold, size: 12), + Colour = colours.YellowDark, + Text = @"DEVELOPER BUILD", + }, + new Sprite + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Texture = textures.Get(@"Menu/dev-build-footer"), + Scale = new Vector2(0.4f, 1), + Y = 2, + }, + }); + } + + protected override void PopIn() + { + this.FadeIn(1400, Easing.OutQuint); + } + + protected override void PopOut() + { + this.FadeOut(500, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Overlays/VersionManager.cs b/osu.Game/Overlays/VersionManager.cs deleted file mode 100644 index 71f8fc05aa..0000000000 --- a/osu.Game/Overlays/VersionManager.cs +++ /dev/null @@ -1,95 +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.Development; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Overlays -{ - public partial class VersionManager : VisibilityContainer - { - [BackgroundDependencyLoader] - private void load(OsuColour colours, TextureStore textures, OsuGameBase game) - { - AutoSizeAxes = Axes.Both; - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; - - Alpha = 0; - - FillFlowContainer mainFill; - - Children = new Drawable[] - { - mainFill = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Children = new Drawable[] - { - new OsuSpriteText - { - Font = OsuFont.GetFont(weight: FontWeight.Bold), - Text = game.Name - }, - new OsuSpriteText - { - Colour = DebugUtils.IsDebugBuild ? colours.Red : Color4.White, - Text = game.Version - }, - } - }, - } - } - }; - - if (!game.IsDeployedBuild) - { - mainFill.AddRange(new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Font = OsuFont.Numeric.With(size: 12), - Colour = colours.Yellow, - Text = @"Development Build" - }, - new Sprite - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Texture = textures.Get(@"Menu/dev-build-footer"), - }, - }); - } - } - - protected override void PopIn() - { - this.FadeIn(1400, Easing.OutQuint); - } - - protected override void PopOut() - { - this.FadeOut(500, Easing.OutQuint); - } - } -} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 35c6bab81b..c753a52657 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -79,9 +79,6 @@ namespace osu.Game.Screens.Menu [Resolved(canBeNull: true)] private IDialogOverlay dialogOverlay { get; set; } - [Resolved(canBeNull: true)] - private VersionManager versionManager { get; set; } - protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(); protected override bool PlayExitSound => false; @@ -294,16 +291,6 @@ namespace osu.Game.Screens.Menu } } - protected override void Update() - { - base.Update(); - - bottomElementsFlow.Margin = new MarginPadding - { - Bottom = (versionManager?.DrawHeight + 5) ?? 0 - }; - } - protected override void LogoSuspending(OsuLogo logo) { var seq = logo.FadeOut(300, Easing.InSine) From 110e4fbb30503114779e18348e098d062a9ea378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Nov 2024 15:37:27 +0100 Subject: [PATCH 160/223] Centralise supported file extensions to one helper class As proposed in https://github.com/ppy/osu-server-beatmap-submission/pull/5#discussion_r1861680837. --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 3 ++- .../Formats/LegacyStoryboardDecoder.cs | 3 ++- .../UserInterfaceV2/OsuFileSelector.cs | 23 ++++++++----------- osu.Game/OsuGameBase.cs | 2 -- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 3 ++- .../Edit/Checks/Components/AudioCheckUtils.cs | 5 ++-- .../Screens/Edit/Setup/ResourcesSection.cs | 7 ++++-- osu.Game/Skinning/SkinnableSprite.cs | 11 +++++---- osu.Game/Storyboards/Storyboard.cs | 5 ++-- osu.Game/Utils/SupportedExtensions.cs | 19 +++++++++++++++ 11 files changed, 50 insertions(+), 33 deletions(-) create mode 100644 osu.Game/Utils/SupportedExtensions.cs diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f1ce977d96..07fcdb9d62 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -408,7 +408,7 @@ namespace osu.Game.Beatmaps // user requested abort return; - var video = b.Files.FirstOrDefault(f => OsuGameBase.VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.OrdinalIgnoreCase))); + var video = b.Files.FirstOrDefault(f => SupportedExtensions.VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.OrdinalIgnoreCase))); if (video != null) { diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 4d7ac355e0..d6c658f824 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit; +using osu.Game.Utils; namespace osu.Game.Beatmaps.Formats { @@ -446,7 +447,7 @@ namespace osu.Game.Beatmaps.Formats // Some very old beatmaps had incorrect type specifications for their backgrounds (ie. using 1 for VIDEO // instead of 0 for BACKGROUND). To handle this gracefully, check the file extension against known supported // video extensions and handle similar to a background if it doesn't match. - if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(filename).ToLowerInvariant())) + if (!SupportedExtensions.VIDEO_EXTENSIONS.Contains(Path.GetExtension(filename).ToLowerInvariant())) { beatmap.BeatmapInfo.Metadata.BackgroundFile = filename; lineSupportedByEncoder = true; diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 2f9a256d31..fe9a852faf 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -10,6 +10,7 @@ using osu.Game.Beatmaps.Legacy; using osu.Game.IO; using osu.Game.Storyboards; using osu.Game.Storyboards.Commands; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -112,7 +113,7 @@ namespace osu.Game.Beatmaps.Formats // // This avoids potential weird crashes when ffmpeg attempts to parse an image file as a video // (see https://github.com/ppy/osu/issues/22829#issuecomment-1465552451). - if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(path).ToLowerInvariant())) + if (!SupportedExtensions.VIDEO_EXTENSIONS.Contains(Path.GetExtension(path).ToLowerInvariant())) break; storyboard.GetLayer("Video").Add(storyboardSprite = new StoryboardVideo(path, offset)); diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs index c7b559d9ed..addea5c4a9 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2.FileSelection; using osu.Game.Overlays; +using osu.Game.Utils; namespace osu.Game.Graphics.UserInterfaceV2 { @@ -96,24 +97,18 @@ namespace osu.Game.Graphics.UserInterfaceV2 { get { - if (OsuGameBase.VIDEO_EXTENSIONS.Contains(File.Extension.ToLowerInvariant())) + string extension = File.Extension.ToLowerInvariant(); + + if (SupportedExtensions.VIDEO_EXTENSIONS.Contains(extension)) return FontAwesome.Regular.FileVideo; - switch (File.Extension) - { - case @".ogg": - case @".mp3": - case @".wav": - return FontAwesome.Regular.FileAudio; + if (SupportedExtensions.AUDIO_EXTENSIONS.Contains(extension)) + return FontAwesome.Regular.FileAudio; - case @".jpg": - case @".jpeg": - case @".png": - return FontAwesome.Regular.FileImage; + if (SupportedExtensions.IMAGE_EXTENSIONS.Contains(extension)) + return FontAwesome.Regular.FileImage; - default: - return FontAwesome.Regular.File; - } + return FontAwesome.Regular.File; } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index dc13924b4f..b028280774 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -73,8 +73,6 @@ namespace osu.Game [Cached(typeof(OsuGameBase))] public partial class OsuGameBase : Framework.Game, ICanAcceptFiles, IBeatSyncProvider { - public static readonly string[] VIDEO_EXTENSIONS = { ".mp4", ".mov", ".avi", ".flv", ".mpg", ".wmv", ".m4v" }; - #if DEBUG public const string GAME_NAME = "osu! (development)"; #else diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index d0ee2ccd71..18e01e2490 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -35,6 +35,7 @@ using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Skinning; using osu.Framework.Graphics.Cursor; using osu.Game.Input.Bindings; +using osu.Game.Utils; namespace osu.Game.Overlays.SkinEditor { @@ -709,7 +710,7 @@ namespace osu.Game.Overlays.SkinEditor Task ICanAcceptFiles.Import(ImportTask[] tasks, ImportParameters parameters) => throw new NotImplementedException(); - public IEnumerable HandledExtensions => new[] { ".jpg", ".jpeg", ".png" }; + public IEnumerable HandledExtensions => SupportedExtensions.IMAGE_EXTENSIONS; #endregion diff --git a/osu.Game/Rulesets/Edit/Checks/Components/AudioCheckUtils.cs b/osu.Game/Rulesets/Edit/Checks/Components/AudioCheckUtils.cs index b8cbe63c1e..8a35b84170 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/AudioCheckUtils.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/AudioCheckUtils.cs @@ -3,13 +3,12 @@ using System.IO; using System.Linq; +using osu.Game.Utils; namespace osu.Game.Rulesets.Edit.Checks.Components { public static class AudioCheckUtils { - public static readonly string[] AUDIO_EXTENSIONS = { "mp3", "ogg", "wav" }; - - public static bool HasAudioExtension(string filename) => AUDIO_EXTENSIONS.Any(Path.GetExtension(filename).ToLowerInvariant().EndsWith); + public static bool HasAudioExtension(string filename) => SupportedExtensions.AUDIO_EXTENSIONS.Contains(Path.GetExtension(filename).ToLowerInvariant()); } } diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 845c21b598..daed658e3b 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -10,6 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osu.Game.Localisation; +using osu.Game.Utils; namespace osu.Game.Screens.Edit.Setup { @@ -48,12 +49,14 @@ namespace osu.Game.Screens.Edit.Setup Children = new Drawable[] { - backgroundChooser = new FormFileSelector(".jpg", ".jpeg", ".png") + backgroundChooser = new FormFileSelector(SupportedExtensions.IMAGE_EXTENSIONS) { Caption = GameplaySettingsStrings.BackgroundHeader, PlaceholderText = EditorSetupStrings.ClickToSelectBackground, }, - audioTrackChooser = new FormFileSelector(".mp3", ".ogg") + // `SupportedExtensions.AUDIO_EXTENSIONS` not used here specifically it includes `.wav` for samples, which is strictly disallowed by ranking criteria + // (https://osu.ppy.sh/wiki/en/Ranking_criteria#audio) + audioTrackChooser = new FormFileSelector(@".mp3", @".ogg") { Caption = EditorSetupStrings.AudioTrack, PlaceholderText = EditorSetupStrings.ClickToSelectTrack, diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index 9effb483c4..47618f6296 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -1,8 +1,8 @@ // 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.IO; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -14,6 +14,7 @@ using osu.Game.Configuration; using osu.Game.Graphics.Sprites; using osu.Game.Localisation.SkinComponents; using osu.Game.Overlays.Settings; +using osu.Game.Utils; using osuTK; namespace osu.Game.Skinning @@ -93,10 +94,10 @@ namespace osu.Game.Skinning // but that requires further thought. var highestPrioritySkin = getHighestPriorityUserSkin(((SkinnableSprite)SettingSourceObject).source.AllSources) as Skin; - string[]? availableFiles = highestPrioritySkin?.SkinInfo.PerformRead(s => s.Files - .Where(f => f.Filename.EndsWith(".png", StringComparison.Ordinal) - || f.Filename.EndsWith(".jpg", StringComparison.Ordinal)) - .Select(f => f.Filename).Distinct()).ToArray(); + string[]? availableFiles = highestPrioritySkin?.SkinInfo.PerformRead( + s => s.Files + .Where(f => SupportedExtensions.IMAGE_EXTENSIONS.Contains(Path.GetExtension(f.Filename).ToLowerInvariant())) + .Select(f => f.Filename).Distinct()).ToArray(); if (availableFiles?.Length > 0) Items = availableFiles; diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 8c43b99702..4d456f7360 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Storyboards.Drawables; +using osu.Game.Utils; namespace osu.Game.Storyboards { @@ -96,8 +97,6 @@ namespace osu.Game.Storyboards public virtual DrawableStoryboard CreateDrawable(IReadOnlyList? mods = null) => new DrawableStoryboard(this, mods); - private static readonly string[] image_extensions = { @".png", @".jpg" }; - public virtual string? GetStoragePathFromStoryboardPath(string path) { string? resolvedPath = null; @@ -109,7 +108,7 @@ namespace osu.Game.Storyboards else { // Some old storyboards don't include a file extension, so let's best guess at one. - foreach (string ext in image_extensions) + foreach (string ext in SupportedExtensions.IMAGE_EXTENSIONS) { if ((resolvedPath = BeatmapInfo.BeatmapSet?.GetPathForFile($"{path}{ext}")) != null) break; diff --git a/osu.Game/Utils/SupportedExtensions.cs b/osu.Game/Utils/SupportedExtensions.cs new file mode 100644 index 0000000000..ec1538a041 --- /dev/null +++ b/osu.Game/Utils/SupportedExtensions.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Utils +{ + public static class SupportedExtensions + { + public static readonly string[] VIDEO_EXTENSIONS = [@".mp4", @".mov", @".avi", @".flv", @".mpg", @".wmv", @".m4v"]; + public static readonly string[] AUDIO_EXTENSIONS = [@".mp3", @".ogg", @".wav"]; + public static readonly string[] IMAGE_EXTENSIONS = [@".jpg", @".jpeg", @".png"]; + + public static readonly string[] ALL_EXTENSIONS = + [ + ..VIDEO_EXTENSIONS, + ..AUDIO_EXTENSIONS, + ..IMAGE_EXTENSIONS + ]; + } +} From 5a9127dfc6568d537e453259bac841b251c448de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 29 Nov 2024 08:46:08 +0100 Subject: [PATCH 161/223] Accidentally a word --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index daed658e3b..f02e4bbb28 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Edit.Setup Caption = GameplaySettingsStrings.BackgroundHeader, PlaceholderText = EditorSetupStrings.ClickToSelectBackground, }, - // `SupportedExtensions.AUDIO_EXTENSIONS` not used here specifically it includes `.wav` for samples, which is strictly disallowed by ranking criteria + // `SupportedExtensions.AUDIO_EXTENSIONS` not used here specifically because it includes `.wav` for samples, which is strictly disallowed by ranking criteria // (https://osu.ppy.sh/wiki/en/Ranking_criteria#audio) audioTrackChooser = new FormFileSelector(@".mp3", @".ogg") { From 5f092811cb4d984a84d2bcc5cc1a7a7d43765d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 29 Nov 2024 09:22:29 +0100 Subject: [PATCH 162/223] Use helper in one more place --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index f02e4bbb28..59a0520a52 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -54,9 +54,7 @@ namespace osu.Game.Screens.Edit.Setup Caption = GameplaySettingsStrings.BackgroundHeader, PlaceholderText = EditorSetupStrings.ClickToSelectBackground, }, - // `SupportedExtensions.AUDIO_EXTENSIONS` not used here specifically because it includes `.wav` for samples, which is strictly disallowed by ranking criteria - // (https://osu.ppy.sh/wiki/en/Ranking_criteria#audio) - audioTrackChooser = new FormFileSelector(@".mp3", @".ogg") + audioTrackChooser = new FormFileSelector(SupportedExtensions.AUDIO_EXTENSIONS) { Caption = EditorSetupStrings.AudioTrack, PlaceholderText = EditorSetupStrings.ClickToSelectTrack, From 3cfa455369c432b69f11a8a7f121abb0d8fac476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 29 Nov 2024 10:54:32 +0100 Subject: [PATCH 163/223] Fix strong drum rolls being counted for double the combo in legacy scoring attributes --- .../Difficulty/TaikoLegacyScoreSimulator.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs index 9839d94277..416a11c2a8 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs @@ -144,6 +144,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty foreach (var nested in hitObject.NestedHitObjects) simulateHit(nested, ref attributes); return; + + case StrongNestedHitObject: + // we never need to deal with these directly. + // the only thing strong hits do in terms of scoring is double their object's score increase, + // which is already handled at the parent object level via the `strongable.IsStrong` check lower down in this method. + // not handling these here can lead to them falsely being counted as combo-increasing when handling strong drum rolls! + return; } if (hitObject is DrumRollTick tick) From 0e1b62ef8521d3cec72b0c60fbc557d25e94762a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2024 21:10:27 +0900 Subject: [PATCH 164/223] Expose more migration helper methods For use in https://github.com/ppy/osu-queue-score-statistics/pull/305. Some of these might look a bit odd, but I personally still prefer having them all in one central location. --- .../StandardisedScoreMigrationTools.cs | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index db44731bed..8181c56876 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -245,7 +245,7 @@ namespace osu.Game.Database var scoreProcessor = ruleset.CreateScoreProcessor(); // warning: ordering is important here - both total score and ranks are dependent on accuracy! - score.Accuracy = computeAccuracy(score, scoreProcessor); + score.Accuracy = ComputeAccuracy(score, scoreProcessor); score.Rank = computeRank(score, scoreProcessor); (score.TotalScoreWithoutMods, score.TotalScore) = convertFromLegacyTotalScore(score, ruleset, beatmap); } @@ -269,7 +269,7 @@ namespace osu.Game.Database var scoreProcessor = ruleset.CreateScoreProcessor(); // warning: ordering is important here - both total score and ranks are dependent on accuracy! - score.Accuracy = computeAccuracy(score, scoreProcessor); + score.Accuracy = ComputeAccuracy(score, scoreProcessor); score.Rank = computeRank(score, scoreProcessor); (score.TotalScoreWithoutMods, score.TotalScore) = convertFromLegacyTotalScore(score, ruleset, difficulty, attributes); } @@ -313,7 +313,8 @@ namespace osu.Game.Database /// The beatmap difficulty. /// The legacy scoring attributes for the beatmap which the score was set on. /// The standardised total score. - private static (long withoutMods, long withMods) convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes) + private static (long withoutMods, long withMods) convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, LegacyBeatmapConversionDifficultyInfo difficulty, + LegacyScoreAttributes attributes) { if (!score.IsLegacyScore) return (score.TotalScoreWithoutMods, score.TotalScore); @@ -620,24 +621,28 @@ namespace osu.Game.Database } } - private static double computeAccuracy(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor) + public static double ComputeAccuracy(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor) + => ComputeAccuracy(scoreInfo.Statistics, scoreInfo.MaximumStatistics, scoreProcessor); + + public static double ComputeAccuracy(IReadOnlyDictionary statistics, IReadOnlyDictionary maximumStatistics, ScoreProcessor scoreProcessor) { - int baseScore = scoreInfo.Statistics.Where(kvp => kvp.Key.AffectsAccuracy()) - .Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key)); - int maxBaseScore = scoreInfo.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy()) - .Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key)); + int baseScore = statistics.Where(kvp => kvp.Key.AffectsAccuracy()) + .Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key)); + int maxBaseScore = maximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy()) + .Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key)); return maxBaseScore == 0 ? 1 : baseScore / (double)maxBaseScore; } - public static ScoreRank ComputeRank(ScoreInfo scoreInfo) => computeRank(scoreInfo, scoreInfo.Ruleset.CreateInstance().CreateScoreProcessor()); + public static ScoreRank ComputeRank(ScoreInfo scoreInfo) => + ComputeRank(scoreInfo.Accuracy, scoreInfo.Statistics, scoreInfo.Mods, scoreInfo.Ruleset.CreateInstance().CreateScoreProcessor()); - private static ScoreRank computeRank(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor) + public static ScoreRank ComputeRank(double accuracy, IReadOnlyDictionary statistics, IList mods, ScoreProcessor scoreProcessor) { - var rank = scoreProcessor.RankFromScore(scoreInfo.Accuracy, scoreInfo.Statistics); + var rank = scoreProcessor.RankFromScore(accuracy, statistics); - foreach (var mod in scoreInfo.Mods.OfType()) - rank = mod.AdjustRank(rank, scoreInfo.Accuracy); + foreach (var mod in mods.OfType()) + rank = mod.AdjustRank(rank, accuracy); return rank; } From a719693d10cb72cb2a098b9d40f968e6578985aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2024 21:21:05 +0900 Subject: [PATCH 165/223] Fix one remaining method call --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 8181c56876..15e3da3c19 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -246,7 +246,7 @@ namespace osu.Game.Database // warning: ordering is important here - both total score and ranks are dependent on accuracy! score.Accuracy = ComputeAccuracy(score, scoreProcessor); - score.Rank = computeRank(score, scoreProcessor); + score.Rank = ComputeRank(score, scoreProcessor); (score.TotalScoreWithoutMods, score.TotalScore) = convertFromLegacyTotalScore(score, ruleset, beatmap); } @@ -270,7 +270,7 @@ namespace osu.Game.Database // warning: ordering is important here - both total score and ranks are dependent on accuracy! score.Accuracy = ComputeAccuracy(score, scoreProcessor); - score.Rank = computeRank(score, scoreProcessor); + score.Rank = ComputeRank(score, scoreProcessor); (score.TotalScoreWithoutMods, score.TotalScore) = convertFromLegacyTotalScore(score, ruleset, difficulty, attributes); } @@ -637,6 +637,9 @@ namespace osu.Game.Database public static ScoreRank ComputeRank(ScoreInfo scoreInfo) => ComputeRank(scoreInfo.Accuracy, scoreInfo.Statistics, scoreInfo.Mods, scoreInfo.Ruleset.CreateInstance().CreateScoreProcessor()); + public static ScoreRank ComputeRank(ScoreInfo scoreInfo, ScoreProcessor processor) => + ComputeRank(scoreInfo.Accuracy, scoreInfo.Statistics, scoreInfo.Mods, processor); + public static ScoreRank ComputeRank(double accuracy, IReadOnlyDictionary statistics, IList mods, ScoreProcessor scoreProcessor) { var rank = scoreProcessor.RankFromScore(accuracy, statistics); From 68f21709a8c314488d5c50e1502cc122ac8c756f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 30 Nov 2024 02:32:09 +0800 Subject: [PATCH 166/223] Fix CA1865 --- .../Visual/Gameplay/TestScenePlayerLocalScoreImport.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 1660f93384..046ae6d953 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -221,7 +221,7 @@ namespace osu.Game.Tests.Visual.Gameplay string? filePath = null; // Files starting with _ are temporary, created by CreateFileSafely call. - AddUntilStep("wait for export file", () => filePath = LocalStorage.GetFiles("exports").SingleOrDefault(f => !Path.GetFileName(f).StartsWith("_", StringComparison.Ordinal)), () => Is.Not.Null); + AddUntilStep("wait for export file", () => filePath = LocalStorage.GetFiles("exports").SingleOrDefault(f => !Path.GetFileName(f).StartsWith('_')), () => Is.Not.Null); AddUntilStep("filesize is non-zero", () => { try From 1e2e364cd3d74281ffb921c7d9542ba82f02d6b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 30 Nov 2024 21:01:22 +0900 Subject: [PATCH 167/223] Stop loudly logging backwards seek bug to sentry Several users have reported stutters when this happens. It's potentially from the error report overhead. We now know that this is a BASS level issue anyway, so having this logging is not helpful. --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index c4feb249f4..92258f3fc9 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -3,19 +3,15 @@ using System; using System.Diagnostics; -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.Logging; -using osu.Framework.Testing; using osu.Framework.Timing; -using osu.Game.Beatmaps; using osu.Game.Input.Handlers; using osu.Game.Screens.Play; -using osu.Game.Utils; namespace osu.Game.Rulesets.UI { @@ -168,13 +164,7 @@ namespace osu.Game.Rulesets.UI if (lastBackwardsSeekLogTime == null || Math.Abs(Clock.CurrentTime - lastBackwardsSeekLogTime.Value) > 1000) { lastBackwardsSeekLogTime = Clock.CurrentTime; - - string loggableContent = $"Denying backwards seek during gameplay (reference: {referenceClock.CurrentTime:N2} stable: {proposedTime:N2})"; - - if (parentGameplayClock is GameplayClockContainer gcc) - loggableContent += $"\n{gcc.ChildrenOfType().Single().GetSnapshot()}"; - - Logger.Error(new SentryOnlyDiagnosticsException("backwards seek"), loggableContent); + Logger.Log($"Denying backwards seek during gameplay (reference: {referenceClock.CurrentTime:N2} stable: {proposedTime:N2})"); } state = PlaybackState.NotValid; From f4e155bfa6a6f8158a578540e9e01621e5b46553 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Sat, 30 Nov 2024 15:19:35 +0100 Subject: [PATCH 168/223] Account for rate changing mods when disabling the "Ready" button --- .../Playlists/PlaylistsReadyButton.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs index a460779ea6..3c7808356c 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; using osu.Framework.Allocation; @@ -10,7 +11,9 @@ using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.Rooms; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Utils; namespace osu.Game.Screens.OnlinePlay.Playlists { @@ -19,6 +22,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [Resolved] private IBindable gameBeatmap { get; set; } = null!; + [Resolved] + private IBindable> mods { get; set; } = null!; + private readonly Room room; public PlaylistsReadyButton(Room room) @@ -63,14 +69,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { base.Update(); - Enabled.Value = hasRemainingAttempts && enoughTimeLeft; + Enabled.Value = hasRemainingAttempts && enoughTimeLeft(); } public override LocalisableString TooltipText { get { - if (!enoughTimeLeft) + if (!enoughTimeLeft()) return "No time left!"; if (!hasRemainingAttempts) @@ -80,9 +86,16 @@ namespace osu.Game.Screens.OnlinePlay.Playlists } } - private bool enoughTimeLeft => + private bool enoughTimeLeft() + { + // this doesn't consider mods which apply variable rates, yet. + double rate = ModUtils.CalculateRateWithMods(mods.Value); + + double hitLength = Math.Round(gameBeatmap.Value.Track.Length / rate); + // This should probably consider the length of the currently selected item, rather than a constant 30 seconds. - room.EndDate != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < room.EndDate; + return room.EndDate != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(hitLength) < room.EndDate; + } protected override void Dispose(bool isDisposing) { From 164b809c8911262c5f8f775b5c8c4c3ce843afc7 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Sat, 30 Nov 2024 23:02:22 +0100 Subject: [PATCH 169/223] Document ready button enable state with some comments --- .../OnlinePlay/Playlists/PlaylistsReadyButton.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs index 3c7808356c..0a4b504749 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs @@ -88,13 +88,15 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private bool enoughTimeLeft() { - // this doesn't consider mods which apply variable rates, yet. + // TODO: This doesn't consider mods which apply variable rates, yet. double rate = ModUtils.CalculateRateWithMods(mods.Value); - double hitLength = Math.Round(gameBeatmap.Value.Track.Length / rate); + // We want to avoid users not being able to submit scores if they chose to not skip, + // so track length is chosen over playable length. + double trackLength = Math.Round(gameBeatmap.Value.Track.Length / rate); - // This should probably consider the length of the currently selected item, rather than a constant 30 seconds. - return room.EndDate != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(hitLength) < room.EndDate; + // Additional 30 second delay added to account for load and/or submit time. + return room.EndDate != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(trackLength) < room.EndDate; } protected override void Dispose(bool isDisposing) From 9140893249037130c9a2bae7bd67be20f8be098a Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sat, 30 Nov 2024 23:36:02 -0500 Subject: [PATCH 170/223] Fix score no longer being saved when quick-restarting after pass --- .../Visual/Mods/TestSceneModFailCondition.cs | 2 +- osu.Game/Screens/Play/Player.cs | 10 +++------- osu.Game/Screens/Play/PlayerLoader.cs | 6 ++---- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs b/osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs index f4732234a7..a7447a92cd 100644 --- a/osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs +++ b/osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Mods protected override TestPlayer CreateModPlayer(Ruleset ruleset) { var player = base.CreateModPlayer(ruleset); - player.RestartRequested = _ => restartRequested = true; + player.PrepareLoaderForRestart = _ => restartRequested = true; return player; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 2d1f602832..cb24b99ce2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -83,7 +83,7 @@ namespace osu.Game.Screens.Play /// protected virtual bool PauseOnFocusLost => true; - public Action RestartRequested; + public Action PrepareLoaderForRestart; private bool isRestarting; private bool skipExitTransition; @@ -719,12 +719,8 @@ namespace osu.Game.Screens.Play // stopping here is to ensure music doesn't become audible after exiting back to PlayerLoader. musicController.Stop(); - if (RestartRequested != null) - { - skipExitTransition = quickRestart; - RestartRequested?.Invoke(quickRestart); - return true; - } + skipExitTransition = quickRestart; + PrepareLoaderForRestart?.Invoke(quickRestart); return PerformExit(quickRestart); } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 0db96b71ad..d2ba5398e4 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -457,7 +457,7 @@ namespace osu.Game.Screens.Play CurrentPlayer = createPlayer(); CurrentPlayer.Configuration.AutomaticallySkipIntro |= quickRestart; CurrentPlayer.RestartCount = restartCount++; - CurrentPlayer.RestartRequested = restartRequested; + CurrentPlayer.PrepareLoaderForRestart = prepareForRestart; LoadTask = LoadComponentAsync(CurrentPlayer, _ => { @@ -470,13 +470,11 @@ namespace osu.Game.Screens.Play { } - private void restartRequested(bool quickRestartRequested) + private void prepareForRestart(bool quickRestartRequested) { quickRestart = quickRestartRequested; hideOverlays = true; ValidForResume = true; - - this.MakeCurrent(); } private void contentIn(double delayBeforeSideDisplays = 0) From 53dce83b56b9c67657c5a8033016150e20c8e939 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 1 Dec 2024 02:11:53 -0500 Subject: [PATCH 171/223] Fix restarting no longer working from results screen Thanks to tests for pointing that out :blobsweat: --- osu.Game/Screens/Play/Player.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index cb24b99ce2..3a0a0613f3 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -722,6 +722,16 @@ namespace osu.Game.Screens.Play skipExitTransition = quickRestart; PrepareLoaderForRestart?.Invoke(quickRestart); + if (!this.IsCurrentScreen()) + { + // if we're called externally (i.e. from results screen), + // use MakeCurrent to exit results screen as well as this player screen + // since ValidForResume = false in here + Debug.Assert(!ValidForResume); + this.MakeCurrent(); + return true; + } + return PerformExit(quickRestart); } From 6afe083ec96f218fbad7638630a6069a761404c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 1 Dec 2024 18:44:26 +0900 Subject: [PATCH 172/223] Fix settings showing up during gameplay --- osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs | 6 ++---- osu.Game/Screens/Play/HUDOverlay.cs | 12 ++++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index e68ca4da7a..18d7f6a503 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -23,17 +23,15 @@ namespace osu.Game.Screens.Play.HUD private const float padding = 10; - public const float CONTRACTED_WIDTH = button_size + padding * 2; public const float EXPANDED_WIDTH = player_settings_width + padding * 2; private const float player_settings_width = 270; - private const float button_size = IconButton.DEFAULT_BUTTON_SIZE; + + private const int fade_duration = 200; public override void Show() => this.FadeIn(fade_duration); public override void Hide() => this.FadeOut(fade_duration); - private const int fade_duration = 200; - // we'll handle this ourselves because we have slightly custom logic. protected override bool ExpandOnHover => false; diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 62d9686aad..1c5277a8d9 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -88,6 +88,7 @@ namespace osu.Game.Screens.Play private readonly FillFlowContainer bottomRightElements; private readonly FillFlowContainer topRightElements; + private readonly Container rightSettings; internal readonly IBindable IsPlaying = new Bindable(); @@ -163,7 +164,14 @@ namespace osu.Game.Screens.Play HoldToQuit = CreateHoldForMenuButton(), } }, - PlayerSettingsOverlay = new PlayerSettingsOverlay(), + rightSettings = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + PlayerSettingsOverlay = new PlayerSettingsOverlay(), + } + }, LeaderboardFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -173,7 +181,7 @@ namespace osu.Game.Screens.Play }, }; - hideTargets = new List { mainComponents, topRightElements, PlayerSettingsOverlay }; + hideTargets = new List { mainComponents, topRightElements, rightSettings }; if (rulesetComponents != null) hideTargets.Add(rulesetComponents); From 23522b02d899a1a73b2ad56452dd932d7ff6ee3e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 1 Dec 2024 19:53:57 +0900 Subject: [PATCH 173/223] Use local instead of field for local only usage --- osu.Game/Screens/Play/HUDOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 1c5277a8d9..fca871e42f 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -88,7 +88,6 @@ namespace osu.Game.Screens.Play private readonly FillFlowContainer bottomRightElements; private readonly FillFlowContainer topRightElements; - private readonly Container rightSettings; internal readonly IBindable IsPlaying = new Bindable(); @@ -116,6 +115,8 @@ namespace osu.Game.Screens.Play public HUDOverlay([CanBeNull] DrawableRuleset drawableRuleset, IReadOnlyList mods, bool alwaysShowLeaderboard = true) { + Container rightSettings; + this.drawableRuleset = drawableRuleset; this.mods = mods; From b14dde937ddc8132cb47cdfca6c3c2ce8426c002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 2 Dec 2024 13:51:41 +0100 Subject: [PATCH 174/223] Add failing test case --- .../Editor/TestSceneOsuComposerSelection.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs index b97fe5c5a8..345965b912 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs @@ -231,6 +231,36 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider still has 2 anchors", () => secondSlider.Path.ControlPoints.Count, () => Is.EqualTo(2)); } + [Test] + public void TestControlClickDoesNotDiscardExistingSelectionEvenIfNothingHit() + { + var firstSlider = new Slider + { + StartTime = 0, + Position = new Vector2(0, 0), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(100)) + } + } + }; + + AddStep("add object", () => EditorBeatmap.AddRange([firstSlider])); + AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.AddRange([firstSlider])); + + AddStep("move mouse to middle of playfield", () => InputManager.MoveMouseTo(blueprintContainer.ScreenSpaceDrawQuad.Centre)); + AddStep("control-click left mouse", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Click(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddAssert("selection preserved", () => EditorBeatmap.SelectedHitObjects.Count, () => Is.EqualTo(1)); + } + private ComposeBlueprintContainer blueprintContainer => Editor.ChildrenOfType().First(); From b505ecc7ba8e44afd38e0ba3cb77edf277d3593c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 2 Dec 2024 13:51:43 +0100 Subject: [PATCH 175/223] Do not deselect objects when control-clicking without hitting anything As per feedback in https://discord.com/channels/90072389919997952/1259818301517725707/1310270647187935284. --- .../Edit/Compose/Components/BlueprintContainer.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index e12574f7ee..4a321f4a81 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -433,7 +433,10 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Finishes the current blueprint selection. /// /// The mouse event which triggered end of selection. - /// Whether a click selection was active. + /// + /// Whether the mouse event is considered to be fully handled. + /// If the return value is , the standard click / mouse up action will follow. + /// private bool endClickSelection(MouseButtonEvent e) { // If already handled a selection, double-click, or drag, we don't want to perform a mouse up / click action. @@ -443,14 +446,16 @@ namespace osu.Game.Screens.Edit.Compose.Components if (e.ControlPressed) { - // if a selection didn't occur, we may want to trigger a deselection. - // Iterate from the top of the input stack (blueprints closest to the front of the screen first). // Priority is given to already-selected blueprints. foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Where(b => b.IsHovered).OrderByDescending(b => b.IsSelected)) return clickSelectionHandled = SelectionHandler.MouseUpSelectionRequested(blueprint, e); - return false; + // can only be reached if there are no hovered blueprints. + // in that case, we still want to suppress mouse up / click handling, because when control is pressed, + // it is presumed we want to add to existing selection, not remove from it + // (unless explicitly control-clicking a selected object, which is handled above). + return true; } if (selectedBlueprintAlreadySelectedOnMouseDown && SelectedItems.Count == 1) From 68f4fa5a5781d1f6d2344cf6041d39c51ef9bf05 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 3 Dec 2024 00:00:31 +0800 Subject: [PATCH 176/223] Turn off CA1507 --- .globalconfig | 4 ++++ osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs | 2 -- .../Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs | 2 -- .../Notifications/WebSocket/Events/NewChatMessageData.cs | 2 -- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.globalconfig b/.globalconfig index e2cd1926f0..ca7b86c778 100644 --- a/.globalconfig +++ b/.globalconfig @@ -50,6 +50,10 @@ dotnet_diagnostic.IDE1006.severity = warning # Too many noisy warnings for parsing/formatting numbers dotnet_diagnostic.CA1305.severity = none +# CA1507: Use nameof to express symbol names +# Flaggs serialization name attributes +dotnet_diagnostic.CA1507.severity = suggestion + # CA1806: Do not ignore method results # The usages for numeric parsing are explicitly optional dotnet_diagnostic.CA1806.severity = suggestion diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs index 8e4cc387ed..583def8eda 100644 --- a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -45,9 +45,7 @@ namespace osu.Game.Online.API.Requests.Responses public KudosuAction Action; -#pragma warning disable CA1507 // Happens to name the same because of casing preference [JsonProperty("action")] -#pragma warning restore CA1507 private string action { set diff --git a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs index 38ad2bd02d..6d5fd59f9c 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs @@ -15,9 +15,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("count")] public int PlayCount { get; set; } -#pragma warning disable CA1507 // Happens to name the same because of casing preference [JsonProperty("beatmap")] -#pragma warning restore CA1507 private APIBeatmap beatmap { get; set; } public APIBeatmap BeatmapInfo diff --git a/osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs b/osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs index 677286bb8a..ff9f5ee9f7 100644 --- a/osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs +++ b/osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs @@ -19,9 +19,7 @@ namespace osu.Game.Online.Notifications.WebSocket.Events [JsonProperty(@"messages")] public List Messages { get; set; } = null!; -#pragma warning disable CA1507 // Happens to name the same because of casing preference [JsonProperty(@"users")] -#pragma warning restore CA1507 private List users { get; set; } = null!; [OnDeserialized] From 7ece8ec1dc466a5f9676b888746c4c33e3140e0f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 3 Dec 2024 00:03:59 +0800 Subject: [PATCH 177/223] Update for suggestions --- osu.Game/Overlays/ChatOverlay.cs | 3 +-- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index a00414522d..c49afa3a66 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -386,9 +386,8 @@ namespace osu.Game.Overlays { channelList.RemoveChannel(channel); - if (loadedChannels.TryGetValue(channel, out var loaded)) + if (loadedChannels.Remove(channel, out var loaded)) { - loadedChannels.Remove(channel); // DrawableChannel removed from cache must be manually disposed loaded.Dispose(); } diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index fb056b457b..83a48599ca 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -173,10 +173,10 @@ namespace osu.Game.Rulesets.Mods }; drawable.OnRevertResult += (_, result) => { - if (!ratesForRewinding.TryGetValue(result.HitObject, out double rewindValue)) return; + if (!ratesForRewinding.TryGetValue(result.HitObject, out double rate)) return; if (!shouldProcessResult(result)) return; - recentRates.Insert(0, rewindValue); + recentRates.Insert(0, rate); ratesForRewinding.Remove(result.HitObject); recentRates.RemoveAt(recentRates.Count - 1); From e920cfa1872d233f90df27f4db76ffd0e75da6a8 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Mon, 2 Dec 2024 23:49:26 +0100 Subject: [PATCH 178/223] Move rate-changing TODO to a common place in CalculateRateWithMods --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs | 1 - osu.Game/Screens/Select/BeatmapInfoWedge.cs | 1 - osu.Game/Utils/ModUtils.cs | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs index 0a4b504749..e72f8be50a 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs @@ -88,7 +88,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private bool enoughTimeLeft() { - // TODO: This doesn't consider mods which apply variable rates, yet. double rate = ModUtils.CalculateRateWithMods(mods.Value); // We want to avoid users not being able to submit scores if they chose to not skip, diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 3b0fdc3e47..fd1c944689 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -401,7 +401,6 @@ namespace osu.Game.Screens.Select if (beatmap == null || bpmLabelContainer == null) return; - // this doesn't consider mods which apply variable rates, yet. double rate = ModUtils.CalculateRateWithMods(mods.Value); int bpmMax = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMaximum, rate); diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index f901f15388..15fc34b468 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -286,6 +286,7 @@ namespace osu.Game.Utils { double rate = 1; + // TODO: This doesn't consider mods which apply variable rates, yet. foreach (var mod in mods.OfType()) rate = mod.ApplyToRate(0, rate); From 2ceb3f6f85e2d592e7b10794b7949de61ff84d6c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Dec 2024 13:43:20 +0900 Subject: [PATCH 179/223] Show an ongoing operation when checking for updates Addresses https://github.com/ppy/osu/discussions/30950. --- osu.Game/Localisation/GeneralSettingsStrings.cs | 5 +++++ .../Overlays/Settings/Sections/General/UpdateSettings.cs | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/osu.Game/Localisation/GeneralSettingsStrings.cs b/osu.Game/Localisation/GeneralSettingsStrings.cs index 42623f4632..83a3af574c 100644 --- a/osu.Game/Localisation/GeneralSettingsStrings.cs +++ b/osu.Game/Localisation/GeneralSettingsStrings.cs @@ -44,6 +44,11 @@ namespace osu.Game.Localisation /// public static LocalisableString CheckUpdate => new TranslatableString(getKey(@"check_update"), @"Check for updates"); + /// + /// "Checking for updates" + /// + public static LocalisableString CheckingForUpdates => new TranslatableString(getKey(@"checking_for_updates"), @"Checking for updates"); + /// /// "Open osu! folder" /// diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 82cc952e53..53567109e3 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -53,8 +53,16 @@ namespace osu.Game.Overlays.Settings.Sections.General Action = () => { checkForUpdatesButton.Enabled.Value = false; + + var checkingNotification = new ProgressNotification { Text = GeneralSettingsStrings.CheckingForUpdates, }; + notifications?.Post(checkingNotification); + Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(task => Schedule(() => { + // This sequence allows the notification to be immediately dismissed. + checkingNotification.State = ProgressNotificationState.Cancelled; + checkingNotification.Close(false); + if (!task.GetResultSafely()) { notifications?.Post(new SimpleNotification From 457957d3b8d9a68b359e15953d4f151a3cc5b44b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Dec 2024 14:20:39 +0900 Subject: [PATCH 180/223] Refactor check-update flow to better handle unobserved exceptions --- .../Sections/General/UpdateSettings.cs | 71 ++++++++++++------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 53567109e3..261103173e 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Logging; @@ -13,6 +12,7 @@ using osu.Framework.Screens; using osu.Framework.Statistics; using osu.Game.Configuration; using osu.Game.Localisation; +using osu.Game.Online.Multiplayer; using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Settings.Sections.Maintenance; using osu.Game.Updater; @@ -36,8 +36,11 @@ namespace osu.Game.Overlays.Settings.Sections.General [Resolved] private Storage storage { get; set; } = null!; + [Resolved] + private OsuGame? game { get; set; } + [BackgroundDependencyLoader] - private void load(OsuConfigManager config, OsuGame? game) + private void load(OsuConfigManager config) { Add(new SettingsEnumDropdown { @@ -50,31 +53,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Add(checkForUpdatesButton = new SettingsButton { Text = GeneralSettingsStrings.CheckUpdate, - Action = () => - { - checkForUpdatesButton.Enabled.Value = false; - - var checkingNotification = new ProgressNotification { Text = GeneralSettingsStrings.CheckingForUpdates, }; - notifications?.Post(checkingNotification); - - Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(task => Schedule(() => - { - // This sequence allows the notification to be immediately dismissed. - checkingNotification.State = ProgressNotificationState.Cancelled; - checkingNotification.Close(false); - - if (!task.GetResultSafely()) - { - notifications?.Post(new SimpleNotification - { - Text = GeneralSettingsStrings.RunningLatestRelease(game!.Version), - Icon = FontAwesome.Solid.CheckCircle, - }); - } - - checkForUpdatesButton.Enabled.Value = true; - })); - } + Action = () => checkForUpdates().FireAndForget() }); } @@ -102,6 +81,44 @@ namespace osu.Game.Overlays.Settings.Sections.General } } + private async Task checkForUpdates() + { + if (updateManager == null || game == null) + return; + + checkForUpdatesButton.Enabled.Value = false; + + var checkingNotification = new ProgressNotification + { + Text = GeneralSettingsStrings.CheckingForUpdates, + }; + notifications?.Post(checkingNotification); + + try + { + bool foundUpdate = await updateManager.CheckForUpdateAsync().ConfigureAwait(true); + + if (!foundUpdate) + { + notifications?.Post(new SimpleNotification + { + Text = GeneralSettingsStrings.RunningLatestRelease(game.Version), + Icon = FontAwesome.Solid.CheckCircle, + }); + } + } + catch + { + } + finally + { + // This sequence allows the notification to be immediately dismissed. + checkingNotification.State = ProgressNotificationState.Cancelled; + checkingNotification.Close(false); + checkForUpdatesButton.Enabled.Value = true; + } + } + private void exportLogs() { ProgressNotification notification = new ProgressNotification From 6ff1dec7b2b9fa2eebcd96620c316ddfc7a67c6e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 3 Dec 2024 15:45:58 +0900 Subject: [PATCH 181/223] Add tests --- .../TestScenePlaylistsRoomSubScreen.cs | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index 5f9e06fda5..de84ca680d 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -5,25 +5,64 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Extensions; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Playlists; +using osu.Game.Tests.Resources; using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Playlists { public partial class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene { + private const double track_length = 10000; + [Resolved] private IAPIProvider api { get; set; } = null!; protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; + private BeatmapManager beatmaps = null!; + private RulesetStore rulesets = null!; + private BeatmapSetInfo? importedSet; + + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, API)); + Dependencies.Cache(Realm); + + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); + + Realm.Write(r => + { + foreach (var set in r.All()) + { + foreach (var b in set.Beatmaps) + { + // These will all have a virtual track length of 1000, see WorkingBeatmap.GetVirtualTrack(). + b.Length = track_length - 1000; + } + } + }); + + importedSet = beatmaps.GetAllUsableBeatmapSets().First(); + } + [Test] public void TestStatusUpdateOnEnter() { @@ -69,5 +108,42 @@ namespace osu.Game.Tests.Visual.Playlists AddAssert("close button present", () => roomScreen.ChildrenOfType().Any()); AddUntilStep("wait for close button to disappear", () => !roomScreen.ChildrenOfType().Any()); } + + [TestCase(120_000, true)] // Definitely enough time. + [TestCase(45_000, true)] // Enough time. + [TestCase(35_000, false)] // Not enough time to complete beatmap after lenience. + [TestCase(20_000, false)] // Not enough time. + [TestCase(5_000, false)] // Not enough time to complete beatmap before lenience. + [TestCase(37_500, true, 2)] // Enough time to complete beatmap after mods are applied. + public void TestReadyButtonEnablementPeriod(int offsetMs, bool enabled, double rate = 1) + { + Room room = null!; + PlaylistsRoomSubScreen roomScreen = null!; + + AddStep("create room", () => + { + RoomManager.AddRoom(room = new Room + { + Name = @"Test Room", + Host = api.LocalUser.Value, + Category = RoomCategory.Normal, + StartDate = DateTimeOffset.Now, + EndDate = DateTimeOffset.Now.AddMilliseconds(offsetMs), + Playlist = + [ + new PlaylistItem(importedSet!.Beatmaps[0]) + { + RequiredMods = rate == 1 + ? [] + : [new APIMod(new OsuModDoubleTime { SpeedChange = { Value = rate } })] + } + ] + }); + }); + + AddStep("push screen", () => LoadScreen(roomScreen = new PlaylistsRoomSubScreen(room))); + AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen()); + AddUntilStep("ready button enabled", () => roomScreen.ChildrenOfType().SingleOrDefault()?.Enabled.Value, () => Is.EqualTo(enabled)); + } } } From 75781e3436adcde31e915da4b3ffa0da01574f47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Dec 2024 16:34:23 +0900 Subject: [PATCH 182/223] Update usages of IPC in line with framework changes --- osu.Desktop/Program.cs | 2 +- .../Visual/Navigation/TestSceneInterProcessCommunication.cs | 2 +- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Tests/CleanRunHeadlessGameHost.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index ebc7509af6..df872ae6c6 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -99,7 +99,7 @@ namespace osu.Desktop var hostOptions = new HostOptions { - IPCPort = !tournamentClient ? OsuGame.IPC_PORT : null, + IPCPipeName = !tournamentClient ? OsuGame.IPC_PIPE_NAME : null, FriendlyGameName = OsuGameBase.GAME_NAME, }; diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs index 83430b5665..be9dc387f2 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Navigation }); AddStep("create IPC sender channels", () => { - ipcSenderHost = new HeadlessGameHost(gameHost.Name, new HostOptions { IPCPort = OsuGame.IPC_PORT }); + ipcSenderHost = new HeadlessGameHost(gameHost.Name, new HostOptions { IPCPipeName = OsuGame.IPC_PIPE_NAME }); osuSchemeLinkIPCSender = new OsuSchemeLinkIPCChannel(ipcSenderHost); archiveImportIPCSender = new ArchiveImportIPCChannel(ipcSenderHost); }); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 08960e3ebb..279530a579 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -87,9 +87,9 @@ namespace osu.Game { #if DEBUG // Different port allows running release and debug builds alongside each other. - public const int IPC_PORT = 44824; + public const string IPC_PIPE_NAME = "osu-lazer-debug"; #else - public const int IPC_PORT = 44823; + public const string IPC_PORT = "osu-lazer"; #endif /// diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs index 00e5b38b1a..df4a91c4e2 100644 --- a/osu.Game/Tests/CleanRunHeadlessGameHost.cs +++ b/osu.Game/Tests/CleanRunHeadlessGameHost.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests [CallerMemberName] string callingMethodName = @"") : base($"{callingMethodName}-{Guid.NewGuid()}", new HostOptions { - IPCPort = bindIPC ? OsuGame.IPC_PORT : null, + IPCPipeName = bindIPC ? OsuGame.IPC_PIPE_NAME : null, }, bypassCleanup: bypassCleanupOnDispose, realtime: realtime) { this.bypassCleanupOnSetup = bypassCleanupOnSetup; From 808934581fa0956e111941efef55fb99f69c54bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 3 Dec 2024 14:17:14 +0100 Subject: [PATCH 183/223] Move bookmarks out of `BeatmapInfo` Not sure why I didn't do that in https://github.com/ppy/osu/pull/28473... --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 ++-- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 4 ++-- .../Visual/Editing/TestSceneEditorSummaryTimeline.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapInfo.cs | 3 --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 4 ++-- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ .../Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs | 2 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ 12 files changed, 26 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index be411128f7..adb1755c11 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -109,9 +109,9 @@ namespace osu.Game.Tests.Beatmaps.Formats 95901, 106450, 116999, 119637, 130186, 140735, 151285, 161834, 164471, 175020, 185570, 196119, 206669, 209306 }; - Assert.AreEqual(expectedBookmarks.Length, beatmap.BeatmapInfo.Bookmarks.Length); + Assert.AreEqual(expectedBookmarks.Length, beatmap.Bookmarks.Length); for (int i = 0; i < expectedBookmarks.Length; i++) - Assert.AreEqual(expectedBookmarks[i], beatmap.BeatmapInfo.Bookmarks[i]); + Assert.AreEqual(expectedBookmarks[i], beatmap.Bookmarks[i]); Assert.AreEqual(1.8, beatmap.DistanceSpacing); Assert.AreEqual(4, beatmap.BeatmapInfo.BeatDivisor); Assert.AreEqual(4, beatmap.GridSize); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index e57a4fff62..c20cf7befd 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -73,9 +73,9 @@ namespace osu.Game.Tests.Beatmaps.Formats 95901, 106450, 116999, 119637, 130186, 140735, 151285, 161834, 164471, 175020, 185570, 196119, 206669, 209306 }; - Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length); + Assert.AreEqual(expectedBookmarks.Length, beatmap.Bookmarks.Length); for (int i = 0; i < expectedBookmarks.Length; i++) - Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]); + Assert.AreEqual(expectedBookmarks[i], beatmap.Bookmarks[i]); Assert.AreEqual(1.8, beatmap.DistanceSpacing); Assert.AreEqual(4, beatmapInfo.BeatDivisor); Assert.AreEqual(4, beatmap.GridSize); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs index 677d3135ba..e584f1b9d7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Editing beatmap.ControlPointInfo.Add(50000, new DifficultyControlPoint { SliderVelocity = 2 }); beatmap.ControlPointInfo.Add(80000, new EffectControlPoint { KiaiMode = true }); beatmap.ControlPointInfo.Add(110000, new EffectControlPoint { KiaiMode = false }); - beatmap.BeatmapInfo.Bookmarks = new[] { 75000, 125000 }; + beatmap.Bookmarks = new[] { 75000, 125000 }; beatmap.Breaks.Add(new ManualBreakPeriod(90000, 120000)); editorBeatmap = new EditorBeatmap(beatmap); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index d8effc2f22..8ea6fa1f51 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -139,6 +139,8 @@ namespace osu.Game.Beatmaps public int CountdownOffset { get; set; } + public int[] Bookmarks { get; set; } = Array.Empty(); + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index 82b40c0318..0cf10c659b 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -85,6 +85,7 @@ namespace osu.Game.Beatmaps beatmap.TimelineZoom = original.TimelineZoom; beatmap.Countdown = original.Countdown; beatmap.CountdownOffset = original.CountdownOffset; + beatmap.Bookmarks = original.Bookmarks; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 2df262eba3..333ec89eab 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -231,9 +231,6 @@ namespace osu.Game.Beatmaps [Obsolete("Use ScoreManager.GetMaximumAchievableComboAsync instead.")] public int? MaxCombo { get; set; } - [Ignored] - public int[] Bookmarks { get; set; } = Array.Empty(); - public int BeatmapVersion; public BeatmapInfo Clone() => (BeatmapInfo)this.Detach().MemberwiseClone(); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 0b5450e5ac..153db6d6b9 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -305,7 +305,7 @@ namespace osu.Game.Beatmaps.Formats switch (pair.Key) { case @"Bookmarks": - beatmap.BeatmapInfo.Bookmarks = pair.Value.Split(',').Select(v => + beatmap.Bookmarks = pair.Value.Split(',').Select(v => { bool result = int.TryParse(v, out int val); return new { result, val }; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 093e76a535..6c855e1346 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -110,8 +110,8 @@ namespace osu.Game.Beatmaps.Formats { writer.WriteLine("[Editor]"); - if (beatmap.BeatmapInfo.Bookmarks.Length > 0) - writer.WriteLine(FormattableString.Invariant($"Bookmarks: {string.Join(',', beatmap.BeatmapInfo.Bookmarks)}")); + if (beatmap.Bookmarks.Length > 0) + writer.WriteLine(FormattableString.Invariant($"Bookmarks: {string.Join(',', beatmap.Bookmarks)}")); writer.WriteLine(FormattableString.Invariant($"DistanceSpacing: {beatmap.DistanceSpacing}")); writer.WriteLine(FormattableString.Invariant($"BeatDivisor: {beatmap.BeatmapInfo.BeatDivisor}")); writer.WriteLine(FormattableString.Invariant($"GridSize: {beatmap.GridSize}")); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 6a41b8ee6c..826d4e19a7 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -107,6 +107,8 @@ namespace osu.Game.Beatmaps /// int CountdownOffset { get; internal set; } + int[] Bookmarks { get; internal set; } + /// /// 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 59b1ac22bc..14acc9b908 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -413,6 +413,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.CountdownOffset = value; } + public int[] Bookmarks + { + get => baseBeatmap.Bookmarks; + set => baseBeatmap.Bookmarks = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs index 189cb4ba4a..04d5a5d618 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts protected override void LoadBeatmap(EditorBeatmap beatmap) { base.LoadBeatmap(beatmap); - foreach (int bookmark in beatmap.BeatmapInfo.Bookmarks) + foreach (int bookmark in beatmap.Bookmarks) Add(new BookmarkVisualisation(bookmark)); } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index b5e18fd38c..66fb5d07fe 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -270,6 +270,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.CountdownOffset = value; } + public int[] Bookmarks + { + get => PlayableBeatmap.Bookmarks; + set => PlayableBeatmap.Bookmarks = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; From d60b7f479801f7a08ea588f17241abaff937ece2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 3 Dec 2024 15:14:22 +0100 Subject: [PATCH 184/223] Implement basic bookmark support in editor --- .../Input/Bindings/GlobalActionContainer.cs | 16 +++++ osu.Game/Localisation/EditorStrings.cs | 32 +++++++++- .../GlobalActionKeyBindingStrings.cs | 20 ++++++ .../Timelines/Summary/Parts/BookmarkPart.cs | 63 ++++++++++++++++--- osu.Game/Screens/Edit/Editor.cs | 62 ++++++++++++++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 12 +++- .../Edit/LegacyEditorBeatmapPatcher.cs | 22 +++++++ 7 files changed, 218 insertions(+), 9 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 02ede0a2f8..42028c044f 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -152,6 +152,10 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Right }, GlobalAction.EditorSeekToNextHitObject), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.Left }, GlobalAction.EditorSeekToPreviousSamplePoint), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.Right }, GlobalAction.EditorSeekToNextSamplePoint), + new KeyBinding(new[] { InputKey.Control, InputKey.B }, GlobalAction.EditorAddBookmark), + new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.B }, GlobalAction.EditorRemoveClosestBookmark), + new KeyBinding(InputKey.None, GlobalAction.EditorSeekToPreviousBookmark), + new KeyBinding(InputKey.None, GlobalAction.EditorSeekToNextBookmark), }; private static IEnumerable editorTestPlayKeyBindings => new[] @@ -476,6 +480,18 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridType))] EditorCycleGridType, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorAddBookmark))] + EditorAddBookmark, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorRemoveClosestBookmark))] + EditorRemoveClosestBookmark, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToPreviousBookmark))] + EditorSeekToPreviousBookmark, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToNextBookmark))] + EditorSeekToNextBookmark, } public enum GlobalActionCategory diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 127bdd8355..3b4026be11 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -154,6 +154,36 @@ namespace osu.Game.Localisation /// public static LocalisableString TimelineShowTicks => new TranslatableString(getKey(@"timeline_show_ticks"), @"Show ticks"); + /// + /// "Bookmarks" + /// + public static LocalisableString Bookmarks => new TranslatableString(getKey(@"bookmarks"), @"Bookmarks"); + + /// + /// "Add bookmark" + /// + public static LocalisableString AddBookmark => new TranslatableString(getKey(@"add_bookmark"), @"Add bookmark"); + + /// + /// "Remove closest bookmark" + /// + public static LocalisableString RemoveClosestBookmark => new TranslatableString(getKey(@"remove_closest_bookmark"), @"Remove closest bookmark"); + + /// + /// "Seek to previous bookmark" + /// + public static LocalisableString SeekToPreviousBookmark => new TranslatableString(getKey(@"seek_to_previous_bookmark"), @"Seek to previous bookmark"); + + /// + /// "Seek to next bookmark" + /// + public static LocalisableString SeekToNextBookmark => new TranslatableString(getKey(@"seek_to_next_bookmark"), @"Seek to next bookmark"); + + /// + /// "Reset bookmarks" + /// + public static LocalisableString ResetBookmarks => new TranslatableString(getKey(@"reset_bookmarks"), @"Reset bookmarks"); + private static string getKey(string key) => $@"{prefix}:{key}"; } -} +} \ No newline at end of file diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index ed80704a0a..f9db0461ce 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -429,6 +429,26 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorSeekToNextSamplePoint => new TranslatableString(getKey(@"editor_seek_to_next_sample_point"), @"Seek to next sample point"); + /// + /// "Add bookmark" + /// + public static LocalisableString EditorAddBookmark => new TranslatableString(getKey(@"editor_add_bookmark"), @"Add bookmark"); + + /// + /// "Remove closest bookmark" + /// + public static LocalisableString EditorRemoveClosestBookmark => new TranslatableString(getKey(@"editor_remove_closest_bookmark"), @"Remove closest bookmark"); + + /// + /// "Seek to previous bookmark" + /// + public static LocalisableString EditorSeekToPreviousBookmark => new TranslatableString(getKey(@"editor_seek_to_previous_bookmark"), @"Seek to previous bookmark"); + + /// + /// "Seek to next bookmark" + /// + public static LocalisableString EditorSeekToNextBookmark => new TranslatableString(getKey(@"editor_seek_to_next_bookmark"), @"Seek to next bookmark"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs index 04d5a5d618..4b178dd831 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs @@ -2,7 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Pooling; +using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Extensions; using osu.Game.Graphics; @@ -15,24 +19,69 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// public partial class BookmarkPart : TimelinePart { + private readonly BindableList bookmarks = new BindableList(); + + private DrawablePool pool = null!; + + [BackgroundDependencyLoader] + private void load() + { + AddInternal(pool = new DrawablePool(10)); + } + protected override void LoadBeatmap(EditorBeatmap beatmap) { base.LoadBeatmap(beatmap); - foreach (int bookmark in beatmap.Bookmarks) - Add(new BookmarkVisualisation(bookmark)); + + bookmarks.UnbindAll(); + bookmarks.BindTo(beatmap.Bookmarks); } - private partial class BookmarkVisualisation : PointVisualisation, IHasTooltip + protected override void LoadComplete() { - public BookmarkVisualisation(double startTime) - : base(startTime) + base.LoadComplete(); + bookmarks.BindCollectionChanged((_, _) => { + Clear(disposeChildren: false); + foreach (int bookmark in bookmarks) + Add(pool.Get(v => v.StartTime = bookmark)); + }, true); + } + + private partial class BookmarkVisualisation : PoolableDrawable, IHasTooltip + { + private int startTime; + + public int StartTime + { + get => startTime; + set + { + if (startTime == value) + return; + + startTime = value; + X = startTime; + } } [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.Blue; + private void load(OsuColour colours) + { + RelativePositionAxes = Axes.Both; + RelativeSizeAxes = Axes.Y; - public LocalisableString TooltipText => $"{StartTime.ToEditorFormattedString()} bookmark"; + Anchor = Anchor.CentreLeft; + Origin = Anchor.Centre; + + Width = PointVisualisation.MAX_WIDTH; + Height = 0.4f; + + Colour = colours.Blue; + InternalChild = new FastCircle { RelativeSizeAxes = Axes.Both }; + } + + public LocalisableString TooltipText => $"{((double)StartTime).ToEditorFormattedString()} bookmark"; } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0e4807dc78..a022ca5435 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -422,6 +422,29 @@ namespace osu.Game.Screens.Edit Items = new MenuItem[] { new EditorMenuItem(EditorStrings.SetPreviewPointToCurrent, MenuItemType.Standard, SetPreviewPointToCurrentTime), + new EditorMenuItem(EditorStrings.Bookmarks) + { + Items = new MenuItem[] + { + new EditorMenuItem(EditorStrings.AddBookmark, MenuItemType.Standard, addBookmarkAtCurrentTime) + { + Hotkey = new Hotkey(GlobalAction.EditorAddBookmark), + }, + new EditorMenuItem(EditorStrings.RemoveClosestBookmark, MenuItemType.Destructive, removeBookmarksInProximityToCurrentTime) + { + Hotkey = new Hotkey(GlobalAction.EditorRemoveClosestBookmark) + }, + new EditorMenuItem(EditorStrings.SeekToPreviousBookmark, MenuItemType.Standard, () => seekBookmark(-1)) + { + Hotkey = new Hotkey(GlobalAction.EditorSeekToPreviousBookmark) + }, + new EditorMenuItem(EditorStrings.SeekToNextBookmark, MenuItemType.Standard, () => seekBookmark(1)) + { + Hotkey = new Hotkey(GlobalAction.EditorSeekToNextBookmark) + }, + new EditorMenuItem(EditorStrings.ResetBookmarks, MenuItemType.Destructive, () => editorBeatmap.Bookmarks.Clear()) + } + } } } } @@ -753,6 +776,14 @@ namespace osu.Game.Screens.Edit case GlobalAction.EditorSeekToNextSamplePoint: seekSamplePoint(1); return true; + + case GlobalAction.EditorSeekToPreviousBookmark: + seekBookmark(-1); + return true; + + case GlobalAction.EditorSeekToNextBookmark: + seekBookmark(1); + return true; } if (e.Repeat) @@ -760,6 +791,14 @@ namespace osu.Game.Screens.Edit switch (e.Action) { + case GlobalAction.EditorAddBookmark: + addBookmarkAtCurrentTime(); + return true; + + case GlobalAction.EditorRemoveClosestBookmark: + removeBookmarksInProximityToCurrentTime(); + return true; + case GlobalAction.EditorCloneSelection: Clone(); return true; @@ -792,6 +831,19 @@ namespace osu.Game.Screens.Edit return false; } + private void addBookmarkAtCurrentTime() + { + int bookmark = (int)clock.CurrentTimeAccurate; + int idx = editorBeatmap.Bookmarks.BinarySearch(bookmark); + if (idx < 0) + editorBeatmap.Bookmarks.Insert(~idx, bookmark); + } + + private void removeBookmarksInProximityToCurrentTime() + { + editorBeatmap.Bookmarks.RemoveAll(b => Math.Abs(b - clock.CurrentTimeAccurate) < 2000); + } + public void OnReleased(KeyBindingReleaseEvent e) { } @@ -1127,6 +1179,16 @@ namespace osu.Game.Screens.Edit clock.SeekSmoothlyTo(found.StartTime); } + private void seekBookmark(int direction) + { + int? targetBookmark = direction < 1 + ? editorBeatmap.Bookmarks.Cast().LastOrDefault(b => b < clock.CurrentTimeAccurate) + : editorBeatmap.Bookmarks.Cast().FirstOrDefault(b => b > clock.CurrentTimeAccurate); + + if (targetBookmark != null) + clock.SeekSmoothlyTo(targetBookmark.Value); + } + private void seekSamplePoint(int direction) { double currentTime = clock.CurrentTimeAccurate; diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 66fb5d07fe..44f9646889 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -118,6 +118,14 @@ namespace osu.Game.Screens.Edit playableBeatmap.Breaks.AddRange(Breaks); }); + Bookmarks = new BindableList(playableBeatmap.Bookmarks); + Bookmarks.BindCollectionChanged((_, _) => + { + BeginChange(); + playableBeatmap.Bookmarks = Bookmarks.OrderBy(x => x).Distinct().ToArray(); + EndChange(); + }); + PreviewTime = new BindableInt(BeatmapInfo.Metadata.PreviewTime); PreviewTime.BindValueChanged(s => { @@ -270,7 +278,9 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.CountdownOffset = value; } - public int[] Bookmarks + public readonly BindableList Bookmarks; + + int[] IBeatmap.Bookmarks { get => PlayableBeatmap.Bookmarks; set => PlayableBeatmap.Bookmarks = value; diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index a1ee41fc48..f3d58a3c3c 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -46,6 +46,7 @@ namespace osu.Game.Screens.Edit processHitObjects(result, () => newBeatmap ??= readBeatmap(newState)); processTimingPoints(() => newBeatmap ??= readBeatmap(newState)); processBreaks(() => newBeatmap ??= readBeatmap(newState)); + processBookmarks(() => newBeatmap ??= readBeatmap(newState)); processHitObjectLocalData(() => newBeatmap ??= readBeatmap(newState)); editorBeatmap.EndChange(); } @@ -97,6 +98,27 @@ namespace osu.Game.Screens.Edit } } + private void processBookmarks(Func getNewBeatmap) + { + var newBookmarks = getNewBeatmap().Bookmarks.ToHashSet(); + + foreach (int oldBookmark in editorBeatmap.Bookmarks.ToArray()) + { + if (newBookmarks.Contains(oldBookmark)) + continue; + + editorBeatmap.Bookmarks.Remove(oldBookmark); + } + + foreach (int newBookmark in newBookmarks) + { + if (editorBeatmap.Bookmarks.Contains(newBookmark)) + continue; + + editorBeatmap.Bookmarks.Add(newBookmark); + } + } + private void processHitObjects(DiffResult result, Func getNewBeatmap) { findChangedIndices(result, LegacyDecoder.Section.HitObjects, out var removedIndices, out var addedIndices); From 837744b0aa9ffbd2587f332721eef868ff80878d Mon Sep 17 00:00:00 2001 From: Susko3 Date: Tue, 3 Dec 2024 23:26:33 +0000 Subject: [PATCH 185/223] Use LocalisationManager.GetLocalisedString() instead of bindable hack Made possible by https://github.com/ppy/osu-framework/pull/6377. --- osu.Desktop/Windows/WindowsAssociationManager.cs | 10 +--------- .../Overlays/Settings/Sections/Input/TabletSettings.cs | 4 ++-- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index c8066cabda..6f53c65ca9 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -148,15 +148,7 @@ namespace osu.Desktop.Windows foreach (var association in uri_associations) association.UpdateDescription(getLocalisedString(association.Description)); - string getLocalisedString(LocalisableString s) - { - if (localisation == null) - return s.ToString(); - - var b = localisation.GetLocalisedBindableString(s); - b.UnbindAll(); - return b.Value; - } + string getLocalisedString(LocalisableString s) => localisation?.GetLocalisedString(s) ?? s.ToString(); } #region Native interop diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 4c9320c2a6..00ffbc1120 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -114,10 +114,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux) { t.NewLine(); - var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedBindableString(TabletSettingsStrings.NoTabletDetectedDescription( + var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedString(TabletSettingsStrings.NoTabletDetectedDescription( RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? @"https://opentabletdriver.net/Wiki/FAQ/Windows" - : @"https://opentabletdriver.net/Wiki/FAQ/Linux")).Value); + : @"https://opentabletdriver.net/Wiki/FAQ/Linux"))); t.AddLinks(formattedSource.Text, formattedSource.Links); } }), From 296fa69edd24c658d7525e8fe903923abb874bfc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Dec 2024 14:30:59 +0900 Subject: [PATCH 186/223] Add "buttons" as a search term for key bindings --- osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs index a93e6c37af..704fa6e907 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { protected override LocalisableString Header => BindingSettingsStrings.ShortcutAndGameplayBindings; - public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { @"keybindings", @"controls", @"keyboard", @"keys" }); + public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { @"keybindings", @"controls", @"keyboard", @"keys", @"buttons" }); public BindingSettings(KeyBindingPanel keyConfig) { From a4d58648e23e19ef9cda687dbb5127ba17becc14 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Dec 2024 14:31:39 +0900 Subject: [PATCH 187/223] Fix quick retry transition from results screen --- osu.Game/Screens/Ranking/ResultsScreen.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 0209fbd39c..507d138d90 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -55,6 +55,8 @@ namespace osu.Game.Screens.Ranking [Resolved] private Player? player { get; set; } + private bool skipExitTransition; + [Resolved] private IAPIProvider api { get; set; } = null!; @@ -203,6 +205,7 @@ namespace osu.Game.Screens.Ranking { if (!this.IsCurrentScreen()) return; + skipExitTransition = true; player?.Restart(true); }, }); @@ -313,7 +316,8 @@ namespace osu.Game.Screens.Ranking // HitObject references from HitEvent. Score?.HitEvents.Clear(); - this.FadeOut(100); + if (!skipExitTransition) + this.FadeOut(100); return false; } From ad4df82593e334b9b9c0522326655479077717f8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 4 Dec 2024 16:26:36 +0900 Subject: [PATCH 188/223] Improve multiplayer listing search by making it fuzzy --- .../Lounge/Components/RoomsContainer.cs | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 17aed021b2..6eda993f94 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; +using System.Globalization; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -80,19 +81,34 @@ 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; - - if (!string.IsNullOrEmpty(criteria.SearchString)) - { - // Room name isn't translatable, so ToString() is used here for simplicity. - matchingFilter &= r.FilterTerms.Any(term => term.ToString().Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); - } - matchingFilter &= matchPermissions(r, criteria.Permissions); + // Room name isn't translatable, so ToString() is used here for simplicity. + string[] filterTerms = r.FilterTerms.Select(t => t.ToString()).ToArray(); + string[] searchTerms = criteria.SearchString.Split(' ', StringSplitOptions.RemoveEmptyEntries); + matchingFilter &= searchTerms.All(searchTerm => filterTerms.Any(filterTerm => checkTerm(filterTerm, searchTerm))); + r.MatchingFilter = matchingFilter; } }); + // Lifted from SearchContainer. + static bool checkTerm(string haystack, string needle) + { + int index = 0; + + for (int i = 0; i < needle.Length; i++) + { + int found = CultureInfo.InvariantCulture.CompareInfo.IndexOf(haystack, needle[i], index, CompareOptions.OrdinalIgnoreCase); + if (found < 0) + return false; + + index = found + 1; + } + + return true; + } + static bool matchPermissions(DrawableLoungeRoom room, RoomPermissionsFilter accessType) { switch (accessType) From 2a6fbb59ffec80028e1b313a2a331c2d16386adb Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 4 Dec 2024 02:12:05 -0500 Subject: [PATCH 189/223] Add failing test case --- .../Editing/TestSceneEditorBeatmapCreation.cs | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index db87987815..ddf6502899 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -19,6 +19,7 @@ using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; @@ -154,6 +155,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName); AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 })); + AddStep("add effect point", () => EditorBeatmap.ControlPointInfo.Add(500, new EffectControlPoint { KiaiMode = true })); AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { new HitCircle @@ -200,6 +202,11 @@ namespace osu.Game.Tests.Visual.Editing var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single(); return timingPoint.Time == 0 && timingPoint.BeatLength == 1000; }); + AddAssert("created difficulty has effect points", () => + { + var effectPoint = EditorBeatmap.ControlPointInfo.EffectPoints.Single(); + return effectPoint.Time == 500 && effectPoint.KiaiMode && effectPoint.ScrollSpeedBindable.IsDefault; + }); AddAssert("created difficulty has no objects", () => EditorBeatmap.HitObjects.Count == 0); AddAssert("status is modified", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.LocallyModified); @@ -219,6 +226,104 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestCreateNewDifficultyWithScrollSpeed_SameRuleset() + { + string firstDifficultyName = Guid.NewGuid().ToString(); + + AddStep("save beatmap", () => Editor.Save()); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new ManiaRuleset().RulesetInfo)); + + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName); + AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 })); + AddStep("add effect points", () => + { + EditorBeatmap.ControlPointInfo.Add(250, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.05 }); + EditorBeatmap.ControlPointInfo.Add(500, new EffectControlPoint { KiaiMode = true, ScrollSpeed = 0.1 }); + EditorBeatmap.ControlPointInfo.Add(750, new EffectControlPoint { KiaiMode = true, ScrollSpeed = 0.15 }); + EditorBeatmap.ControlPointInfo.Add(1000, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.2 }); + EditorBeatmap.ControlPointInfo.Add(1500, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.3 }); + }); + + AddStep("save beatmap", () => Editor.Save()); + + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new ManiaRuleset().RulesetInfo)); + + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName != firstDifficultyName; + }); + + AddAssert("created difficulty has timing point", () => + { + var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single(); + return timingPoint.Time == 0 && timingPoint.BeatLength == 1000; + }); + + AddAssert("created difficulty has effect points", () => + { + return EditorBeatmap.ControlPointInfo.EffectPoints.SequenceEqual(new[] + { + new EffectControlPoint { Time = 250, KiaiMode = false, ScrollSpeed = 0.05 }, + new EffectControlPoint { Time = 500, KiaiMode = true, ScrollSpeed = 0.1 }, + new EffectControlPoint { Time = 750, KiaiMode = true, ScrollSpeed = 0.15 }, + new EffectControlPoint { Time = 1000, KiaiMode = false, ScrollSpeed = 0.2 }, + new EffectControlPoint { Time = 1500, KiaiMode = false, ScrollSpeed = 0.3 }, + }); + }); + } + + [Test] + public void TestCreateNewDifficultyWithScrollSpeed_DifferentRuleset() + { + string firstDifficultyName = Guid.NewGuid().ToString(); + + AddStep("save beatmap", () => Editor.Save()); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new ManiaRuleset().RulesetInfo)); + + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName); + AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 })); + AddStep("add effect points", () => + { + EditorBeatmap.ControlPointInfo.Add(250, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.05 }); + EditorBeatmap.ControlPointInfo.Add(500, new EffectControlPoint { KiaiMode = true, ScrollSpeed = 0.1 }); + EditorBeatmap.ControlPointInfo.Add(750, new EffectControlPoint { KiaiMode = true, ScrollSpeed = 0.15 }); + EditorBeatmap.ControlPointInfo.Add(1000, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.2 }); + EditorBeatmap.ControlPointInfo.Add(1500, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.3 }); + }); + + AddStep("save beatmap", () => Editor.Save()); + + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new TaikoRuleset().RulesetInfo)); + + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName != firstDifficultyName; + }); + + AddAssert("created difficulty has timing point", () => + { + var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single(); + return timingPoint.Time == 0 && timingPoint.BeatLength == 1000; + }); + + AddAssert("created difficulty has effect points", () => + { + // since this difficulty is on another ruleset, scroll speed specifications are completely reset, + // therefore discarding some effect points in the process due to being redundant. + return EditorBeatmap.ControlPointInfo.EffectPoints.SequenceEqual(new[] + { + new EffectControlPoint { Time = 500, KiaiMode = true }, + new EffectControlPoint { Time = 1000, KiaiMode = false }, + }); + }); + } + [Test] public void TestCopyDifficulty() { From e3abbf1177418ced2251ebbd7720a27bfd1691dc Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 4 Dec 2024 02:12:21 -0500 Subject: [PATCH 190/223] Copy effect points across on blank difficulty creation --- osu.Game/Beatmaps/BeatmapManager.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 07fcdb9d62..da556316cd 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -154,9 +154,18 @@ namespace osu.Game.Beatmaps DifficultyName = NamingUtils.GetNextBestName(targetBeatmapSet.Beatmaps.Select(b => b.DifficultyName), "New Difficulty") }; var newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo }; + foreach (var timingPoint in referenceWorkingBeatmap.Beatmap.ControlPointInfo.TimingPoints) newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone()); + foreach (var effectPoint in referenceWorkingBeatmap.Beatmap.ControlPointInfo.EffectPoints) + { + if (!rulesetInfo.Equals(referenceWorkingBeatmap.BeatmapInfo.Ruleset)) + effectPoint.ScrollSpeedBindable.SetDefault(); + + newBeatmap.ControlPointInfo.Add(effectPoint.Time, effectPoint.DeepClone()); + } + return addDifficultyToSet(targetBeatmapSet, newBeatmap, referenceWorkingBeatmap.Skin); } From 06824c1658c714849276f13e614edc084db05536 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 4 Dec 2024 04:20:09 -0500 Subject: [PATCH 191/223] Add failing test case --- .../Editing/TestSceneEditorBeatmapCreation.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 7a390ac131..75759edaea 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -4,15 +4,20 @@ using System; using System.IO; using System.Linq; +using System.Text; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; +using osu.Framework.Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; +using osu.Framework.IO.Stores; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Formats; using osu.Game.Collections; using osu.Game.Database; using osu.Game.Overlays.Dialog; @@ -27,6 +32,7 @@ using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.Setup; +using osu.Game.Skinning; using osu.Game.Storyboards; using osu.Game.Tests.Resources; using osuTK; @@ -527,6 +533,32 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("other files removed", () => !Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg" || f.Filename == "bg (2).jpg")); } + [Test] + public void TestBackgroundFileChangesPreserveOnEncode() + { + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); + AddAssert("set background", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg")); + + createNewDifficulty(); + createNewDifficulty(); + + switchToDifficulty(0); + + AddAssert("set different background on all diff", () => setBackgroundDifferentExtension(applyToAllDifficulties: true, expected: "bg.jpeg")); + AddAssert("all diff uses one background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.BackgroundFile == "bg.jpeg")); + AddAssert("all diff encode same background", () => + { + return Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => + { + var files = new RealmFileStore(Realm, Dependencies.Get().Storage); + using var store = new RealmBackedResourceStore(b.BeatmapSet!.ToLive(Realm), files.Store, Realm); + string[] osu = Encoding.UTF8.GetString(store.Get(b.File!.Filename)).Split(Environment.NewLine); + Assert.That(osu, Does.Contain("0,0,\"bg.jpeg\",0,0")); + return true; + }); + }); + } + [Test] public void TestSingleAudioFile() { @@ -644,6 +676,25 @@ namespace osu.Game.Tests.Visual.Editing }); } + private bool setBackgroundDifferentExtension(bool applyToAllDifficulties, string expected) + { + var setup = Editor.ChildrenOfType().First(); + + return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder => + { + File.Move( + Path.Combine(extractedFolder, @"machinetop_background.jpg"), + Path.Combine(extractedFolder, @"machinetop_background.jpeg")); + + bool success = setup.ChildrenOfType().First().ChangeBackgroundImage( + new FileInfo(Path.Combine(extractedFolder, @"machinetop_background.jpeg")), + applyToAllDifficulties); + + Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected)); + return success; + }); + } + private bool setAudio(bool applyToAllDifficulties, string expected) { var setup = Editor.ChildrenOfType().First(); From 8e0f6fc12dc04a224a9aefb0121f990a8b007af2 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 4 Dec 2024 04:36:00 -0500 Subject: [PATCH 192/223] Re-encode difficulties on resource change --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 3 --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 10 ++++++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 75759edaea..157deef80a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -8,16 +8,13 @@ using System.Text; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; -using osu.Framework.Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; -using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Formats; using osu.Game.Collections; using osu.Game.Database; using osu.Game.Overlays.Dialog; diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 098877ebe7..84107a57e9 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -103,6 +103,7 @@ namespace osu.Game.Screens.Edit.Setup private void changeResource(FileInfo source, bool applyToAllDifficulties, string baseFilename, Func readFilename, Action writeFilename) { + var thisBeatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; string newFilename = string.Empty; @@ -117,12 +118,17 @@ namespace osu.Game.Screens.Edit.Setup beatmaps.DeleteFile(set, otherExistingFile); writeFilename(beatmap.Metadata, newFilename); + + if (!beatmap.Equals(thisBeatmap)) + { + // save the difficulty to re-encode the .osu file, updating any reference of the old filename. + var beatmapWorking = beatmaps.GetWorkingBeatmap(beatmap); + beatmaps.Save(beatmap, beatmapWorking.Beatmap, beatmapWorking.GetSkin()); + } } } else { - var thisBeatmap = working.Value.BeatmapInfo; - string[] filenames = set.Files.Select(f => f.Filename).Where(f => f.StartsWith(baseFilename, StringComparison.OrdinalIgnoreCase) && f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); From fa87df6c6af7879f1bc2e60b276724dddd3c2136 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 4 Dec 2024 04:55:40 -0500 Subject: [PATCH 193/223] Move non-current handling to `PerformExit` Co-authored-by: Dean Herbert --- osu.Game/Screens/Play/Player.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3a0a0613f3..1866ed26ce 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -646,7 +646,6 @@ namespace osu.Game.Screens.Play // import current score if possible. prepareAndImportScoreAsync(); - // Screen may not be current if a restart has been performed. if (this.IsCurrentScreen()) { skipExitTransition = skipTransition; @@ -657,6 +656,12 @@ namespace osu.Game.Screens.Play // - the pause / fail dialog was requested but couldn't be displayed due to the type or state of this Player instance. this.Exit(); } + else + { + // May be restarting from results screen. + if (this.GetChildScreen() != null) + this.MakeCurrent(); + } return true; } @@ -722,16 +727,6 @@ namespace osu.Game.Screens.Play skipExitTransition = quickRestart; PrepareLoaderForRestart?.Invoke(quickRestart); - if (!this.IsCurrentScreen()) - { - // if we're called externally (i.e. from results screen), - // use MakeCurrent to exit results screen as well as this player screen - // since ValidForResume = false in here - Debug.Assert(!ValidForResume); - this.MakeCurrent(); - return true; - } - return PerformExit(quickRestart); } From f83ec721fb31f9f3ac9840c246d6bf3f176fdd96 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 5 Dec 2024 02:41:00 -0500 Subject: [PATCH 194/223] Move latency certifier and import files button outside debug section --- .../Sections/DebugSettings/GeneralSettings.cs | 16 +-------- .../Sections/Maintenance/GeneralSettings.cs | 36 +++++++++++++++++++ .../Settings/Sections/MaintenanceSection.cs | 1 + 3 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs index df46e38491..57f36e2875 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs @@ -5,11 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Localisation; -using osu.Framework.Screens; using osu.Game.Localisation; -using osu.Game.Screens; -using osu.Game.Screens.Import; -using osu.Game.Screens.Utility; namespace osu.Game.Overlays.Settings.Sections.DebugSettings { @@ -18,7 +14,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings protected override LocalisableString Header => CommonStrings.General; [BackgroundDependencyLoader] - private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig, IPerformFromScreenRunner? performer) + private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig) { Children = new Drawable[] { @@ -32,16 +28,6 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings LabelText = DebugSettingsStrings.BypassFrontToBackPass, Current = config.GetBindable(DebugSetting.BypassFrontToBackPass) }, - new SettingsButton - { - Text = DebugSettingsStrings.ImportFiles, - Action = () => performer?.PerformFromScreen(menu => menu.Push(new FileImportScreen())) - }, - new SettingsButton - { - Text = DebugSettingsStrings.RunLatencyCertifier, - Action = () => performer?.PerformFromScreen(menu => menu.Push(new LatencyCertifierScreen())) - } }; } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs new file mode 100644 index 0000000000..f75fc2c8bc --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -0,0 +1,36 @@ +// 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.Localisation; +using osu.Framework.Screens; +using osu.Game.Localisation; +using osu.Game.Screens; +using osu.Game.Screens.Import; +using osu.Game.Screens.Utility; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public partial class GeneralSettings : SettingsSubsection + { + protected override LocalisableString Header => CommonStrings.General; + + [BackgroundDependencyLoader] + private void load(IPerformFromScreenRunner? performer) + { + Children = new[] + { + new SettingsButton + { + Text = DebugSettingsStrings.ImportFiles, + Action = () => performer?.PerformFromScreen(menu => menu.Push(new FileImportScreen())) + }, + new SettingsButton + { + Text = DebugSettingsStrings.RunLatencyCertifier, + Action = () => performer?.PerformFromScreen(menu => menu.Push(new LatencyCertifierScreen())) + } + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs index bd90e4c35d..f1b1511df8 100644 --- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs +++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs @@ -23,6 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections { Children = new Drawable[] { + new GeneralSettings(), new BeatmapSettings(), new SkinSettings(), new CollectionsSettings(), From 7ab16a55e561f37a4ffe07b2076f6917a46ea414 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 5 Dec 2024 02:41:17 -0500 Subject: [PATCH 195/223] Make debug section only visible on debug builds --- .../Overlays/FirstRunSetup/ScreenBehaviour.cs | 5 ++- osu.Game/Overlays/SettingsOverlay.cs | 36 +++++++++++-------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs b/osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs index 31a56c9748..d31ce7ea18 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs @@ -5,6 +5,7 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; @@ -90,11 +91,13 @@ namespace osu.Game.Overlays.FirstRunSetup new GraphicsSection(), new OnlineSection(), new MaintenanceSection(), - new DebugSection(), }, SearchTerm = SettingsItem.CLASSIC_DEFAULT_SEARCH_TERM, } }; + + if (DebugUtils.IsDebugBuild) + searchContainer.Add(new DebugSection()); } private void applyClassic() diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 9076dadf93..1157860e03 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -27,21 +28,28 @@ namespace osu.Game.Overlays public LocalisableString Title => SettingsStrings.HeaderTitle; public LocalisableString Description => SettingsStrings.HeaderDescription; - protected override IEnumerable CreateSections() => new SettingsSection[] + protected override IEnumerable CreateSections() { - // This list should be kept in sync with ScreenBehaviour. - new GeneralSection(), - new SkinSection(), - new InputSection(createSubPanel(new KeyBindingPanel())), - new UserInterfaceSection(), - new GameplaySection(), - new RulesetSection(), - new AudioSection(), - new GraphicsSection(), - new OnlineSection(), - new MaintenanceSection(), - new DebugSection(), - }; + var sections = new List + { + // This list should be kept in sync with ScreenBehaviour. + new GeneralSection(), + new SkinSection(), + new InputSection(createSubPanel(new KeyBindingPanel())), + new UserInterfaceSection(), + new GameplaySection(), + new RulesetSection(), + new AudioSection(), + new GraphicsSection(), + new OnlineSection(), + new MaintenanceSection(), + }; + + if (DebugUtils.IsDebugBuild) + sections.Add(new DebugSection()); + + return sections; + } private readonly List subPanels = new List(); From 7c1be5eca25cdf1749329b8a9c0972711673dbe5 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 5 Dec 2024 02:45:43 -0500 Subject: [PATCH 196/223] Remove unnecessary localisations --- osu.Game/Localisation/DebugSettingsStrings.cs | 25 ------------------- .../Settings/Sections/DebugSection.cs | 3 +-- .../Sections/DebugSettings/GeneralSettings.cs | 7 +++--- .../Sections/DebugSettings/MemorySettings.cs | 5 ++-- 4 files changed, 6 insertions(+), 34 deletions(-) diff --git a/osu.Game/Localisation/DebugSettingsStrings.cs b/osu.Game/Localisation/DebugSettingsStrings.cs index 066c07858c..bdb0348981 100644 --- a/osu.Game/Localisation/DebugSettingsStrings.cs +++ b/osu.Game/Localisation/DebugSettingsStrings.cs @@ -9,21 +9,6 @@ namespace osu.Game.Localisation { private const string prefix = @"osu.Game.Resources.Localisation.DebugSettings"; - /// - /// "Debug" - /// - public static LocalisableString DebugSectionHeader => new TranslatableString(getKey(@"debug_section_header"), @"Debug"); - - /// - /// "Show log overlay" - /// - public static LocalisableString ShowLogOverlay => new TranslatableString(getKey(@"show_log_overlay"), @"Show log overlay"); - - /// - /// "Bypass front-to-back render pass" - /// - public static LocalisableString BypassFrontToBackPass => new TranslatableString(getKey(@"bypass_front_to_back_pass"), @"Bypass front-to-back render pass"); - /// /// "Import files" /// @@ -34,16 +19,6 @@ namespace osu.Game.Localisation /// public static LocalisableString RunLatencyCertifier => new TranslatableString(getKey(@"run_latency_certifier"), @"Run latency certifier"); - /// - /// "Memory" - /// - public static LocalisableString MemoryHeader => new TranslatableString(getKey(@"memory_header"), @"Memory"); - - /// - /// "Clear all caches" - /// - public static LocalisableString ClearAllCaches => new TranslatableString(getKey(@"clear_all_caches"), @"Clear all caches"); - private static string getKey(string key) => $"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/Settings/Sections/DebugSection.cs b/osu.Game/Overlays/Settings/Sections/DebugSection.cs index b84c441057..15951d462b 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSection.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSection.cs @@ -6,14 +6,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Graphics; -using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.DebugSettings; namespace osu.Game.Overlays.Settings.Sections { public partial class DebugSection : SettingsSection { - public override LocalisableString Header => DebugSettingsStrings.DebugSectionHeader; + public override LocalisableString Header => "Debug"; public override Drawable CreateIcon() => new SpriteIcon { diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs index 57f36e2875..280aa685a9 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs @@ -5,13 +5,12 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Localisation; -using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.DebugSettings { public partial class GeneralSettings : SettingsSubsection { - protected override LocalisableString Header => CommonStrings.General; + protected override LocalisableString Header => "General"; [BackgroundDependencyLoader] private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig) @@ -20,12 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { new SettingsCheckbox { - LabelText = DebugSettingsStrings.ShowLogOverlay, + LabelText = "Show log overlay", Current = frameworkConfig.GetBindable(FrameworkSetting.ShowLogOverlay) }, new SettingsCheckbox { - LabelText = DebugSettingsStrings.BypassFrontToBackPass, + LabelText = @"Bypass front-to-back render pass", Current = config.GetBindable(DebugSetting.BypassFrontToBackPass) }, }; diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index d5de7ae2db..d43abd58a8 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -11,13 +11,12 @@ using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Database; -using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.DebugSettings { public partial class MemorySettings : SettingsSubsection { - protected override LocalisableString Header => DebugSettingsStrings.MemoryHeader; + protected override LocalisableString Header => "Memory"; [BackgroundDependencyLoader] private void load(GameHost host, RealmAccess realm) @@ -29,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { new SettingsButton { - Text = DebugSettingsStrings.ClearAllCaches, + Text = "Clear all caches", Action = host.Collect }, new SettingsButton From 1b1e7b63e96717903f71e73ec775ae77bbee407f Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 5 Dec 2024 02:48:46 -0500 Subject: [PATCH 197/223] Clean up code slightly --- .../Overlays/Settings/Sections/DebugSection.cs | 15 +++++++-------- .../Sections/DebugSettings/GeneralSettings.cs | 4 ++-- .../Sections/DebugSettings/MemorySettings.cs | 16 ++++++++-------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/DebugSection.cs b/osu.Game/Overlays/Settings/Sections/DebugSection.cs index 15951d462b..1d2129413c 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSection.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSection.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 osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; @@ -12,7 +11,7 @@ namespace osu.Game.Overlays.Settings.Sections { public partial class DebugSection : SettingsSection { - public override LocalisableString Header => "Debug"; + public override LocalisableString Header => @"Debug"; public override Drawable CreateIcon() => new SpriteIcon { @@ -21,12 +20,12 @@ namespace osu.Game.Overlays.Settings.Sections public DebugSection() { - Add(new GeneralSettings()); - - if (DebugUtils.IsDebugBuild) - Add(new BatchImportSettings()); - - Add(new MemorySettings()); + Children = new Drawable[] + { + new GeneralSettings(), + new BatchImportSettings(), + new MemorySettings(), + }; } } } diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs index 280aa685a9..bd6ada4ca7 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { public partial class GeneralSettings : SettingsSubsection { - protected override LocalisableString Header => "General"; + protected override LocalisableString Header => @"General"; [BackgroundDependencyLoader] private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig) @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { new SettingsCheckbox { - LabelText = "Show log overlay", + LabelText = @"Show log overlay", Current = frameworkConfig.GetBindable(FrameworkSetting.ShowLogOverlay) }, new SettingsCheckbox diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index d43abd58a8..b693822838 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { public partial class MemorySettings : SettingsSubsection { - protected override LocalisableString Header => "Memory"; + protected override LocalisableString Header => @"Memory"; [BackgroundDependencyLoader] private void load(GameHost host, RealmAccess realm) @@ -28,27 +28,27 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { new SettingsButton { - Text = "Clear all caches", + Text = @"Clear all caches", Action = host.Collect }, new SettingsButton { - Text = "Compact realm", + Text = @"Compact realm", Action = () => { // Blocking operations implicitly causes a Compact(). - using (realm.BlockAllOperations("compact")) + using (realm.BlockAllOperations(@"compact")) { } } }, blockAction = new SettingsButton { - Text = "Block realm", + Text = @"Block realm", }, unblockAction = new SettingsButton { - Text = "Unblock realm", + Text = @"Unblock realm", }, }; @@ -56,7 +56,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { try { - IDisposable? token = realm.BlockAllOperations("maintenance"); + IDisposable? token = realm.BlockAllOperations(@"maintenance"); blockAction.Enabled.Value = false; @@ -88,7 +88,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings } catch (Exception e) { - Logger.Error(e, "Blocking realm failed"); + Logger.Error(e, @"Blocking realm failed"); } }; } From 68e400dd0c8d4839bab9a2265f9c699fcc8d1b27 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 5 Dec 2024 18:00:42 +0800 Subject: [PATCH 198/223] Put globalconfig into seperated folder and reference explicitly --- .globalconfig => CodeAnalysis/osu.globalconfig | 1 + Directory.Build.props | 2 ++ osu.sln | 7 ++++++- 3 files changed, 9 insertions(+), 1 deletion(-) rename .globalconfig => CodeAnalysis/osu.globalconfig (99%) diff --git a/.globalconfig b/CodeAnalysis/osu.globalconfig similarity index 99% rename from .globalconfig rename to CodeAnalysis/osu.globalconfig index ca7b86c778..247a825033 100644 --- a/.globalconfig +++ b/CodeAnalysis/osu.globalconfig @@ -1,5 +1,6 @@ # .NET Code Style # IDE styles reference: https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ +is_global = true # IDE0001: Simplify names dotnet_diagnostic.IDE0001.severity = warning diff --git a/Directory.Build.props b/Directory.Build.props index 0ab41d27a0..3acb86ee0c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,6 +18,8 @@ + + Default diff --git a/osu.sln b/osu.sln index 2d9a4e86d0..63da18c23e 100644 --- a/osu.sln +++ b/osu.sln @@ -56,7 +56,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{10DF8F12-50FD-45D8-8A38-17BA764BF54D}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig - .globalconfig = .globalconfig Directory.Build.props = Directory.Build.props osu.Android.props = osu.Android.props osu.iOS.props = osu.iOS.props @@ -95,6 +94,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Pippidon" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Pippidon.Tests", "Templates\Rulesets\ruleset-scrolling-example\osu.Game.Rulesets.Pippidon.Tests\osu.Game.Rulesets.Pippidon.Tests.csproj", "{1743BF7C-E6AE-4A06-BAD9-166D62894303}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CodeAnalysis", "CodeAnalysis", "{FB156649-D457-4D1A-969C-D3A23FD31513}" + ProjectSection(SolutionItems) = preProject + CodeAnalysis\BannedSymbols.txt = CodeAnalysis\BannedSymbols.txt + CodeAnalysis\osu.globalconfig = CodeAnalysis\osu.globalconfig + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU From 84a85000afab3f37985ef59e3efe03b0bdd69d36 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2024 21:07:08 +0900 Subject: [PATCH 199/223] 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 02898623a9..ebfb136150 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 80e695e5d1..252c7a14c4 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 49f4b0e6eff6423758382a5c7d79aed21e9c7077 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2024 21:07:13 +0900 Subject: [PATCH 200/223] 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 43353370b7..7b4453b015 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 69014550b5499a1bc60bcd6144a43180b94ff5ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2024 12:48:06 +0900 Subject: [PATCH 201/223] Remove unnecessary null checks --- osu.Game/Graphics/UserInterface/OsuContextMenu.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs index 7a5d2c369b..433d37834f 100644 --- a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs @@ -53,8 +53,8 @@ namespace osu.Game.Graphics.UserInterface if (!playClickSample) return; - menuSamples?.PlayClickSample(); - menuSamples?.PlayOpenSample(); + menuSamples.PlayClickSample(); + menuSamples.PlayOpenSample(); } protected override void AnimateClose() @@ -62,7 +62,7 @@ namespace osu.Game.Graphics.UserInterface this.FadeOut(fade_duration, Easing.OutQuint); if (wasOpened) - menuSamples?.PlayCloseSample(); + menuSamples.PlayCloseSample(); wasOpened = false; } From 5cb6b86b1cd0814b76fcd11cfcf29a04680d4022 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Fri, 6 Dec 2024 05:47:41 -0500 Subject: [PATCH 202/223] Fix reference effect point getting mutated --- osu.Game/Beatmaps/BeatmapManager.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index da556316cd..148bd90f28 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -15,6 +15,7 @@ using osu.Framework.Audio.Track; using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Framework.Platform; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; using osu.Game.Database; using osu.Game.Extensions; @@ -160,10 +161,12 @@ namespace osu.Game.Beatmaps foreach (var effectPoint in referenceWorkingBeatmap.Beatmap.ControlPointInfo.EffectPoints) { - if (!rulesetInfo.Equals(referenceWorkingBeatmap.BeatmapInfo.Ruleset)) - effectPoint.ScrollSpeedBindable.SetDefault(); + var clonedEffectPoint = (EffectControlPoint)effectPoint.DeepClone(); - newBeatmap.ControlPointInfo.Add(effectPoint.Time, effectPoint.DeepClone()); + if (!rulesetInfo.Equals(referenceWorkingBeatmap.BeatmapInfo.Ruleset)) + clonedEffectPoint.ScrollSpeedBindable.SetDefault(); + + newBeatmap.ControlPointInfo.Add(clonedEffectPoint.Time, clonedEffectPoint); } return addDifficultyToSet(targetBeatmapSet, newBeatmap, referenceWorkingBeatmap.Skin); From b9f1fef2501ea0cce933b7522d658d3ae9f3d577 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 7 Dec 2024 10:46:03 +0900 Subject: [PATCH 203/223] 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 ebfb136150..632325725a 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 252c7a14c4..62a65f291d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From b04f5ab6e4dbd0a486015dbc513f35d8d84d48c5 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 7 Dec 2024 14:13:14 +0200 Subject: [PATCH 204/223] Fix IPC not working in release --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 279530a579..d8145c8246 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -89,7 +89,7 @@ namespace osu.Game // Different port allows running release and debug builds alongside each other. public const string IPC_PIPE_NAME = "osu-lazer-debug"; #else - public const string IPC_PORT = "osu-lazer"; + public const string IPC_PIPE_NAME = "osu-lazer"; #endif /// From a46070be30588410675e618bba79c51f852175a3 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 9 Dec 2024 07:02:40 -0500 Subject: [PATCH 205/223] Add description to osu! file associations in iOS --- osu.iOS/Info.plist | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index 1330e29bc1..ae36d00910 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -68,6 +68,8 @@ sh.ppy.osu.items + UTTypeDescription + osu! replay UTTypeIdentifier sh.ppy.osu.osr UTTypeTagSpecification @@ -81,6 +83,8 @@ sh.ppy.osu.items + UTTypeDescription + osu! skin UTTypeIdentifier sh.ppy.osu.osk UTTypeTagSpecification @@ -94,6 +98,8 @@ sh.ppy.osu.items + UTTypeDescription + osu! beatmap UTTypeIdentifier sh.ppy.osu.osz UTTypeTagSpecification @@ -107,6 +113,8 @@ sh.ppy.osu.items + UTTypeDescription + osu! beatmap UTTypeIdentifier sh.ppy.osu.olz UTTypeTagSpecification From e9868c631851a1f8f41e5296db787648ece29597 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 9 Dec 2024 07:47:28 -0500 Subject: [PATCH 206/223] Enable exporting beatmaps in iOS --- osu.Game/Screens/Edit/Editor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0e4807dc78..47ccc8f72e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1215,12 +1215,15 @@ namespace osu.Game.Screens.Edit saveRelatedMenuItems.Add(save); yield return save; - if (RuntimeInfo.IsDesktop) + if (RuntimeInfo.OS != RuntimeInfo.Platform.Android) { var export = createExportMenu(); saveRelatedMenuItems.AddRange(export.Items); yield return export; + } + if (RuntimeInfo.IsDesktop) + { var externalEdit = new EditorMenuItem("Edit externally", MenuItemType.Standard, editExternally); saveRelatedMenuItems.Add(externalEdit); yield return externalEdit; From 0c0dcb1e1545febd175212fcdff25625052b161d Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 9 Dec 2024 08:16:37 -0500 Subject: [PATCH 207/223] Use temporary storage for exported files on iOS --- osu.Game/Database/LegacyExporter.cs | 10 ++++++++-- osu.Game/IO/OsuStorage.cs | 5 +++++ osu.iOS/OsuGameIOS.cs | 4 ++++ osu.iOS/OsuStorageIOS.cs | 23 +++++++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 osu.iOS/OsuStorageIOS.cs diff --git a/osu.Game/Database/LegacyExporter.cs b/osu.Game/Database/LegacyExporter.cs index f9164e34cd..193887765d 100644 --- a/osu.Game/Database/LegacyExporter.cs +++ b/osu.Game/Database/LegacyExporter.cs @@ -3,12 +3,14 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Platform; using osu.Game.Extensions; +using osu.Game.IO; using osu.Game.Overlays.Notifications; using osu.Game.Utils; using Realms; @@ -40,13 +42,15 @@ namespace osu.Game.Database protected abstract string FileExtension { get; } protected readonly Storage UserFileStorage; - private readonly Storage exportStorage; + private readonly Storage? exportStorage; public Action? PostNotification { get; set; } protected LegacyExporter(Storage storage) { - exportStorage = storage.GetStorageForDirectory(@"exports"); + if (storage is OsuStorage osuStorage) + exportStorage = osuStorage.GetExportStorage(); + UserFileStorage = storage.GetStorageForDirectory(@"files"); } @@ -68,6 +72,8 @@ namespace osu.Game.Database /// A cancellation token. public async Task ExportAsync(Live model, CancellationToken cancellationToken = default) { + Debug.Assert(exportStorage != null); + string itemFilename = model.PerformRead(s => GetFilename(s).GetValidFilename()); if (itemFilename.Length > MAX_FILENAME_LENGTH - FileExtension.Length) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index a936fa74da..27e1889c6a 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -61,6 +61,11 @@ namespace osu.Game.IO TryChangeToCustomStorage(out Error); } + /// + /// Returns the used for storing exported files. + /// + public virtual Storage GetExportStorage() => GetStorageForDirectory(@"exports"); + /// /// Resets the custom storage path, changing the target storage to the default location. /// diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index 2a4f9b87ac..c0bd77366e 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -5,6 +5,8 @@ using System; using Foundation; using Microsoft.Maui.Devices; using osu.Framework.Graphics; +using osu.Framework.iOS; +using osu.Framework.Platform; using osu.Game; using osu.Game.Updater; using osu.Game.Utils; @@ -19,6 +21,8 @@ namespace osu.iOS protected override BatteryInfo CreateBatteryInfo() => new IOSBatteryInfo(); + protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorageIOS((IOSGameHost)host, defaultStorage); + protected override Edges SafeAreaOverrideEdges => // iOS shows a home indicator at the bottom, and adds a safe area to account for this. // Because we have the home indicator (mostly) hidden we don't really care about drawing in this region. diff --git a/osu.iOS/OsuStorageIOS.cs b/osu.iOS/OsuStorageIOS.cs new file mode 100644 index 0000000000..f3a5eec737 --- /dev/null +++ b/osu.iOS/OsuStorageIOS.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using osu.Framework.iOS; +using osu.Framework.Platform; +using osu.Game.IO; + +namespace osu.iOS +{ + public class OsuStorageIOS : OsuStorage + { + private readonly IOSGameHost host; + + public OsuStorageIOS(IOSGameHost host, Storage defaultStorage) + : base(host, defaultStorage) + { + this.host = host; + } + + public override Storage GetExportStorage() => new IOSStorage(Path.GetTempPath(), host); + } +} From f0f3c5357164334ea788ec928292b6b59768e55f Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 9 Dec 2024 08:26:18 -0500 Subject: [PATCH 208/223] Update exporter test to use `OsuStorage` --- osu.Game.Tests/Database/LegacyModelExporterTest.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Database/LegacyModelExporterTest.cs b/osu.Game.Tests/Database/LegacyModelExporterTest.cs index 0c4b0cc9c4..d261c49517 100644 --- a/osu.Game.Tests/Database/LegacyModelExporterTest.cs +++ b/osu.Game.Tests/Database/LegacyModelExporterTest.cs @@ -12,6 +12,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Database; +using osu.Game.IO; using osu.Game.Overlays.Notifications; using Realms; @@ -21,7 +22,9 @@ namespace osu.Game.Tests.Database public class LegacyModelExporterTest { private TestLegacyModelExporter legacyExporter = null!; - private TemporaryNativeStorage storage = null!; + + private OsuStorage storage = null!; + private TemporaryNativeStorage underlyingStorage = null!; private const string short_filename = "normal file name"; @@ -31,7 +34,7 @@ namespace osu.Game.Tests.Database [SetUp] public void SetUp() { - storage = new TemporaryNativeStorage("export-storage"); + storage = new OsuStorage(new HeadlessGameHost(), underlyingStorage = new TemporaryNativeStorage("export-storage")); legacyExporter = new TestLegacyModelExporter(storage); } @@ -102,8 +105,8 @@ namespace osu.Game.Tests.Database [TearDown] public void TearDown() { - if (storage.IsNotNull()) - storage.Dispose(); + if (underlyingStorage.IsNotNull()) + underlyingStorage.Dispose(); } private class TestLegacyModelExporter : LegacyExporter From 1febed66cfabc03c2930c55c8aeecd9cd288e1d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 9 Dec 2024 23:51:57 +0900 Subject: [PATCH 209/223] Fix top score statistics section total score display being terminally broken Closes https://github.com/ppy/osu/issues/31038. If you don't realise why this does anything, realise this: the drawable creation callback runs for every created sprite text in the text flow. ANd the created sprite texts are split by whitespace. And Russian / Ukrainian / Polish etc. use spaces as thousands separators. So on those languages the first encountered part of the score would duplicate itself to the remaining parts. I'm actively convinced it was _more difficult_ to produce what was in place in `master` than to do it properly. Why did `TextColumn` even have `LocalisableString Text` and `Bindable Current` next to each other????? --- .../Scores/TopScoreStatisticsSection.cs | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index e8833fa0a3..8e342b49c9 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -35,7 +34,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly FontUsage smallFont = OsuFont.GetFont(size: 16); private readonly FontUsage largeFont = OsuFont.GetFont(size: 22, weight: FontWeight.Light); - private readonly TextColumn totalScoreColumn; + private readonly TotalScoreColumn totalScoreColumn; private readonly TextColumn accuracyColumn; private readonly TextColumn maxComboColumn; private readonly TextColumn ppColumn; @@ -67,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Spacing = new Vector2(margin, 0), Children = new Drawable[] { - totalScoreColumn = new TextColumn(BeatmapsetsStrings.ShowScoreboardHeadersScoreTotal, largeFont, top_columns_min_width), + totalScoreColumn = new TotalScoreColumn(BeatmapsetsStrings.ShowScoreboardHeadersScoreTotal, largeFont, top_columns_min_width), accuracyColumn = new TextColumn(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy, largeFont, top_columns_min_width), maxComboColumn = new TextColumn(BeatmapsetsStrings.ShowScoreboardHeadersCombo, largeFont, top_columns_min_width) } @@ -226,7 +225,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } - private partial class TextColumn : InfoColumn, IHasCurrentValue + private partial class TextColumn : InfoColumn { private readonly OsuTextFlowContainer text; @@ -249,18 +248,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } - private Bindable current; - - public Bindable Current - { - get => current; - set - { - text.Clear(); - text.AddText(value.Value, t => t.Current = current = value); - } - } - public TextColumn(LocalisableString title, FontUsage font, float? minWidth = null) : this(title, new OsuTextFlowContainer(t => t.Font = font) { @@ -276,6 +263,28 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } + private partial class TotalScoreColumn : TextColumn + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public TotalScoreColumn(LocalisableString title, FontUsage font, float? minWidth = null) + : base(title, font, minWidth) + { + } + + public Bindable Current + { + get => current; + set => current.Current = value; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Current.BindValueChanged(_ => Text = current.Value, true); + } + } + private partial class ModsInfoColumn : InfoColumn { private readonly FillFlowContainer modsContainer; From 92dfcae6eba4a545c6f2bdab0fdb6ddb6536ff0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2024 14:35:09 +0900 Subject: [PATCH 210/223] Adjust bad grammar --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 0b5450e5ac..975f962f7f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -51,7 +51,7 @@ namespace osu.Game.Beatmaps.Formats } /// - /// Whether or not beatmap or runtime offsets should be applied. Defaults on; only disable for testing purposes. + /// Whether beatmap or runtime offsets should be applied. Defaults on; only disable for testing purposes. /// public bool ApplyOffsets = true; From d69f5fd4cfd9bdc5720c12a4ccca9400e78784bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2024 14:45:12 +0900 Subject: [PATCH 211/223] Avoid beatmap lookup per bar in logo visualisation Just noticed in passing. --- osu.Game/Screens/Menu/LogoVisualisation.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 6d9d2f69b7..53d153ab31 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -106,9 +106,12 @@ namespace osu.Game.Screens.Menu foreach (var source in amplitudeSources) addAmplitudesFromSource(source); + float kiaiMultiplier = beatSyncProvider.CheckIsKiaiTime() ? 1 : 0.5f; + for (int i = 0; i < bars_per_visualiser; i++) { - float targetAmplitude = (temporalAmplitudes[(i + indexOffset) % bars_per_visualiser]) * (beatSyncProvider.CheckIsKiaiTime() ? 1 : 0.5f); + float targetAmplitude = (temporalAmplitudes[(i + indexOffset) % bars_per_visualiser]) * kiaiMultiplier; + if (targetAmplitude > frequencyAmplitudes[i]) frequencyAmplitudes[i] = targetAmplitude; } From 7fcfebf4b43b5eee6e3f981d4df291d348641835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 Dec 2024 22:51:09 +0900 Subject: [PATCH 212/223] Use Alt-{Left,Right} as default bindings for bookmark navigation --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 42028c044f..170d247023 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -154,8 +154,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.Right }, GlobalAction.EditorSeekToNextSamplePoint), new KeyBinding(new[] { InputKey.Control, InputKey.B }, GlobalAction.EditorAddBookmark), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.B }, GlobalAction.EditorRemoveClosestBookmark), - new KeyBinding(InputKey.None, GlobalAction.EditorSeekToPreviousBookmark), - new KeyBinding(InputKey.None, GlobalAction.EditorSeekToNextBookmark), + new KeyBinding(new[] { InputKey.Alt, InputKey.Left }, GlobalAction.EditorSeekToPreviousBookmark), + new KeyBinding(new[] { InputKey.Alt, InputKey.Right }, GlobalAction.EditorSeekToNextBookmark), }; private static IEnumerable editorTestPlayKeyBindings => new[] From 3cac5837547f5ca08baa9f615948d4a4166a4505 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2024 16:40:47 +0900 Subject: [PATCH 213/223] Rewrite resource changing code to be more legible (to my eye) --- .../Screens/Edit/Setup/ResourcesSection.cs | 71 +++++++++++-------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 84107a57e9..6cde0e6792 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -103,55 +103,64 @@ namespace osu.Game.Screens.Edit.Setup private void changeResource(FileInfo source, bool applyToAllDifficulties, string baseFilename, Func readFilename, Action writeFilename) { - var thisBeatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; + var beatmap = working.Value.BeatmapInfo; - string newFilename = string.Empty; + var otherBeatmaps = set.Beatmaps.Where(b => !b.Equals(beatmap)); + // First, clean up files which will no longer be used. if (applyToAllDifficulties) { - newFilename = $"{baseFilename}{source.Extension}"; - - foreach (var beatmap in set.Beatmaps) + foreach (var b in set.Beatmaps) { - if (set.GetFile(readFilename(beatmap.Metadata)) is RealmNamedFileUsage otherExistingFile) + if (set.GetFile(readFilename(b.Metadata)) is RealmNamedFileUsage otherExistingFile) beatmaps.DeleteFile(set, otherExistingFile); - - writeFilename(beatmap.Metadata, newFilename); - - if (!beatmap.Equals(thisBeatmap)) - { - // save the difficulty to re-encode the .osu file, updating any reference of the old filename. - var beatmapWorking = beatmaps.GetWorkingBeatmap(beatmap); - beatmaps.Save(beatmap, beatmapWorking.Beatmap, beatmapWorking.GetSkin()); - } } } else { - string[] filenames = set.Files.Select(f => f.Filename).Where(f => + RealmNamedFileUsage? oldFile = set.GetFile(readFilename(working.Value.Metadata)); + + if (oldFile != null) + { + bool oldFileUsedInOtherDiff = otherBeatmaps + .Any(b => readFilename(b.Metadata) == oldFile.Filename); + if (!oldFileUsedInOtherDiff) + beatmaps.DeleteFile(set, oldFile); + } + } + + // Choose a new filename that doesn't clash with any other existing files. + string newFilename = $"{baseFilename}{source.Extension}"; + + if (set.GetFile(newFilename) != null) + { + string[] existingFilenames = set.Files.Select(f => f.Filename).Where(f => f.StartsWith(baseFilename, StringComparison.OrdinalIgnoreCase) && f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - - string currentFilename = readFilename(working.Value.Metadata); - - var oldFile = set.GetFile(currentFilename); - - if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(thisBeatmap)).All(b => readFilename(b.Metadata) != currentFilename)) - { - beatmaps.DeleteFile(set, oldFile); - newFilename = currentFilename; - } - - if (string.IsNullOrEmpty(newFilename)) - newFilename = NamingUtils.GetNextBestFilename(filenames, $@"{baseFilename}{source.Extension}"); - - writeFilename(working.Value.Metadata, newFilename); + newFilename = NamingUtils.GetNextBestFilename(existingFilenames, $@"{baseFilename}{source.Extension}"); } using (var stream = source.OpenRead()) beatmaps.AddFile(set, stream, newFilename); + if (applyToAllDifficulties) + { + foreach (var b in otherBeatmaps) + { + if (readFilename(b.Metadata) != newFilename) + { + writeFilename(b.Metadata, newFilename); + + // save the difficulty to re-encode the .osu file, updating any reference of the old filename. + var beatmapWorking = beatmaps.GetWorkingBeatmap(b); + beatmaps.Save(b, beatmapWorking.Beatmap, beatmapWorking.GetSkin()); + } + } + } + + writeFilename(beatmap.Metadata, newFilename); + // editor change handler cannot be aware of any file changes or other difficulties having their metadata modified. // for simplicity's sake, trigger a save when changing any resource to ensure the change is correctly saved. editor?.Save(); From bbaa542d4a376e490c7e58d794ff49ca7e1bdddb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2024 16:44:35 +0900 Subject: [PATCH 214/223] Add note about expensive operation --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 6cde0e6792..7fcd09d7e7 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -148,14 +148,17 @@ namespace osu.Game.Screens.Edit.Setup { foreach (var b in otherBeatmaps) { - if (readFilename(b.Metadata) != newFilename) - { - writeFilename(b.Metadata, newFilename); + // This operation is quite expensive, so only perform it if required. + if (readFilename(b.Metadata) == newFilename) continue; - // save the difficulty to re-encode the .osu file, updating any reference of the old filename. - var beatmapWorking = beatmaps.GetWorkingBeatmap(b); - beatmaps.Save(b, beatmapWorking.Beatmap, beatmapWorking.GetSkin()); - } + writeFilename(b.Metadata, newFilename); + + // save the difficulty to re-encode the .osu file, updating any reference of the old filename. + // + // note that this triggers a full save flow, including triggering a difficulty calculation. + // this is not a cheap operation and should be reconsidered in the future. + var beatmapWorking = beatmaps.GetWorkingBeatmap(b); + beatmaps.Save(b, beatmapWorking.Beatmap, beatmapWorking.GetSkin()); } } From dae380b7fa927c351e2e413c5b23834f717908d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2024 22:03:07 +0900 Subject: [PATCH 215/223] Fix lookups of hit circle slider pieces potentially using wrong source skin Addresses https://github.com/ppy/osu/discussions/30927. --- .../Skinning/Legacy/LegacyMainCirclePiece.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index ef616ae964..0dc0f065d4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -57,11 +57,22 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy [BackgroundDependencyLoader] private void load() { + const string base_lookup = @"hitcircle"; + var drawableOsuObject = (DrawableOsuHitObject?)drawableObject; + // As a precondition, ensure that any prefix lookups are run against the skin which is providing "hitcircle". + // This is to correctly handle a case such as: + // + // - Beatmap provides `hitcircle` + // - User skin provides `sliderstartcircle` + // + // In such a case, the `hitcircle` should be used for slider start circles rather than the user's skin override. + var provider = skin.FindProvider(s => s.GetTexture(base_lookup) != null) ?? skin; + // if a base texture for the specified prefix exists, continue using it for subsequent lookups. // otherwise fall back to the default prefix "hitcircle". - string circleName = (priorityLookupPrefix != null && skin.GetTexture(priorityLookupPrefix) != null) ? priorityLookupPrefix : @"hitcircle"; + string circleName = (priorityLookupPrefix != null && provider.GetTexture(priorityLookupPrefix) != null) ? priorityLookupPrefix : base_lookup; Vector2 maxSize = OsuHitObject.OBJECT_DIMENSIONS * 2; @@ -70,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy // expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png. InternalChildren = new[] { - CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName)?.WithMaximumSize(maxSize) }) + CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = provider.GetTexture(circleName)?.WithMaximumSize(maxSize) }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -79,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(@$"{circleName}overlay")?.WithMaximumSize(maxSize) }) + Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = provider.GetTexture(@$"{circleName}overlay")?.WithMaximumSize(maxSize) }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 315a9dba9bd0d9f3a994a7e50d7a8acf0c6a024d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 11 Dec 2024 09:59:18 +0900 Subject: [PATCH 216/223] Allow tsunyoku and stanriders to trigger diffcalc spreadsheet generator --- .github/workflows/diffcalc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 4297a88e89..8461208a2e 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -115,7 +115,7 @@ jobs: steps: - name: Check permissions run: | - ALLOWED_USERS=(smoogipoo peppy bdach frenzibyte) + ALLOWED_USERS=(smoogipoo peppy bdach frenzibyte tsunyoku stanriders) for i in "${ALLOWED_USERS[@]}"; do if [[ "${{ github.actor }}" == "$i" ]]; then exit 0 From 723883e1f06dc7277f1441c8ab73130b0437adbc Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 11 Dec 2024 01:03:20 -0500 Subject: [PATCH 217/223] Revert "Update exporter test to use `OsuStorage`" This reverts commit f0f3c5357164334ea788ec928292b6b59768e55f. --- osu.Game.Tests/Database/LegacyModelExporterTest.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Database/LegacyModelExporterTest.cs b/osu.Game.Tests/Database/LegacyModelExporterTest.cs index d261c49517..0c4b0cc9c4 100644 --- a/osu.Game.Tests/Database/LegacyModelExporterTest.cs +++ b/osu.Game.Tests/Database/LegacyModelExporterTest.cs @@ -12,7 +12,6 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Database; -using osu.Game.IO; using osu.Game.Overlays.Notifications; using Realms; @@ -22,9 +21,7 @@ namespace osu.Game.Tests.Database public class LegacyModelExporterTest { private TestLegacyModelExporter legacyExporter = null!; - - private OsuStorage storage = null!; - private TemporaryNativeStorage underlyingStorage = null!; + private TemporaryNativeStorage storage = null!; private const string short_filename = "normal file name"; @@ -34,7 +31,7 @@ namespace osu.Game.Tests.Database [SetUp] public void SetUp() { - storage = new OsuStorage(new HeadlessGameHost(), underlyingStorage = new TemporaryNativeStorage("export-storage")); + storage = new TemporaryNativeStorage("export-storage"); legacyExporter = new TestLegacyModelExporter(storage); } @@ -105,8 +102,8 @@ namespace osu.Game.Tests.Database [TearDown] public void TearDown() { - if (underlyingStorage.IsNotNull()) - underlyingStorage.Dispose(); + if (storage.IsNotNull()) + storage.Dispose(); } private class TestLegacyModelExporter : LegacyExporter From e0aec6f907f81f04cf6c802eea618b7f2c0c062a Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 11 Dec 2024 01:03:55 -0500 Subject: [PATCH 218/223] Revert unnecessary complexity --- osu.Game/Database/LegacyExporter.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Database/LegacyExporter.cs b/osu.Game/Database/LegacyExporter.cs index 193887765d..80393c27f7 100644 --- a/osu.Game/Database/LegacyExporter.cs +++ b/osu.Game/Database/LegacyExporter.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; @@ -42,15 +41,13 @@ namespace osu.Game.Database protected abstract string FileExtension { get; } protected readonly Storage UserFileStorage; - private readonly Storage? exportStorage; + private readonly Storage exportStorage; public Action? PostNotification { get; set; } protected LegacyExporter(Storage storage) { - if (storage is OsuStorage osuStorage) - exportStorage = osuStorage.GetExportStorage(); - + exportStorage = (storage as OsuStorage)?.GetExportStorage() ?? storage.GetStorageForDirectory(@"exports"); UserFileStorage = storage.GetStorageForDirectory(@"files"); } @@ -72,8 +69,6 @@ namespace osu.Game.Database /// A cancellation token. public async Task ExportAsync(Live model, CancellationToken cancellationToken = default) { - Debug.Assert(exportStorage != null); - string itemFilename = model.PerformRead(s => GetFilename(s).GetValidFilename()); if (itemFilename.Length > MAX_FILENAME_LENGTH - FileExtension.Length) From 5a0b732ee32e66e24118e390706795a419cd3954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 Dec 2024 16:26:11 +0900 Subject: [PATCH 219/223] Add comments backreferences to copies of duplicated code for future use --- .../Edit/Blueprints/JuiceStreamSelectionBlueprint.cs | 2 ++ .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index a61478f5d5..6a0ce35a07 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -190,6 +190,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints lastSliderPathVersion = HitObject.Path.Version.Value; } + // duplicated in `SliderSelectionBlueprint.convertToStream()` + // consider extracting common helper when applying changes here private void convertToStream() { if (editorBeatmap == null || beatDivisor == null) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 34de81f1ba..02f76b51b0 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -551,6 +551,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders HitObject.Position += first; } + // duplicated in `JuiceStreamSelectionBlueprint.convertToStream()` + // consider extracting common helper when applying changes here private void convertToStream() { if (editorBeatmap == null || beatDivisor == null) From de31a48beb3d1f2ec47b9421a12492a70c054667 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2024 23:29:50 +0900 Subject: [PATCH 220/223] Some `Carousel` classes can be abstract --- .../Screens/Select/Carousel/CarouselGroup.cs | 48 +++++++++---------- .../Carousel/CarouselGroupEagerSelect.cs | 4 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 62d694976f..c0fb5fa397 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -10,8 +10,31 @@ namespace osu.Game.Screens.Select.Carousel /// /// A group which ensures only one item is selected. /// - public class CarouselGroup : CarouselItem + public abstract class CarouselGroup : CarouselItem { + protected CarouselGroup(List? items = null) + { + if (items != null) this.items = items; + + State.ValueChanged += state => + { + switch (state.NewValue) + { + case CarouselItemState.Collapsed: + case CarouselItemState.NotSelected: + this.items.ForEach(c => c.State.Value = CarouselItemState.Collapsed); + break; + + case CarouselItemState.Selected: + this.items.ForEach(c => + { + if (c.State.Value == CarouselItemState.Collapsed) c.State.Value = CarouselItemState.NotSelected; + }); + break; + } + }; + } + public override DrawableCarouselItem? CreateDrawableRepresentation() => null; public SlimReadOnlyListWrapper Items => items.AsSlimReadOnly(); @@ -67,29 +90,6 @@ namespace osu.Game.Screens.Select.Carousel TotalItemsNotFiltered++; } - public CarouselGroup(List? items = null) - { - if (items != null) this.items = items; - - State.ValueChanged += state => - { - switch (state.NewValue) - { - case CarouselItemState.Collapsed: - case CarouselItemState.NotSelected: - this.items.ForEach(c => c.State.Value = CarouselItemState.Collapsed); - break; - - case CarouselItemState.Selected: - this.items.ForEach(c => - { - if (c.State.Value == CarouselItemState.Collapsed) c.State.Value = CarouselItemState.NotSelected; - }); - break; - } - }; - } - public override void Filter(FilterCriteria criteria) { base.Filter(criteria); diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index cf4ba5924f..8cc1ea258a 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -10,9 +10,9 @@ namespace osu.Game.Screens.Select.Carousel /// /// A group which ensures at least one item is selected (if the group itself is selected). /// - public class CarouselGroupEagerSelect : CarouselGroup + public abstract class CarouselGroupEagerSelect : CarouselGroup { - public CarouselGroupEagerSelect() + protected CarouselGroupEagerSelect() { State.ValueChanged += state => { From bab9b9c937748bc283febc553b8b0b8e2510599b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2024 23:52:37 +0900 Subject: [PATCH 221/223] Remove no-longer-correct comment --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index fc7c7989e2..f0c3b1f477 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -246,9 +246,6 @@ namespace osu.Game.Screens.Select if (detachedBeatmapStore != null && detachedBeatmapSets == null) { - // This is performing an unnecessary second lookup on realm (in addition to the subscription), but for performance reasons - // we require it to be separate: the subscription's initial callback (with `ChangeSet` of `null`) will run on the update - // thread. If we attempt to detach beatmaps in this callback the game will fall over (it takes time). detachedBeatmapSets = detachedBeatmapStore.GetDetachedBeatmaps(cancellationToken); detachedBeatmapSets.BindCollectionChanged(beatmapSetsChanged); loadNewRoot(); From c94b393e309cd4a9f0e5d4f0b5f3e53a6d2e5b30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2024 00:18:33 +0900 Subject: [PATCH 222/223] Access beatmap store via abstract base class The intention here is to make things more testable going forward. Specifically, to remove the "back-door" entrance into `BeatmapCarousel` where `BeatmapSets` can be set by tests and bypas/block realm retrieval. --- .../Background/TestSceneUserDimBackgrounds.cs | 6 ++-- .../Visual/Multiplayer/QueueModeTestScene.cs | 6 ++-- .../Multiplayer/TestSceneMultiplayer.cs | 6 ++-- .../TestSceneMultiplayerMatchSongSelect.cs | 6 ++-- .../TestScenePlaylistsSongSelect.cs | 6 ++-- .../SongSelect/TestScenePlaySongSelect.cs | 6 ++-- osu.Game/Database/BeatmapStore.cs | 35 +++++++++++++++++++ ...pStore.cs => RealmDetachedBeatmapStore.cs} | 5 ++- osu.Game/OsuGame.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 ++-- 10 files changed, 59 insertions(+), 25 deletions(-) create mode 100644 osu.Game/Database/BeatmapStore.cs rename osu.Game/Database/{DetachedBeatmapStore.cs => RealmDetachedBeatmapStore.cs} (96%) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index d8be57382f..5bbbfb0284 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -49,17 +49,17 @@ namespace osu.Game.Tests.Visual.Background [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - DetachedBeatmapStore detachedBeatmapStore; + BeatmapStore beatmapStore; Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); - Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); + Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - Add(detachedBeatmapStore); + Add(beatmapStore); Beatmap.SetDefault(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 2b738743ea..ab0a4e8e03 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -44,14 +44,14 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - DetachedBeatmapStore detachedBeatmapStore; + BeatmapStore beatmapStore; Dependencies.Cache(new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); + Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); - Add(detachedBeatmapStore); + Add(beatmapStore); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 9213a52c0e..0f3fa7511d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -66,14 +66,14 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - DetachedBeatmapStore detachedBeatmapStore; + BeatmapStore beatmapStore; Dependencies.Cache(new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); + Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); - Add(detachedBeatmapStore); + Add(beatmapStore); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 2a5f16d091..3ea96bae84 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -46,16 +46,16 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - DetachedBeatmapStore detachedBeatmapStore; + BeatmapStore beatmapStore; Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); + Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); importedBeatmapSet = manager.Import(TestResources.CreateTestBeatmapSetInfo(8, rulesets.AvailableRulesets.ToArray()))!; - Add(detachedBeatmapStore); + Add(beatmapStore); } private void setUp() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index fa1909254a..24b67bc4a1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -31,18 +31,18 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - DetachedBeatmapStore detachedBeatmapStore; + BeatmapStore beatmapStore; Dependencies.Cache(new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); + Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); manager.Import(beatmapSet); - Add(detachedBeatmapStore); + Add(beatmapStore); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 3a95aca6b9..3d86b214fd 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -56,20 +56,20 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - DetachedBeatmapStore detachedBeatmapStore; + BeatmapStore beatmapStore; // These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install. // At a point we have isolated interactive test runs enough, this can likely be removed. Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(Realm); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); - Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); + Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(music = new MusicController()); // required to get bindables attached Add(music); - Add(detachedBeatmapStore); + Add(beatmapStore); Dependencies.Cache(config = new OsuConfigManager(LocalStorage)); } diff --git a/osu.Game/Database/BeatmapStore.cs b/osu.Game/Database/BeatmapStore.cs new file mode 100644 index 0000000000..f288279a79 --- /dev/null +++ b/osu.Game/Database/BeatmapStore.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; + +namespace osu.Game.Database +{ + /// + /// A store which contains a thread-safe representation of beatmaps available game-wide. + /// This exposes changes to available beatmaps, such as post-import or deletion. + /// + /// + /// The main goal of classes which implement this interface should be to provide change + /// tracking and thread safety in a performant way, rather than having to worry about such + /// concerns at the point of usage. + /// + public abstract partial class BeatmapStore : Component + { + /// + /// Get all available beatmaps. + /// + /// A cancellation token which allows early abort from the operation. + /// A bindable list of all available beatmap sets. + /// + /// This operation may block during the initial load process. + /// + /// It is generally expected that once a beatmap store is in a good state, the overhead of this call + /// should be negligible. + /// + public abstract IBindableList GetBeatmaps(CancellationToken? cancellationToken); + } +} diff --git a/osu.Game/Database/DetachedBeatmapStore.cs b/osu.Game/Database/RealmDetachedBeatmapStore.cs similarity index 96% rename from osu.Game/Database/DetachedBeatmapStore.cs rename to osu.Game/Database/RealmDetachedBeatmapStore.cs index 5b65f608b2..bc0dc2ae93 100644 --- a/osu.Game/Database/DetachedBeatmapStore.cs +++ b/osu.Game/Database/RealmDetachedBeatmapStore.cs @@ -8,14 +8,13 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; using Realms; namespace osu.Game.Database { - public partial class DetachedBeatmapStore : Component + public partial class RealmDetachedBeatmapStore : BeatmapStore { private readonly ManualResetEventSlim loaded = new ManualResetEventSlim(); @@ -28,7 +27,7 @@ namespace osu.Game.Database [Resolved] private RealmAccess realm { get; set; } = null!; - public IBindableList GetDetachedBeatmaps(CancellationToken? cancellationToken) + public override IBindableList GetBeatmaps(CancellationToken? cancellationToken) { loaded.Wait(cancellationToken ?? CancellationToken.None); return detachedBeatmapSets.GetBoundCopy(); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index d8145c8246..e808e570c7 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1143,7 +1143,7 @@ namespace osu.Game loadComponentSingleFile(new MedalOverlay(), topMostOverlayContent.Add); loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add); - loadComponentSingleFile(new DetachedBeatmapStore(), Add, true); + loadComponentSingleFile(new RealmDetachedBeatmapStore(), Add, true); Add(externalLinkOpener = new ExternalLinkOpener()); Add(new MusicKeyBindingHandler()); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f0c3b1f477..6dfb834317 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select private RealmAccess realm { get; set; } = null!; [Resolved] - private DetachedBeatmapStore? detachedBeatmapStore { get; set; } + private BeatmapStore? beatmapStore { get; set; } private IBindableList? detachedBeatmapSets; @@ -244,9 +244,9 @@ namespace osu.Game.Screens.Select RightClickScrollingEnabled.BindValueChanged(enabled => Scroll.RightMouseScrollbar = enabled.NewValue, true); - if (detachedBeatmapStore != null && detachedBeatmapSets == null) + if (beatmapStore != null && detachedBeatmapSets == null) { - detachedBeatmapSets = detachedBeatmapStore.GetDetachedBeatmaps(cancellationToken); + detachedBeatmapSets = beatmapStore.GetBeatmaps(cancellationToken); detachedBeatmapSets.BindCollectionChanged(beatmapSetsChanged); loadNewRoot(); } From a868c33380e4423c572e3a3ce13cedbe63753d88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2024 16:17:18 +0900 Subject: [PATCH 223/223] Remove `BeatmapCarousel` testing backdoor --- .../Background/TestSceneUserDimBackgrounds.cs | 2 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 2 +- .../Multiplayer/TestSceneMultiplayer.cs | 2 +- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../TestScenePlaylistsSongSelect.cs | 2 +- .../SongSelect/TestSceneBeatmapCarousel.cs | 8 +++++- .../SongSelect/TestScenePlaySongSelect.cs | 2 +- .../TestSceneUpdateBeatmapSetButton.cs | 13 +++++---- .../TestSceneFirstRunScreenUIScale.cs | 5 ++++ .../TestSceneFirstRunSetupOverlay.cs | 3 +++ osu.Game/Database/BeatmapStore.cs | 2 +- .../Database/RealmDetachedBeatmapStore.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 27 ++++--------------- osu.Game/Tests/Beatmaps/TestBeatmapStore.cs | 16 +++++++++++ 14 files changed, 52 insertions(+), 36 deletions(-) create mode 100644 osu.Game/Tests/Beatmaps/TestBeatmapStore.cs diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 5bbbfb0284..693e1e48d4 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Background Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); - Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); + Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index ab0a4e8e03..0e01751d76 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); + Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); Add(beatmapStore); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 0f3fa7511d..fb653cea8b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); + Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); Add(beatmapStore); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 3ea96bae84..8e4c83c4b4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); + Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); importedBeatmapSet = manager.Import(TestResources.CreateTestBeatmapSetInfo(8, rulesets.AvailableRulesets.ToArray()))!; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 24b67bc4a1..726d0ac9f9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); + Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 97c46a11fc..11e754c868 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -16,6 +16,7 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; @@ -23,6 +24,7 @@ using osu.Game.Rulesets.Taiko; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; using osuTK.Input; @@ -42,6 +44,9 @@ namespace osu.Game.Tests.Visual.SongSelect private const int set_count = 5; private const int diff_count = 3; + [Cached(typeof(BeatmapStore))] + private TestBeatmapStore beatmaps = new TestBeatmapStore(); + [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { @@ -1329,7 +1334,8 @@ namespace osu.Game.Tests.Visual.SongSelect carouselAdjust?.Invoke(carousel); - carousel.BeatmapSets = beatmapSets; + beatmaps.BeatmapSets.Clear(); + beatmaps.BeatmapSets.AddRange(beatmapSets); (target ?? this).Child = carousel; }); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 3d86b214fd..c415fc876f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.SongSelect Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(Realm); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); - Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); + Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(music = new MusicController()); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs index 0b0cd0317a..ff0f35576c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -10,12 +9,14 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Online; using osu.Game.Tests.Resources; using osuTK.Input; @@ -31,6 +32,9 @@ namespace osu.Game.Tests.Visual.SongSelect private BeatmapSetInfo testBeatmapSetInfo = null!; + [Cached(typeof(BeatmapStore))] + private TestBeatmapStore beatmaps = new TestBeatmapStore(); + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -246,13 +250,12 @@ namespace osu.Game.Tests.Visual.SongSelect private BeatmapCarousel createCarousel() { + beatmaps.BeatmapSets.Clear(); + beatmaps.BeatmapSets.Add(testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(5)); + return carousel = new BeatmapCarousel(new FilterCriteria()) { RelativeSizeAxes = Axes.Both, - BeatmapSets = new List - { - (testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(5)), - } }; } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunScreenUIScale.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunScreenUIScale.cs index 2dee57f4cb..4d180f6507 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunScreenUIScale.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunScreenUIScale.cs @@ -3,8 +3,10 @@ using osu.Framework.Allocation; using osu.Framework.Screens; +using osu.Game.Database; using osu.Game.Overlays; using osu.Game.Overlays.FirstRunSetup; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.UserInterface { @@ -13,6 +15,9 @@ namespace osu.Game.Tests.Visual.UserInterface [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + [Cached(typeof(BeatmapStore))] + private BeatmapStore beatmapStore = new TestBeatmapStore(); + public TestSceneFirstRunScreenUIScale() { AddStep("load screen", () => diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs index 2ca06bf2f4..dc51e5516a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs @@ -17,12 +17,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.FirstRunSetup; using osu.Game.Overlays.Notifications; using osu.Game.Screens; using osu.Game.Screens.Footer; +using osu.Game.Tests.Beatmaps; using osuTK; using osuTK.Input; @@ -47,6 +49,7 @@ namespace osu.Game.Tests.Visual.UserInterface Dependencies.Cache(LocalConfig = new OsuConfigManager(LocalStorage)); Dependencies.CacheAs(performer.Object); Dependencies.CacheAs(notificationOverlay.Object); + Dependencies.CacheAs(new TestBeatmapStore()); } [SetUpSteps] diff --git a/osu.Game/Database/BeatmapStore.cs b/osu.Game/Database/BeatmapStore.cs index f288279a79..9853e4b9cf 100644 --- a/osu.Game/Database/BeatmapStore.cs +++ b/osu.Game/Database/BeatmapStore.cs @@ -30,6 +30,6 @@ namespace osu.Game.Database /// It is generally expected that once a beatmap store is in a good state, the overhead of this call /// should be negligible. /// - public abstract IBindableList GetBeatmaps(CancellationToken? cancellationToken); + public abstract IBindableList GetBeatmapSets(CancellationToken? cancellationToken); } } diff --git a/osu.Game/Database/RealmDetachedBeatmapStore.cs b/osu.Game/Database/RealmDetachedBeatmapStore.cs index bc0dc2ae93..b05e07ef31 100644 --- a/osu.Game/Database/RealmDetachedBeatmapStore.cs +++ b/osu.Game/Database/RealmDetachedBeatmapStore.cs @@ -27,7 +27,7 @@ namespace osu.Game.Database [Resolved] private RealmAccess realm { get; set; } = null!; - public override IBindableList GetBeatmaps(CancellationToken? cancellationToken) + public override IBindableList GetBeatmapSets(CancellationToken? cancellationToken) { loaded.Wait(cancellationToken ?? CancellationToken.None); return detachedBeatmapSets.GetBoundCopy(); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 6dfb834317..65c4133ea2 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -112,27 +112,13 @@ namespace osu.Game.Screens.Select [Resolved] private RealmAccess realm { get; set; } = null!; - [Resolved] - private BeatmapStore? beatmapStore { get; set; } - private IBindableList? detachedBeatmapSets; private readonly NoResultsPlaceholder noResultsPlaceholder; private IEnumerable beatmapSets => root.Items.OfType(); - internal IEnumerable BeatmapSets - { - get => beatmapSets.Select(g => g.BeatmapSet); - set - { - if (LoadState != LoadState.NotLoaded) - throw new InvalidOperationException("If not using a realm source, beatmap sets must be set before load."); - - detachedBeatmapSets = new BindableList(value); - Schedule(loadNewRoot); - } - } + internal IEnumerable BeatmapSets => beatmapSets.Select(g => g.BeatmapSet); private void loadNewRoot() { @@ -234,7 +220,7 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, AudioManager audio, CancellationToken? cancellationToken) + private void load(OsuConfigManager config, AudioManager audio, BeatmapStore beatmaps, CancellationToken? cancellationToken) { spinSample = audio.Samples.Get("SongSelect/random-spin"); randomSelectSample = audio.Samples.Get(@"SongSelect/select-random"); @@ -244,12 +230,9 @@ namespace osu.Game.Screens.Select RightClickScrollingEnabled.BindValueChanged(enabled => Scroll.RightMouseScrollbar = enabled.NewValue, true); - if (beatmapStore != null && detachedBeatmapSets == null) - { - detachedBeatmapSets = beatmapStore.GetBeatmaps(cancellationToken); - detachedBeatmapSets.BindCollectionChanged(beatmapSetsChanged); - loadNewRoot(); - } + detachedBeatmapSets = beatmaps.GetBeatmapSets(cancellationToken); + detachedBeatmapSets.BindCollectionChanged(beatmapSetsChanged); + loadNewRoot(); } private readonly HashSet setsRequiringUpdate = new HashSet(); diff --git a/osu.Game/Tests/Beatmaps/TestBeatmapStore.cs b/osu.Game/Tests/Beatmaps/TestBeatmapStore.cs new file mode 100644 index 0000000000..1734f1397f --- /dev/null +++ b/osu.Game/Tests/Beatmaps/TestBeatmapStore.cs @@ -0,0 +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.Threading; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Database; + +namespace osu.Game.Tests.Beatmaps +{ + internal partial class TestBeatmapStore : BeatmapStore + { + public readonly BindableList BeatmapSets = new BindableList(); + public override IBindableList GetBeatmapSets(CancellationToken? cancellationToken) => BeatmapSets; + } +}