From 91fb59ee15caf75b08a43bd6508a4edf3f49e8f5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 11 Feb 2024 07:37:13 +0300 Subject: [PATCH 001/139] 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/139] 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/139] 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/139] 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/139] 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 d4bfbe59174a7669d6027d704092ff829cb990d5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 29 Aug 2024 20:21:32 +0900 Subject: [PATCH 006/139] Add test --- .../ManiaScoreProcessorTest.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/ManiaScoreProcessorTest.cs diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaScoreProcessorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaScoreProcessorTest.cs new file mode 100644 index 0000000000..1516aaf058 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/ManiaScoreProcessorTest.cs @@ -0,0 +1,39 @@ +// 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.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [TestFixture] + public class ManiaScoreProcessorTest + { + [TestCase(ScoreRank.X, 1, HitResult.Perfect)] + [TestCase(ScoreRank.X, 0.99, HitResult.Great)] + [TestCase(ScoreRank.D, 0.1, HitResult.Great)] + [TestCase(ScoreRank.X, 0.99, HitResult.Perfect, HitResult.Great)] + [TestCase(ScoreRank.X, 0.99, HitResult.Great, HitResult.Great)] + [TestCase(ScoreRank.S, 0.99, HitResult.Perfect, HitResult.Good)] + [TestCase(ScoreRank.S, 0.99, HitResult.Perfect, HitResult.Ok)] + [TestCase(ScoreRank.S, 0.99, HitResult.Perfect, HitResult.Meh)] + [TestCase(ScoreRank.S, 0.99, HitResult.Perfect, HitResult.Miss)] + [TestCase(ScoreRank.S, 0.99, HitResult.Great, HitResult.Good)] + [TestCase(ScoreRank.S, 0.99, HitResult.Great, HitResult.Ok)] + [TestCase(ScoreRank.S, 0.99, HitResult.Great, HitResult.Meh)] + [TestCase(ScoreRank.S, 0.99, HitResult.Great, HitResult.Miss)] + public void TestRanks(ScoreRank expected, double accuracy, params HitResult[] results) + { + var scoreProcessor = new ManiaScoreProcessor(); + + Dictionary resultsDict = new Dictionary(); + foreach (var result in results) + resultsDict[result] = resultsDict.GetValueOrDefault(result) + 1; + + Assert.That(scoreProcessor.RankFromScore(accuracy, resultsDict), Is.EqualTo(expected)); + } + } +} From d59d5685d08ef99f15beba32e60f2ebe0c2b9c18 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 29 Aug 2024 20:21:59 +0900 Subject: [PATCH 007/139] Make mania award SS even if there are GREAT judgements --- .../Scoring/ManiaScoreProcessor.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 0444394d87..dfd6ed6dd2 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mania.Scoring { @@ -58,6 +59,24 @@ namespace osu.Game.Rulesets.Mania.Scoring return GetBaseScoreForResult(result); } + public override ScoreRank RankFromScore(double accuracy, IReadOnlyDictionary results) + { + ScoreRank rank = base.RankFromScore(accuracy, results); + + if (rank != ScoreRank.S) + return rank; + + // SS is expected as long as all hitobjects have been hit with either a GREAT or PERFECT result. + + bool anyImperfect = + results.GetValueOrDefault(HitResult.Good) > 0 + || results.GetValueOrDefault(HitResult.Ok) > 0 + || results.GetValueOrDefault(HitResult.Meh) > 0 + || results.GetValueOrDefault(HitResult.Miss) > 0; + + return anyImperfect ? rank : ScoreRank.X; + } + private class JudgementOrderComparer : IComparer { public static readonly JudgementOrderComparer DEFAULT = new JudgementOrderComparer(); From d97a94aadd68aada70a8d808fdb7622e96267e44 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 29 Aug 2024 20:22:54 +0900 Subject: [PATCH 008/139] Change accuracy circle to show badges based on score rank --- osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index cebc54f490..319a87fdfc 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -298,7 +298,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { foreach (var badge in badges) { - if (badge.Accuracy > score.Accuracy) + if (badge.Rank > score.Rank) continue; using (BeginDelayedSequence( From be5cb209e502c1df833ad7dc542c6fc4a1c8a846 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Oct 2024 17:51:29 +0900 Subject: [PATCH 009/139] Update notification text when import is paused due to gameplay Addresses https://github.com/ppy/osu/discussions/30388. --- osu.Game/Database/RealmArchiveModelImporter.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index cf0625c51c..a27e35fc4e 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -105,7 +105,6 @@ namespace osu.Game.Database } notification.Progress = 0; - notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is initialising..."; int current = 0; @@ -113,6 +112,18 @@ namespace osu.Game.Database parameters.Batch |= tasks.Length >= minimum_items_considered_batch_import; + // A paused state could obviously be entered mid-import (during the `Task.WhenAll` below), + // but in order to keep things simple let's focus on the most common scenario. + notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is paused due to gameplay..."; + + try + { + pauseIfNecessary(parameters, notification.CancellationToken); + } + catch { } + + notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is initialising..."; + await Task.WhenAll(tasks.Select(async task => { if (notification.CancellationToken.IsCancellationRequested) From 61f0cfd688f45d8bc02ca24a71566ed662aa8f56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Oct 2024 17:56:51 +0900 Subject: [PATCH 010/139] Make flow more `async` to avoid any chance of deadlocks --- .../Database/RealmArchiveModelImporter.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index a27e35fc4e..200c2051fc 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -112,26 +112,20 @@ namespace osu.Game.Database parameters.Batch |= tasks.Length >= minimum_items_considered_batch_import; - // A paused state could obviously be entered mid-import (during the `Task.WhenAll` below), - // but in order to keep things simple let's focus on the most common scenario. - notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is paused due to gameplay..."; - - try - { - pauseIfNecessary(parameters, notification.CancellationToken); - } - catch { } - notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is initialising..."; + notification.State = ProgressNotificationState.Active; - await Task.WhenAll(tasks.Select(async task => + await pauseIfNecessaryAsync(parameters, notification, notification.CancellationToken).ConfigureAwait(false); + + await Parallel.ForEachAsync(tasks, notification.CancellationToken, async (task, cancellation) => { - if (notification.CancellationToken.IsCancellationRequested) - return; + cancellation.ThrowIfCancellationRequested(); try { - var model = await Import(task, parameters, notification.CancellationToken).ConfigureAwait(false); + await pauseIfNecessaryAsync(parameters, notification, cancellation).ConfigureAwait(false); + + var model = await Import(task, parameters, cancellation).ConfigureAwait(false); lock (imported) { @@ -150,7 +144,7 @@ namespace osu.Game.Database { Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database); } - })).ConfigureAwait(false); + }).ConfigureAwait(false); if (imported.Count == 0) { @@ -297,8 +291,6 @@ namespace osu.Game.Database /// An optional cancellation token. public virtual Live? ImportModel(TModel item, ArchiveReader? archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default) => Realm.Run(realm => { - pauseIfNecessary(parameters, cancellationToken); - TModel? existing; if (parameters.Batch && archive != null) @@ -586,21 +578,29 @@ namespace osu.Game.Database /// Whether to perform deletion. protected virtual bool ShouldDeleteArchive(string path) => false; - private void pauseIfNecessary(ImportParameters importParameters, CancellationToken cancellationToken) + private async Task pauseIfNecessaryAsync(ImportParameters importParameters, ProgressNotification notification, CancellationToken cancellationToken) { if (!PauseImports || importParameters.ImportImmediately) return; Logger.Log($@"{GetType().Name} is being paused."); + // A paused state could obviously be entered mid-import (during the `Task.WhenAll` below), + // but in order to keep things simple let's focus on the most common scenario. + notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is paused due to gameplay..."; + notification.State = ProgressNotificationState.Queued; + while (PauseImports) { cancellationToken.ThrowIfCancellationRequested(); - Thread.Sleep(500); + await Task.Delay(500, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); Logger.Log($@"{GetType().Name} is being resumed."); + + notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is resuming..."; + notification.State = ProgressNotificationState.Active; } private IEnumerable getIDs(IEnumerable files) From 2fd495228c21f9bd8e8700aefdb1b93c69027d3e Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Fri, 25 Oct 2024 02:25:32 -0400 Subject: [PATCH 011/139] 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 012/139] 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 013/139] 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 014/139] 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 015/139] 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 72564b5c98c530f2ea39b98844e592528b44d439 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 12 Nov 2024 01:38:31 +0900 Subject: [PATCH 016/139] Make `CurrentPlaylistItem` not a bindable --- .../TestSceneDrawableLoungeRoom.cs | 20 ++-- .../Multiplayer/TestSceneDrawableRoom.cs | 94 +++++++++---------- .../Multiplayer/TestSceneMatchStartControl.cs | 22 ++--- .../TestSceneMultiplayerMatchFooter.cs | 9 +- .../TestSceneMultiplayerPlaylist.cs | 6 +- .../TestSceneMultiplayerSpectateButton.cs | 11 +-- .../UpdateableBeatmapBackgroundSprite.cs | 19 ++-- .../Online/Multiplayer/MultiplayerClient.cs | 4 +- osu.Game/Online/Rooms/Room.cs | 49 +++++++--- .../Components/OnlinePlayBackgroundSprite.cs | 41 -------- .../Lounge/Components/DrawableRoom.cs | 58 +++++++----- .../Lounge/Components/RoomsContainer.cs | 2 +- .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 61 ++++++++---- .../OnlinePlay/Match/DrawableMatchRoom.cs | 34 +++---- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 3 +- .../Multiplayer/Match/MatchStartControl.cs | 14 ++- .../Match/MultiplayerMatchFooter.cs | 16 +++- .../Match/MultiplayerMatchSettingsOverlay.cs | 19 ++-- .../Match/MultiplayerSpectateButton.cs | 16 ++-- .../Match/Playlist/MultiplayerPlaylist.cs | 14 ++- .../Multiplayer/MultiplayerMatchSubScreen.cs | 36 ++++--- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 74 +++++---------- .../Multiplayer/TestMultiplayerClient.cs | 2 +- .../OnlinePlay/TestRoomRequestsHandler.cs | 9 +- 24 files changed, 326 insertions(+), 307 deletions(-) delete mode 100644 osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs index 4de911b6b6..9fe7189a0a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.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 System.Linq; using System.Threading; @@ -10,6 +8,7 @@ using System.Threading.Tasks; using Moq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Testing; @@ -31,8 +30,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [Cached] protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); - private DrawableLoungeRoom drawableRoom; - private SearchTextBox searchTextBox; + private DrawableLoungeRoom drawableRoom = null!; + private SearchTextBox searchTextBox = null!; private readonly ManualResetEventSlim allowResponseCallback = new ManualResetEventSlim(); @@ -78,6 +77,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Anchor = Anchor.Centre, Origin = Anchor.Centre, + SelectedRoom = new Bindable() } } }; @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFocusViaKeyboardCommit() { - DrawableLoungeRoom.PasswordEntryPopover popover = null; + DrawableLoungeRoom.PasswordEntryPopover? popover = null; AddAssert("search textbox has focus", () => checkFocus(searchTextBox)); AddStep("click room twice", () => @@ -103,11 +103,11 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("enter password", () => popover.ChildrenOfType().Single().Text = "password"); AddStep("commit via enter", () => InputManager.Key(Key.Enter)); - AddAssert("popover has focus", () => checkFocus(popover)); + AddAssert("popover has focus", () => checkFocus(popover!)); AddStep("attempt another enter", () => InputManager.Key(Key.Enter)); - AddAssert("popover still has focus", () => checkFocus(popover)); + AddAssert("popover still has focus", () => checkFocus(popover!)); AddStep("unblock response", () => allowResponseCallback.Set()); @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFocusViaMouseCommit() { - DrawableLoungeRoom.PasswordEntryPopover popover = null; + DrawableLoungeRoom.PasswordEntryPopover? popover = null; AddAssert("search textbox has focus", () => checkFocus(searchTextBox)); AddStep("click room twice", () => @@ -144,11 +144,11 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); - AddAssert("popover has focus", () => checkFocus(popover)); + AddAssert("popover has focus", () => checkFocus(popover!)); AddStep("attempt another click", () => InputManager.Click(MouseButton.Left)); - AddAssert("popover still has focus", () => checkFocus(popover)); + AddAssert("popover still has focus", () => checkFocus(popover!)); AddStep("unblock response", () => allowResponseCallback.Set()); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 98242e2d92..4c609613a3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -41,6 +41,31 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create rooms", () => { + PlaylistItem item1 = new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = { StarRating = 2.5 } + }.BeatmapInfo); + + PlaylistItem item2 = new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = { StarRating = 4.5 } + }.BeatmapInfo); + + PlaylistItem item3 = new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = + { + StarRating = 2.5, + Metadata = + { + Artist = "very very very very very very very very very long artist", + ArtistUnicode = "very very very very very very very very very long artist", + Title = "very very very very very very very very very very very long title", + TitleUnicode = "very very very very very very very very very very very long title", + } + } + }.BeatmapInfo); + Child = rooms = new FillFlowContainer { Anchor = Anchor.Centre, @@ -56,16 +81,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Status = { Value = new RoomStatusOpen() }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, Type = { Value = MatchType.HeadToHead }, - Playlist = - { - new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo) - { - BeatmapInfo = - { - StarRating = 2.5 - } - }.BeatmapInfo) - } + Playlist = { item1 }, + CurrentPlaylistItem = item1 }), createLoungeRoom(new Room { @@ -74,46 +91,16 @@ namespace osu.Game.Tests.Visual.Multiplayer HasPassword = { Value = true }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, Type = { Value = MatchType.HeadToHead }, - Playlist = - { - new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo) - { - BeatmapInfo = - { - StarRating = 2.5, - Metadata = - { - Artist = "very very very very very very very very very long artist", - ArtistUnicode = "very very very very very very very very very long artist", - Title = "very very very very very very very very very very very long title", - TitleUnicode = "very very very very very very very very very very very long title", - } - } - }.BeatmapInfo) - } + Playlist = { item3 }, + CurrentPlaylistItem = item3 }), createLoungeRoom(new Room { Name = { Value = "Playlist room with multiple beatmaps" }, Status = { Value = new RoomStatusPlaying() }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, - Playlist = - { - new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo) - { - BeatmapInfo = - { - StarRating = 2.5 - } - }.BeatmapInfo), - new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo) - { - BeatmapInfo = - { - StarRating = 4.5 - } - }.BeatmapInfo) - } + Playlist = { item1, item2 }, + CurrentPlaylistItem = item1 }), createLoungeRoom(new Room { @@ -181,20 +168,29 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = { Value = "A host-only room" }, QueueMode = { Value = QueueMode.HostOnly }, - Type = { Value = MatchType.HeadToHead } - }), + Type = { Value = MatchType.HeadToHead }, + }) + { + SelectedItem = new Bindable() + }, new DrawableMatchRoom(new Room { Name = { Value = "An all-players, team-versus room" }, QueueMode = { Value = QueueMode.AllPlayers }, Type = { Value = MatchType.TeamVersus } - }), + }) + { + SelectedItem = new Bindable() + }, new DrawableMatchRoom(new Room { Name = { Value = "A round-robin room" }, QueueMode = { Value = QueueMode.AllPlayersRoundRobin }, Type = { Value = MatchType.HeadToHead } - }), + }) + { + SelectedItem = new Bindable() + }, } }); } @@ -215,7 +211,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return new DrawableLoungeRoom(room) { MatchingFilter = true, - SelectedRoom = { BindTarget = selectedRoom } + SelectedRoom = selectedRoom }; } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index 3b10509895..4eddac8c7a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -43,9 +43,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private OsuButton readyButton => control.ChildrenOfType().Single(); - [Cached(typeof(IBindable))] - private readonly Bindable currentItem = new Bindable(); - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) { Model = { BindTarget = room } }; @@ -107,31 +104,33 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUpSteps] public void SetUpSteps() { + PlaylistItem item = null!; + AddStep("reset state", () => { multiplayerClient.Invocations.Clear(); beatmapAvailability.Value = BeatmapAvailability.LocallyAvailable(); - currentItem.Value = new PlaylistItem(Beatmap.Value.BeatmapInfo) + item = new PlaylistItem(Beatmap.Value.BeatmapInfo) { RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID }; room.Value = new Room { - Playlist = { currentItem.Value }, - CurrentPlaylistItem = { BindTarget = currentItem } + Playlist = { item }, + CurrentPlaylistItem = item }; - localUser = new MultiplayerRoomUser(API.LocalUser.Value.Id) { User = API.LocalUser.Value }; + localUser = new MultiplayerRoomUser(API.LocalUser.Value.Id) + { + User = API.LocalUser.Value + }; multiplayerRoom = new MultiplayerRoom(0) { - Playlist = - { - TestMultiplayerClient.CreateMultiplayerPlaylistItem(currentItem.Value), - }, + Playlist = { TestMultiplayerClient.CreateMultiplayerPlaylistItem(item) }, Users = { localUser }, Host = localUser, }; @@ -144,6 +143,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(250, 50), + SelectedItem = new Bindable(item) }; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs index 9d8ef76e75..edeb1708e0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.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.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -13,9 +12,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerMatchFooter : MultiplayerTestScene { - [Cached(typeof(IBindable))] - private readonly Bindable currentItem = new Bindable(); - public override void SetUpSteps() { base.SetUpSteps(); @@ -33,7 +29,10 @@ namespace osu.Game.Tests.Visual.Multiplayer Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, Height = 50, - Child = new MultiplayerMatchFooter() + Child = new MultiplayerMatchFooter + { + SelectedItem = new Bindable() + } } }; }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 3baabecd84..e12c2bee49 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -28,9 +28,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerPlaylist : MultiplayerTestScene { - [Cached(typeof(IBindable))] - private readonly Bindable currentItem = new Bindable(); - private MultiplayerPlaylist list = null!; private BeatmapManager beatmaps = null!; private BeatmapSetInfo importedSet = null!; @@ -56,7 +53,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.4f, 0.8f) + Size = new Vector2(0.4f, 0.8f), + SelectedItem = new Bindable() }; }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 5ae5d1e228..b4f55bb6a3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerSpectateButton : MultiplayerTestScene { - [Cached(typeof(IBindable))] - private readonly Bindable currentItem = new Bindable(); - private MultiplayerSpectateButton spectateButton = null!; private MatchStartControl startControl = null!; @@ -51,13 +48,13 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create button", () => { - AvailabilityTracker.SelectedItem.BindTo(currentItem); + PlaylistItem item = SelectedRoom.Value.Playlist.First(); + + AvailabilityTracker.SelectedItem.Value = item; importedSet = beatmaps.GetAllUsableBeatmapSets().First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); - currentItem.Value = SelectedRoom.Value.Playlist.First(); - Child = new PopoverContainer { RelativeSizeAxes = Axes.Both, @@ -72,12 +69,14 @@ namespace osu.Game.Tests.Visual.Multiplayer Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200, 50), + SelectedItem = new Bindable(item) }, startControl = new MatchStartControl { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200, 50), + SelectedItem = new Bindable(item) } } } diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index 6f71fa90b8..8ddda485b5 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -14,9 +14,17 @@ namespace osu.Game.Beatmaps.Drawables /// public partial class UpdateableBeatmapBackgroundSprite : ModelBackedDrawable { - public readonly Bindable Beatmap = new Bindable(); + public readonly Bindable Beatmap = new Bindable(); - protected override double LoadDelay => 500; + /// + /// Delay before the background is loaded while on-screen. + /// + public double BackgroundLoadDelay = 500; + + /// + /// Delay before the background is unloaded while off-screen. + /// + public double BackgroundUnloadDelay = 10000; [Resolved] private BeatmapManager beatmaps { get; set; } = null!; @@ -29,10 +37,9 @@ namespace osu.Game.Beatmaps.Drawables this.beatmapSetCoverType = beatmapSetCoverType; } - /// - /// Delay before the background is unloaded while off-screen. - /// - protected virtual double UnloadDelay => 10000; + protected override double LoadDelay => BackgroundLoadDelay; + + protected virtual double UnloadDelay => BackgroundUnloadDelay; protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad, UnloadDelay) { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index ff147aba10..5519d58060 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -203,7 +203,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.Playlist.Clear(); APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(item => new PlaylistItem(item))); - APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId); + APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId); // The server will null out the end date upon the host joining the room, but the null value is never communicated to the client. APIRoom.EndDate.Value = null; @@ -847,7 +847,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.Type.Value = Room.Settings.MatchType; APIRoom.QueueMode.Value = Room.Settings.QueueMode; APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration; - APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId); + APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId); APIRoom.AutoSkip.Value = Room.Settings.AutoSkip; RoomUpdated?.Invoke(); diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index c39932c3bf..1f5fec6089 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -1,10 +1,11 @@ // 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 System.Collections.Generic; +using System.ComponentModel; using System.Linq; +using System.Runtime.CompilerServices; using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -16,8 +17,25 @@ using osu.Game.Online.Rooms.RoomStatuses; namespace osu.Game.Online.Rooms { [JsonObject(MemberSerialization.OptIn)] - public partial class Room : IDependencyInjectionCandidate + public partial class Room : IDependencyInjectionCandidate, INotifyPropertyChanged { + public event PropertyChangedEventHandler? PropertyChanged; + + [JsonProperty("current_playlist_item")] + private PlaylistItem? currentPlaylistItem; + + /// + /// Represents the current item selected within the room. + /// + /// + /// Only valid for room listing requests (i.e. in the lounge screen), and may not be valid while inside the room. + /// + public PlaylistItem? CurrentPlaylistItem + { + get => currentPlaylistItem; + set => SetField(ref currentPlaylistItem, value); + } + [Cached] [JsonProperty("id")] public readonly Bindable RoomID = new Bindable(); @@ -28,7 +46,7 @@ namespace osu.Game.Online.Rooms [Cached] [JsonProperty("host")] - public readonly Bindable Host = new Bindable(); + public readonly Bindable Host = new Bindable(); [Cached] [JsonProperty("playlist")] @@ -38,10 +56,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("channel_id")] public readonly Bindable ChannelId = new Bindable(); - [JsonProperty("current_playlist_item")] - [Cached] - public readonly Bindable CurrentPlaylistItem = new Bindable(); - [JsonProperty("playlist_item_stats")] [Cached] public readonly Bindable PlaylistItemStats = new Bindable(); @@ -126,7 +140,7 @@ namespace osu.Game.Online.Rooms [Cached(Name = nameof(Password))] [JsonProperty("password")] - public readonly Bindable Password = new Bindable(); + public readonly Bindable Password = new Bindable(); [Cached] public readonly Bindable Duration = new Bindable(); @@ -203,7 +217,7 @@ namespace osu.Game.Online.Rooms AutoStartDuration.Value = other.AutoStartDuration.Value; DifficultyRange.Value = other.DifficultyRange.Value; PlaylistItemStats.Value = other.PlaylistItemStats.Value; - CurrentPlaylistItem.Value = other.CurrentPlaylistItem.Value; + CurrentPlaylistItem = other.CurrentPlaylistItem; AutoSkip.Value = other.AutoSkip.Value; other.RemoveExpiredPlaylistItems(); @@ -240,7 +254,7 @@ namespace osu.Game.Online.Rooms public int CountTotal; [JsonProperty("ruleset_ids")] - public int[] RulesetIDs; + public int[] RulesetIDs = []; } [JsonObject(MemberSerialization.OptIn)] @@ -252,5 +266,18 @@ namespace osu.Game.Online.Rooms [JsonProperty("max")] public double Max; } + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null!) + => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + + protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null!) + { + if (EqualityComparer.Default.Equals(field, value)) + return false; + + field = value; + OnPropertyChanged(propertyName); + return true; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs deleted file mode 100644 index 0d4cd30090..0000000000 --- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.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. - -#nullable disable - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Beatmaps.Drawables; -using osu.Game.Online.Rooms; - -namespace osu.Game.Screens.OnlinePlay.Components -{ - public partial class OnlinePlayBackgroundSprite : OnlinePlayComposite - { - protected readonly BeatmapSetCoverType BeatmapSetCoverType; - private UpdateableBeatmapBackgroundSprite sprite; - - public OnlinePlayBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover) - { - BeatmapSetCoverType = beatmapSetCoverType; - } - - [BackgroundDependencyLoader] - private void load() - { - InternalChild = sprite = CreateBackgroundSprite(); - - CurrentPlaylistItem.BindValueChanged(_ => updateBeatmap()); - Playlist.CollectionChanged += (_, _) => updateBeatmap(); - - updateBeatmap(); - } - - private void updateBeatmap() - { - sprite.Beatmap.Value = CurrentPlaylistItem.Value?.Beatmap ?? Playlist.GetCurrentItem()?.Beatmap; - } - - protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both }; - } -} diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index ef06d21655..59c8047062 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -1,9 +1,8 @@ // 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.Collections.Generic; +using System.Diagnostics; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -16,6 +15,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -29,27 +29,28 @@ using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public partial class DrawableRoom : CompositeDrawable + public abstract partial class DrawableRoom : CompositeDrawable { protected const float CORNER_RADIUS = 10; private const float height = 100; public readonly Room Room; - protected Container ButtonsContainer { get; private set; } + protected readonly Bindable SelectedItem = new Bindable(); + protected Container ButtonsContainer { get; private set; } = null!; private readonly Bindable roomType = new Bindable(); private readonly Bindable roomCategory = new Bindable(); private readonly Bindable hasPassword = new Bindable(); - private DrawableRoomParticipantsList drawableRoomParticipantsList; - private RoomSpecialCategoryPill specialCategoryPill; - private PasswordProtectedIcon passwordIcon; - private EndDateInfo endDateInfo; + private DrawableRoomParticipantsList? drawableRoomParticipantsList; + private RoomSpecialCategoryPill? specialCategoryPill; + private PasswordProtectedIcon? passwordIcon; + private EndDateInfo? endDateInfo; + private UpdateableBeatmapBackgroundSprite background = null!; + private DelayedLoadWrapper wrapper = null!; - private DelayedLoadWrapper wrapper; - - public DrawableRoom(Room room) + protected DrawableRoom(Room room) { Room = room; @@ -77,7 +78,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components AutoSizeAxes = Axes.X }; - InternalChildren = new[] + InternalChildren = new Drawable[] { // This resolves internal 1px gaps due to applying the (parenting) corner radius and masking across multiple filling background sprites. new Box @@ -85,7 +86,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components RelativeSizeAxes = Axes.Both, Colour = colours.Background5, }, - CreateBackground().With(d => + background = CreateBackground().With(d => { d.RelativeSizeAxes = Axes.Both; }), @@ -186,7 +187,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Font = OsuFont.GetFont(size: 28), Current = { BindTarget = Room.Name } }, - new RoomStatusText() + new RoomStatusText + { + SelectedItem = { BindTarget = SelectedItem } + } } } }, @@ -245,6 +249,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components wrapper.DelayedLoadComplete += _ => { + Debug.Assert(specialCategoryPill != null); + Debug.Assert(endDateInfo != null); + Debug.Assert(passwordIcon != null); + wrapper.FadeInFromZero(200); roomCategory.BindTo(Room.Category); @@ -265,6 +273,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components hasPassword.BindTo(Room.HasPassword); hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true); }; + + SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap, true); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -289,7 +299,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } } - protected virtual Drawable CreateBackground() => new OnlinePlayBackgroundSprite(); + protected virtual UpdateableBeatmapBackgroundSprite CreateBackground() => new UpdateableBeatmapBackgroundSprite(); protected virtual IEnumerable CreateBottomDetails() { @@ -324,14 +334,16 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private partial class RoomStatusText : OnlinePlayComposite { - [Resolved] - private OsuColour colours { get; set; } + public readonly IBindable SelectedItem = new Bindable(); [Resolved] - private BeatmapLookupCache beatmapLookupCache { get; set; } + private OsuColour colours { get; set; } = null!; - private SpriteText statusText; - private LinkFlowContainer beatmapText; + [Resolved] + private BeatmapLookupCache beatmapLookupCache { get; set; } = null!; + + private SpriteText statusText = null!; + private LinkFlowContainer beatmapText = null!; public RoomStatusText() { @@ -383,12 +395,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components protected override void LoadComplete() { base.LoadComplete(); - CurrentPlaylistItem.BindValueChanged(onSelectedItemChanged, true); + SelectedItem.BindValueChanged(onSelectedItemChanged, true); } - private CancellationTokenSource beatmapLookupCancellation; + private CancellationTokenSource? beatmapLookupCancellation; - private void onSelectedItemChanged(ValueChangedEvent item) + private void onSelectedItemChanged(ValueChangedEvent item) { beatmapLookupCancellation?.Cancel(); beatmapText.Clear(); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index e842f8c436..9e60f43ed7 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -140,7 +140,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void addRooms(IEnumerable rooms) { foreach (var room in rooms) - roomFlow.Add(new DrawableLoungeRoom(room) { SelectedRoom = { BindTarget = SelectedRoom } }); + roomFlow.Add(new DrawableLoungeRoom(room) { SelectedRoom = SelectedRoom }); applyFilterCriteria(Filter?.Value); } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index fed47e847a..36db1ab29b 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -1,9 +1,8 @@ // 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.Collections.Generic; +using System.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -28,6 +27,7 @@ using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge.Components; using osuTK; using osuTK.Graphics; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Lounge { @@ -39,14 +39,19 @@ namespace osu.Game.Screens.OnlinePlay.Lounge private const float transition_duration = 60; private const float selection_border_width = 4; - public readonly Bindable SelectedRoom = new Bindable(); + public required Bindable SelectedRoom + { + get => selectedRoom; + set => selectedRoom.Current = value; + } [Resolved(canBeNull: true)] - private LoungeSubScreen lounge { get; set; } + private LoungeSubScreen? lounge { get; set; } - private Sample sampleSelect; - private Sample sampleJoin; - private Drawable selectionBox; + private readonly BindableWithCurrent selectedRoom = new BindableWithCurrent(); + private Sample? sampleSelect; + private Sample? sampleJoin; + private Drawable selectionBox = null!; public DrawableLoungeRoom(Room room) : base(room) @@ -89,12 +94,24 @@ namespace osu.Game.Screens.OnlinePlay.Lounge base.LoadComplete(); Alpha = matchingFilter ? 1 : 0; - selectionBox.Alpha = SelectedRoom.Value == Room ? 1 : 0; + selectionBox.Alpha = selectedRoom.Value == Room ? 1 : 0; - SelectedRoom.BindValueChanged(updateSelectedRoom); + selectedRoom.BindValueChanged(updateSelectedRoom); + + Room.PropertyChanged += onRoomPropertyChanged; + updateSelectedItem(); } - private void updateSelectedRoom(ValueChangedEvent selected) + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.CurrentPlaylistItem)) + updateSelectedItem(); + } + + private void updateSelectedItem() + => SelectedItem.Value = Room.CurrentPlaylistItem; + + private void updateSelectedRoom(ValueChangedEvent selected) { if (selected.NewValue == Room) selectionBox.FadeIn(transition_duration); @@ -140,7 +157,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge if (e.Repeat) return false; - if (SelectedRoom.Value != Room) + if (selectedRoom.Value != Room) return false; switch (e.Action) @@ -157,14 +174,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { } - protected override bool ShouldBeConsideredForInput(Drawable child) => SelectedRoom.Value == Room || child is HoverSounds; + protected override bool ShouldBeConsideredForInput(Drawable child) => selectedRoom.Value == Room || child is HoverSounds; protected override bool OnClick(ClickEvent e) { - if (Room != SelectedRoom.Value) + if (Room != selectedRoom.Value) { sampleSelect?.Play(); - SelectedRoom.Value = Room; + selectedRoom.Value = Room; return true; } @@ -179,12 +196,18 @@ namespace osu.Game.Screens.OnlinePlay.Lounge return true; } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + Room.PropertyChanged -= onRoomPropertyChanged; + } + public partial class PasswordEntryPopover : OsuPopover { private readonly Room room; [Resolved(canBeNull: true)] - private LoungeSubScreen lounge { get; set; } + private LoungeSubScreen? lounge { get; set; } public override bool HandleNonPositionalInput => true; @@ -195,10 +218,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge this.room = room; } - private OsuPasswordTextBox passwordTextBox; - private RoundedButton joinButton; - private OsuSpriteText errorText; - private Sample sampleJoinFail; + private OsuPasswordTextBox passwordTextBox = null!; + private RoundedButton joinButton = null!; + private OsuSpriteText errorText = null!; + private Sample? sampleJoinFail; [BackgroundDependencyLoader] private void load(OsuColour colours, AudioManager audio) diff --git a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs index 3bda93c909..5d4b6b6404 100644 --- a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs @@ -1,10 +1,7 @@ // 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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -21,25 +18,22 @@ namespace osu.Game.Screens.OnlinePlay.Match { public partial class DrawableMatchRoom : DrawableRoom { - public readonly IBindable SelectedItem = new Bindable(); - public Action OnEdit; + public Action? OnEdit; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; - private readonly IBindable host = new Bindable(); + private readonly BindableWithCurrent current = new BindableWithCurrent(); + private readonly IBindable host = new Bindable(); private readonly bool allowEdit; - - [CanBeNull] - private Drawable editButton; - - private BackgroundSprite background; + private Drawable? editButton; public DrawableMatchRoom(Room room, bool allowEdit = true) : base(room) { this.allowEdit = allowEdit; + base.SelectedItem.BindTo(SelectedItem); host.BindTo(room.Host); } @@ -58,21 +52,23 @@ namespace osu.Game.Screens.OnlinePlay.Match } } + public new required Bindable SelectedItem + { + get => current; + set => current.Current = value; + } + protected override void LoadComplete() { base.LoadComplete(); if (editButton != null) host.BindValueChanged(h => editButton.Alpha = h.NewValue?.Equals(api.LocalUser.Value) == true ? 1 : 0, true); - - SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap, true); } - protected override Drawable CreateBackground() => background = new BackgroundSprite(); - - private partial class BackgroundSprite : UpdateableBeatmapBackgroundSprite + protected override UpdateableBeatmapBackgroundSprite CreateBackground() => base.CreateBackground().With(d => { - protected override double LoadDelay => 0; - } + d.BackgroundLoadDelay = 0; + }); } } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 7c8931c04e..9801405dd3 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -35,7 +35,6 @@ namespace osu.Game.Screens.OnlinePlay.Match [Cached(typeof(IPreviewTrackOwner))] public abstract partial class RoomSubScreen : OnlinePlaySubScreen, IPreviewTrackOwner { - [Cached(typeof(IBindable))] public readonly Bindable SelectedItem = new Bindable(); public override bool? ApplyModTrackAdjustments => true; @@ -164,7 +163,7 @@ namespace osu.Game.Screens.OnlinePlay.Match new DrawableMatchRoom(Room, allowEdit) { OnEdit = () => settingsOverlay.Show(), - SelectedItem = { BindTarget = SelectedItem } + SelectedItem = SelectedItem } }, null, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index a82fa6e4bc..0d90d44496 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -23,6 +23,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public partial class MatchStartControl : CompositeDrawable { + public required Bindable SelectedItem + { + get => selectedItem; + set => selectedItem.Current = value; + } + [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!; @@ -32,9 +38,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match [Resolved] private MultiplayerClient client { get; set; } = null!; - [Resolved] - private IBindable currentItem { get; set; } = null!; - + private readonly BindableWithCurrent selectedItem = new BindableWithCurrent(); private readonly MultiplayerReadyButton readyButton; private readonly MultiplayerCountdownButton countdownButton; @@ -94,7 +98,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { base.LoadComplete(); - currentItem.BindValueChanged(_ => updateState()); + SelectedItem.BindValueChanged(_ => updateState()); client.RoomUpdated += onRoomUpdated; client.LoadRequested += onLoadRequested; updateState(); @@ -210,7 +214,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match readyButton.Enabled.Value = countdownButton.Enabled.Value = client.Room.State != MultiplayerRoomState.Closed - && currentItem.Value?.ID == client.Room.Settings.PlaylistItemId + && SelectedItem.Value?.ID == client.Room.Settings.PlaylistItemId && !client.Room.Playlist.Single(i => i.ID == client.Room.Settings.PlaylistItemId).Expired && !operationInProgress.Value; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs index fcb6480b58..2b592bd8b9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.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. -#nullable disable - +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { @@ -13,6 +13,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private const float ready_button_width = 600; private const float spectate_button_width = 200; + public required Bindable SelectedItem + { + get => selectedItem; + set => selectedItem.Current = value; + } + + private readonly BindableWithCurrent selectedItem = new BindableWithCurrent(); + public MultiplayerMatchFooter() { RelativeSizeAxes = Axes.Both; @@ -22,17 +30,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match RelativeSizeAxes = Axes.Both, Content = new[] { - new Drawable[] + new Drawable?[] { null, new MultiplayerSpectateButton { RelativeSizeAxes = Axes.Both, + SelectedItem = selectedItem }, null, new MatchStartControl { RelativeSizeAxes = Axes.Both, + SelectedItem = selectedItem }, null } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 5446211ced..7a1868bfbb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -28,14 +28,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public partial class MultiplayerMatchSettingsOverlay : RoomSettingsOverlay { - private MatchSettings settings = null!; + public required Bindable SelectedItem + { + get => selectedItem; + set => selectedItem.Current = value; + } protected override OsuButton SubmitButton => settings.ApplyButton; + protected override bool IsLoading => ongoingOperationTracker.InProgress.Value; [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!; - protected override bool IsLoading => ongoingOperationTracker.InProgress.Value; + private readonly BindableWithCurrent selectedItem = new BindableWithCurrent(); + private MatchSettings settings = null!; public MultiplayerMatchSettingsOverlay(Room room) : base(room) @@ -48,7 +54,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Y, - SettingsApplied = Hide + SettingsApplied = Hide, + SelectedItem = { BindTarget = SelectedItem } }; protected partial class MatchSettings : OnlinePlayComposite @@ -57,6 +64,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; + public readonly Bindable SelectedItem = new Bindable(); public Action? SettingsApplied; public OsuTextBox NameField = null!; @@ -66,7 +74,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match public OsuTextBox PasswordTextBox = null!; public OsuCheckbox AutoSkipCheckbox = null!; public RoundedButton ApplyButton = null!; - public OsuSpriteText ErrorText = null!; private OsuEnumDropdown startModeDropdown = null!; @@ -367,7 +374,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match base.LoadComplete(); drawablePlaylist.Items.BindTo(Playlist); - drawablePlaylist.SelectedItem.BindTo(CurrentPlaylistItem); + drawablePlaylist.SelectedItem.BindTo(SelectedItem); } protected override void Update() @@ -448,7 +455,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match if (text.StartsWith(not_found_prefix, StringComparison.Ordinal)) { ErrorText.Text = "The selected beatmap is not available online."; - CurrentPlaylistItem.Value.MarkInvalid(); + SelectedItem.Value?.MarkInvalid(); } else { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs index 92edc9b979..905a2bf31b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs @@ -21,6 +21,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public partial class MultiplayerSpectateButton : CompositeDrawable { + public required Bindable SelectedItem + { + get => selectedItem; + set => selectedItem.Current = value; + } + [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!; @@ -30,13 +36,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match [Resolved] private MultiplayerClient client { get; set; } = null!; - [Resolved] - private IBindable currentItem { get; set; } = null!; + private readonly BindableWithCurrent selectedItem = new BindableWithCurrent(); + private readonly RoundedButton button; private IBindable operationInProgress = null!; - private readonly RoundedButton button; - public MultiplayerSpectateButton() { InternalChild = button = new RoundedButton @@ -71,7 +75,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { base.LoadComplete(); - currentItem.BindValueChanged(_ => Scheduler.AddOnce(checkForAutomaticDownload), true); + SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(checkForAutomaticDownload), true); client.RoomUpdated += onRoomUpdated; updateState(); } @@ -117,7 +121,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void checkForAutomaticDownload() { - PlaylistItem? item = currentItem.Value; + PlaylistItem? item = SelectedItem.Value; downloadCheckCancellation?.Cancel(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs index 8ba85019d5..a44891e934 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs @@ -19,6 +19,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist { public readonly Bindable DisplayMode = new Bindable(); + public required Bindable SelectedItem + { + get => selectedItem; + set => selectedItem.Current = value; + } + /// /// Invoked when an item requests to be edited. /// @@ -27,9 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist [Resolved] private MultiplayerClient client { get; set; } = null!; - [Resolved] - private IBindable currentItem { get; set; } = null!; - + private readonly BindableWithCurrent selectedItem = new BindableWithCurrent(); private MultiplayerPlaylistTabControl playlistTabControl = null!; private MultiplayerQueueList queueList = null!; private MultiplayerHistoryList historyList = null!; @@ -58,14 +62,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist queueList = new MultiplayerQueueList { RelativeSizeAxes = Axes.Both, - SelectedItem = { BindTarget = currentItem }, + SelectedItem = { BindTarget = selectedItem }, RequestEdit = item => RequestEdit?.Invoke(item) }, historyList = new MultiplayerHistoryList { RelativeSizeAxes = Axes.Both, Alpha = 0, - SelectedItem = { BindTarget = currentItem } + SelectedItem = { BindTarget = selectedItem } } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index a37314de0e..e0c8675ec5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -1,13 +1,12 @@ // 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.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; @@ -45,12 +44,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public override string ShortTitle => "room"; [Resolved] - private MultiplayerClient client { get; set; } + private MultiplayerClient client { get; set; } = null!; [Resolved(canBeNull: true)] - private OsuGame game { get; set; } + private OsuGame? game { get; set; } - private AddItemButton addItemButton; + private AddItemButton addItemButton = null!; public MultiplayerMatchSubScreen(Room room) : base(room) @@ -95,7 +94,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer }, Content = new[] { - new Drawable[] + new Drawable?[] { // Participants column new GridContainer @@ -142,7 +141,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new MultiplayerPlaylist { RelativeSizeAxes = Axes.Both, - RequestEdit = OpenSongSelection + RequestEdit = OpenSongSelection, + SelectedItem = SelectedItem } }, new[] @@ -220,7 +220,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer /// Opens the song selection screen to add or edit an item. /// /// An optional playlist item to edit. If null, a new item will be added instead. - internal void OpenSongSelection(PlaylistItem itemToEdit = null) + internal void OpenSongSelection(PlaylistItem? itemToEdit = null) { if (!this.IsCurrentScreen()) return; @@ -228,9 +228,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit)); } - protected override Drawable CreateFooter() => new MultiplayerMatchFooter(); + protected override Drawable CreateFooter() => new MultiplayerMatchFooter + { + SelectedItem = SelectedItem + }; - protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new MultiplayerMatchSettingsOverlay(room); + protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new MultiplayerMatchSettingsOverlay(room) + { + SelectedItem = SelectedItem + }; protected override void UpdateMods() { @@ -245,7 +251,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } [Resolved(canBeNull: true)] - private IDialogOverlay dialogOverlay { get; set; } + private IDialogOverlay? dialogOverlay { get; set; } private bool exitConfirmed; @@ -275,8 +281,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return base.OnExiting(e); } - private ModSettingChangeTracker modSettingChangeTracker; - private ScheduledDelegate debouncedModSettingsUpdate; + private ModSettingChangeTracker? modSettingChangeTracker; + private ScheduledDelegate? debouncedModSettingsUpdate; private void onUserModsChanged(ValueChangedEvent> mods) { @@ -422,7 +428,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; // If there's only one playlist item and we are the host, assume we want to change it. Else add a new one. - PlaylistItem itemToEdit = client.IsHost && Room.Playlist.Count == 1 ? Room.Playlist.Single() : null; + PlaylistItem? itemToEdit = client.IsHost && Room.Playlist.Count == 1 ? Room.Playlist.Single() : null; OpenSongSelection(itemToEdit); @@ -434,7 +440,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.Dispose(isDisposing); - if (client != null) + if (client.IsNotNull()) { client.RoomUpdated -= onRoomUpdated; client.LoadRequested -= onLoadRequested; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 5be5c4b4f4..0d96f348ac 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.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 osu.Framework.Allocation; using osu.Framework.Bindables; @@ -10,7 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Screens.OnlinePlay.Match; namespace osu.Game.Screens.OnlinePlay { @@ -20,96 +17,69 @@ namespace osu.Game.Screens.OnlinePlay public partial class OnlinePlayComposite : CompositeDrawable { [Resolved(typeof(Room))] - protected Bindable RoomID { get; private set; } + protected Bindable RoomID { get; private set; } = null!; [Resolved(typeof(Room), nameof(Room.Name))] - protected Bindable RoomName { get; private set; } + protected Bindable RoomName { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable Host { get; private set; } + protected Bindable Host { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable Status { get; private set; } + protected Bindable Status { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable Type { get; private set; } - - /// - /// The currently selected item in the , or the current item from - /// if this is not within a . - /// - [Resolved(typeof(Room))] - protected Bindable CurrentPlaylistItem { get; private set; } + protected Bindable Type { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable PlaylistItemStats { get; private set; } + protected Bindable PlaylistItemStats { get; private set; } = null!; [Resolved(typeof(Room))] - protected BindableList Playlist { get; private set; } + protected BindableList Playlist { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable DifficultyRange { get; private set; } + protected Bindable DifficultyRange { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable Category { get; private set; } + protected Bindable Category { get; private set; } = null!; [Resolved(typeof(Room))] - protected BindableList RecentParticipants { get; private set; } + protected BindableList RecentParticipants { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable ParticipantCount { get; private set; } + protected Bindable ParticipantCount { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable MaxParticipants { get; private set; } + protected Bindable MaxParticipants { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable MaxAttempts { get; private set; } + protected Bindable MaxAttempts { get; private set; } = null!; [Resolved(typeof(Room))] - public Bindable UserScore { get; private set; } + public Bindable UserScore { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable StartDate { get; private set; } + protected Bindable StartDate { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable EndDate { get; private set; } + protected Bindable EndDate { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable Availability { get; private set; } + protected Bindable Availability { get; private set; } = null!; [Resolved(typeof(Room))] - public Bindable Password { get; private set; } + public Bindable Password { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable Duration { get; private set; } + protected Bindable Duration { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable QueueMode { get; private set; } + protected Bindable QueueMode { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable AutoStartDuration { get; private set; } + protected Bindable AutoStartDuration { get; private set; } = null!; [Resolved(typeof(Room))] - protected Bindable AutoSkip { get; private set; } - - [Resolved(CanBeNull = true)] - private IBindable subScreenSelectedItem { get; set; } - - protected override void LoadComplete() - { - base.LoadComplete(); - - subScreenSelectedItem?.BindValueChanged(_ => UpdateSelectedItem()); - Playlist.BindCollectionChanged((_, _) => UpdateSelectedItem(), true); - } - - protected void UpdateSelectedItem() - { - // null room ID means this is a room in the process of being created. - if (RoomID.Value == null) - CurrentPlaylistItem.Value = Playlist.GetCurrentItem(); - else if (subScreenSelectedItem != null) - CurrentPlaylistItem.Value = subScreenSelectedItem.Value; - } + protected Bindable AutoSkip { get; private set; } = null!; } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index efa9dc4990..65a4019bb5 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -232,7 +232,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = ServerAPIRoom.Name.Value, MatchType = ServerAPIRoom.Type.Value, - Password = password, + Password = password ?? string.Empty, QueueMode = ServerAPIRoom.QueueMode.Value, AutoStartDuration = ServerAPIRoom.AutoStartDuration.Value }, diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index ec89f81597..41d288ba60 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.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 System.Collections.Generic; using System.Diagnostics; @@ -296,10 +294,11 @@ namespace osu.Game.Tests.Visual.OnlinePlay Debug.Assert(result != null); // Playlist item IDs and beatmaps aren't serialised. - if (source.CurrentPlaylistItem.Value != null) + if (source.CurrentPlaylistItem != null) { - result.CurrentPlaylistItem.Value = result.CurrentPlaylistItem.Value.With(new Optional(source.CurrentPlaylistItem.Value.Beatmap)); - result.CurrentPlaylistItem.Value.ID = source.CurrentPlaylistItem.Value.ID; + Debug.Assert(result.CurrentPlaylistItem != null); + result.CurrentPlaylistItem = result.CurrentPlaylistItem.With(new Optional(source.CurrentPlaylistItem.Beatmap)); + result.CurrentPlaylistItem.ID = source.CurrentPlaylistItem.ID; } for (int i = 0; i < source.Playlist.Count; i++) From 8d5cd2b3531c86f8b0e85c769c2a610e1dd6b881 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 12 Nov 2024 20:12:50 +0900 Subject: [PATCH 017/139] Fix inspection --- .../Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 0883c626fe..d518565be9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("select first room", () => container.Rooms.First().TriggerClick()); AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); - AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID?.Value))); + AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID.Value))); AddAssert("first spotlight still selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); AddStep("remove spotlight room", () => RoomManager.RemoveRoom(RoomManager.Rooms.Single(r => r.Category.Value == RoomCategory.Spotlight))); From f3fd87af7bb083dc97d054d3a0c6b731acdeca59 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 12 Nov 2024 20:32:59 +0900 Subject: [PATCH 018/139] Make `DrawableMatchRoom` structurally match other implementations --- .../Screens/OnlinePlay/Match/DrawableMatchRoom.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs index 5d4b6b6404..e5d3bd1586 100644 --- a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs @@ -20,10 +20,16 @@ namespace osu.Game.Screens.OnlinePlay.Match { public Action? OnEdit; + public new required Bindable SelectedItem + { + get => selectedItem; + set => selectedItem.Current = value; + } + [Resolved] private IAPIProvider api { get; set; } = null!; - private readonly BindableWithCurrent current = new BindableWithCurrent(); + private readonly BindableWithCurrent selectedItem = new BindableWithCurrent(); private readonly IBindable host = new Bindable(); private readonly bool allowEdit; private Drawable? editButton; @@ -52,12 +58,6 @@ namespace osu.Game.Screens.OnlinePlay.Match } } - public new required Bindable SelectedItem - { - get => current; - set => current.Current = value; - } - protected override void LoadComplete() { base.LoadComplete(); From 48212dfaebe9a5e941f3a1eac413b6ccfe488cf6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Nov 2024 21:08:06 +0900 Subject: [PATCH 019/139] Fix test failures due to early disposal of import stream --- .../Online/TestSceneReplayMissingBeatmap.cs | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs b/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs index 60197e0eb7..b986901dcf 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs @@ -40,15 +40,13 @@ namespace osu.Game.Tests.Visual.Online AddStep("import score", () => { - using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr")) - { - var importTask = new ImportTask(resourceStream, "replay.osr"); + var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr"); + var importTask = new ImportTask(resourceStream, "replay.osr"); - Game.ScoreManager.Import(new[] { importTask }); - } + Game.ScoreManager.Import(new[] { importTask }); }); - AddUntilStep("Replay missing notification show", () => Game.Notifications.ChildrenOfType().Any()); + AddUntilStep("Replay missing notification shown", () => Game.Notifications.ChildrenOfType().Any()); } [Test] @@ -58,15 +56,13 @@ namespace osu.Game.Tests.Visual.Online AddStep("import score", () => { - using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr")) - { - var importTask = new ImportTask(resourceStream, "replay.osr"); + var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr"); + var importTask = new ImportTask(resourceStream, "replay.osr"); - Game.ScoreManager.Import(new[] { importTask }); - } + Game.ScoreManager.Import(new[] { importTask }); }); - AddUntilStep("Replay missing notification not show", () => !Game.Notifications.ChildrenOfType().Any()); + AddUntilStep("Replay missing notification not shown", () => !Game.Notifications.ChildrenOfType().Any()); } private void setupBeatmapResponse(APIBeatmap b) From db025d81eea10954d12ab31f11f2ea61116c853e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 15:50:01 +0900 Subject: [PATCH 020/139] Reorder public property vs private field --- osu.Game/Online/Rooms/Room.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 1f5fec6089..6a4d14189d 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -21,9 +21,6 @@ namespace osu.Game.Online.Rooms { public event PropertyChangedEventHandler? PropertyChanged; - [JsonProperty("current_playlist_item")] - private PlaylistItem? currentPlaylistItem; - /// /// Represents the current item selected within the room. /// @@ -36,6 +33,9 @@ namespace osu.Game.Online.Rooms set => SetField(ref currentPlaylistItem, value); } + [JsonProperty("current_playlist_item")] + private PlaylistItem? currentPlaylistItem; + [Cached] [JsonProperty("id")] public readonly Bindable RoomID = new Bindable(); From 99762da7b8fd5f1439d4a3e2ff5f8dd6b88964b9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 16:28:39 +0900 Subject: [PATCH 021/139] Make RoomID non-bindable Most important changes are to `RoomSubScreen` and `PlaylistsRoomSubScreen`, because those are the only two cases that now bind to the event instead. --- .../StatefulMultiplayerClientTest.cs | 2 +- .../DailyChallenge/TestSceneDailyChallenge.cs | 6 +- .../TestSceneDailyChallengeIntro.cs | 2 +- .../TestSceneDailyChallengeLeaderboard.cs | 4 +- .../Visual/Menus/TestSceneMainMenu.cs | 2 +- .../Multiplayer/TestSceneMatchLeaderboard.cs | 2 +- .../TestScenePlaylistsParticipantsList.cs | 2 +- .../UserInterface/TestSceneMainMenuButton.cs | 4 +- .../Online/Multiplayer/MultiplayerClient.cs | 4 +- osu.Game/Online/Rooms/JoinRoomRequest.cs | 2 +- osu.Game/Online/Rooms/PartRoomRequest.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 18 ++++-- osu.Game/Screens/Menu/DailyChallengeButton.cs | 4 +- .../OnlinePlay/Components/RoomManager.cs | 28 ++++----- .../Components/SelectionPollingComponent.cs | 8 +-- .../DailyChallenge/DailyChallenge.cs | 10 ++-- .../DailyChallengeLeaderboard.cs | 2 +- .../Lounge/Components/RoomsContainer.cs | 23 ++++--- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 8 +-- .../Match/Components/MatchChatDisplay.cs | 2 +- .../Match/Components/RoomSettingsOverlay.cs | 4 +- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 58 ++++++++++-------- .../Match/MultiplayerMatchSettingsOverlay.cs | 2 +- .../Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- .../Multiplayer/MultiplayerPlayer.cs | 23 ++++--- .../Multiplayer/MultiplayerRoomManager.cs | 12 ++-- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 - .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 10 ++-- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 2 +- .../Playlists/PlaylistsRoomSubScreen.cs | 60 ++++++++++++------- osu.Game/Screens/Play/RoomSubmittingPlayer.cs | 12 ++-- .../Multiplayer/TestMultiplayerClient.cs | 2 +- .../Visual/OnlinePlay/TestRoomManager.cs | 10 ++-- .../OnlinePlay/TestRoomRequestsHandler.cs | 6 +- osu.Game/Users/UserActivity.cs | 2 +- 35 files changed, 176 insertions(+), 167 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index b4bbe274a5..2e9170019c 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer var newRoom = new Room(); newRoom.CopyFrom(SelectedRoom.Value); - newRoom.RoomID.Value = null; + newRoom.RoomID = null; MultiplayerClient.RoomSetupAction = room => { room.State = MultiplayerRoomState.Playing; diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index 2791563954..488289cb74 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge { var room = new Room { - RoomID = { Value = 1234 }, + RoomID = 1234, Name = { Value = "Daily Challenge: June 4, 2024" }, Playlist = { @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge { var room = new Room { - RoomID = { Value = 1234 }, + RoomID = 1234, Name = { Value = "Daily Challenge: June 4, 2024" }, Playlist = { @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge { var room = new Room { - RoomID = { Value = 1234 }, + RoomID = 1234, Name = { Value = "Daily Challenge: June 4, 2024" }, Playlist = { diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs index f1a2d6b5f2..c08398ac17 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge { API.Perform(new CreateRoomRequest(room = new Room { - RoomID = { Value = roomId }, + RoomID = roomId, Name = { Value = "Daily Challenge: June 4, 2024" }, Playlist = { diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeLeaderboard.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeLeaderboard.cs index 5fff6bb010..d21ca22e1b 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeLeaderboard.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeLeaderboard.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge return false; }; }); - AddStep("create leaderboard", () => Child = leaderboard = new DailyChallengeLeaderboard(new Room { RoomID = { Value = 1 } }, new PlaylistItem(Beatmap.Value.BeatmapInfo)) + AddStep("create leaderboard", () => Child = leaderboard = new DailyChallengeLeaderboard(new Room { RoomID = 1 }, new PlaylistItem(Beatmap.Value.BeatmapInfo)) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge return false; }; }); - AddStep("create leaderboard", () => Child = leaderboard = new DailyChallengeLeaderboard(new Room { RoomID = { Value = 1 } }, new PlaylistItem(Beatmap.Value.BeatmapInfo)) + AddStep("create leaderboard", () => Child = leaderboard = new DailyChallengeLeaderboard(new Room { RoomID = 1 }, new PlaylistItem(Beatmap.Value.BeatmapInfo)) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index aab3716463..8b81516a34 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Menus beatmap.OnlineID = 1001; getRoomRequest.TriggerSuccess(new Room { - RoomID = { Value = 1234 }, + RoomID = 1234, Name = { Value = "Aug 8, 2024" }, Playlist = { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index ea8fe8873d..f96e0bdd4c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create leaderboard", () => { - SelectedRoom.Value = new Room { RoomID = { Value = 3 } }; + SelectedRoom.Value = new Room { RoomID = 3 }; Child = new MatchLeaderboard { diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index 3b60c28dc0..b9e5875e06 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("create list", () => { - SelectedRoom.Value = new Room { RoomID = { Value = 7 } }; + SelectedRoom.Value = new Room { RoomID = 7 }; for (int i = 0; i < 50; i++) { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs index 41543669eb..3894b6ea5b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.UserInterface beatmap.OnlineID = 1001; getRoomRequest.TriggerSuccess(new Room { - RoomID = { Value = 1234 }, + RoomID = 1234, Playlist = { new PlaylistItem(beatmap) @@ -131,7 +131,7 @@ namespace osu.Game.Tests.Visual.UserInterface beatmap.OnlineID = 1001; getRoomRequest.TriggerSuccess(new Room { - RoomID = { Value = 1234 }, + RoomID = 1234, Playlist = { new PlaylistItem(beatmap) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 5519d58060..bd0fa0cb72 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -181,10 +181,10 @@ namespace osu.Game.Online.Multiplayer await joinOrLeaveTaskChain.Add(async () => { - Debug.Assert(room.RoomID.Value != null); + Debug.Assert(room.RoomID != null); // Join the server-side room. - var joinedRoom = await JoinRoom(room.RoomID.Value.Value, password ?? room.Password.Value).ConfigureAwait(false); + var joinedRoom = await JoinRoom(room.RoomID.Value, password ?? room.Password.Value).ConfigureAwait(false); Debug.Assert(joinedRoom != null); // Populate users. diff --git a/osu.Game/Online/Rooms/JoinRoomRequest.cs b/osu.Game/Online/Rooms/JoinRoomRequest.cs index 9a73104b60..2ceb37fc01 100644 --- a/osu.Game/Online/Rooms/JoinRoomRequest.cs +++ b/osu.Game/Online/Rooms/JoinRoomRequest.cs @@ -27,6 +27,6 @@ namespace osu.Game.Online.Rooms return req; } - protected override string Target => $@"rooms/{Room.RoomID.Value}/users/{User!.Id}"; + protected override string Target => $@"rooms/{Room.RoomID}/users/{User!.Id}"; } } diff --git a/osu.Game/Online/Rooms/PartRoomRequest.cs b/osu.Game/Online/Rooms/PartRoomRequest.cs index 2416833a1e..77b5619efb 100644 --- a/osu.Game/Online/Rooms/PartRoomRequest.cs +++ b/osu.Game/Online/Rooms/PartRoomRequest.cs @@ -23,6 +23,6 @@ namespace osu.Game.Online.Rooms return req; } - protected override string Target => $"rooms/{room.RoomID.Value}/users/{User!.Id}"; + protected override string Target => $"rooms/{room.RoomID}/users/{User!.Id}"; } } diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 6a4d14189d..de854596e9 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -21,6 +21,15 @@ namespace osu.Game.Online.Rooms { public event PropertyChangedEventHandler? PropertyChanged; + /// + /// The online room ID. Will be null while the room has not yet been created. + /// + public long? RoomID + { + get => roomId; + set => SetField(ref roomId, value); + } + /// /// Represents the current item selected within the room. /// @@ -33,13 +42,12 @@ namespace osu.Game.Online.Rooms set => SetField(ref currentPlaylistItem, value); } + [JsonProperty("id")] + private long? roomId; + [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; - [Cached] - [JsonProperty("id")] - public readonly Bindable RoomID = new Bindable(); - [Cached] [JsonProperty("name")] public readonly Bindable Name = new Bindable(); @@ -196,7 +204,7 @@ namespace osu.Game.Online.Rooms /// The to copy values from. public void CopyFrom(Room other) { - RoomID.Value = other.RoomID.Value; + RoomID = other.RoomID; Name.Value = other.Name.Value; Category.Value = other.Category.Value; diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index 4dbebf0ae9..0013c9e033 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -155,9 +155,9 @@ namespace osu.Game.Screens.Menu Room = room; cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet; - if (room.StartDate.Value != null && room.RoomID.Value != lastDailyChallengeRoomID) + if (room.StartDate.Value != null && room.RoomID != lastDailyChallengeRoomID) { - lastDailyChallengeRoomID = room.RoomID.Value; + lastDailyChallengeRoomID = room.RoomID; // new challenge is live, reset intro played static. statics.SetValue(Static.DailyChallengeIntroPlayed, false); diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index cb27d1ee61..555a1f66bd 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -1,13 +1,10 @@ // 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 System.Collections.Generic; using System.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Development; @@ -20,18 +17,17 @@ namespace osu.Game.Screens.OnlinePlay.Components { public partial class RoomManager : Component, IRoomManager { - [CanBeNull] - public event Action RoomsUpdated; + public event Action? RoomsUpdated; private readonly BindableList rooms = new BindableList(); public IBindableList Rooms => rooms; - protected IBindable JoinedRoom => joinedRoom; - private readonly Bindable joinedRoom = new Bindable(); + protected IBindable JoinedRoom => joinedRoom; + private readonly Bindable joinedRoom = new Bindable(); [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; public RoomManager() { @@ -44,7 +40,7 @@ namespace osu.Game.Screens.OnlinePlay.Components PartRoom(); } - public virtual void CreateRoom(Room room, Action onSuccess = null, Action onError = null) + public virtual void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null) { room.Host.Value = api.LocalUser.Value; @@ -69,9 +65,9 @@ namespace osu.Game.Screens.OnlinePlay.Components api.Queue(req); } - private JoinRoomRequest currentJoinRoomRequest; + private JoinRoomRequest? currentJoinRoomRequest; - public virtual void JoinRoom(Room room, string password = null, Action onSuccess = null, Action onError = null) + public virtual void JoinRoom(Room room, string? password = null, Action? onSuccess = null, Action? onError = null) { currentJoinRoomRequest?.Cancel(); currentJoinRoomRequest = new JoinRoomRequest(room, password); @@ -97,7 +93,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { currentJoinRoomRequest?.Cancel(); - if (JoinedRoom.Value == null) + if (joinedRoom.Value == null) return; if (api.State.Value == APIState.Online) @@ -111,14 +107,14 @@ namespace osu.Game.Screens.OnlinePlay.Components public void AddOrUpdateRoom(Room room) { Debug.Assert(ThreadSafety.IsUpdateThread); - Debug.Assert(room.RoomID.Value != null); + Debug.Assert(room.RoomID != null); - if (ignoredRooms.Contains(room.RoomID.Value.Value)) + if (ignoredRooms.Contains(room.RoomID.Value)) return; try { - var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value); + var existing = rooms.FirstOrDefault(e => e.RoomID == room.RoomID); if (existing == null) rooms.Add(room); else @@ -128,7 +124,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { Logger.Error(ex, $"Failed to update room: {room.Name.Value}."); - ignoredRooms.Add(room.RoomID.Value.Value); + ignoredRooms.Add(room.RoomID.Value); rooms.Remove(room); } diff --git a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs index 780ee29e41..8b80228ae1 100644 --- a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.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.Threading.Tasks; using osu.Game.Online.Rooms; @@ -20,21 +18,21 @@ namespace osu.Game.Screens.OnlinePlay.Components this.room = room; } - private GetRoomRequest lastPollRequest; + private GetRoomRequest? lastPollRequest; protected override Task Poll() { if (!API.IsLoggedIn) return base.Poll(); - if (room.RoomID.Value == null) + if (room.RoomID == null) return base.Poll(); var tcs = new TaskCompletionSource(); lastPollRequest?.Cancel(); - var req = new GetRoomRequest(room.RoomID.Value.Value); + var req = new GetRoomRequest(room.RoomID.Value); req.Success += result => { diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 1aaf0a4321..968345c713 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -353,12 +353,12 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private void presentScore(long id) { if (this.IsCurrentScreen()) - this.Push(new PlaylistItemScoreResultsScreen(room.RoomID.Value!.Value, playlistItem, id)); + this.Push(new PlaylistItemScoreResultsScreen(room.RoomID!.Value, playlistItem, id)); } private void onRoomScoreSet(MultiplayerRoomScoreSetEvent e) { - if (e.RoomID != room.RoomID.Value || e.PlaylistItemID != playlistItem.ID) + if (e.RoomID != room.RoomID || e.PlaylistItemID != playlistItem.ID) return; userLookupCache.GetUserAsync(e.UserID).ContinueWith(t => @@ -410,7 +410,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private void dailyChallengeChanged(ValueChangedEvent change) { - if (change.OldValue?.RoomID == room.RoomID.Value && change.NewValue == null && metadataClient.IsConnected.Value) + if (change.OldValue?.RoomID == room.RoomID && change.NewValue == null && metadataClient.IsConnected.Value) { notificationOverlay?.Post(new SimpleNotification { Text = DailyChallengeStrings.ChallengeEndedNotification }); } @@ -437,7 +437,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge roomManager.JoinRoom(room); startLoopingTrack(this, musicController); - metadataClient.BeginWatchingMultiplayerRoom(room.RoomID.Value!.Value).ContinueWith(t => + metadataClient.BeginWatchingMultiplayerRoom(room.RoomID!.Value).ContinueWith(t => { if (t.Exception != null) { @@ -489,7 +489,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut(); roomManager.PartRoom(); - metadataClient.EndWatchingMultiplayerRoom(room.RoomID.Value!.Value).FireAndForget(); + metadataClient.EndWatchingMultiplayerRoom(room.RoomID!.Value).FireAndForget(); return base.OnExiting(e); } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs index c9152393e7..9fe2b70a5a 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs @@ -138,7 +138,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge if (request?.CompletionState == APIRequestCompletionState.Waiting) return; - request = new IndexPlaylistScoresRequest(room.RoomID.Value!.Value, playlistItem.ID); + request = new IndexPlaylistScoresRequest(room.RoomID!.Value, playlistItem.ID); request.Success += req => Schedule(() => { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 9e60f43ed7..3d9b7a46d0 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.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 System.Collections.Generic; using System.Collections.Specialized; @@ -11,6 +9,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; @@ -24,8 +23,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public partial class RoomsContainer : CompositeDrawable, IKeyBindingHandler { - public readonly Bindable SelectedRoom = new Bindable(); - public readonly Bindable Filter = new Bindable(); + public readonly Bindable SelectedRoom = new Bindable(); + public readonly Bindable Filter = new Bindable(); public IReadOnlyList Rooms => roomFlow.FlowingChildren.Cast().ToArray(); @@ -33,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private readonly FillFlowContainer roomFlow; [Resolved] - private IRoomManager roomManager { get; set; } + private IRoomManager roomManager { get; set; } = null!; // handle deselection public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; @@ -67,10 +66,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components rooms.BindTo(roomManager.Rooms); - Filter?.BindValueChanged(criteria => applyFilterCriteria(criteria.NewValue), true); + Filter.BindValueChanged(criteria => applyFilterCriteria(criteria.NewValue), true); } - private void applyFilterCriteria(FilterCriteria criteria) + private void applyFilterCriteria(FilterCriteria? criteria) { roomFlow.Children.ForEach(r => { @@ -113,7 +112,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } } - private void roomsChanged(object sender, NotifyCollectionChangedEventArgs args) + private void roomsChanged(object? sender, NotifyCollectionChangedEventArgs args) { switch (args.Action) { @@ -142,7 +141,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components foreach (var room in rooms) roomFlow.Add(new DrawableLoungeRoom(room) { SelectedRoom = SelectedRoom }); - applyFilterCriteria(Filter?.Value); + applyFilterCriteria(Filter.Value); } private void removeRooms(IEnumerable rooms) @@ -173,7 +172,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components roomFlow.SetLayoutPosition(room, room.Room.Category.Value > RoomCategory.Normal // Always show spotlight playlists at the top of the listing. ? float.MinValue - : -(room.Room.RoomID.Value ?? 0)); + : -(room.Room.RoomID ?? 0)); } } @@ -213,7 +212,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components var visibleRooms = Rooms.AsEnumerable().Where(r => r.IsPresent); - Room room; + Room? room; if (SelectedRoom.Value == null) room = visibleRooms.FirstOrDefault()?.Room; @@ -236,7 +235,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.Dispose(isDisposing); - if (roomManager != null) + if (roomManager.IsNotNull()) roomManager.RoomsUpdated -= updateSorting; } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index 3792a67896..a2eba3188d 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -259,7 +259,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge selectionLease.Return(); selectionLease = null; - if (SelectedRoom.Value?.RoomID.Value == null) + if (SelectedRoom.Value?.RoomID == null) SelectedRoom.Value = new Room(); music?.EnsurePlayingSomething(); @@ -326,19 +326,19 @@ namespace osu.Game.Screens.OnlinePlay.Lounge /// The room to copy. public void OpenCopy(Room room) { - Debug.Assert(room.RoomID.Value != null); + Debug.Assert(room.RoomID != null); if (joiningRoomOperation != null) return; joiningRoomOperation = ongoingOperationTracker?.BeginOperation(); - var req = new GetRoomRequest(room.RoomID.Value.Value); + var req = new GetRoomRequest(room.RoomID.Value); req.Success += r => { // ID must be unset as we use this as a marker for whether this is a client-side (not-yet-created) room or not. - r.RoomID.Value = null; + r.RoomID = null; // Null out dates because end date is not supported client-side and the settings overlay will populate a duration. r.EndDate.Value = null; diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs index 8dc1704fcd..ed4f0ad6d8 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components private void updateChannel() { - if (room.RoomID.Value == null || channelId.Value == 0) + if (room.RoomID == null || channelId.Value == 0) return; Channel.Value = channelManager?.JoinChannel(new Channel { Id = channelId.Value, Type = ChannelType.Multiplayer, Name = $"#lazermp_{room.RoomID.Value}" }); diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs index 916b799d50..14d506a368 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -23,7 +21,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected const float TRANSITION_DURATION = 350; protected const float FIELD_PADDING = 25; - protected OnlinePlayComposite Settings { get; set; } + protected OnlinePlayComposite Settings { get; set; } = null!; protected override bool BlockScrollInput => false; diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 9801405dd3..1b7df9c213 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Linq; using JetBrains.Annotations; @@ -29,6 +30,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Screens.Menu; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Match { @@ -59,8 +61,6 @@ namespace osu.Game.Screens.OnlinePlay.Match /// protected readonly Bindable> UserMods = new Bindable>(Array.Empty()); - protected readonly IBindable RoomId = new Bindable(); - [Resolved(CanBeNull = true)] private IOverlayManager overlayManager { get; set; } @@ -82,6 +82,9 @@ namespace osu.Game.Screens.OnlinePlay.Match [Resolved] private PreviewTrackManager previewTrackManager { get; set; } = null!; + [Resolved(canBeNull: true)] + private IDialogOverlay dialogOverlay { get; set; } + [Cached] private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); @@ -109,8 +112,6 @@ namespace osu.Game.Screens.OnlinePlay.Match this.allowEdit = allowEdit; Padding = new MarginPadding { Top = Header.HEIGHT }; - - RoomId.BindTo(room.RoomID); } [BackgroundDependencyLoader] @@ -252,22 +253,6 @@ namespace osu.Game.Screens.OnlinePlay.Match { base.LoadComplete(); - RoomId.BindValueChanged(id => - { - if (id.NewValue == null) - { - // A new room is being created. - // The main content should be hidden until the settings overlay is hidden, signaling the room is ready to be displayed. - mainContent.Hide(); - settingsOverlay.Show(); - } - else - { - mainContent.Show(); - settingsOverlay.Hide(); - } - }, true); - SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged)); UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods)); @@ -275,6 +260,31 @@ namespace osu.Game.Screens.OnlinePlay.Match beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateWorkingBeatmap()); userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(UserModsSelectOverlay); + + Room.PropertyChanged += onRoomPropertyChanged; + updateSetupState(); + } + + private void onRoomPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.RoomID)) + updateSetupState(); + } + + private void updateSetupState() + { + if (Room.RoomID == null) + { + // A new room is being created. + // The main content should be hidden until the settings overlay is hidden, signaling the room is ready to be displayed. + mainContent.Hide(); + settingsOverlay.Show(); + } + else + { + mainContent.Show(); + settingsOverlay.Hide(); + } } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -285,14 +295,11 @@ namespace osu.Game.Screens.OnlinePlay.Match }; } - [Resolved(canBeNull: true)] - private IDialogOverlay dialogOverlay { get; set; } - protected virtual bool IsConnected => api.State.Value == APIState.Online; public override bool OnBackButton() { - if (Room.RoomID.Value == null) + if (Room.RoomID == null) { if (!ensureExitConfirmed()) return true; @@ -365,7 +372,7 @@ namespace osu.Game.Screens.OnlinePlay.Match if (!IsConnected) return true; - bool hasUnsavedChanges = Room.RoomID.Value == null && Room.Playlist.Count > 0; + bool hasUnsavedChanges = Room.RoomID == null && Room.Playlist.Count > 0; if (dialogOverlay == null || !hasUnsavedChanges) return true; @@ -538,6 +545,7 @@ namespace osu.Game.Screens.OnlinePlay.Match base.Dispose(isDisposing); userModsSelectOverlayRegistration?.Dispose(); + Room.PropertyChanged -= onRoomPropertyChanged; } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 7a1868bfbb..edcad9c3ac 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -353,7 +353,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true); Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); - RoomID.BindValueChanged(roomId => playlistContainer.Alpha = roomId.NewValue == null ? 1 : 0, true); Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true); QueueMode.BindValueChanged(mode => QueueModeDropdown.Current.Value = mode.NewValue, true); AutoStartDuration.BindValueChanged(duration => startModeDropdown.Current.Value = (StartMode)(int)duration.NewValue.TotalSeconds, true); @@ -382,6 +381,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match base.Update(); ApplyButton.Enabled.Value = Playlist.Count > 0 && NameField.Text.Length > 0 && !operationInProgress.Value; + playlistContainer.Alpha = room.RoomID == null ? 1 : 0; } private void apply() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index e0c8675ec5..696eb58c3f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public MultiplayerMatchSubScreen(Room room) : base(room) { - Title = room.RoomID.Value == null ? "New room" : room.Name.Value; + Title = room.RoomID == null ? "New room" : room.Name.Value; Activity.Value = new UserActivity.InLobby(room); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index e560c5ca5d..d444af2fd3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -1,14 +1,13 @@ // 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 System.Diagnostics; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Graphics.UserInterface; @@ -29,17 +28,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override UserActivity InitialActivity => new UserActivity.InMultiplayerGame(Beatmap.Value.BeatmapInfo, Ruleset.Value); [Resolved] - private MultiplayerClient client { get; set; } + private MultiplayerClient client { get; set; } = null!; - private IBindable isConnected; + private IBindable isConnected = null!; private readonly TaskCompletionSource resultsReady = new TaskCompletionSource(); - private readonly MultiplayerRoomUser[] users; - private LoadingLayer loadingDisplay; - - private MultiplayerGameplayLeaderboard multiplayerLeaderboard; + private LoadingLayer loadingDisplay = null!; + private MultiplayerGameplayLeaderboard multiplayerLeaderboard = null!; /// /// Construct a multiplayer player. @@ -153,7 +150,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer GameplayClockContainer.Reset(); } - private void failAndBail(string message = null) + private void failAndBail(string? message = null) { if (!string.IsNullOrEmpty(message)) Logger.Log(message, LoggingTarget.Runtime, LogLevel.Important); @@ -196,14 +193,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override ResultsScreen CreateResults(ScoreInfo score) { - Debug.Assert(Room.RoomID.Value != null); + Debug.Assert(Room.RoomID != null); return multiplayerLeaderboard.TeamScores.Count == 2 - ? new MultiplayerTeamResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem, multiplayerLeaderboard.TeamScores) + ? new MultiplayerTeamResultsScreen(score, Room.RoomID.Value, PlaylistItem, multiplayerLeaderboard.TeamScores) { ShowUserStatistics = true, } - : new MultiplayerResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem) + : new MultiplayerResultsScreen(score, Room.RoomID.Value, PlaylistItem) { ShowUserStatistics = true }; @@ -213,7 +210,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.Dispose(isDisposing); - if (client != null) + if (client.IsNotNull()) { client.GameplayStarted -= onGameplayStarted; client.ResultsReady -= onResultsReady; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs index 5f51ccc8d4..d7efd30435 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.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 System.Diagnostics; using osu.Framework.Allocation; @@ -18,12 +16,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public partial class MultiplayerRoomManager : RoomManager { [Resolved] - private MultiplayerClient multiplayerClient { get; set; } + private MultiplayerClient multiplayerClient { get; set; } = null!; - public override void CreateRoom(Room room, Action onSuccess = null, Action onError = null) + public override void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null) => base.CreateRoom(room, r => joinMultiplayerRoom(r, r.Password.Value, onSuccess, onError), onError); - public override void JoinRoom(Room room, string password = null, Action onSuccess = null, Action onError = null) + public override void JoinRoom(Room room, string? password = null, Action? onSuccess = null, Action? onError = null) { if (!multiplayerClient.IsConnected.Value) { @@ -51,9 +49,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer multiplayerClient.LeaveRoom(); } - private void joinMultiplayerRoom(Room room, string password, Action onSuccess = null, Action onError = null) + private void joinMultiplayerRoom(Room room, string? password, Action? onSuccess = null, Action? onError = null) { - Debug.Assert(room.RoomID.Value != null); + Debug.Assert(room.RoomID != null); multiplayerClient.JoinRoom(room, password).ContinueWith(t => { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 0d96f348ac..9911a08b81 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -16,9 +16,6 @@ namespace osu.Game.Screens.OnlinePlay /// public partial class OnlinePlayComposite : CompositeDrawable { - [Resolved(typeof(Room))] - protected Bindable RoomID { get; private set; } = null!; - [Resolved(typeof(Room), nameof(Room.Name))] protected Bindable RoomName { get; private set; } = null!; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 4a2d8f8f6b..7ca09b5563 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.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 System.Diagnostics; using System.Linq; @@ -21,11 +19,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { public partial class PlaylistsPlayer : RoomSubmittingPlayer { - public Action Exited; + public Action? Exited; protected override UserActivity InitialActivity => new UserActivity.InPlaylistGame(Beatmap.Value.BeatmapInfo, Ruleset.Value); - public PlaylistsPlayer(Room room, PlaylistItem playlistItem, PlayerConfiguration configuration = null) + public PlaylistsPlayer(Room room, PlaylistItem playlistItem, PlayerConfiguration? configuration = null) : base(room, playlistItem, configuration) { } @@ -57,8 +55,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected override ResultsScreen CreateResults(ScoreInfo score) { - Debug.Assert(Room.RoomID.Value != null); - return new PlaylistItemUserResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem) + Debug.Assert(Room.RoomID != null); + return new PlaylistItemUserResultsScreen(score, Room.RoomID.Value, PlaylistItem) { AllowRetry = true, ShowUserStatistics = true, diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 9166cac9de..4d85206473 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -372,7 +372,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void onPlaylistChanged(object? sender, NotifyCollectionChangedEventArgs e) => playlistLength.Text = $"Length: {Playlist.GetTotalDuration(rulesets)}"; - private bool hasValidSettings => RoomID.Value == null && NameField.Text.Length > 0 && Playlist.Count > 0 + private bool hasValidSettings => room.RoomID == null && NameField.Text.Length > 0 && Playlist.Count > 0 && hasValidDuration; private bool hasValidDuration => DurationField.Current.Value <= TimeSpan.FromDays(14) || localUser.Value.IsSupporter; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 3126bbf2eb..9667e210a4 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -1,11 +1,9 @@ // 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.ComponentModel; using System.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -22,6 +20,7 @@ using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Users; using osuTK; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Playlists { @@ -33,20 +32,22 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private readonly IBindable isIdle = new BindableBool(); - private MatchLeaderboard leaderboard; - private SelectionPollingComponent selectionPollingComponent; + [Resolved(CanBeNull = true)] + private IdleTracker? idleTracker { get; set; } - private FillFlowContainer progressSection; + private MatchLeaderboard leaderboard = null!; + private SelectionPollingComponent selectionPollingComponent = null!; + private FillFlowContainer progressSection = null!; public PlaylistsRoomSubScreen(Room room) : base(room, false) // Editing is temporarily not allowed. { - Title = room.RoomID.Value == null ? "New playlist" : room.Name.Value; + Title = room.RoomID == null ? "New playlist" : room.Name.Value; Activity.Value = new UserActivity.InLobby(room); } - [BackgroundDependencyLoader(true)] - private void load([CanBeNull] IdleTracker idleTracker) + [BackgroundDependencyLoader] + private void load() { if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle); @@ -59,17 +60,26 @@ namespace osu.Game.Screens.OnlinePlay.Playlists base.LoadComplete(); isIdle.BindValueChanged(_ => updatePollingRate(), true); - RoomId.BindValueChanged(id => - { - if (id.NewValue != null) - { - // Set the first playlist item. - // This is scheduled since updating the room and playlist may happen in an arbitrary order (via Room.CopyFrom()). - Schedule(() => SelectedItem.Value = Room.Playlist.FirstOrDefault()); - } - }, true); - Room.MaxAttempts.BindValueChanged(_ => progressSection.Alpha = Room.MaxAttempts.Value != null ? 1 : 0, true); + + Room.PropertyChanged += onRoomPropertyChanged; + updateSetupState(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.RoomID)) + updateSetupState(); + } + + private void updateSetupState() + { + if (Room.RoomID != null) + { + // Set the first playlist item. + // This is scheduled since updating the room and playlist may happen in an arbitrary order (via Room.CopyFrom()). + Schedule(() => SelectedItem.Value = Room.Playlist.FirstOrDefault()); + } } protected override Drawable CreateMainContent() => new Container @@ -92,7 +102,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists }, Content = new[] { - new Drawable[] + new Drawable?[] { // Playlist items column new GridContainer @@ -113,8 +123,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists AllowShowingResults = true, RequestResults = item => { - Debug.Assert(RoomId.Value != null); - ParentScreen?.Push(new PlaylistItemUserResultsScreen(null, RoomId.Value.Value, item)); + Debug.Assert(Room.RoomID != null); + ParentScreen?.Push(new PlaylistItemUserResultsScreen(null, Room.RoomID.Value, item)); } } }, @@ -248,5 +258,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { Exited = () => leaderboard.RefetchScores() }); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + Room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs index 3f74f49384..74ee7e1868 100644 --- a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs +++ b/osu.Game/Screens/Play/RoomSubmittingPlayer.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.Diagnostics; using osu.Game.Extensions; using osu.Game.Online.API; @@ -19,16 +17,16 @@ namespace osu.Game.Screens.Play protected readonly PlaylistItem PlaylistItem; protected readonly Room Room; - protected RoomSubmittingPlayer(Room room, PlaylistItem playlistItem, PlayerConfiguration configuration = null) + protected RoomSubmittingPlayer(Room room, PlaylistItem playlistItem, PlayerConfiguration? configuration = null) : base(configuration) { Room = room; PlaylistItem = playlistItem; } - protected override APIRequest CreateTokenRequest() + protected override APIRequest? CreateTokenRequest() { - if (!(Room.RoomID.Value is long roomId)) + if (Room.RoomID is not long roomId) return null; int beatmapId = Beatmap.Value.BeatmapInfo.OnlineID; @@ -45,8 +43,8 @@ namespace osu.Game.Screens.Play protected override APIRequest CreateSubmissionRequest(Score score, long token) { - Debug.Assert(Room.RoomID.Value != null); - return new SubmitRoomScoreRequest(score.ScoreInfo, token, Room.RoomID.Value.Value, PlaylistItem.ID); + Debug.Assert(Room.RoomID != null); + return new SubmitRoomScoreRequest(score.ScoreInfo, token, Room.RoomID.Value, PlaylistItem.ID); } } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 65a4019bb5..0c17998487 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -214,7 +214,7 @@ namespace osu.Game.Tests.Visual.Multiplayer roomId = clone(roomId); password = clone(password); - ServerAPIRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == roomId); + ServerAPIRoom = roomManager.ServerSideRooms.Single(r => r.RoomID == roomId); if (password != ServerAPIRoom.Password.Value) throw new InvalidOperationException("Invalid password."); diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index e9980e822c..2ce511928f 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.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 osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; @@ -17,23 +15,23 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// public partial class TestRoomManager : RoomManager { - public Action JoinRoomRequested; + public Action? JoinRoomRequested; private int currentRoomId; - public override void JoinRoom(Room room, string password = null, Action onSuccess = null, Action onError = null) + public override void JoinRoom(Room room, string? password = null, Action? onSuccess = null, Action? onError = null) { JoinRoomRequested?.Invoke(room, password); base.JoinRoom(room, password, onSuccess, onError); } - public void AddRooms(int count, RulesetInfo ruleset = null, bool withPassword = false, bool withSpotlightRooms = false) + public void AddRooms(int count, RulesetInfo? ruleset = null, bool withPassword = false, bool withSpotlightRooms = false) { for (int i = 0; i < count; i++) { var room = new Room { - RoomID = { Value = -currentRoomId }, + RoomID = -currentRoomId, Name = { Value = $@"Room {currentRoomId}" }, Host = { Value = new APIUser { Username = @"Host" } }, EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 41d288ba60..cc8bd27d91 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay case JoinRoomRequest joinRoomRequest: { - var room = ServerSideRooms.Single(r => r.RoomID.Value == joinRoomRequest.Room.RoomID.Value); + var room = ServerSideRooms.Single(r => r.RoomID == joinRoomRequest.Room.RoomID); if (joinRoomRequest.Password != room.Password.Value) { @@ -162,7 +162,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay return true; case GetRoomRequest getRoomRequest: - getRoomRequest.TriggerSuccess(createResponseRoom(ServerSideRooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId), true)); + getRoomRequest.TriggerSuccess(createResponseRoom(ServerSideRooms.Single(r => r.RoomID == getRoomRequest.RoomId), true)); return true; case CreateRoomScoreRequest createRoomScoreRequest: @@ -261,7 +261,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// The room host. public void AddServerSideRoom(Room room, APIUser host) { - room.RoomID.Value ??= currentRoomId++; + room.RoomID ??= currentRoomId++; room.Host.Value = host; for (int i = 0; i < room.Playlist.Count; i++) diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs index a431b204bc..ac0672c10f 100644 --- a/osu.Game/Users/UserActivity.cs +++ b/osu.Game/Users/UserActivity.cs @@ -248,7 +248,7 @@ namespace osu.Game.Users public InLobby(Room room) { - RoomID = room.RoomID.Value ?? -1; + RoomID = room.RoomID ?? -1; RoomName = room.Name.Value; } From 5b2568d18b973b2fda1ef56bfbaf0a78ad92c136 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 16:38:49 +0900 Subject: [PATCH 022/139] Fix cases where the bindable is resolved directly --- .../Multiplayer/TestSceneMatchLeaderboard.cs | 2 +- .../Match/Components/MatchLeaderboard.cs | 49 +++++++++++++------ .../Match/MultiplayerMatchSettingsOverlay.cs | 17 ++++--- .../Playlists/PlaylistsRoomSubScreen.cs | 2 +- 4 files changed, 47 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index f96e0bdd4c..38522db4d4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { SelectedRoom.Value = new Room { RoomID = 3 }; - Child = new MatchLeaderboard + Child = new MatchLeaderboard(SelectedRoom.Value) { Origin = Anchor.Centre, Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs index 4627cd4072..a7148abcde 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs @@ -1,9 +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.ComponentModel; using System.Threading; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; @@ -13,30 +12,44 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components { public partial class MatchLeaderboard : Leaderboard { - [Resolved(typeof(Room), nameof(Room.RoomID))] - private Bindable roomId { get; set; } = null!; + private readonly Room room; - [BackgroundDependencyLoader] - private void load() + public MatchLeaderboard(Room room) { - roomId.BindValueChanged(id => - { - if (id.NewValue == null) - return; + this.room = room; + } - SetScores(null); - RefetchScores(); - }, true); + protected override void LoadComplete() + { + base.LoadComplete(); + + room.PropertyChanged += onRoomPropertyChanged; + fetchInitialScores(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.RoomID)) + fetchInitialScores(); + } + + private void fetchInitialScores() + { + if (room.RoomID == null) + return; + + SetScores(null); + RefetchScores(); } protected override bool IsOnlineScope => true; protected override APIRequest? FetchScores(CancellationToken cancellationToken) { - if (roomId.Value == null) + if (room.RoomID == null) return null; - var req = new GetRoomLeaderboardRequest(roomId.Value ?? 0); + var req = new GetRoomLeaderboardRequest(room.RoomID.Value); req.Success += r => Schedule(() => { @@ -52,6 +65,12 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected override LeaderboardScore CreateDrawableScore(APIUserScoreAggregate model, int index) => new MatchLeaderboardScore(model, index); protected override LeaderboardScore CreateDrawableTopScore(APIUserScoreAggregate model) => new MatchLeaderboardScore(model, model.Position, false); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } public enum MatchLeaderboardScope diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index edcad9c3ac..3e80a5531a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -323,7 +323,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Padding = new MarginPadding { Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, Children = new Drawable[] { - ApplyButton = new CreateOrUpdateButton + ApplyButton = new CreateOrUpdateButton(room) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, @@ -471,13 +471,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match public partial class CreateOrUpdateButton : RoundedButton { - [Resolved(typeof(Room), nameof(Room.RoomID))] - private Bindable roomId { get; set; } = null!; + private readonly Room room; - protected override void LoadComplete() + public CreateOrUpdateButton(Room room) { - base.LoadComplete(); - roomId.BindValueChanged(id => Text = id.NewValue == null ? "Create" : "Update", true); + this.room = room; } [BackgroundDependencyLoader] @@ -485,6 +483,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { BackgroundColour = colours.YellowDark; } + + protected override void Update() + { + base.Update(); + + Text = room.RoomID == null ? "Create" : "Update"; + } } private enum StartMode diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 9667e210a4..a53a89d601 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -201,7 +201,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { new OverlinedHeader("Leaderboard") }, - new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, }, + new Drawable[] { leaderboard = new MatchLeaderboard(Room) { RelativeSizeAxes = Axes.Both }, }, }, RowDimensions = new[] { From a2a930aa3592065d565f6716df2e3dea0d934ef5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 17:15:50 +0900 Subject: [PATCH 023/139] Fix CI issues --- .../Multiplayer/TestSceneLoungeRoomsContainer.cs | 6 +++--- .../Visual/Multiplayer/TestSceneMultiplayerPlayer.cs | 10 ++++------ .../OnlinePlay/Components/ListingPollingComponent.cs | 6 ++---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index d518565be9..112fdfd9ab 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -58,14 +58,14 @@ namespace osu.Game.Tests.Visual.Multiplayer .SkipWhile(r => r.Room.Category.Value == RoomCategory.Spotlight) .All(r => r.Room.Category.Value == RoomCategory.Normal)); - AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.First(r => r.RoomID.Value == 0))); + AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.First(r => r.RoomID == 0))); AddAssert("has 4 rooms", () => container.Rooms.Count == 4); - AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0)); + AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID != 0)); AddStep("select first room", () => container.Rooms.First().TriggerClick()); AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); - AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID.Value))); + AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID))); AddAssert("first spotlight still selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); AddStep("remove spotlight room", () => RoomManager.RemoveRoom(RoomManager.Rooms.Single(r => r.Category.Value == RoomCategory.Spotlight))); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs index aaf85dab7c..94dd114c32 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.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 System.Collections.Generic; using System.Linq; @@ -22,7 +20,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerPlayer : MultiplayerTestScene { - private MultiplayerPlayer player; + private MultiplayerPlayer player = null!; [Test] public void TestGameplay() @@ -49,7 +47,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("score changed", () => player.GameplayState.ScoreProcessor.TotalScore.Value > 0); } - private void setup(Func> mods = null) + private void setup(Func>? mods = null) { AddStep("set beatmap", () => { @@ -64,10 +62,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("initialise gameplay", () => { - Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.ServerAPIRoom, new PlaylistItem(Beatmap.Value.BeatmapInfo) + Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.ServerAPIRoom!, new PlaylistItem(Beatmap.Value.BeatmapInfo) { RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, - }, MultiplayerClient.ServerRoom?.Users.ToArray())); + }, MultiplayerClient.ServerRoom!.Users.ToArray())); }); AddUntilStep("wait for player to be current", () => player.IsCurrentScreen() && player.IsLoaded); diff --git a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs index 4b38ea68b3..0020a7dfb6 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.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.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -35,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Components }); } - private GetRoomsRequest lastPollRequest; + private GetRoomsRequest? lastPollRequest; protected override Task Poll() { @@ -57,7 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Components foreach (var existing in RoomManager.Rooms.ToArray()) { - if (result.All(r => r.RoomID.Value != existing.RoomID.Value)) + if (result.All(r => r.RoomID != existing.RoomID)) RoomManager.RemoveRoom(existing); } From 2afd3579015f7b88c1aaf0aa5a8e42638729e41d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Nov 2024 13:14:35 +0900 Subject: [PATCH 024/139] Re-throw `OperationCanceledException` for consistency? Mostly to see if it breaks anything. --- osu.Game/Database/RealmArchiveModelImporter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index c4eb93b754..22bcc622e9 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -139,6 +139,7 @@ namespace osu.Game.Database } catch (OperationCanceledException) { + throw; } catch (Exception e) { @@ -531,7 +532,8 @@ namespace osu.Game.Database /// The new model proposed for import. /// The current realm context. /// An existing model which matches the criteria to skip importing, else null. - protected TModel? CheckForExisting(TModel model, Realm realm) => string.IsNullOrEmpty(model.Hash) ? null : realm.All().OrderBy(b => b.DeletePending).FirstOrDefault(b => b.Hash == model.Hash); + protected TModel? CheckForExisting(TModel model, Realm realm) => + string.IsNullOrEmpty(model.Hash) ? null : realm.All().OrderBy(b => b.DeletePending).FirstOrDefault(b => b.Hash == model.Hash); /// /// Whether import can be skipped after finding an existing import early in the process. From 6160df158615f16cae5e00d810ea0930132fd1e0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 16:55:18 +0900 Subject: [PATCH 025/139] Make `Room.Name` non-bindable --- .../DailyChallenge/TestSceneDailyChallenge.cs | 6 +- .../TestSceneDailyChallengeIntro.cs | 2 +- .../Visual/Menus/TestSceneMainMenu.cs | 2 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 14 ++--- .../Multiplayer/TestSceneDrawableRoom.cs | 36 ++++++------ .../TestSceneDrawableRoomParticipantsList.cs | 6 +- .../Multiplayer/TestSceneMultiplayer.cs | 54 +++++++++--------- .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../TestSceneMultiplayerPlaylist.cs | 2 +- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 16 +++--- .../TestScenePlaylistsMatchSettingsOverlay.cs | 26 ++++----- .../TestScenePlaylistsRoomCreation.cs | 56 +++++++------------ .../Online/Multiplayer/MultiplayerClient.cs | 4 +- osu.Game/Online/Rooms/Room.cs | 18 ++++-- .../OnlinePlay/Components/RoomManager.cs | 2 +- .../DailyChallenge/DailyChallengeIntro.cs | 2 +- .../Lounge/Components/DrawableRoom.cs | 30 +++++++++- .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 +- .../Match/MultiplayerMatchSettingsOverlay.cs | 22 +++++++- .../Multiplayer/MultiplayerLoungeSubScreen.cs | 2 +- .../Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 - .../Playlists/PlaylistsLoungeSubScreen.cs | 2 +- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 32 ++++++++++- .../Playlists/PlaylistsRoomSubScreen.cs | 2 +- .../Multiplayer/MultiplayerTestScene.cs | 2 +- .../Multiplayer/TestMultiplayerClient.cs | 2 +- .../Visual/OnlinePlay/TestRoomManager.cs | 2 +- osu.Game/Users/UserActivity.cs | 2 +- 29 files changed, 200 insertions(+), 153 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index 488289cb74..46a951dcff 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge var room = new Room { RoomID = 1234, - Name = { Value = "Daily Challenge: June 4, 2024" }, + Name = "Daily Challenge: June 4, 2024", Playlist = { new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge var room = new Room { RoomID = 1234, - Name = { Value = "Daily Challenge: June 4, 2024" }, + Name = "Daily Challenge: June 4, 2024", Playlist = { new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge var room = new Room { RoomID = 1234, - Name = { Value = "Daily Challenge: June 4, 2024" }, + Name = "Daily Challenge: June 4, 2024", Playlist = { new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs index c08398ac17..7b3595b064 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge API.Perform(new CreateRoomRequest(room = new Room { RoomID = roomId, - Name = { Value = "Daily Challenge: June 4, 2024" }, + Name = "Daily Challenge: June 4, 2024", Playlist = { new PlaylistItem(CreateAPIBeatmap(new OsuRuleset().RulesetInfo)) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index 8b81516a34..9b00449e11 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Menus getRoomRequest.TriggerSuccess(new Room { RoomID = 1234, - Name = { Value = "Aug 8, 2024" }, + Name = "Aug 8, 2024", Playlist = { new PlaylistItem(beatmap) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 8bcd5aab1c..2d9d979022 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.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.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -30,16 +28,16 @@ namespace osu.Game.Tests.Visual.Multiplayer { protected abstract QueueMode Mode { get; } - protected BeatmapInfo InitialBeatmap { get; private set; } - protected BeatmapInfo OtherBeatmap { get; private set; } + protected BeatmapInfo InitialBeatmap { get; private set; } = null!; + protected BeatmapInfo OtherBeatmap { get; private set; } = null!; protected IScreen CurrentScreen => multiplayerComponents.CurrentScreen; protected IScreen CurrentSubScreen => multiplayerComponents.MultiplayerScreen.CurrentSubScreen; - private BeatmapManager beatmaps; - private BeatmapSetInfo importedSet; + private BeatmapManager beatmaps = null!; + private BeatmapSetInfo importedSet = null!; - private TestMultiplayerComponents multiplayerComponents; + private TestMultiplayerComponents multiplayerComponents = null!; protected TestMultiplayerClient MultiplayerClient => multiplayerComponents.MultiplayerClient; @@ -75,7 +73,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for lounge", () => multiplayerComponents.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); AddStep("open room", () => multiplayerComponents.ChildrenOfType().Single().Open(new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", QueueMode = { Value = Mode }, Playlist = { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 4c609613a3..a386a26d32 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.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 System.Linq; using NUnit.Framework; @@ -32,12 +30,12 @@ namespace osu.Game.Tests.Visual.Multiplayer [Cached] protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); - private readonly Bindable selectedRoom = new Bindable(); + private readonly Bindable selectedRoom = new Bindable(); [Test] public void TestMultipleStatuses() { - FillFlowContainer rooms = null; + FillFlowContainer rooms = null!; AddStep("create rooms", () => { @@ -77,7 +75,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createLoungeRoom(new Room { - Name = { Value = "Multiplayer room" }, + Name = "Multiplayer room", Status = { Value = new RoomStatusOpen() }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, Type = { Value = MatchType.HeadToHead }, @@ -86,7 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }), createLoungeRoom(new Room { - Name = { Value = "Private room" }, + Name = "Private room", Status = { Value = new RoomStatusOpenPrivate() }, HasPassword = { Value = true }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, @@ -96,7 +94,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }), createLoungeRoom(new Room { - Name = { Value = "Playlist room with multiple beatmaps" }, + Name = "Playlist room with multiple beatmaps", Status = { Value = new RoomStatusPlaying() }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, Playlist = { item1, item2 }, @@ -104,19 +102,19 @@ namespace osu.Game.Tests.Visual.Multiplayer }), createLoungeRoom(new Room { - Name = { Value = "Finished room" }, + Name = "Finished room", Status = { Value = new RoomStatusEnded() }, EndDate = { Value = DateTimeOffset.Now }, }), createLoungeRoom(new Room { - Name = { Value = "Spotlight room" }, + Name = "Spotlight room", Status = { Value = new RoomStatusOpen() }, Category = { Value = RoomCategory.Spotlight }, }), createLoungeRoom(new Room { - Name = { Value = "Featured artist room" }, + Name = "Featured artist room", Status = { Value = new RoomStatusOpen() }, Category = { Value = RoomCategory.FeaturedArtist }, }), @@ -132,12 +130,12 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestEnableAndDisablePassword() { - DrawableRoom drawableRoom = null; - Room room = null; + DrawableRoom drawableRoom = null!; + Room room = null!; AddStep("create room", () => Child = drawableRoom = createLoungeRoom(room = new Room { - Name = { Value = "Room with password" }, + Name = "Room with password", Status = { Value = new RoomStatusOpen() }, Type = { Value = MatchType.HeadToHead }, })); @@ -166,30 +164,30 @@ namespace osu.Game.Tests.Visual.Multiplayer { new DrawableMatchRoom(new Room { - Name = { Value = "A host-only room" }, + Name = "A host-only room", QueueMode = { Value = QueueMode.HostOnly }, Type = { Value = MatchType.HeadToHead }, }) { - SelectedItem = new Bindable() + SelectedItem = new Bindable() }, new DrawableMatchRoom(new Room { - Name = { Value = "An all-players, team-versus room" }, + Name = "An all-players, team-versus room", QueueMode = { Value = QueueMode.AllPlayers }, Type = { Value = MatchType.TeamVersus } }) { - SelectedItem = new Bindable() + SelectedItem = new Bindable() }, new DrawableMatchRoom(new Room { - Name = { Value = "A round-robin room" }, + Name = "A round-robin room", QueueMode = { Value = QueueMode.AllPlayersRoundRobin }, Type = { Value = MatchType.HeadToHead } }) { - SelectedItem = new Bindable() + SelectedItem = new Bindable() }, } }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs index 98abc93994..c959c07ffb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.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.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -17,7 +15,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneDrawableRoomParticipantsList : OnlinePlayTestScene { - private DrawableRoomParticipantsList list; + private DrawableRoomParticipantsList list = null!; public override void SetUpSteps() { @@ -27,7 +25,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { SelectedRoom.Value = new Room { - Name = { Value = "test room" }, + Name = "test room", Host = { Value = new APIUser diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index df2021dbaf..0b662083b5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -238,7 +238,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -259,7 +259,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { roomManager.AddServerSideRoom(new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -288,7 +288,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { roomManager.AddServerSideRoom(new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -317,7 +317,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Password = { Value = "password" }, Playlist = { @@ -338,7 +338,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { roomManager.AddServerSideRoom(new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Password = { Value = "password" }, Playlist = { @@ -370,7 +370,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Password = { Value = "password" }, Playlist = { @@ -401,7 +401,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -430,7 +430,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }; return new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { item } }; }); @@ -471,7 +471,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }; return new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { item } }; }); @@ -512,7 +512,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }; return new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { item } }; }); @@ -548,7 +548,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -581,7 +581,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -620,7 +620,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -639,7 +639,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -679,7 +679,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -724,7 +724,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -754,7 +754,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -791,7 +791,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { roomManager.AddServerSideRoom(new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", QueueMode = { Value = QueueMode.AllPlayers }, Playlist = { @@ -810,7 +810,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("disable polling", () => this.ChildrenOfType().Single().TimeBetweenPolls.Value = 0); AddStep("change server-side settings", () => { - roomManager.ServerSideRooms[0].Name.Value = "New name"; + roomManager.ServerSideRooms[0].Name = "New name"; roomManager.ServerSideRooms[0].Playlist.Add(new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { ID = 2, @@ -825,7 +825,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("local room has correct settings", () => { var localRoom = this.ChildrenOfType().Single().Room; - return localRoom.Name.Value == roomManager.ServerSideRooms[0].Name.Value + return localRoom.Name == roomManager.ServerSideRooms[0].Name && localRoom.Playlist.SequenceEqual(roomManager.ServerSideRooms[0].Playlist); }); } @@ -836,7 +836,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", QueueMode = { Value = QueueMode.AllPlayers }, Playlist = { @@ -872,7 +872,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", QueueMode = { Value = QueueMode.AllPlayers }, Playlist = { @@ -911,7 +911,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", QueueMode = { Value = QueueMode.AllPlayers }, Playlist = { @@ -942,7 +942,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", QueueMode = { Value = QueueMode.AllPlayers }, Playlist = { @@ -976,7 +976,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -1022,7 +1022,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", QueueMode = { Value = QueueMode.AllPlayers }, Playlist = { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 9bf29c7bf8..0c2a657c5a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("load match", () => { - SelectedRoom.Value = new Room { Name = { Value = "Test Room" } }; + SelectedRoom.Value = new Room { Name = "Test Room" }; LoadScreen(screen = new TestMultiplayerMatchSubScreen(SelectedRoom.Value)); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index e12c2bee49..92b44d9502 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -164,7 +164,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { RoomManager.CreateRoom(new Room { - Name = { Value = "test name" }, + Name = "test name", Playlist = { new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 32e90153d8..7c30b72747 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.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 System.Linq; using NUnit.Framework; @@ -29,10 +27,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneTeamVersus : ScreenTestScene { - private BeatmapManager beatmaps; - private BeatmapSetInfo importedSet; + private BeatmapManager beatmaps = null!; + private BeatmapSetInfo importedSet = null!; - private TestMultiplayerComponents multiplayerComponents; + private TestMultiplayerComponents multiplayerComponents = null!; private TestMultiplayerClient multiplayerClient => multiplayerComponents.MultiplayerClient; @@ -64,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Type = { Value = MatchType.TeamVersus }, Playlist = { @@ -84,7 +82,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Type = { Value = MatchType.TeamVersus }, Playlist = { @@ -121,7 +119,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Type = { Value = MatchType.HeadToHead }, Playlist = { @@ -147,7 +145,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoom(() => new Room { - Name = { Value = "Test Room" }, + Name = "Test Room", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index 9f7b20ad43..3652710e32 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.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.Bindables; @@ -22,7 +20,7 @@ namespace osu.Game.Tests.Visual.Playlists { protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; - private TestRoomSettings settings; + private TestRoomSettings settings = null!; protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies(); @@ -47,19 +45,19 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("clear name and beatmap", () => { - SelectedRoom.Value.Name.Value = ""; + SelectedRoom.Value.Name = ""; SelectedRoom.Value.Playlist.Clear(); }); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set name", () => SelectedRoom.Value.Name.Value = "Room name"); + AddStep("set name", () => SelectedRoom.Value.Name = "Room name"); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); AddStep("set beatmap", () => SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo))); AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value); - AddStep("clear name", () => SelectedRoom.Value.Name.Value = ""); + AddStep("clear name", () => SelectedRoom.Value.Name = ""); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); } @@ -69,7 +67,7 @@ namespace osu.Game.Tests.Visual.Playlists const string expected_name = "expected name"; TimeSpan expectedDuration = TimeSpan.FromMinutes(15); - Room createdRoom = null; + Room createdRoom = null!; AddStep("setup", () => { @@ -85,7 +83,7 @@ namespace osu.Game.Tests.Visual.Playlists }); AddStep("create room", () => settings.ApplyButton.Action.Invoke()); - AddAssert("has correct name", () => createdRoom.Name.Value == expected_name); + AddAssert("has correct name", () => createdRoom.Name == expected_name); AddAssert("has correct duration", () => createdRoom.Duration.Value == expectedDuration); } @@ -94,13 +92,13 @@ namespace osu.Game.Tests.Visual.Playlists { const string not_found_prefix = "beatmaps not found:"; - string errorMessage = null; + string errorMessage = null!; AddStep("setup", () => { var beatmap = CreateBeatmap(Ruleset.Value).BeatmapInfo; - SelectedRoom.Value.Name.Value = "Test Room"; + SelectedRoom.Value.Name = "Test Room"; SelectedRoom.Value.Playlist.Add(new PlaylistItem(beatmap)); errorMessage = $"{not_found_prefix} {beatmap.OnlineID}"; @@ -127,7 +125,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("setup", () => { - SelectedRoom.Value.Name.Value = "Test Room"; + SelectedRoom.Value.Name = "Test Room"; SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)); RoomManager.CreateRequested = _ => failText; @@ -169,7 +167,7 @@ namespace osu.Game.Tests.Visual.Playlists protected class TestRoomManager : IRoomManager { - public Func CreateRequested; + public Func? CreateRequested; public event Action RoomsUpdated { @@ -187,7 +185,7 @@ namespace osu.Game.Tests.Visual.Playlists public void ClearRooms() => throw new NotImplementedException(); - public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) + public void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null) { if (CreateRequested == null) return; @@ -200,7 +198,7 @@ namespace osu.Game.Tests.Visual.Playlists onSuccess?.Invoke(room); } - public void JoinRoom(Room room, string password, Action onSuccess = null, Action onError = null) => throw new NotImplementedException(); + public void JoinRoom(Room room, string? password, Action? onSuccess = null, Action? onError = null) => throw new NotImplementedException(); public void PartRoom() => throw new NotImplementedException(); } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 1636a3d4b8..8769ff2e27 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -1,12 +1,9 @@ // 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 System.Diagnostics; using System.Linq; -using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -35,11 +32,9 @@ namespace osu.Game.Tests.Visual.Playlists { public partial class TestScenePlaylistsRoomCreation : OnlinePlayTestScene { - private BeatmapManager manager; - - private TestPlaylistsRoomSubScreen match; - - private BeatmapSetInfo importedBeatmap; + private BeatmapManager manager = null!; + private TestPlaylistsRoomSubScreen match = null!; + private BeatmapSetInfo importedBeatmap = null!; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -52,11 +47,11 @@ namespace osu.Game.Tests.Visual.Playlists [SetUpSteps] public void SetupSteps() { - AddStep("set room", () => SelectedRoom!.Value = new Room()); + AddStep("set room", () => SelectedRoom.Value = new Room()); importBeatmap(); - AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom!.Value))); + AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom.Value))); AddUntilStep("wait for load", () => match.IsCurrentScreen()); } @@ -65,7 +60,7 @@ namespace osu.Game.Tests.Visual.Playlists { setupAndCreateRoom(room => { - room.Name.Value = "my awesome room"; + room.Name = "my awesome room"; room.Host.Value = API.LocalUser.Value; room.RecentParticipants.Add(room.Host.Value); room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); @@ -88,7 +83,7 @@ namespace osu.Game.Tests.Visual.Playlists { setupAndCreateRoom(room => { - room.Name.Value = "my awesome room"; + room.Name = "my awesome room"; room.MaxAttempts.Value = 5; room.Host.Value = API.LocalUser.Value; room.RecentParticipants.Add(room.Host.Value); @@ -107,7 +102,7 @@ namespace osu.Game.Tests.Visual.Playlists { setupAndCreateRoom(room => { - room.Name.Value = "my awesome room"; + room.Name = "my awesome room"; room.Host.Value = API.LocalUser.Value; room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { @@ -115,13 +110,13 @@ namespace osu.Game.Tests.Visual.Playlists }); }); - AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom!.Value.Playlist[0]); + AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]); } [Test] public void TestBeatmapUpdatedOnReImport() { - string realHash = null; + string realHash = null!; int realOnlineId = 0; int realOnlineSetId = 0; @@ -139,26 +134,23 @@ namespace osu.Game.Tests.Visual.Playlists BeatmapInfo = { OnlineID = realOnlineId, - Metadata = new BeatmapMetadata(), - BeatmapSet = - { - OnlineID = realOnlineSetId - } + Metadata = new BeatmapMetadata() }, }; + Debug.Assert(modifiedBeatmap.BeatmapInfo.BeatmapSet != null); + modifiedBeatmap.BeatmapInfo.BeatmapSet!.OnlineID = realOnlineSetId; + modifiedBeatmap.HitObjects.Clear(); modifiedBeatmap.HitObjects.Add(new HitCircle { StartTime = 5000 }); - Debug.Assert(modifiedBeatmap.BeatmapInfo.BeatmapSet != null); - manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet); }); // Create the room using the real beatmap values. setupAndCreateRoom(room => { - room.Name.Value = "my awesome room"; + room.Name = "my awesome room"; room.Host.Value = API.LocalUser.Value; room.Playlist.Add(new PlaylistItem(new BeatmapInfo { @@ -181,17 +173,11 @@ namespace osu.Game.Tests.Visual.Playlists { var originalBeatmap = new TestBeatmap(new OsuRuleset().RulesetInfo) { - BeatmapInfo = - { - OnlineID = realOnlineId, - BeatmapSet = - { - OnlineID = realOnlineSetId - } - }, + BeatmapInfo = { OnlineID = realOnlineId }, }; Debug.Assert(originalBeatmap.BeatmapInfo.BeatmapSet != null); + originalBeatmap.BeatmapInfo.BeatmapSet.OnlineID = realOnlineSetId; manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet); }); @@ -201,7 +187,7 @@ namespace osu.Game.Tests.Visual.Playlists private void setupAndCreateRoom(Action room) { - AddStep("setup room", () => room(SelectedRoom!.Value)); + AddStep("setup room", () => room(SelectedRoom.Value)); AddStep("click create button", () => { @@ -215,8 +201,7 @@ namespace osu.Game.Tests.Visual.Playlists var beatmap = CreateBeatmap(new OsuRuleset().RulesetInfo); Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null); - - importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet)?.Value.Detach(); + importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet)!.Value.Detach(); }); private partial class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen @@ -226,8 +211,7 @@ namespace osu.Game.Tests.Visual.Playlists public new Bindable Beatmap => base.Beatmap; [Resolved(canBeNull: true)] - [CanBeNull] - private IDialogOverlay dialogOverlay { get; set; } + private IDialogOverlay? dialogOverlay { get; set; } public TestPlaylistsRoomSubScreen(Room room) : base(room) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index bd0fa0cb72..b6ad361c44 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -459,7 +459,7 @@ namespace osu.Game.Online.Multiplayer if (apiUser == null || apiRoom == null) return; PostNotification?.Invoke( - new UserAvatarNotification(apiUser, NotificationsStrings.InvitedYouToTheMultiplayer(apiUser.Username, apiRoom.Name.Value)) + new UserAvatarNotification(apiUser, NotificationsStrings.InvitedYouToTheMultiplayer(apiUser.Username, apiRoom.Name)) { Activated = () => { @@ -841,7 +841,7 @@ namespace osu.Game.Online.Multiplayer // Update a few properties of the room instantaneously. Room.Settings = settings; - APIRoom.Name.Value = Room.Settings.Name; + APIRoom.Name = Room.Settings.Name; APIRoom.Password.Value = Room.Settings.Password; APIRoom.Status.Value = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate(); APIRoom.Type.Value = Room.Settings.MatchType; diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index de854596e9..d2aa8ee4fe 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -30,6 +30,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref roomId, value); } + /// + /// The room name. + /// + public string Name + { + get => name; + set => SetField(ref name, value); + } + /// /// Represents the current item selected within the room. /// @@ -45,13 +54,12 @@ namespace osu.Game.Online.Rooms [JsonProperty("id")] private long? roomId; + [JsonProperty("name")] + private string name = string.Empty; + [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; - [Cached] - [JsonProperty("name")] - public readonly Bindable Name = new Bindable(); - [Cached] [JsonProperty("host")] public readonly Bindable Host = new Bindable(); @@ -205,7 +213,7 @@ namespace osu.Game.Online.Rooms public void CopyFrom(Room other) { RoomID = other.RoomID; - Name.Value = other.Name.Value; + Name = other.Name; Category.Value = other.Category.Value; diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index 555a1f66bd..0bb33df966 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -122,7 +122,7 @@ namespace osu.Game.Screens.OnlinePlay.Components } catch (Exception ex) { - Logger.Error(ex, $"Failed to update room: {room.Name.Value}."); + Logger.Error(ex, $"Failed to update room: {room.Name}."); ignoredRooms.Add(room.RoomID.Value); rooms.Remove(room); diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 7f0f26097c..7fddb8d1c4 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -169,7 +169,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = room.Name.Value.Split(':', StringSplitOptions.TrimEntries).Last(), + Text = room.Name.Split(':', StringSplitOptions.TrimEntries).Last(), Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, Shear = new Vector2(-OsuGame.SHEAR, 0f), Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 59c8047062..e1235ab41e 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Threading; using osu.Framework.Allocation; @@ -26,6 +27,7 @@ using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.Components; using osuTK; using osuTK.Graphics; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { @@ -47,6 +49,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private RoomSpecialCategoryPill? specialCategoryPill; private PasswordProtectedIcon? passwordIcon; private EndDateInfo? endDateInfo; + private SpriteText? roomName; private UpdateableBeatmapBackgroundSprite background = null!; private DelayedLoadWrapper wrapper = null!; @@ -181,11 +184,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Direction = FillDirection.Vertical, Children = new Drawable[] { - new TruncatingSpriteText + roomName = new TruncatingSpriteText { RelativeSizeAxes = Axes.X, - Font = OsuFont.GetFont(size: 28), - Current = { BindTarget = Room.Name } + Font = OsuFont.GetFont(size: 28) }, new RoomStatusText { @@ -247,6 +249,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.LoadComplete(); + Room.PropertyChanged += onRoomPropertyChanged; + wrapper.DelayedLoadComplete += _ => { Debug.Assert(specialCategoryPill != null); @@ -272,11 +276,25 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components hasPassword.BindTo(Room.HasPassword); hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true); + + updateRoomName(); }; SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap, true); } + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Name)) + updateRoomName(); + } + + private void updateRoomName() + { + if (roomName != null) + roomName.Text = Room.Name; + } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { return new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) @@ -332,6 +350,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components return pills; } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + Room.PropertyChanged -= onRoomPropertyChanged; + } + private partial class RoomStatusText : OnlinePlayComposite { public readonly IBindable SelectedItem = new Bindable(); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 36db1ab29b..a277b3771b 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -121,7 +121,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge public bool FilteringActive { get; set; } - public IEnumerable FilterTerms => new LocalisableString[] { Room.Name.Value }; + public IEnumerable FilterTerms => new LocalisableString[] { Room.Name }; private bool matchingFilter = true; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 3e80a5531a..3dc01cee54 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -350,7 +350,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }; TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue.GetLocalisableDescription(), true); - RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true); Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true); @@ -374,8 +373,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match drawablePlaylist.Items.BindTo(Playlist); drawablePlaylist.SelectedItem.BindTo(SelectedItem); + + room.PropertyChanged += onRoomPropertyChanged; + + updateRoomName(); } + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Name)) + updateRoomName(); + } + + private void updateRoomName() + => NameField.Text = room.Name; + protected override void Update() { base.Update(); @@ -417,7 +429,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match } else { - room.Name.Value = NameField.Text; + room.Name = NameField.Text; room.Type.Value = TypePicker.Current.Value; room.Password.Value = PasswordTextBox.Current.Value; room.QueueMode.Value = QueueModeDropdown.Current.Value; @@ -467,6 +479,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match applyingSettingsOperation.Dispose(); applyingSettingsOperation = null; }); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } public partial class CreateOrUpdateButton : RoundedButton diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index a3a6fd2d8e..1ccd02ba8d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override Room CreateNewRoom() => new Room { - Name = { Value = $"{api.LocalUser}'s awesome room" }, + Name = $"{api.LocalUser}'s awesome room", Type = { Value = MatchType.HeadToHead }, }; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 696eb58c3f..60c2a586f9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public MultiplayerMatchSubScreen(Room room) : base(room) { - Title = room.RoomID == null ? "New room" : room.Name.Value; + Title = room.RoomID == null ? "New room" : room.Name; Activity.Value = new UserActivity.InLobby(room); } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 9911a08b81..f642b9c989 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -16,9 +16,6 @@ namespace osu.Game.Screens.OnlinePlay /// public partial class OnlinePlayComposite : CompositeDrawable { - [Resolved(typeof(Room), nameof(Room.Name))] - protected Bindable RoomName { get; private set; } = null!; - [Resolved(typeof(Room))] protected Bindable Host { get; private set; } = null!; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs index e1d747c3b0..a35d114418 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { return new Room { - Name = { Value = $"{api.LocalUser}'s awesome playlist" }, + Name = $"{api.LocalUser}'s awesome playlist", Type = { Value = MatchType.Playlists } }; } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 4d85206473..84a4657693 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Specialized; +using System.ComponentModel; using System.Linq; using Humanizer; using Humanizer.Localisation; @@ -25,6 +26,7 @@ using osu.Game.Screens.OnlinePlay.Match.Components; using osuTK; using osu.Game.Localisation; using osu.Game.Rulesets; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Playlists { @@ -142,7 +144,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { RelativeSizeAxes = Axes.X, TabbableContentContainer = this, - LengthLimit = 100 + LengthLimit = 100, + Text = room.Name }, }, new Section("Duration") @@ -313,7 +316,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists loadingLayer = new LoadingLayer(true) }; - RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true); Availability.BindValueChanged(availability => AvailabilityPicker.Current.Value = availability.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true); @@ -337,6 +339,24 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Playlist.BindCollectionChanged(onPlaylistChanged, true); } + protected override void LoadComplete() + { + base.LoadComplete(); + + room.PropertyChanged += onRoomPropertyChanged; + + updateRoomName(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Name)) + updateRoomName(); + } + + private void updateRoomName() + => NameField.Text = room.Name; + private void populateDurations(ValueChangedEvent user) { // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427) @@ -384,7 +404,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists hideError(); - RoomName.Value = NameField.Text; + room.Name = NameField.Text; Availability.Value = AvailabilityPicker.Current.Value; if (int.TryParse(MaxParticipantsField.Text, out int max)) @@ -436,6 +456,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists ErrorText.FadeIn(50); loadingLayer.Hide(); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } public partial class CreateRoomButton : RoundedButton diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index a53a89d601..9780c48f1f 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists public PlaylistsRoomSubScreen(Room room) : base(room, false) // Editing is temporarily not allowed. { - Title = room.RoomID == null ? "New playlist" : room.Name.Value; + Title = room.RoomID == null ? "New playlist" : room.Name; Activity.Value = new UserActivity.InLobby(room); } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 80c69db8b1..e540c78b0f 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { return new Room { - Name = { Value = "test name" }, + Name = "test name", Type = { Value = MatchType.HeadToHead }, Playlist = { diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 0c17998487..7a8e553452 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -230,7 +230,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Settings = { - Name = ServerAPIRoom.Name.Value, + Name = ServerAPIRoom.Name, MatchType = ServerAPIRoom.Type.Value, Password = password ?? string.Empty, QueueMode = ServerAPIRoom.QueueMode.Value, diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index 2ce511928f..d8c110d12a 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay var room = new Room { RoomID = -currentRoomId, - Name = { Value = $@"Room {currentRoomId}" }, + Name = $@"Room {currentRoomId}", Host = { Value = new APIUser { Username = @"Host" } }, EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, Category = { Value = withSpotlightRooms && i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal }, diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs index ac0672c10f..93812e3f6b 100644 --- a/osu.Game/Users/UserActivity.cs +++ b/osu.Game/Users/UserActivity.cs @@ -249,7 +249,7 @@ namespace osu.Game.Users public InLobby(Room room) { RoomID = room.RoomID ?? -1; - RoomName = room.Name.Value; + RoomName = room.Name; } [SerializationConstructor] From 8694f7e1cc2dca48df8458fed22f433c2a7cdfa9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 17:32:32 +0900 Subject: [PATCH 026/139] Make `Room.Host` non-bindable --- .../Multiplayer/TestSceneDrawableRoom.cs | 2 +- .../TestSceneDrawableRoomParticipantsList.cs | 11 ++-- .../TestScenePlaylistsRoomCreation.cs | 12 ++--- .../Online/Multiplayer/MultiplayerClient.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 17 ++++--- .../OnlinePlay/Components/RoomManager.cs | 2 +- .../Lounge/Components/DrawableRoom.cs | 2 +- .../DrawableRoomParticipantsList.cs | 51 ++++++++++++------- .../OnlinePlay/Match/DrawableMatchRoom.cs | 24 +++++++-- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 -- .../Visual/OnlinePlay/TestRoomManager.cs | 2 +- .../OnlinePlay/TestRoomRequestsHandler.cs | 4 +- 12 files changed, 80 insertions(+), 52 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index a386a26d32..17c0a159e9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -195,7 +195,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private DrawableRoom createLoungeRoom(Room room) { - room.Host.Value ??= new APIUser { Username = "peppy", Id = 2 }; + room.Host ??= new APIUser { Username = "peppy", Id = 2 }; if (room.RecentParticipants.Count == 0) { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs index c959c07ffb..1e7003c612 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs @@ -26,17 +26,14 @@ namespace osu.Game.Tests.Visual.Multiplayer SelectedRoom.Value = new Room { Name = "test room", - Host = + Host = new APIUser { - Value = new APIUser - { - Id = 2, - Username = "peppy", - } + Id = 2, + Username = "peppy", } }; - Child = list = new DrawableRoomParticipantsList + Child = list = new DrawableRoomParticipantsList(SelectedRoom.Value) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 8769ff2e27..7cc86e71bf 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -61,8 +61,8 @@ namespace osu.Game.Tests.Visual.Playlists setupAndCreateRoom(room => { room.Name = "my awesome room"; - room.Host.Value = API.LocalUser.Value; - room.RecentParticipants.Add(room.Host.Value); + room.Host = API.LocalUser.Value; + room.RecentParticipants.Add(room.Host); room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { @@ -85,8 +85,8 @@ namespace osu.Game.Tests.Visual.Playlists { room.Name = "my awesome room"; room.MaxAttempts.Value = 5; - room.Host.Value = API.LocalUser.Value; - room.RecentParticipants.Add(room.Host.Value); + room.Host = API.LocalUser.Value; + room.RecentParticipants.Add(room.Host); room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Playlists setupAndCreateRoom(room => { room.Name = "my awesome room"; - room.Host.Value = API.LocalUser.Value; + room.Host = API.LocalUser.Value; room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID @@ -151,7 +151,7 @@ namespace osu.Game.Tests.Visual.Playlists setupAndCreateRoom(room => { room.Name = "my awesome room"; - room.Host.Value = API.LocalUser.Value; + room.Host = API.LocalUser.Value; room.Playlist.Add(new PlaylistItem(new BeatmapInfo { MD5Hash = realHash, diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index b6ad361c44..0d2b18a483 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -528,7 +528,7 @@ namespace osu.Game.Online.Multiplayer var user = Room.Users.FirstOrDefault(u => u.UserID == userId); Room.Host = user; - APIRoom.Host.Value = user?.User; + APIRoom.Host = user?.User; RoomUpdated?.Invoke(); }, false); diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index d2aa8ee4fe..48282ba5e9 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -39,6 +39,12 @@ namespace osu.Game.Online.Rooms set => SetField(ref name, value); } + public APIUser? Host + { + get => host; + set => SetField(ref host, value); + } + /// /// Represents the current item selected within the room. /// @@ -57,13 +63,12 @@ namespace osu.Game.Online.Rooms [JsonProperty("name")] private string name = string.Empty; + [JsonProperty("host")] + private APIUser? host; + [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; - [Cached] - [JsonProperty("host")] - public readonly Bindable Host = new Bindable(); - [Cached] [JsonProperty("playlist")] public readonly BindableList Playlist = new BindableList(); @@ -217,8 +222,8 @@ namespace osu.Game.Online.Rooms Category.Value = other.Category.Value; - if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id) - Host.Value = other.Host.Value; + if (other.Host != null && Host?.Id != other.Host.Id) + Host = other.Host; ChannelId.Value = other.ChannelId.Value; Status.Value = other.Status.Value; diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index 0bb33df966..ca42e98e3c 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.OnlinePlay.Components public virtual void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null) { - room.Host.Value = api.LocalUser.Value; + room.Host = api.LocalUser.Value; var req = new CreateRoomRequest(room); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index e1235ab41e..99809a2a50 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -224,7 +224,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Children = new Drawable[] { ButtonsContainer, - drawableRoomParticipantsList = new DrawableRoomParticipantsList + drawableRoomParticipantsList = new DrawableRoomParticipantsList(Room) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs index 60e05285d9..2ffe740e24 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs @@ -1,13 +1,11 @@ // 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.Collections.Specialized; +using System.ComponentModel; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -16,31 +14,33 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Users.Drawables; using osuTK; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public partial class DrawableRoomParticipantsList : OnlinePlayComposite { public const float SHEAR_WIDTH = 12f; - private const float avatar_size = 36; - private const float height = 60f; - private static readonly Vector2 shear = new Vector2(SHEAR_WIDTH / height, 0); - private FillFlowContainer avatarFlow; + private readonly Room room; - private CircularAvatar hostAvatar; - private LinkFlowContainer hostText; - private HiddenUserCount hiddenUsers; - private OsuSpriteText totalCount; + private FillFlowContainer avatarFlow = null!; + private CircularAvatar hostAvatar = null!; + private LinkFlowContainer hostText = null!; + private HiddenUserCount hiddenUsers = null!; + private OsuSpriteText totalCount = null!; - public DrawableRoomParticipantsList() + public DrawableRoomParticipantsList(Room room) { + this.room = room; + AutoSizeAxes = Axes.X; Height = height; } @@ -172,7 +172,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components totalCount.Text = ParticipantCount.Value.ToString(); }, true); - Host.BindValueChanged(onHostChanged, true); + room.PropertyChanged += onRoomPropertyChanged; + updateRoomHost(); } private int numberOfCircles = 4; @@ -199,7 +200,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } } - private void onParticipantsChanged(object sender, NotifyCollectionChangedEventArgs e) + private void onParticipantsChanged(object? sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { @@ -269,21 +270,33 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } } - private void onHostChanged(ValueChangedEvent host) + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - hostAvatar.User = host.NewValue; + if (e.PropertyName == nameof(Room.Host)) + updateRoomHost(); + } + + private void updateRoomHost() + { + hostAvatar.User = room.Host; hostText.Clear(); - if (host.NewValue != null) + if (room.Host != null) { hostText.AddText("hosted by "); - hostText.AddUserLink(host.NewValue); + hostText.AddUserLink(room.Host); } } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } + private partial class CircularAvatar : CompositeDrawable { - public APIUser User + public APIUser? User { get => avatar.User; set => avatar.User = value; diff --git a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs index e5d3bd1586..0c993f4abf 100644 --- a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs @@ -2,12 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps.Drawables; using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Resources.Localisation.Web; using osu.Game.Screens.OnlinePlay.Lounge.Components; @@ -30,7 +30,6 @@ namespace osu.Game.Screens.OnlinePlay.Match private IAPIProvider api { get; set; } = null!; private readonly BindableWithCurrent selectedItem = new BindableWithCurrent(); - private readonly IBindable host = new Bindable(); private readonly bool allowEdit; private Drawable? editButton; @@ -40,7 +39,6 @@ namespace osu.Game.Screens.OnlinePlay.Match this.allowEdit = allowEdit; base.SelectedItem.BindTo(SelectedItem); - host.BindTo(room.Host); } [BackgroundDependencyLoader] @@ -62,13 +60,31 @@ namespace osu.Game.Screens.OnlinePlay.Match { base.LoadComplete(); + Room.PropertyChanged += onRoomPropertyChanged; + updateRoomHost(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Host)) + updateRoomHost(); + } + + private void updateRoomHost() + { if (editButton != null) - host.BindValueChanged(h => editButton.Alpha = h.NewValue?.Equals(api.LocalUser.Value) == true ? 1 : 0, true); + editButton.Alpha = Room.Host?.Equals(api.LocalUser.Value) == true ? 1 : 0; } protected override UpdateableBeatmapBackgroundSprite CreateBackground() => base.CreateBackground().With(d => { d.BackgroundLoadDelay = 0; }); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + Room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index f642b9c989..4c614bba8f 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -16,9 +16,6 @@ namespace osu.Game.Screens.OnlinePlay /// public partial class OnlinePlayComposite : CompositeDrawable { - [Resolved(typeof(Room))] - protected Bindable Host { get; private set; } = null!; - [Resolved(typeof(Room))] protected Bindable Status { get; private set; } = null!; diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index d8c110d12a..b0f91ca0b9 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay { RoomID = -currentRoomId, Name = $@"Room {currentRoomId}", - Host = { Value = new APIUser { Username = @"Host" } }, + Host = new APIUser { Username = @"Host" }, EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, Category = { Value = withSpotlightRooms && i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal }, }; diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index cc8bd27d91..271b036d8e 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -262,12 +262,12 @@ namespace osu.Game.Tests.Visual.OnlinePlay public void AddServerSideRoom(Room room, APIUser host) { room.RoomID ??= currentRoomId++; - room.Host.Value = host; + room.Host = host; for (int i = 0; i < room.Playlist.Count; i++) { room.Playlist[i].ID = currentPlaylistItemId++; - room.Playlist[i].OwnerID = room.Host.Value.OnlineID; + room.Playlist[i].OwnerID = room.Host.OnlineID; } serverSideRooms.Add(room); From bde7b8e610d9f72a33cf05a6d2ae208e4daf3f58 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 18:27:32 +0900 Subject: [PATCH 027/139] Make `Room.Category` non-bindable --- .../DailyChallenge/TestSceneDailyChallenge.cs | 6 ++-- .../TestSceneDailyChallengeIntro.cs | 2 +- .../Multiplayer/TestSceneDrawableRoom.cs | 4 +-- .../TestSceneLoungeRoomsContainer.cs | 10 +++--- osu.Game/Online/Rooms/Room.cs | 30 +++++++++------- .../Components/ListingPollingComponent.cs | 2 +- .../Components/StatusColouredContainer.cs | 12 +++---- .../Lounge/Components/DrawableRoom.cs | 32 ++++++++++------- .../Components/RoomSpecialCategoryPill.cs | 34 ++++++++++++++++--- .../Lounge/Components/RoomsContainer.cs | 2 +- .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 -- .../Visual/OnlinePlay/TestRoomManager.cs | 2 +- 13 files changed, 86 insertions(+), 55 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index 46a951dcff..e05f5f13ae 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge } }, EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, - Category = { Value = RoomCategory.DailyChallenge } + Category = RoomCategory.DailyChallenge }; AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge } }, EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, - Category = { Value = RoomCategory.DailyChallenge } + Category = RoomCategory.DailyChallenge }; AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge } }, EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, - Category = { Value = RoomCategory.DailyChallenge } + Category = RoomCategory.DailyChallenge }; AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs index 7b3595b064..33be952e20 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge }, StartDate = { Value = DateTimeOffset.Now }, EndDate = { Value = DateTimeOffset.Now.AddHours(24) }, - Category = { Value = RoomCategory.DailyChallenge } + Category = RoomCategory.DailyChallenge })); }); AddStep("signal client", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { RoomID = roomId })); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 17c0a159e9..07e557fe36 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -110,13 +110,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Spotlight room", Status = { Value = new RoomStatusOpen() }, - Category = { Value = RoomCategory.Spotlight }, + Category = RoomCategory.Spotlight, }), createLoungeRoom(new Room { Name = "Featured artist room", Status = { Value = new RoomStatusOpen() }, - Category = { Value = RoomCategory.FeaturedArtist }, + Category = RoomCategory.FeaturedArtist, }), } }; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 112fdfd9ab..8654fe1f11 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -55,20 +55,20 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("has 5 rooms", () => container.Rooms.Count == 5); AddAssert("all spotlights at top", () => container.Rooms - .SkipWhile(r => r.Room.Category.Value == RoomCategory.Spotlight) - .All(r => r.Room.Category.Value == RoomCategory.Normal)); + .SkipWhile(r => r.Room.Category == RoomCategory.Spotlight) + .All(r => r.Room.Category == RoomCategory.Normal)); AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.First(r => r.RoomID == 0))); AddAssert("has 4 rooms", () => container.Rooms.Count == 4); AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID != 0)); AddStep("select first room", () => container.Rooms.First().TriggerClick()); - AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); + AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category == RoomCategory.Spotlight))); AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID))); - AddAssert("first spotlight still selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); + AddAssert("first spotlight still selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category == RoomCategory.Spotlight))); - AddStep("remove spotlight room", () => RoomManager.RemoveRoom(RoomManager.Rooms.Single(r => r.Category.Value == RoomCategory.Spotlight))); + AddStep("remove spotlight room", () => RoomManager.RemoveRoom(RoomManager.Rooms.Single(r => r.Category == RoomCategory.Spotlight))); AddAssert("selection vacated", () => checkRoomSelected(null)); } diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 48282ba5e9..1a7a3f73e8 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -39,12 +39,24 @@ namespace osu.Game.Online.Rooms set => SetField(ref name, value); } + /// + /// The room host. Will be null while the room has not yet been created. + /// public APIUser? Host { get => host; set => SetField(ref host, value); } + /// + /// The room category. + /// + public RoomCategory Category + { + get => category; + set => SetField(ref category, value); + } + /// /// Represents the current item selected within the room. /// @@ -66,6 +78,10 @@ namespace osu.Game.Online.Rooms [JsonProperty("host")] private APIUser? host; + [JsonProperty("category")] + [JsonConverter(typeof(SnakeCaseStringEnumConverter))] + private RoomCategory category; + [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; @@ -85,18 +101,6 @@ namespace osu.Game.Online.Rooms [Cached] public readonly Bindable DifficultyRange = new Bindable(); - [Cached] - public readonly Bindable Category = new Bindable(); - - // Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106) - [JsonProperty("category")] - [JsonConverter(typeof(SnakeCaseStringEnumConverter))] - private RoomCategory category - { - get => Category.Value; - set => Category.Value = value; - } - [Cached] public readonly Bindable MaxAttempts = new Bindable(); @@ -220,7 +224,7 @@ namespace osu.Game.Online.Rooms RoomID = other.RoomID; Name = other.Name; - Category.Value = other.Category.Value; + Category = other.Category; if (other.Host != null && Host?.Id != other.Host.Id) Host = other.Host; diff --git a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs index 0020a7dfb6..67c3586a35 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.OnlinePlay.Components req.Success += result => { - result = result.Where(r => r.Category.Value != RoomCategory.DailyChallenge).ToList(); + result = result.Where(r => r.Category != RoomCategory.DailyChallenge).ToList(); foreach (var existing in RoomManager.Rooms.ToArray()) { diff --git a/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs b/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs index ed39021a73..90127dc961 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -17,13 +15,13 @@ namespace osu.Game.Screens.OnlinePlay.Components private readonly double transitionDuration; [Resolved(typeof(Room), nameof(Room.Status))] - private Bindable status { get; set; } + private Bindable status { get; set; } = null!; - [Resolved(typeof(Room), nameof(Room.Category))] - private Bindable category { get; set; } + private readonly Room room; - public StatusColouredContainer(double transitionDuration = 100) + public StatusColouredContainer(Room room, double transitionDuration = 100) { + this.room = room; this.transitionDuration = transitionDuration; } @@ -32,7 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { status.BindValueChanged(s => { - this.FadeColour(colours.ForRoomCategory(category.Value) ?? s.NewValue.GetAppropriateColour(colours), transitionDuration); + this.FadeColour(colours.ForRoomCategory(room.Category) ?? s.NewValue.GetAppropriateColour(colours), transitionDuration); }, true); } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 99809a2a50..8b2f2bfe4a 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -164,7 +164,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft }, - specialCategoryPill = new RoomSpecialCategoryPill + specialCategoryPill = new RoomSpecialCategoryPill(Room) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft @@ -259,15 +259,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components wrapper.FadeInFromZero(200); - roomCategory.BindTo(Room.Category); - roomCategory.BindValueChanged(c => - { - if (c.NewValue > RoomCategory.Normal) - specialCategoryPill.Show(); - else - specialCategoryPill.Hide(); - }, true); - roomType.BindTo(Room.Type); roomType.BindValueChanged(t => { @@ -278,6 +269,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true); updateRoomName(); + updateRoomCategory(); }; SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap, true); @@ -285,8 +277,16 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.Name)) - updateRoomName(); + switch (e.PropertyName) + { + case nameof(Room.Name): + updateRoomName(); + break; + + case nameof(Room.Category): + updateRoomCategory(); + break; + } } private void updateRoomName() @@ -295,6 +295,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components roomName.Text = Room.Name; } + private void updateRoomCategory() + { + if (Room.Category > RoomCategory.Normal) + specialCategoryPill?.Show(); + else + specialCategoryPill?.Hide(); + } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { return new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs index 9b8954bb33..9bb3a59d0c 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs @@ -1,21 +1,30 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; +using osu.Game.Online.Rooms; using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public partial class RoomSpecialCategoryPill : OnlinePlayPill { + private readonly Room room; + [Resolved] private OsuColour colours { get; set; } = null!; protected override FontUsage Font => base.Font.With(weight: FontWeight.SemiBold); + public RoomSpecialCategoryPill(Room room) + { + this.room = room; + } + protected override void LoadComplete() { base.LoadComplete(); @@ -23,11 +32,26 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Pill.Background.Alpha = 1; TextFlow.Colour = Color4.Black; - Category.BindValueChanged(c => - { - TextFlow.Text = c.NewValue.GetLocalisableDescription(); - Pill.Background.Colour = colours.ForRoomCategory(c.NewValue) ?? colours.Pink; - }, true); + room.PropertyChanged += onRoomPropertyChanged; + updateRoomCategory(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Category)) + updateRoomCategory(); + } + + private void updateRoomCategory() + { + TextFlow.Text = room.Category.GetLocalisableDescription(); + Pill.Background.Colour = colours.ForRoomCategory(room.Category) ?? colours.Pink; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 3d9b7a46d0..f197a7d018 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -169,7 +169,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { foreach (var room in roomFlow) { - roomFlow.SetLayoutPosition(room, room.Room.Category.Value > RoomCategory.Normal + roomFlow.SetLayoutPosition(room, room.Room.Category > RoomCategory.Normal // Always show spotlight playlists at the top of the listing. ? float.MinValue : -(room.Room.RoomID ?? 0)); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index a277b3771b..1fc177ce0b 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge AddRangeInternal(new Drawable[] { - new StatusColouredContainer(transition_duration) + new StatusColouredContainer(Room, transition_duration) { RelativeSizeAxes = Axes.Both, Child = selectionBox = new Container diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 4c614bba8f..0a51dcc8eb 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -31,9 +31,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable DifficultyRange { get; private set; } = null!; - [Resolved(typeof(Room))] - protected Bindable Category { get; private set; } = null!; - [Resolved(typeof(Room))] protected BindableList RecentParticipants { get; private set; } = null!; diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index b0f91ca0b9..07dd57eb68 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay Name = $@"Room {currentRoomId}", Host = new APIUser { Username = @"Host" }, EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, - Category = { Value = withSpotlightRooms && i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal }, + Category = withSpotlightRooms && i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal, }; if (withPassword) From 81e4cb348fe4f39b4ff1198857845694e99c7f55 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 18:36:47 +0900 Subject: [PATCH 028/139] Make `Room.Type` non-bindable --- .../Multiplayer/TestSceneDrawableRoom.cs | 12 ++++---- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 10 +++---- .../Online/Multiplayer/MultiplayerClient.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 27 ++++++++--------- .../Lounge/Components/DrawableRoom.cs | 29 ++++++++++++------- .../Lounge/Components/MatchTypePill.cs | 28 +++++++++++++++--- .../Match/MultiplayerMatchSettingsOverlay.cs | 19 +++++++++--- .../Multiplayer/MultiplayerLoungeSubScreen.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 -- .../Playlists/PlaylistsLoungeSubScreen.cs | 8 ++--- .../Multiplayer/MultiplayerTestScene.cs | 4 +-- .../Multiplayer/TestMultiplayerClient.cs | 2 +- 12 files changed, 89 insertions(+), 57 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 07e557fe36..4c9aa6af15 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Multiplayer room", Status = { Value = new RoomStatusOpen() }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, - Type = { Value = MatchType.HeadToHead }, + Type = MatchType.HeadToHead, Playlist = { item1 }, CurrentPlaylistItem = item1 }), @@ -88,7 +88,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Status = { Value = new RoomStatusOpenPrivate() }, HasPassword = { Value = true }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, - Type = { Value = MatchType.HeadToHead }, + Type = MatchType.HeadToHead, Playlist = { item3 }, CurrentPlaylistItem = item3 }), @@ -137,7 +137,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Room with password", Status = { Value = new RoomStatusOpen() }, - Type = { Value = MatchType.HeadToHead }, + Type = MatchType.HeadToHead, })); AddUntilStep("wait for panel load", () => drawableRoom.ChildrenOfType().Any()); @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "A host-only room", QueueMode = { Value = QueueMode.HostOnly }, - Type = { Value = MatchType.HeadToHead }, + Type = MatchType.HeadToHead, }) { SelectedItem = new Bindable() @@ -175,7 +175,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "An all-players, team-versus room", QueueMode = { Value = QueueMode.AllPlayers }, - Type = { Value = MatchType.TeamVersus } + Type = MatchType.TeamVersus }) { SelectedItem = new Bindable() @@ -184,7 +184,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "A round-robin room", QueueMode = { Value = QueueMode.AllPlayersRoundRobin }, - Type = { Value = MatchType.HeadToHead } + Type = MatchType.HeadToHead }) { SelectedItem = new Bindable() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 7c30b72747..b68089015e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - Type = { Value = MatchType.TeamVersus }, + Type = MatchType.TeamVersus, Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - Type = { Value = MatchType.TeamVersus }, + Type = MatchType.TeamVersus, Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - Type = { Value = MatchType.HeadToHead }, + Type = MatchType.HeadToHead, Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -130,14 +130,14 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddUntilStep("match type head to head", () => multiplayerClient.ClientAPIRoom?.Type.Value == MatchType.HeadToHead); + AddUntilStep("match type head to head", () => multiplayerClient.ClientAPIRoom?.Type == MatchType.HeadToHead); AddStep("change match type", () => multiplayerClient.ChangeSettings(new MultiplayerRoomSettings { MatchType = MatchType.TeamVersus }).WaitSafely()); - AddUntilStep("api room updated to team versus", () => multiplayerClient.ClientAPIRoom?.Type.Value == MatchType.TeamVersus); + AddUntilStep("api room updated to team versus", () => multiplayerClient.ClientAPIRoom?.Type == MatchType.TeamVersus); } [Test] diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 0d2b18a483..a6037e8907 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -844,7 +844,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.Name = Room.Settings.Name; APIRoom.Password.Value = Room.Settings.Password; APIRoom.Status.Value = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate(); - APIRoom.Type.Value = Room.Settings.MatchType; + APIRoom.Type = Room.Settings.MatchType; APIRoom.QueueMode.Value = Room.Settings.QueueMode; APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration; APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId); diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 1a7a3f73e8..acc7d7b3df 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -57,6 +57,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref category, value); } + /// + /// The match type. + /// + public MatchType Type + { + get => type; + set => SetField(ref type, value); + } + /// /// Represents the current item selected within the room. /// @@ -82,6 +91,10 @@ namespace osu.Game.Online.Rooms [JsonConverter(typeof(SnakeCaseStringEnumConverter))] private RoomCategory category; + [JsonConverter(typeof(SnakeCaseStringEnumConverter))] + [JsonProperty("type")] + private MatchType type; + [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; @@ -110,18 +123,6 @@ namespace osu.Game.Online.Rooms [Cached] public readonly Bindable Availability = new Bindable(); - [Cached] - public readonly Bindable Type = new Bindable(); - - // Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106) - [JsonConverter(typeof(SnakeCaseStringEnumConverter))] - [JsonProperty("type")] - private MatchType type - { - get => Type.Value; - set => Type.Value = value; - } - [Cached] public readonly Bindable QueueMode = new Bindable(); @@ -233,7 +234,7 @@ namespace osu.Game.Online.Rooms Status.Value = other.Status.Value; Availability.Value = other.Availability.Value; HasPassword.Value = other.HasPassword.Value; - Type.Value = other.Type.Value; + Type = other.Type; MaxParticipants.Value = other.MaxParticipants.Value; ParticipantCount.Value = other.ParticipantCount.Value; EndDate.Value = other.EndDate.Value; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 8b2f2bfe4a..d05d887038 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -189,7 +189,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components RelativeSizeAxes = Axes.X, Font = OsuFont.GetFont(size: 28) }, - new RoomStatusText + new RoomStatusText(Room) { SelectedItem = { BindTarget = SelectedItem } } @@ -259,17 +259,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components wrapper.FadeInFromZero(200); - roomType.BindTo(Room.Type); - roomType.BindValueChanged(t => - { - endDateInfo.Alpha = t.NewValue == MatchType.Playlists ? 1 : 0; - }, true); - hasPassword.BindTo(Room.HasPassword); hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true); updateRoomName(); updateRoomCategory(); + updateRoomType(); }; SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap, true); @@ -286,6 +281,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components case nameof(Room.Category): updateRoomCategory(); break; + + case nameof(Room.Type): + updateRoomType(); + break; } } @@ -303,6 +302,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components specialCategoryPill?.Hide(); } + private void updateRoomType() + { + if (endDateInfo != null) + endDateInfo.Alpha = Room.Type == MatchType.Playlists ? 1 : 0; + } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { return new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) @@ -331,11 +336,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { var pills = new List(); - if (Room.Type.Value != MatchType.Playlists) + if (Room.Type != MatchType.Playlists) { pills.AddRange(new OnlinePlayComposite[] { - new MatchTypePill(), + new MatchTypePill(Room), new QueueModePill(), }); } @@ -374,11 +379,13 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components [Resolved] private BeatmapLookupCache beatmapLookupCache { get; set; } = null!; + private readonly Room room; private SpriteText statusText = null!; private LinkFlowContainer beatmapText = null!; - public RoomStatusText() + public RoomStatusText(Room room) { + this.room = room; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; } @@ -437,7 +444,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components beatmapLookupCancellation?.Cancel(); beatmapText.Clear(); - if (Type.Value == MatchType.Playlists) + if (room.Type == MatchType.Playlists) { statusText.Text = "Ready to play"; return; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs index e30d673b26..d5405c2d0e 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs @@ -1,7 +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 osu.Framework.Bindables; +using System.ComponentModel; using osu.Framework.Extensions; using osu.Game.Online.Rooms; @@ -9,16 +9,36 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public partial class MatchTypePill : OnlinePlayPill { + private readonly Room room; + + public MatchTypePill(Room room) + { + this.room = room; + } + protected override void LoadComplete() { base.LoadComplete(); - Type.BindValueChanged(onMatchTypeChanged, true); + room.PropertyChanged += onRoomPropertyChanged; + updateRoomType(); } - private void onMatchTypeChanged(ValueChangedEvent type) + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - TextFlow.Text = type.NewValue.GetLocalisableDescription(); + if (e.PropertyName == nameof(Room.Type)) + updateRoomType(); + } + + private void updateRoomType() + { + TextFlow.Text = room.Type.GetLocalisableDescription(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 3dc01cee54..226484e9a5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -350,7 +350,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }; TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue.GetLocalisableDescription(), true); - Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true); QueueMode.BindValueChanged(mode => QueueModeDropdown.Current.Value = mode.NewValue, true); @@ -377,17 +376,29 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match room.PropertyChanged += onRoomPropertyChanged; updateRoomName(); + updateRoomType(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.Name)) - updateRoomName(); + switch (e.PropertyName) + { + case nameof(Room.Name): + updateRoomName(); + break; + + case nameof(Room.Type): + updateRoomName(); + break; + } } private void updateRoomName() => NameField.Text = room.Name; + private void updateRoomType() + => TypePicker.Current.Value = room.Type; + protected override void Update() { base.Update(); @@ -430,7 +441,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match else { room.Name = NameField.Text; - room.Type.Value = TypePicker.Current.Value; + room.Type = TypePicker.Current.Value; room.Password.Value = PasswordTextBox.Current.Value; room.QueueMode.Value = QueueModeDropdown.Current.Value; room.AutoStartDuration.Value = autoStartDuration; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index 1ccd02ba8d..a5cc267569 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -74,7 +74,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override Room CreateNewRoom() => new Room { Name = $"{api.LocalUser}'s awesome room", - Type = { Value = MatchType.HeadToHead }, + Type = MatchType.HeadToHead, }; protected override RoomSubScreen CreateRoomSubScreen(Room room) => new MultiplayerMatchSubScreen(room); diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 0a51dcc8eb..7e29c2f117 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -19,9 +19,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable Status { get; private set; } = null!; - [Resolved(typeof(Room))] - protected Bindable Type { get; private set; } = null!; - [Resolved(typeof(Room))] protected Bindable PlaylistItemStats { get; private set; } = null!; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs index a35d114418..d66b4f844c 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.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.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -22,9 +20,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists public partial class PlaylistsLoungeSubScreen : LoungeSubScreen { [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; - private Dropdown categoryDropdown; + private Dropdown categoryDropdown = null!; protected override IEnumerable CreateFilterControls() { @@ -68,7 +66,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists return new Room { Name = $"{api.LocalUser}'s awesome playlist", - Type = { Value = MatchType.Playlists } + Type = MatchType.Playlists }; } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index e540c78b0f..774b1d5931 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.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 osu.Game.Online.Rooms; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual.OnlinePlay; @@ -38,7 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return new Room { Name = "test name", - Type = { Value = MatchType.HeadToHead }, + Type = MatchType.HeadToHead, Playlist = { new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 7a8e553452..6111ba0a6d 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -231,7 +231,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Settings = { Name = ServerAPIRoom.Name, - MatchType = ServerAPIRoom.Type.Value, + MatchType = ServerAPIRoom.Type, Password = password ?? string.Empty, QueueMode = ServerAPIRoom.QueueMode.Value, AutoStartDuration = ServerAPIRoom.AutoStartDuration.Value From 5d4838a08b93cd4ae5d4d963710b6e679fdada69 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 18:48:13 +0900 Subject: [PATCH 029/139] Make `Room.Status` non-bindable --- .../Multiplayer/TestSceneDrawableRoom.cs | 14 +++---- .../Online/Multiplayer/MultiplayerClient.cs | 8 ++-- osu.Game/Online/Rooms/GetRoomsRequest.cs | 6 +-- osu.Game/Online/Rooms/Room.cs | 19 ++++++--- .../Components/StatusColouredContainer.cs | 39 +++++++++++++------ .../Lounge/Components/DrawableRoom.cs | 2 +- .../Lounge/Components/RoomStatusPill.cs | 35 +++++++++++++---- .../Multiplayer/MultiplayerRoomManager.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 -- 9 files changed, 84 insertions(+), 44 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 4c9aa6af15..a40a75c885 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createLoungeRoom(new Room { Name = "Multiplayer room", - Status = { Value = new RoomStatusOpen() }, + Status = new RoomStatusOpen(), EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, Type = MatchType.HeadToHead, Playlist = { item1 }, @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createLoungeRoom(new Room { Name = "Private room", - Status = { Value = new RoomStatusOpenPrivate() }, + Status = new RoomStatusOpenPrivate(), HasPassword = { Value = true }, EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, Type = MatchType.HeadToHead, @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createLoungeRoom(new Room { Name = "Playlist room with multiple beatmaps", - Status = { Value = new RoomStatusPlaying() }, + Status = new RoomStatusPlaying(), EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, Playlist = { item1, item2 }, CurrentPlaylistItem = item1 @@ -103,19 +103,19 @@ namespace osu.Game.Tests.Visual.Multiplayer createLoungeRoom(new Room { Name = "Finished room", - Status = { Value = new RoomStatusEnded() }, + Status = new RoomStatusEnded(), EndDate = { Value = DateTimeOffset.Now }, }), createLoungeRoom(new Room { Name = "Spotlight room", - Status = { Value = new RoomStatusOpen() }, + Status = new RoomStatusOpen(), Category = RoomCategory.Spotlight, }), createLoungeRoom(new Room { Name = "Featured artist room", - Status = { Value = new RoomStatusOpen() }, + Status = new RoomStatusOpen(), Category = RoomCategory.FeaturedArtist, }), } @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create room", () => Child = drawableRoom = createLoungeRoom(room = new Room { Name = "Room with password", - Status = { Value = new RoomStatusOpen() }, + Status = new RoomStatusOpen(), Type = MatchType.HeadToHead, })); diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index a6037e8907..f26dbb3239 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -397,15 +397,15 @@ namespace osu.Game.Online.Multiplayer switch (state) { case MultiplayerRoomState.Open: - APIRoom.Status.Value = APIRoom.HasPassword.Value ? new RoomStatusOpenPrivate() : new RoomStatusOpen(); + APIRoom.Status = APIRoom.HasPassword.Value ? new RoomStatusOpenPrivate() : new RoomStatusOpen(); break; case MultiplayerRoomState.Playing: - APIRoom.Status.Value = new RoomStatusPlaying(); + APIRoom.Status = new RoomStatusPlaying(); break; case MultiplayerRoomState.Closed: - APIRoom.Status.Value = new RoomStatusEnded(); + APIRoom.Status = new RoomStatusEnded(); break; } @@ -843,7 +843,7 @@ namespace osu.Game.Online.Multiplayer Room.Settings = settings; APIRoom.Name = Room.Settings.Name; APIRoom.Password.Value = Room.Settings.Password; - APIRoom.Status.Value = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate(); + APIRoom.Status = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate(); APIRoom.Type = Room.Settings.MatchType; APIRoom.QueueMode.Value = Room.Settings.QueueMode; APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration; diff --git a/osu.Game/Online/Rooms/GetRoomsRequest.cs b/osu.Game/Online/Rooms/GetRoomsRequest.cs index 1b5e08c729..980ce74718 100644 --- a/osu.Game/Online/Rooms/GetRoomsRequest.cs +++ b/osu.Game/Online/Rooms/GetRoomsRequest.cs @@ -45,11 +45,11 @@ namespace osu.Game.Online.Rooms foreach (var room in Response) { if (room.EndDate.Value != null && DateTimeOffset.Now >= room.EndDate.Value) - room.Status.Value = new RoomStatusEnded(); + room.Status = new RoomStatusEnded(); else if (room.HasPassword.Value) - room.Status.Value = new RoomStatusOpenPrivate(); + room.Status = new RoomStatusOpenPrivate(); else - room.Status.Value = new RoomStatusOpen(); + room.Status = new RoomStatusOpen(); } } } diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index acc7d7b3df..760580c003 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -78,6 +78,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref currentPlaylistItem, value); } + /// + /// The current room status. + /// + public RoomStatus Status + { + get => status; + set => SetField(ref status, value); + } + [JsonProperty("id")] private long? roomId; @@ -98,6 +107,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; + // Not serialised (see: GetRoomsRequest). + private RoomStatus status = new RoomStatusOpen(); + [Cached] [JsonProperty("playlist")] public readonly BindableList Playlist = new BindableList(); @@ -117,9 +129,6 @@ namespace osu.Game.Online.Rooms [Cached] public readonly Bindable MaxAttempts = new Bindable(); - [Cached] - public readonly Bindable Status = new Bindable(new RoomStatusOpen()); - [Cached] public readonly Bindable Availability = new Bindable(); @@ -231,7 +240,7 @@ namespace osu.Game.Online.Rooms Host = other.Host; ChannelId.Value = other.ChannelId.Value; - Status.Value = other.Status.Value; + Status = other.Status; Availability.Value = other.Availability.Value; HasPassword.Value = other.HasPassword.Value; Type = other.Type; @@ -266,7 +275,7 @@ namespace osu.Game.Online.Rooms // Todo: This is not the best way/place to do this, but the intention is to display all playlist items when the room has ended, // and display only the non-expired playlist items while the room is still active. In order to achieve this, all expired items are removed from the source Room. // More refactoring is required before this can be done locally instead - DrawableRoomPlaylist is currently directly bound to the playlist to display items in the room. - if (!(Status.Value is RoomStatusEnded)) + if (Status is not RoomStatusEnded) Playlist.RemoveAll(i => i.Expired); } diff --git a/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs b/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs index 90127dc961..2b1233506f 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs @@ -1,22 +1,21 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Online.Rooms; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Components { public partial class StatusColouredContainer : Container { + [Resolved] + private OsuColour colours { get; set; } = null!; + private readonly double transitionDuration; - - [Resolved(typeof(Room), nameof(Room.Status))] - private Bindable status { get; set; } = null!; - private readonly Room room; public StatusColouredContainer(Room room, double transitionDuration = 100) @@ -25,13 +24,29 @@ namespace osu.Game.Screens.OnlinePlay.Components this.transitionDuration = transitionDuration; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + protected override void LoadComplete() { - status.BindValueChanged(s => - { - this.FadeColour(colours.ForRoomCategory(room.Category) ?? s.NewValue.GetAppropriateColour(colours), transitionDuration); - }, true); + base.LoadComplete(); + + room.PropertyChanged += onRoomPropertyChanged; + updateRoomStatus(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Status)) + updateRoomStatus(); + } + + private void updateRoomStatus() + { + this.FadeColour(colours.ForRoomCategory(room.Category) ?? room.Status.GetAppropriateColour(colours), transitionDuration); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index d05d887038..8fb439452c 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -159,7 +159,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Spacing = new Vector2(5), Children = new Drawable[] { - new RoomStatusPill + new RoomStatusPill(Room) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs index 96d698a184..10d3695d14 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.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.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; @@ -19,25 +20,43 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components protected override FontUsage Font => base.Font.With(weight: FontWeight.SemiBold); + private readonly Room room; + + public RoomStatusPill(Room room) + { + this.room = room; + } + protected override void LoadComplete() { base.LoadComplete(); - EndDate.BindValueChanged(_ => updateDisplay()); - Status.BindValueChanged(_ => updateDisplay(), true); - - FinishTransforms(true); - TextFlow.Colour = Colour4.Black; Pill.Background.Alpha = 1; + + EndDate.BindValueChanged(_ => updateDisplay()); + room.PropertyChanged += onRoomPropertyChanged; + updateDisplay(); + + FinishTransforms(true); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Status)) + updateDisplay(); } private void updateDisplay() { - RoomStatus status = Status.Value; + Pill.Background.FadeColour(room.Status.GetAppropriateColour(colours), 100); + TextFlow.Text = room.Status.Message; + } - Pill.Background.FadeColour(status.GetAppropriateColour(colours), 100); - TextFlow.Text = status.Message; + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs index d7efd30435..b119c6d59d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer // this is done here as a pre-check to avoid clicking on already closed rooms in the lounge from triggering a server join. // should probably be done at a higher level, but due to the current structure of things this is the easiest place for now. - if (room.Status.Value is RoomStatusEnded) + if (room.Status is RoomStatusEnded) { onError?.Invoke("Cannot join an ended room."); return; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 7e29c2f117..82774abac7 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -16,9 +16,6 @@ namespace osu.Game.Screens.OnlinePlay /// public partial class OnlinePlayComposite : CompositeDrawable { - [Resolved(typeof(Room))] - protected Bindable Status { get; private set; } = null!; - [Resolved(typeof(Room))] protected Bindable PlaylistItemStats { get; private set; } = null!; From 7e3e5208f0c719993f7bcc46809069979287509e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 18:52:04 +0900 Subject: [PATCH 030/139] Make `Room.Availability` non-bindable --- osu.Game/Online/Rooms/Room.cs | 17 +++++++++++++---- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 --- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 19 +++++++++++++++---- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 760580c003..d43d40bd66 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -87,6 +87,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref status, value); } + /// + /// Describes which players are able to join the room. + /// + public RoomAvailability Availability + { + get => availability; + set => SetField(ref availability, value); + } + [JsonProperty("id")] private long? roomId; @@ -110,6 +119,9 @@ namespace osu.Game.Online.Rooms // Not serialised (see: GetRoomsRequest). private RoomStatus status = new RoomStatusOpen(); + // Not yet serialised (not implemented). + private RoomAvailability availability; + [Cached] [JsonProperty("playlist")] public readonly BindableList Playlist = new BindableList(); @@ -129,9 +141,6 @@ namespace osu.Game.Online.Rooms [Cached] public readonly Bindable MaxAttempts = new Bindable(); - [Cached] - public readonly Bindable Availability = new Bindable(); - [Cached] public readonly Bindable QueueMode = new Bindable(); @@ -241,7 +250,7 @@ namespace osu.Game.Online.Rooms ChannelId.Value = other.ChannelId.Value; Status = other.Status; - Availability.Value = other.Availability.Value; + Availability = other.Availability; HasPassword.Value = other.HasPassword.Value; Type = other.Type; MaxParticipants.Value = other.MaxParticipants.Value; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 82774abac7..d444067da6 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -46,9 +46,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable EndDate { get; private set; } = null!; - [Resolved(typeof(Room))] - protected Bindable Availability { get; private set; } = null!; - [Resolved(typeof(Room))] public Bindable Password { get; private set; } = null!; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 84a4657693..aee26e544a 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -316,7 +316,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists loadingLayer = new LoadingLayer(true) }; - Availability.BindValueChanged(availability => AvailabilityPicker.Current.Value = availability.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true); Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue ?? TimeSpan.FromMinutes(30), true); @@ -346,17 +345,29 @@ namespace osu.Game.Screens.OnlinePlay.Playlists room.PropertyChanged += onRoomPropertyChanged; updateRoomName(); + updateRoomAvailability(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.Name)) - updateRoomName(); + switch (e.PropertyName) + { + case nameof(Room.Name): + updateRoomName(); + break; + + case nameof(Room.Availability): + updateRoomAvailability(); + break; + } } private void updateRoomName() => NameField.Text = room.Name; + private void updateRoomAvailability() + => AvailabilityPicker.Current.Value = room.Availability; + private void populateDurations(ValueChangedEvent user) { // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427) @@ -405,7 +416,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists hideError(); room.Name = NameField.Text; - Availability.Value = AvailabilityPicker.Current.Value; + room.Availability = AvailabilityPicker.Current.Value; if (int.TryParse(MaxParticipantsField.Text, out int max)) MaxParticipants.Value = max; From 198681e64495bace774d65ebec02bddeba0ffc62 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 18:57:51 +0900 Subject: [PATCH 031/139] Make `Room.QueueMode` non-bindable --- .../Visual/Multiplayer/QueueModeTestScene.cs | 4 +-- .../Multiplayer/TestSceneDrawableRoom.cs | 6 ++-- .../Multiplayer/TestSceneHostOnlyQueueMode.cs | 2 +- .../Multiplayer/TestSceneMultiplayer.cs | 12 ++++---- .../TestSceneMultiplayerQueueList.cs | 8 +++--- .../Online/Multiplayer/MultiplayerClient.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 27 ++++++++++-------- .../Lounge/Components/DrawableRoom.cs | 2 +- .../Lounge/Components/QueueModePill.cs | 28 +++++++++++++++---- .../Match/MultiplayerMatchSettingsOverlay.cs | 11 ++++++-- .../Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 4 --- .../Multiplayer/TestMultiplayerClient.cs | 2 +- 13 files changed, 67 insertions(+), 43 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 2d9d979022..7dce7daf6d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("open room", () => multiplayerComponents.ChildrenOfType().Single().Open(new Room { Name = "Test Room", - QueueMode = { Value = Mode }, + QueueMode = Mode, Playlist = { new PlaylistItem(InitialBeatmap) @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestCreatedWithCorrectMode() { - AddUntilStep("room created with correct mode", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == Mode); + AddUntilStep("room created with correct mode", () => MultiplayerClient.ClientAPIRoom?.QueueMode == Mode); } protected void RunGameplay() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index a40a75c885..79ac0fa7b6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -165,7 +165,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new DrawableMatchRoom(new Room { Name = "A host-only room", - QueueMode = { Value = QueueMode.HostOnly }, + QueueMode = QueueMode.HostOnly, Type = MatchType.HeadToHead, }) { @@ -174,7 +174,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new DrawableMatchRoom(new Room { Name = "An all-players, team-versus room", - QueueMode = { Value = QueueMode.AllPlayers }, + QueueMode = QueueMode.AllPlayers, Type = MatchType.TeamVersus }) { @@ -183,7 +183,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new DrawableMatchRoom(new Room { Name = "A round-robin room", - QueueMode = { Value = QueueMode.AllPlayersRoundRobin }, + QueueMode = QueueMode.AllPlayersRoundRobin, Type = MatchType.HeadToHead }) { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index 78baa4a39b..e0364fd65d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("api room updated", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("api room updated", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 0b662083b5..2ce6e5a577 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -792,7 +792,7 @@ namespace osu.Game.Tests.Visual.Multiplayer roomManager.AddServerSideRoom(new Room { Name = "Test Room", - QueueMode = { Value = QueueMode.AllPlayers }, + QueueMode = QueueMode.AllPlayers, Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -837,7 +837,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - QueueMode = { Value = QueueMode.AllPlayers }, + QueueMode = QueueMode.AllPlayers, Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -873,7 +873,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - QueueMode = { Value = QueueMode.AllPlayers }, + QueueMode = QueueMode.AllPlayers, Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -912,7 +912,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - QueueMode = { Value = QueueMode.AllPlayers }, + QueueMode = QueueMode.AllPlayers, Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -943,7 +943,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - QueueMode = { Value = QueueMode.AllPlayers }, + QueueMode = QueueMode.AllPlayers, Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -1023,7 +1023,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - QueueMode = { Value = QueueMode.AllPlayers }, + QueueMode = QueueMode.AllPlayers, Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 47fb4e06ea..10625d57aa 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestDeleteButtonAlwaysVisibleForHost() { AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers); addPlaylistItem(() => API.LocalUser.Value.OnlineID); assertDeleteButtonVisibility(1, true); @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestDeleteButtonOnlyVisibleForItemOwnerIfNotHost() { AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers); AddStep("join other user", () => MultiplayerClient.AddUser(new APIUser { Id = 1234 })); AddStep("set other user as host", () => MultiplayerClient.TransferHost(1234)); @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestSingleItemDoesNotHaveDeleteButton() { AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers); assertDeleteButtonVisibility(0, false); } @@ -109,7 +109,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestCurrentItemHasDeleteButtonIfNotSingle() { AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers); addPlaylistItem(() => API.LocalUser.Value.OnlineID); diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index f26dbb3239..f36c53204d 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -845,7 +845,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.Password.Value = Room.Settings.Password; APIRoom.Status = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate(); APIRoom.Type = Room.Settings.MatchType; - APIRoom.QueueMode.Value = Room.Settings.QueueMode; + APIRoom.QueueMode = Room.Settings.QueueMode; APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration; APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId); APIRoom.AutoSkip.Value = Room.Settings.AutoSkip; diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index d43d40bd66..b85b59e992 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -66,6 +66,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref type, value); } + /// + /// The playlist queueing mode. Only valid for multiplayer rooms. + /// + public QueueMode QueueMode + { + get => queueMode; + set => SetField(ref queueMode, value); + } + /// /// Represents the current item selected within the room. /// @@ -96,6 +105,7 @@ namespace osu.Game.Online.Rooms set => SetField(ref availability, value); } + [JsonProperty("id")] private long? roomId; @@ -113,6 +123,10 @@ namespace osu.Game.Online.Rooms [JsonProperty("type")] private MatchType type; + [JsonConverter(typeof(SnakeCaseStringEnumConverter))] + [JsonProperty("queue_mode")] + private QueueMode queueMode; + [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; @@ -141,17 +155,6 @@ namespace osu.Game.Online.Rooms [Cached] public readonly Bindable MaxAttempts = new Bindable(); - [Cached] - public readonly Bindable QueueMode = new Bindable(); - - [JsonConverter(typeof(SnakeCaseStringEnumConverter))] - [JsonProperty("queue_mode")] - private QueueMode queueMode - { - get => QueueMode.Value; - set => QueueMode.Value = value; - } - [Cached] public readonly Bindable AutoStartDuration = new Bindable(); @@ -257,7 +260,7 @@ namespace osu.Game.Online.Rooms ParticipantCount.Value = other.ParticipantCount.Value; EndDate.Value = other.EndDate.Value; UserScore.Value = other.UserScore.Value; - QueueMode.Value = other.QueueMode.Value; + QueueMode = other.QueueMode; AutoStartDuration.Value = other.AutoStartDuration.Value; DifficultyRange.Value = other.DifficultyRange.Value; PlaylistItemStats.Value = other.PlaylistItemStats.Value; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 8fb439452c..f5e72f4b99 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -341,7 +341,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components pills.AddRange(new OnlinePlayComposite[] { new MatchTypePill(Room), - new QueueModePill(), + new QueueModePill(Room), }); } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs index 23f4ecf8db..c7d7876644 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs @@ -1,24 +1,42 @@ // 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 System.ComponentModel; using osu.Framework.Extensions; -using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public partial class QueueModePill : OnlinePlayPill { + private readonly Room room; + + public QueueModePill(Room room) + { + this.room = room; + } + protected override void LoadComplete() { base.LoadComplete(); - QueueMode.BindValueChanged(onQueueModeChanged, true); + room.PropertyChanged += onRoomPropertyChanged; + updateRoomQueueMode(); } - private void onQueueModeChanged(ValueChangedEvent mode) + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - TextFlow.Text = mode.NewValue.GetLocalisableDescription(); + if (e.PropertyName == nameof(Room.QueueMode)) + updateRoomQueueMode(); + } + + private void updateRoomQueueMode() + => TextFlow.Text = room.QueueMode.GetLocalisableDescription(); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 226484e9a5..36bdb97e9e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -352,7 +352,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue.GetLocalisableDescription(), true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true); - QueueMode.BindValueChanged(mode => QueueModeDropdown.Current.Value = mode.NewValue, true); AutoStartDuration.BindValueChanged(duration => startModeDropdown.Current.Value = (StartMode)(int)duration.NewValue.TotalSeconds, true); AutoSkip.BindValueChanged(autoSkip => AutoSkipCheckbox.Current.Value = autoSkip.NewValue, true); @@ -377,6 +376,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match updateRoomName(); updateRoomType(); + updateRoomQueueMode(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -390,6 +390,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match case nameof(Room.Type): updateRoomName(); break; + + case nameof(Room.QueueMode): + updateRoomQueueMode(); + break; } } @@ -399,6 +403,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateRoomType() => TypePicker.Current.Value = room.Type; + private void updateRoomQueueMode() + => QueueModeDropdown.Current.Value = room.QueueMode; + protected override void Update() { base.Update(); @@ -443,7 +450,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match room.Name = NameField.Text; room.Type = TypePicker.Current.Value; room.Password.Value = PasswordTextBox.Current.Value; - room.QueueMode.Value = QueueModeDropdown.Current.Value; + room.QueueMode = QueueModeDropdown.Current.Value; room.AutoStartDuration.Value = autoStartDuration; room.AutoSkip.Value = AutoSkipCheckbox.Current.Value; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 60c2a586f9..8f2f3aa678 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -358,7 +358,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Activity.Value = new UserActivity.InLobby(Room); } - private bool localUserCanAddItem => client.IsHost || Room.QueueMode.Value != QueueMode.HostOnly; + private bool localUserCanAddItem => client.IsHost || Room.QueueMode != QueueMode.HostOnly; private void updateCurrentItem() { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index d444067da6..22e9e72f19 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay @@ -52,9 +51,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable Duration { get; private set; } = null!; - [Resolved(typeof(Room))] - protected Bindable QueueMode { get; private set; } = null!; - [Resolved(typeof(Room))] protected Bindable AutoStartDuration { get; private set; } = null!; diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 6111ba0a6d..7f110ae6a5 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -233,7 +233,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = ServerAPIRoom.Name, MatchType = ServerAPIRoom.Type, Password = password ?? string.Empty, - QueueMode = ServerAPIRoom.QueueMode.Value, + QueueMode = ServerAPIRoom.QueueMode, AutoStartDuration = ServerAPIRoom.AutoStartDuration.Value }, Playlist = ServerAPIRoom.Playlist.Select(CreateMultiplayerPlaylistItem).ToList(), From ec5be6dbc3719bc42ff6eeb9f6ffd54aa530b776 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 19:11:05 +0900 Subject: [PATCH 032/139] Make `Room.Password` & `Room.HasPassword` non-bindable --- .../TestSceneDrawableLoungeRoom.cs | 2 +- .../Multiplayer/TestSceneDrawableRoom.cs | 6 +-- .../TestSceneLoungeRoomsContainer.cs | 4 +- .../Multiplayer/TestSceneMultiplayer.cs | 10 ++-- .../Online/Multiplayer/MultiplayerClient.cs | 6 +-- osu.Game/Online/Rooms/GetRoomsRequest.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 49 ++++++++++++++----- .../Lounge/Components/DrawableRoom.cs | 14 ++++-- .../Lounge/Components/RoomsContainer.cs | 4 +- .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 +- .../Match/MultiplayerMatchSettingsOverlay.cs | 11 ++++- .../Multiplayer/MultiplayerRoomManager.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 -- .../Multiplayer/TestMultiplayerClient.cs | 2 +- .../Visual/OnlinePlay/TestRoomManager.cs | 2 +- .../OnlinePlay/TestRoomRequestsHandler.cs | 9 ++-- 16 files changed, 80 insertions(+), 48 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs index 9fe7189a0a..c5fb52461a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { private readonly Room room = new Room { - HasPassword = { Value = true } + Password = "*" }; [Cached] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 79ac0fa7b6..b5cfecfa25 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Private room", Status = new RoomStatusOpenPrivate(), - HasPassword = { Value = true }, + Password = "*", EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, Type = MatchType.HeadToHead, Playlist = { item3 }, @@ -144,10 +144,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType().Single().Alpha)); - AddStep("set password", () => room.Password.Value = "password"); + AddStep("set password", () => room.Password = "password"); AddAssert("password icon visible", () => Precision.AlmostEquals(1, drawableRoom.ChildrenOfType().Single().Alpha)); - AddStep("unset password", () => room.Password.Value = string.Empty); + AddStep("unset password", () => room.Password = string.Empty); AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType().Single().Alpha)); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 8654fe1f11..e28790f2ed 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -182,11 +182,11 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("filter public rooms", () => container.Filter.Value = new FilterCriteria { Permissions = RoomPermissionsFilter.Public }); - AddUntilStep("private room hidden", () => container.Rooms.All(r => !r.Room.HasPassword.Value)); + AddUntilStep("private room hidden", () => container.Rooms.All(r => !r.Room.HasPassword)); AddStep("filter private rooms", () => container.Filter.Value = new FilterCriteria { Permissions = RoomPermissionsFilter.Private }); - AddUntilStep("public room hidden", () => container.Rooms.All(r => r.Room.HasPassword.Value)); + AddUntilStep("public room hidden", () => container.Rooms.All(r => r.Room.HasPassword)); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 2ce6e5a577..6e9c18f040 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -318,7 +318,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - Password = { Value = "password" }, + Password = "password", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -328,7 +328,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddUntilStep("room has password", () => multiplayerClient.ClientAPIRoom?.Password.Value == "password"); + AddUntilStep("room has password", () => multiplayerClient.ClientAPIRoom?.Password == "password"); } [Test] @@ -339,7 +339,7 @@ namespace osu.Game.Tests.Visual.Multiplayer roomManager.AddServerSideRoom(new Room { Name = "Test Room", - Password = { Value = "password" }, + Password = "password", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -371,7 +371,7 @@ namespace osu.Game.Tests.Visual.Multiplayer createRoom(() => new Room { Name = "Test Room", - Password = { Value = "password" }, + Password = "password", Playlist = { new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) @@ -382,7 +382,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddStep("change password", () => multiplayerClient.ChangeSettings(password: "password2")); - AddUntilStep("local password changed", () => multiplayerClient.ClientAPIRoom?.Password.Value == "password2"); + AddUntilStep("local password changed", () => multiplayerClient.ClientAPIRoom?.Password == "password2"); } [Test] diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index f36c53204d..db35f228fe 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -184,7 +184,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(room.RoomID != null); // Join the server-side room. - var joinedRoom = await JoinRoom(room.RoomID.Value, password ?? room.Password.Value).ConfigureAwait(false); + var joinedRoom = await JoinRoom(room.RoomID.Value, password ?? room.Password).ConfigureAwait(false); Debug.Assert(joinedRoom != null); // Populate users. @@ -397,7 +397,7 @@ namespace osu.Game.Online.Multiplayer switch (state) { case MultiplayerRoomState.Open: - APIRoom.Status = APIRoom.HasPassword.Value ? new RoomStatusOpenPrivate() : new RoomStatusOpen(); + APIRoom.Status = APIRoom.HasPassword ? new RoomStatusOpenPrivate() : new RoomStatusOpen(); break; case MultiplayerRoomState.Playing: @@ -842,7 +842,7 @@ namespace osu.Game.Online.Multiplayer // Update a few properties of the room instantaneously. Room.Settings = settings; APIRoom.Name = Room.Settings.Name; - APIRoom.Password.Value = Room.Settings.Password; + APIRoom.Password = Room.Settings.Password; APIRoom.Status = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate(); APIRoom.Type = Room.Settings.MatchType; APIRoom.QueueMode = Room.Settings.QueueMode; diff --git a/osu.Game/Online/Rooms/GetRoomsRequest.cs b/osu.Game/Online/Rooms/GetRoomsRequest.cs index 980ce74718..0c8df642fa 100644 --- a/osu.Game/Online/Rooms/GetRoomsRequest.cs +++ b/osu.Game/Online/Rooms/GetRoomsRequest.cs @@ -46,7 +46,7 @@ namespace osu.Game.Online.Rooms { if (room.EndDate.Value != null && DateTimeOffset.Now >= room.EndDate.Value) room.Status = new RoomStatusEnded(); - else if (room.HasPassword.Value) + else if (room.HasPassword) room.Status = new RoomStatusOpenPrivate(); else room.Status = new RoomStatusOpen(); diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index b85b59e992..3f50611372 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -39,6 +39,35 @@ namespace osu.Game.Online.Rooms set => SetField(ref name, value); } + /// + /// Sets the room password. Will be null after the room is created. + /// + /// + /// To check if the room has a password, use . + /// + public string? Password + { + get => password; + set + { + SetField(ref password, value); + HasPassword = !string.IsNullOrEmpty(value); + } + } + + /// + /// Whether the room has a password. + /// + /// + /// To set a password, use . + /// + [JsonProperty("has_password")] + public bool HasPassword + { + get => hasPassword; + private set => SetField(ref hasPassword, value); + } + /// /// The room host. Will be null while the room has not yet been created. /// @@ -112,6 +141,12 @@ namespace osu.Game.Online.Rooms [JsonProperty("name")] private string name = string.Empty; + [JsonProperty("password")] + private string? password; + + // Not serialised (internal use only). + private bool hasPassword; + [JsonProperty("host")] private APIUser? host; @@ -172,9 +207,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("current_user_score")] public readonly Bindable UserScore = new Bindable(); - [JsonProperty("has_password")] - public readonly Bindable HasPassword = new Bindable(); - [Cached] [JsonProperty("recent_participants")] public readonly BindableList RecentParticipants = new BindableList(); @@ -185,10 +217,6 @@ namespace osu.Game.Online.Rooms #region Properties only used for room creation request - [Cached(Name = nameof(Password))] - [JsonProperty("password")] - public readonly Bindable Password = new Bindable(); - [Cached] public readonly Bindable Duration = new Bindable(); @@ -229,11 +257,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("auto_skip")] public readonly Bindable AutoSkip = new Bindable(); - public Room() - { - Password.BindValueChanged(p => HasPassword.Value = !string.IsNullOrEmpty(p.NewValue)); - } - /// /// Copies values from another into this one. /// @@ -254,7 +277,7 @@ namespace osu.Game.Online.Rooms ChannelId.Value = other.ChannelId.Value; Status = other.Status; Availability = other.Availability; - HasPassword.Value = other.HasPassword.Value; + HasPassword = other.HasPassword; Type = other.Type; MaxParticipants.Value = other.MaxParticipants.Value; ParticipantCount.Value = other.ParticipantCount.Value; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index f5e72f4b99..390ea06c1b 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -259,12 +259,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components wrapper.FadeInFromZero(200); - hasPassword.BindTo(Room.HasPassword); - hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true); - updateRoomName(); updateRoomCategory(); updateRoomType(); + updateRoomHasPassword(); }; SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap, true); @@ -285,6 +283,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components case nameof(Room.Type): updateRoomType(); break; + + case nameof(Room.HasPassword): + updateRoomHasPassword(); + break; } } @@ -308,6 +310,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components endDateInfo.Alpha = Room.Type == MatchType.Playlists ? 1 : 0; } + private void updateRoomHasPassword() + { + if (passwordIcon != null) + passwordIcon.Alpha = Room.HasPassword ? 1 : 0; + } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { return new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index f197a7d018..11ad886773 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -101,10 +101,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components return true; case RoomPermissionsFilter.Public: - return !room.Room.HasPassword.Value; + return !room.Room.HasPassword; case RoomPermissionsFilter.Private: - return room.Room.HasPassword.Value; + return room.Room.HasPassword; default: throw new ArgumentOutOfRangeException(nameof(accessType), accessType, $"Unsupported {nameof(RoomPermissionsFilter)} in filter"); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 1fc177ce0b..d396d18b4f 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -185,7 +185,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge return true; } - if (Room.HasPassword.Value) + if (Room.HasPassword) { this.ShowPopover(); return true; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 36bdb97e9e..a3441fbd09 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -351,7 +351,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue.GetLocalisableDescription(), true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); - Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true); AutoStartDuration.BindValueChanged(duration => startModeDropdown.Current.Value = (StartMode)(int)duration.NewValue.TotalSeconds, true); AutoSkip.BindValueChanged(autoSkip => AutoSkipCheckbox.Current.Value = autoSkip.NewValue, true); @@ -377,6 +376,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match updateRoomName(); updateRoomType(); updateRoomQueueMode(); + updateRoomPassword(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -394,6 +394,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match case nameof(Room.QueueMode): updateRoomQueueMode(); break; + + case nameof(Room.Password): + updateRoomPassword(); + break; } } @@ -406,6 +410,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateRoomQueueMode() => QueueModeDropdown.Current.Value = room.QueueMode; + private void updateRoomPassword() + => PasswordTextBox.Text = room.Password ?? string.Empty; + protected override void Update() { base.Update(); @@ -449,7 +456,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { room.Name = NameField.Text; room.Type = TypePicker.Current.Value; - room.Password.Value = PasswordTextBox.Current.Value; + room.Password = PasswordTextBox.Current.Value; room.QueueMode = QueueModeDropdown.Current.Value; room.AutoStartDuration.Value = autoStartDuration; room.AutoSkip.Value = AutoSkipCheckbox.Current.Value; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs index b119c6d59d..e16582a6e1 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private MultiplayerClient multiplayerClient { get; set; } = null!; public override void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null) - => base.CreateRoom(room, r => joinMultiplayerRoom(r, r.Password.Value, onSuccess, onError), onError); + => base.CreateRoom(room, r => joinMultiplayerRoom(r, r.Password, onSuccess, onError), onError); public override void JoinRoom(Room room, string? password = null, Action? onSuccess = null, Action? onError = null) { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 22e9e72f19..93abc23e55 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -45,9 +45,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable EndDate { get; private set; } = null!; - [Resolved(typeof(Room))] - public Bindable Password { get; private set; } = null!; - [Resolved(typeof(Room))] protected Bindable Duration { get; private set; } = null!; diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 7f110ae6a5..dcb6f2503d 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -216,7 +216,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ServerAPIRoom = roomManager.ServerSideRooms.Single(r => r.RoomID == roomId); - if (password != ServerAPIRoom.Password.Value) + if (password != ServerAPIRoom.Password) throw new InvalidOperationException("Invalid password."); lastPlaylistItemId = ServerAPIRoom.Playlist.Max(item => item.ID); diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index 07dd57eb68..3db9e12ab7 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay }; if (withPassword) - room.Password.Value = @"password"; + room.Password = @"password"; if (ruleset != null) { diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 271b036d8e..0934c4f3dc 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -51,8 +51,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay var apiRoom = cloneRoom(createRoomRequest.Room); // Passwords are explicitly not copied between rooms. - apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value); - apiRoom.Password.Value = createRoomRequest.Room.Password.Value; + apiRoom.Password = createRoomRequest.Room.Password; AddServerSideRoom(apiRoom, localUser); @@ -66,7 +65,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay { var room = ServerSideRooms.Single(r => r.RoomID == joinRoomRequest.Room.RoomID); - if (joinRoomRequest.Password != room.Password.Value) + if (joinRoomRequest.Password != room.Password) { joinRoomRequest.TriggerFailure(new InvalidOperationException("Invalid password.")); return true; @@ -278,9 +277,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay var responseRoom = cloneRoom(room); // Password is hidden from the response, and is only propagated via HasPassword. - bool hadPassword = responseRoom.HasPassword.Value; - responseRoom.Password.Value = null; - responseRoom.HasPassword.Value = hadPassword; + responseRoom.Password = responseRoom.HasPassword ? Guid.NewGuid().ToString() : null; if (!withParticipants) responseRoom.RecentParticipants.Clear(); From f001cce24adcea7dfce18d8a1ce41bd8f494f660 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 19:15:29 +0900 Subject: [PATCH 033/139] Make `Room.AutoSkip` non-bindable --- .../Online/Multiplayer/MultiplayerClient.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 19 +++++++++++++------ .../Match/MultiplayerMatchSettingsOverlay.cs | 11 +++++++++-- .../Multiplayer/MultiplayerPlayer.cs | 4 ++-- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 --- 5 files changed, 25 insertions(+), 14 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index db35f228fe..a9c0108661 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -848,7 +848,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.QueueMode = Room.Settings.QueueMode; APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration; APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId); - APIRoom.AutoSkip.Value = Room.Settings.AutoSkip; + APIRoom.AutoSkip = Room.Settings.AutoSkip; RoomUpdated?.Invoke(); } diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 3f50611372..b945d1b886 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -104,6 +104,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref queueMode, value); } + /// + /// Whether to automatically skip map intros. Only valid for multiplayer rooms. + /// + public bool AutoSkip + { + get => autoSkip; + set => SetField(ref autoSkip, value); + } + /// /// Represents the current item selected within the room. /// @@ -134,7 +143,6 @@ namespace osu.Game.Online.Rooms set => SetField(ref availability, value); } - [JsonProperty("id")] private long? roomId; @@ -162,6 +170,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("queue_mode")] private QueueMode queueMode; + [JsonProperty("auto_skip")] + private bool autoSkip; + [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; @@ -253,10 +264,6 @@ namespace osu.Game.Online.Rooms set => MaxAttempts.Value = value; } - [Cached] - [JsonProperty("auto_skip")] - public readonly Bindable AutoSkip = new Bindable(); - /// /// Copies values from another into this one. /// @@ -288,7 +295,7 @@ namespace osu.Game.Online.Rooms DifficultyRange.Value = other.DifficultyRange.Value; PlaylistItemStats.Value = other.PlaylistItemStats.Value; CurrentPlaylistItem = other.CurrentPlaylistItem; - AutoSkip.Value = other.AutoSkip.Value; + AutoSkip = other.AutoSkip; other.RemoveExpiredPlaylistItems(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index a3441fbd09..62d6459487 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -352,7 +352,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue.GetLocalisableDescription(), true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); AutoStartDuration.BindValueChanged(duration => startModeDropdown.Current.Value = (StartMode)(int)duration.NewValue.TotalSeconds, true); - AutoSkip.BindValueChanged(autoSkip => AutoSkipCheckbox.Current.Value = autoSkip.NewValue, true); operationInProgress.BindTo(ongoingOperationTracker.InProgress); operationInProgress.BindValueChanged(v => @@ -377,6 +376,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match updateRoomType(); updateRoomQueueMode(); updateRoomPassword(); + updateRoomAutoSkip(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -398,6 +398,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match case nameof(Room.Password): updateRoomPassword(); break; + + case nameof(Room.AutoSkip): + updateRoomAutoSkip(); + break; } } @@ -413,6 +417,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateRoomPassword() => PasswordTextBox.Text = room.Password ?? string.Empty; + private void updateRoomAutoSkip() + => AutoSkipCheckbox.Current.Value = room.AutoSkip; + protected override void Update() { base.Update(); @@ -459,7 +466,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match room.Password = PasswordTextBox.Current.Value; room.QueueMode = QueueModeDropdown.Current.Value; room.AutoStartDuration.Value = autoStartDuration; - room.AutoSkip.Value = AutoSkipCheckbox.Current.Value; + room.AutoSkip = AutoSkipCheckbox.Current.Value; if (int.TryParse(MaxParticipantsField.Text, out int max)) room.MaxParticipants.Value = max; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index d444af2fd3..c058699b0e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -50,8 +50,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer AllowPause = false, AllowRestart = false, AllowFailAnimation = false, - AllowSkipping = room.AutoSkip.Value, - AutomaticallySkipIntro = room.AutoSkip.Value, + AllowSkipping = room.AutoSkip, + AutomaticallySkipIntro = room.AutoSkip, AlwaysShowLeaderboard = true, }) { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 93abc23e55..2b1c9cf00a 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -50,8 +50,5 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable AutoStartDuration { get; private set; } = null!; - - [Resolved(typeof(Room))] - protected Bindable AutoSkip { get; private set; } = null!; } } From b8bae30b66b6f171191314b13d0810e6529373f9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 19:45:03 +0900 Subject: [PATCH 034/139] Make `Room.ParticipantCount` & `Room.MaxParticipants` non-bindable --- .../TestSceneDrawableRoomParticipantsList.cs | 4 +- .../Multiplayer/TestSceneMultiplayer.cs | 4 +- .../TestScenePlaylistsParticipantsList.cs | 4 +- .../Online/Multiplayer/MultiplayerClient.cs | 4 +- osu.Game/Online/Rooms/Room.cs | 35 +++++++++--- .../Components/ParticipantCountDisplay.cs | 55 +++++++++++++++---- .../Components/ParticipantsDisplay.cs | 40 +++++++++++--- .../DrawableRoomParticipantsList.cs | 27 ++++++--- .../Match/MultiplayerMatchSettingsOverlay.cs | 13 ++++- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 6 -- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 15 +++-- 11 files changed, 146 insertions(+), 61 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs index 1e7003c612..3d0c1be22c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs @@ -143,13 +143,13 @@ namespace osu.Game.Tests.Visual.Multiplayer Id = id, Username = $"User {id}" }); - SelectedRoom.Value.ParticipantCount.Value++; + SelectedRoom.Value.ParticipantCount++; } private void removeUserAt(int index) { SelectedRoom.Value.RecentParticipants.RemoveAt(index); - SelectedRoom.Value.ParticipantCount.Value--; + SelectedRoom.Value.ParticipantCount--; } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 6e9c18f040..e625d23779 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -248,7 +248,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddUntilStep("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount.Value == 1); + AddUntilStep("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount == 1); AddUntilStep("Check participant list contains user", () => multiplayerClient.ClientAPIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); } @@ -308,7 +308,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); AddUntilStep("wait for join", () => multiplayerClient.RoomJoined); - AddUntilStep("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount.Value == 1); + AddUntilStep("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount == 1); AddUntilStep("Check participant list contains user", () => multiplayerClient.ClientAPIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index b9e5875e06..2368a2068d 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - Child = new ParticipantsDisplay(Direction.Horizontal) + Child = new ParticipantsDisplay(SelectedRoom.Value, Direction.Horizontal) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - Child = new ParticipantsDisplay(Direction.Vertical) + Child = new ParticipantsDisplay(SelectedRoom.Value, Direction.Vertical) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index a9c0108661..3a9f728b41 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -492,7 +492,7 @@ namespace osu.Game.Online.Multiplayer Id = user.UserID, Username = "[Unresolved]" }); - APIRoom.ParticipantCount.Value++; + APIRoom.ParticipantCount++; } private Task handleUserLeft(MultiplayerRoomUser user, Action? callback) @@ -507,7 +507,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); APIRoom.RecentParticipants.RemoveAll(u => u.Id == user.UserID); - APIRoom.ParticipantCount.Value--; + APIRoom.ParticipantCount--; callback?.Invoke(user); RoomUpdated?.Invoke(); diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index b945d1b886..b63ebd107d 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -86,6 +86,24 @@ namespace osu.Game.Online.Rooms set => SetField(ref category, value); } + /// + /// The maximum number of users allowed in the room. + /// + public int? MaxParticipants + { + get => maxParticipants; + set => SetField(ref maxParticipants, value); + } + + /// + /// The current number of users in the room. + /// + public int ParticipantCount + { + get => participantCount; + set => SetField(ref participantCount, value); + } + /// /// The match type. /// @@ -162,6 +180,12 @@ namespace osu.Game.Online.Rooms [JsonConverter(typeof(SnakeCaseStringEnumConverter))] private RoomCategory category; + // Not yet serialised (not implemented). + private int? maxParticipants; + + [JsonProperty("participant_count")] + private int participantCount; + [JsonConverter(typeof(SnakeCaseStringEnumConverter))] [JsonProperty("type")] private MatchType type; @@ -211,9 +235,6 @@ namespace osu.Game.Online.Rooms set => AutoStartDuration.Value = TimeSpan.FromSeconds(value); } - [Cached] - public readonly Bindable MaxParticipants = new Bindable(); - [Cached] [JsonProperty("current_user_score")] public readonly Bindable UserScore = new Bindable(); @@ -222,10 +243,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("recent_participants")] public readonly BindableList RecentParticipants = new BindableList(); - [Cached] - [JsonProperty("participant_count")] - public readonly Bindable ParticipantCount = new Bindable(); - #region Properties only used for room creation request [Cached] @@ -286,8 +303,8 @@ namespace osu.Game.Online.Rooms Availability = other.Availability; HasPassword = other.HasPassword; Type = other.Type; - MaxParticipants.Value = other.MaxParticipants.Value; - ParticipantCount.Value = other.ParticipantCount.Value; + MaxParticipants = other.MaxParticipants; + ParticipantCount = other.ParticipantCount; EndDate.Value = other.EndDate.Value; UserScore.Value = other.UserScore.Value; QueueMode = other.QueueMode; diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs index 9f7e700ab3..f9744717d5 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs @@ -1,13 +1,13 @@ // 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.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { @@ -16,18 +16,21 @@ namespace osu.Game.Screens.OnlinePlay.Components private const float text_size = 30; private const float transition_duration = 100; - private OsuSpriteText slash, maxText; + private readonly Room room; - public ParticipantCountDisplay() + private OsuSpriteText slash = null!; + private OsuSpriteText maxText = null!; + private OsuSpriteText count = null!; + + public ParticipantCountDisplay(Room room) { + this.room = room; AutoSizeAxes = Axes.Both; } [BackgroundDependencyLoader] private void load() { - OsuSpriteText count; - InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -50,14 +53,33 @@ namespace osu.Game.Screens.OnlinePlay.Components }, } }; - - MaxParticipants.BindValueChanged(_ => updateMax(), true); - ParticipantCount.BindValueChanged(c => count.Text = c.NewValue.ToString("#,0"), true); } - private void updateMax() + protected override void LoadComplete() { - if (MaxParticipants.Value == null) + base.LoadComplete(); + + room.PropertyChanged += onRoomPropertyChanged; + updateRoomParticipantCount(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + switch (e.PropertyName) + { + case nameof(Room.MaxParticipants): + updateRoomMaxParticipants(); + break; + + case nameof(Room.ParticipantCount): + updateRoomParticipantCount(); + break; + } + } + + private void updateRoomMaxParticipants() + { + if (room.MaxParticipants == null) { slash.FadeOut(transition_duration); maxText.FadeOut(transition_duration); @@ -65,9 +87,18 @@ namespace osu.Game.Screens.OnlinePlay.Components else { slash.FadeIn(transition_duration); - maxText.Text = MaxParticipants.Value.ToString(); + maxText.Text = room.MaxParticipants.ToString()!; maxText.FadeIn(transition_duration); } } + + private void updateRoomParticipantCount() + => count.Text = room.ParticipantCount.ToString("#,0"); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs index 5128bc4c14..1ae2756514 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs @@ -1,19 +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.Framework.Allocation; +using System.ComponentModel; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { public partial class ParticipantsDisplay : OnlinePlayComposite { - public Bindable Details = new Bindable(); + public readonly Bindable Details = new Bindable(); - public ParticipantsDisplay(Direction direction) + private readonly Room room; + + public ParticipantsDisplay(Room room, Direction direction) { + this.room = room; OsuScrollContainer scroll; ParticipantsList list; @@ -46,14 +50,32 @@ namespace osu.Game.Screens.OnlinePlay.Components } } - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { - ParticipantCount.BindValueChanged(_ => setParticipantCount()); - MaxParticipants.BindValueChanged(_ => setParticipantCount(), true); + base.LoadComplete(); + + room.PropertyChanged += onRoomPropertyChanged; + updateRoomParticipantCount(); } - private void setParticipantCount() => - Details.Value = MaxParticipants.Value != null ? $"{ParticipantCount.Value}/{MaxParticipants.Value}" : ParticipantCount.Value.ToString(); + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + switch (e.PropertyName) + { + case nameof(Room.MaxParticipants): + case nameof(Room.ParticipantCount): + updateRoomParticipantCount(); + break; + } + } + + private void updateRoomParticipantCount() + => Details.Value = room.MaxParticipants != null ? $"{room.ParticipantCount}/{room.MaxParticipants}" : room.ParticipantCount.ToString(); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs index 2ffe740e24..06ae939842 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs @@ -166,14 +166,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components base.LoadComplete(); RecentParticipants.BindCollectionChanged(onParticipantsChanged, true); - ParticipantCount.BindValueChanged(_ => - { - updateHiddenUsers(); - totalCount.Text = ParticipantCount.Value.ToString(); - }, true); room.PropertyChanged += onRoomPropertyChanged; + updateRoomHost(); + updateRoomParticipantCount(); } private int numberOfCircles = 4; @@ -257,7 +254,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { int hiddenCount = 0; if (RecentParticipants.Count > NumberOfCircles) - hiddenCount = ParticipantCount.Value - NumberOfCircles + 1; + hiddenCount = room.ParticipantCount - NumberOfCircles + 1; hiddenUsers.Count = hiddenCount; @@ -272,8 +269,16 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.Host)) - updateRoomHost(); + switch (e.PropertyName) + { + case nameof(Room.Host): + updateRoomHost(); + break; + + case nameof(Room.ParticipantCount): + updateRoomParticipantCount(); + break; + } } private void updateRoomHost() @@ -288,6 +293,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } } + private void updateRoomParticipantCount() + { + updateHiddenUsers(); + totalCount.Text = room.ParticipantCount.ToString(); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 62d6459487..141bddc4d4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -350,7 +350,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }; TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue.GetLocalisableDescription(), true); - MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); AutoStartDuration.BindValueChanged(duration => startModeDropdown.Current.Value = (StartMode)(int)duration.NewValue.TotalSeconds, true); operationInProgress.BindTo(ongoingOperationTracker.InProgress); @@ -377,6 +376,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match updateRoomQueueMode(); updateRoomPassword(); updateRoomAutoSkip(); + updateRoomMaxParticipants(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -402,6 +402,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match case nameof(Room.AutoSkip): updateRoomAutoSkip(); break; + + case nameof(Room.MaxParticipants): + updateRoomMaxParticipants(); + break; } } @@ -420,6 +424,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateRoomAutoSkip() => AutoSkipCheckbox.Current.Value = room.AutoSkip; + private void updateRoomMaxParticipants() + => MaxParticipantsField.Text = room.MaxParticipants?.ToString(); + protected override void Update() { base.Update(); @@ -469,9 +476,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match room.AutoSkip = AutoSkipCheckbox.Current.Value; if (int.TryParse(MaxParticipantsField.Text, out int max)) - room.MaxParticipants.Value = max; + room.MaxParticipants = max; else - room.MaxParticipants.Value = null; + room.MaxParticipants = null; manager.CreateRoom(room, onSuccess, onError); } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 2b1c9cf00a..7623cbabbd 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -27,12 +27,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected BindableList RecentParticipants { get; private set; } = null!; - [Resolved(typeof(Room))] - protected Bindable ParticipantCount { get; private set; } = null!; - - [Resolved(typeof(Room))] - protected Bindable MaxParticipants { get; private set; } = null!; - [Resolved(typeof(Room))] protected Bindable MaxAttempts { get; private set; } = null!; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index aee26e544a..20fbe42dd5 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -316,7 +316,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists loadingLayer = new LoadingLayer(true) }; - MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true); Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue ?? TimeSpan.FromMinutes(30), true); @@ -346,6 +345,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists updateRoomName(); updateRoomAvailability(); + updateRoomMaxParticipants(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -359,6 +359,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists case nameof(Room.Availability): updateRoomAvailability(); break; + + case nameof(Room.MaxParticipants): + updateRoomMaxParticipants(); + break; } } @@ -368,6 +372,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void updateRoomAvailability() => AvailabilityPicker.Current.Value = room.Availability; + private void updateRoomMaxParticipants() + => MaxParticipantsField.Text = room.MaxParticipants?.ToString(); + private void populateDurations(ValueChangedEvent user) { // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427) @@ -417,11 +424,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists room.Name = NameField.Text; room.Availability = AvailabilityPicker.Current.Value; - - if (int.TryParse(MaxParticipantsField.Text, out int max)) - MaxParticipants.Value = max; - else - MaxParticipants.Value = null; + room.MaxParticipants = int.TryParse(MaxParticipantsField.Text, out int max) ? max : null; if (int.TryParse(MaxAttemptsField.Text, out max)) MaxAttempts.Value = max; From 89de4f0f87a81feae6714b8bea2b85fd36bf7a35 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 19:53:21 +0900 Subject: [PATCH 035/139] Make `Room.AutoStartDuration` non-bindable --- .../Online/Multiplayer/MultiplayerClient.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 24 ++++++++++--------- .../Match/MultiplayerMatchSettingsOverlay.cs | 15 ++++++++---- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 --- .../Multiplayer/TestMultiplayerClient.cs | 2 +- 5 files changed, 25 insertions(+), 21 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 3a9f728b41..6a7d460922 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -846,7 +846,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.Status = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate(); APIRoom.Type = Room.Settings.MatchType; APIRoom.QueueMode = Room.Settings.QueueMode; - APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration; + APIRoom.AutoStartDuration = Room.Settings.AutoStartDuration; APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId); APIRoom.AutoSkip = Room.Settings.AutoSkip; diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index b63ebd107d..61f944a728 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -131,6 +131,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref autoSkip, value); } + /// + /// The amount of time before the match is automatically started. Only valid for multiplayer rooms. + /// + public TimeSpan AutoStartDuration + { + get => TimeSpan.FromSeconds(autoStartDuration); + set => SetField(ref autoStartDuration, (ushort)value.TotalSeconds); + } + /// /// Represents the current item selected within the room. /// @@ -197,6 +206,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("auto_skip")] private bool autoSkip; + [JsonProperty("auto_start_duration")] + private ushort autoStartDuration; + [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; @@ -225,16 +237,6 @@ namespace osu.Game.Online.Rooms [Cached] public readonly Bindable MaxAttempts = new Bindable(); - [Cached] - public readonly Bindable AutoStartDuration = new Bindable(); - - [JsonProperty("auto_start_duration")] - private ushort autoStartDuration - { - get => (ushort)AutoStartDuration.Value.TotalSeconds; - set => AutoStartDuration.Value = TimeSpan.FromSeconds(value); - } - [Cached] [JsonProperty("current_user_score")] public readonly Bindable UserScore = new Bindable(); @@ -308,7 +310,7 @@ namespace osu.Game.Online.Rooms EndDate.Value = other.EndDate.Value; UserScore.Value = other.UserScore.Value; QueueMode = other.QueueMode; - AutoStartDuration.Value = other.AutoStartDuration.Value; + AutoStartDuration = other.AutoStartDuration; DifficultyRange.Value = other.DifficultyRange.Value; PlaylistItemStats.Value = other.PlaylistItemStats.Value; CurrentPlaylistItem = other.CurrentPlaylistItem; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 141bddc4d4..f883fde600 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -350,7 +350,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }; TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue.GetLocalisableDescription(), true); - AutoStartDuration.BindValueChanged(duration => startModeDropdown.Current.Value = (StartMode)(int)duration.NewValue.TotalSeconds, true); operationInProgress.BindTo(ongoingOperationTracker.InProgress); operationInProgress.BindValueChanged(v => @@ -377,6 +376,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match updateRoomPassword(); updateRoomAutoSkip(); updateRoomMaxParticipants(); + updateRoomAutoStartDuration(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -406,6 +406,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match case nameof(Room.MaxParticipants): updateRoomMaxParticipants(); break; + + case nameof(Room.AutoStartDuration): + updateRoomAutoStartDuration(); + break; } } @@ -427,6 +431,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateRoomMaxParticipants() => MaxParticipantsField.Text = room.MaxParticipants?.ToString(); + private void updateRoomAutoStartDuration() + => typeLabel.Text = room.AutoStartDuration.GetLocalisableDescription(); + protected override void Update() { base.Update(); @@ -445,8 +452,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Debug.Assert(applyingSettingsOperation == null); applyingSettingsOperation = ongoingOperationTracker.BeginOperation(); - TimeSpan autoStartDuration = TimeSpan.FromSeconds((int)startModeDropdown.Current.Value); - // If the client is already in a room, update via the client. // Otherwise, update the room directly in preparation for it to be submitted to the API on match creation. if (client.Room != null) @@ -456,7 +461,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match password: PasswordTextBox.Text, matchType: TypePicker.Current.Value, queueMode: QueueModeDropdown.Current.Value, - autoStartDuration: autoStartDuration, + autoStartDuration: TimeSpan.FromSeconds((int)startModeDropdown.Current.Value), autoSkip: AutoSkipCheckbox.Current.Value) .ContinueWith(t => Schedule(() => { @@ -472,7 +477,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match room.Type = TypePicker.Current.Value; room.Password = PasswordTextBox.Current.Value; room.QueueMode = QueueModeDropdown.Current.Value; - room.AutoStartDuration.Value = autoStartDuration; + room.AutoStartDuration = TimeSpan.FromSeconds((int)startModeDropdown.Current.Value); room.AutoSkip = AutoSkipCheckbox.Current.Value; if (int.TryParse(MaxParticipantsField.Text, out int max)) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 7623cbabbd..70418d1a7e 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -41,8 +41,5 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable Duration { get; private set; } = null!; - - [Resolved(typeof(Room))] - protected Bindable AutoStartDuration { get; private set; } = null!; } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index dcb6f2503d..29e62cb590 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -234,7 +234,7 @@ namespace osu.Game.Tests.Visual.Multiplayer MatchType = ServerAPIRoom.Type, Password = password ?? string.Empty, QueueMode = ServerAPIRoom.QueueMode, - AutoStartDuration = ServerAPIRoom.AutoStartDuration.Value + AutoStartDuration = ServerAPIRoom.AutoStartDuration }, Playlist = ServerAPIRoom.Playlist.Select(CreateMultiplayerPlaylistItem).ToList(), Users = { localUser }, From 0ceaafe7318ac0b73c12dc746623b63e02de8e7e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 20:20:14 +0900 Subject: [PATCH 036/139] Make `Room.Duration` & `Room.StartDate` & `Room.EndDate` non-bindable --- .../DailyChallenge/TestSceneDailyChallenge.cs | 6 +- .../TestSceneDailyChallengeCarousel.cs | 6 +- .../TestSceneDailyChallengeIntro.cs | 4 +- ...estSceneDailyChallengeTimeRemainingRing.cs | 22 +++--- .../Visual/Menus/TestSceneMainMenu.cs | 4 +- .../Multiplayer/TestSceneDrawableRoom.cs | 8 +- .../TestScenePlaylistsMatchSettingsOverlay.cs | 2 +- .../TestScenePlaylistsRoomCreation.cs | 4 +- .../UserInterface/TestSceneMainMenuButton.cs | 8 +- .../Online/Multiplayer/MultiplayerClient.cs | 2 +- osu.Game/Online/Rooms/GetRoomsRequest.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 74 +++++++++++-------- osu.Game/Screens/Menu/DailyChallengeButton.cs | 6 +- .../DailyChallenge/DailyChallenge.cs | 4 +- .../DailyChallengeTimeRemainingRing.cs | 40 ++++++++-- .../Lounge/Components/DrawableRoom.cs | 2 +- .../Lounge/Components/EndDateInfo.cs | 51 +++++++++---- .../Lounge/Components/RoomStatusPill.cs | 10 ++- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 4 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 10 --- .../Playlists/PlaylistsReadyButton.cs | 18 ++--- .../Playlists/PlaylistsRoomFooter.cs | 9 +-- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 11 ++- .../Playlists/PlaylistsRoomSubScreen.cs | 2 +- .../Visual/OnlinePlay/TestRoomManager.cs | 2 +- 25 files changed, 186 insertions(+), 125 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index e05f5f13ae..1a19c423fc 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge AllowedMods = [new APIMod(new OsuModDoubleTime())] } }, - EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, + EndDate = DateTimeOffset.Now.AddHours(12), Category = RoomCategory.DailyChallenge }; @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge AllowedMods = [new APIMod(new OsuModDoubleTime())] } }, - EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, + EndDate = DateTimeOffset.Now.AddHours(12), Category = RoomCategory.DailyChallenge }; @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge AllowedMods = [new APIMod(new OsuModDoubleTime())] } }, - EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, + EndDate = DateTimeOffset.Now.AddHours(12), Category = RoomCategory.DailyChallenge }; diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs index d53e386ad4..5854c2e793 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge Origin = Anchor.Centre, Children = new Drawable[] { - new DailyChallengeTimeRemainingRing(), + new DailyChallengeTimeRemainingRing(room.Value), breakdown = new DailyChallengeScoreBreakdown(), } } @@ -125,8 +125,8 @@ namespace osu.Game.Tests.Visual.DailyChallenge AddSliderStep("update time remaining", 0f, 1f, 0f, progress => { var startedTimeAgo = TimeSpan.FromHours(24) * progress; - room.Value.StartDate.Value = DateTimeOffset.Now - startedTimeAgo; - room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1); + room.Value.StartDate = DateTimeOffset.Now - startedTimeAgo; + room.Value.EndDate = room.Value.StartDate.Value.AddDays(1); }); AddStep("add normal score", () => { diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs index 33be952e20..aa311766d5 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs @@ -78,8 +78,8 @@ namespace osu.Game.Tests.Visual.DailyChallenge AllowedMods = [new APIMod(new OsuModDoubleTime())] } }, - StartDate = { Value = DateTimeOffset.Now }, - EndDate = { Value = DateTimeOffset.Now.AddHours(24) }, + StartDate = DateTimeOffset.Now, + EndDate = DateTimeOffset.Now.AddHours(24), Category = RoomCategory.DailyChallenge })); }); diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs index baa1eb8318..2f522d4d6f 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background4, }, - ring = new DailyChallengeTimeRemainingRing + ring = new DailyChallengeTimeRemainingRing(room.Value) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -59,29 +59,29 @@ namespace osu.Game.Tests.Visual.DailyChallenge AddStep("just started", () => { - room.Value.StartDate.Value = DateTimeOffset.Now.AddMinutes(-1); - room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1); + room.Value.StartDate = DateTimeOffset.Now.AddMinutes(-1); + room.Value.EndDate = room.Value.StartDate.Value.AddDays(1); }); AddStep("midway through", () => { - room.Value.StartDate.Value = DateTimeOffset.Now.AddHours(-12); - room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1); + room.Value.StartDate = DateTimeOffset.Now.AddHours(-12); + room.Value.EndDate = room.Value.StartDate.Value.AddDays(1); }); AddStep("nearing end", () => { - room.Value.StartDate.Value = DateTimeOffset.Now.AddDays(-1).AddMinutes(8); - room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1); + room.Value.StartDate = DateTimeOffset.Now.AddDays(-1).AddMinutes(8); + room.Value.EndDate = room.Value.StartDate.Value.AddDays(1); }); AddStep("already ended", () => { - room.Value.StartDate.Value = DateTimeOffset.Now.AddDays(-2); - room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1); + room.Value.StartDate = DateTimeOffset.Now.AddDays(-2); + room.Value.EndDate = room.Value.StartDate.Value.AddDays(1); }); AddSliderStep("manual progress", 0f, 1f, 0f, progress => { var startedTimeAgo = TimeSpan.FromHours(24) * progress; - room.Value.StartDate.Value = DateTimeOffset.Now - startedTimeAgo; - room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1); + room.Value.StartDate = DateTimeOffset.Now - startedTimeAgo; + room.Value.EndDate = room.Value.StartDate.Value.AddDays(1); }); } } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index 9b00449e11..bc6c3ed709 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -48,8 +48,8 @@ namespace osu.Game.Tests.Visual.Menus { new PlaylistItem(beatmap) }, - StartDate = { Value = DateTimeOffset.Now.AddMinutes(-30) }, - EndDate = { Value = DateTimeOffset.Now.AddSeconds(60) } + StartDate = DateTimeOffset.Now.AddMinutes(-30), + EndDate = DateTimeOffset.Now.AddSeconds(60) }); return true; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index b5cfecfa25..31923b676a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Multiplayer room", Status = new RoomStatusOpen(), - EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, + EndDate = DateTimeOffset.Now.AddDays(1), Type = MatchType.HeadToHead, Playlist = { item1 }, CurrentPlaylistItem = item1 @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Private room", Status = new RoomStatusOpenPrivate(), Password = "*", - EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, + EndDate = DateTimeOffset.Now.AddDays(1), Type = MatchType.HeadToHead, Playlist = { item3 }, CurrentPlaylistItem = item3 @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Playlist room with multiple beatmaps", Status = new RoomStatusPlaying(), - EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, + EndDate = DateTimeOffset.Now.AddDays(1), Playlist = { item1, item2 }, CurrentPlaylistItem = item1 }), @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Finished room", Status = new RoomStatusEnded(), - EndDate = { Value = DateTimeOffset.Now }, + EndDate = DateTimeOffset.Now, }), createLoungeRoom(new Room { diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index 3652710e32..b45d1e68cb 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("create room", () => settings.ApplyButton.Action.Invoke()); AddAssert("has correct name", () => createdRoom.Name == expected_name); - AddAssert("has correct duration", () => createdRoom.Duration.Value == expectedDuration); + AddAssert("has correct duration", () => createdRoom.Duration == expectedDuration); } [Test] diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 7cc86e71bf..d0cbee9e50 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Playlists room.Name = "my awesome room"; room.Host = API.LocalUser.Value; room.RecentParticipants.Add(room.Host); - room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); + room.EndDate = DateTimeOffset.Now.AddMinutes(5); room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Playlists room.MaxAttempts.Value = 5; room.Host = API.LocalUser.Value; room.RecentParticipants.Add(room.Host); - room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5); + room.EndDate = DateTimeOffset.Now.AddMinutes(5); room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs index 3894b6ea5b..8b17749afc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs @@ -58,8 +58,8 @@ namespace osu.Game.Tests.Visual.UserInterface { new PlaylistItem(beatmap) }, - StartDate = { Value = DateTimeOffset.Now.AddMinutes(-5) }, - EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) } + StartDate = DateTimeOffset.Now.AddMinutes(-5), + EndDate = DateTimeOffset.Now.AddSeconds(30) }); return true; @@ -136,8 +136,8 @@ namespace osu.Game.Tests.Visual.UserInterface { new PlaylistItem(beatmap) }, - StartDate = { Value = DateTimeOffset.Now.AddMinutes(-50) }, - EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) } + StartDate = DateTimeOffset.Now.AddMinutes(-50), + EndDate = DateTimeOffset.Now.AddSeconds(30) }); return true; diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 6a7d460922..fe0b4c6a21 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -206,7 +206,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId); // The server will null out the end date upon the host joining the room, but the null value is never communicated to the client. - APIRoom.EndDate.Value = null; + APIRoom.EndDate = null; Debug.Assert(LocalUser != null); addUserToAPIRoom(LocalUser); diff --git a/osu.Game/Online/Rooms/GetRoomsRequest.cs b/osu.Game/Online/Rooms/GetRoomsRequest.cs index 0c8df642fa..cd797a9668 100644 --- a/osu.Game/Online/Rooms/GetRoomsRequest.cs +++ b/osu.Game/Online/Rooms/GetRoomsRequest.cs @@ -44,7 +44,7 @@ namespace osu.Game.Online.Rooms // API doesn't populate status so let's do it here. foreach (var room in Response) { - if (room.EndDate.Value != null && DateTimeOffset.Now >= room.EndDate.Value) + if (room.EndDate != null && DateTimeOffset.Now >= room.EndDate) room.Status = new RoomStatusEnded(); else if (room.HasPassword) room.Status = new RoomStatusOpenPrivate(); diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 61f944a728..fd958d8725 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -86,6 +86,39 @@ namespace osu.Game.Online.Rooms set => SetField(ref category, value); } + /// + /// The duration for which the room will be open. Will be null after the room is created. + /// + /// + /// To check the room end time, use . + /// + public TimeSpan? Duration + { + get => duration == null ? null : TimeSpan.FromMinutes(duration.Value); + set => SetField(ref duration, value == null ? null : (int)value.Value.TotalMinutes); + } + + /// + /// The date at which the room was opened. Will be null while the room has not yet been created. + /// + public DateTimeOffset? StartDate + { + get => startDate; + set => SetField(ref startDate, value); + } + + /// + /// The date at which the room will be closed. + /// + /// + /// To set the room duration, use . + /// + public DateTimeOffset? EndDate + { + get => endDate; + set => SetField(ref endDate, value); + } + /// /// The maximum number of users allowed in the room. /// @@ -189,6 +222,15 @@ namespace osu.Game.Online.Rooms [JsonConverter(typeof(SnakeCaseStringEnumConverter))] private RoomCategory category; + [JsonProperty("duration")] + private int? duration; + + [JsonProperty("starts_at")] + private DateTimeOffset? startDate; + + [JsonProperty("ends_at")] + private DateTimeOffset? endDate; + // Not yet serialised (not implemented). private int? maxParticipants; @@ -245,36 +287,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("recent_participants")] public readonly BindableList RecentParticipants = new BindableList(); - #region Properties only used for room creation request - - [Cached] - public readonly Bindable Duration = new Bindable(); - - [JsonProperty("duration")] - private int? duration - { - get => (int?)Duration.Value?.TotalMinutes; - set - { - if (value == null) - Duration.Value = null; - else - Duration.Value = TimeSpan.FromMinutes(value.Value); - } - } - - #endregion - - // Only supports retrieval for now - [Cached] - [JsonProperty("starts_at")] - public readonly Bindable StartDate = new Bindable(); - - // Only supports retrieval for now - [Cached] - [JsonProperty("ends_at")] - public readonly Bindable EndDate = new Bindable(); - // Todo: Find a better way to do this (https://github.com/ppy/osu-framework/issues/1930) [JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)] private int? maxAttempts @@ -307,7 +319,7 @@ namespace osu.Game.Online.Rooms Type = other.Type; MaxParticipants = other.MaxParticipants; ParticipantCount = other.ParticipantCount; - EndDate.Value = other.EndDate.Value; + EndDate = other.EndDate; UserScore.Value = other.UserScore.Value; QueueMode = other.QueueMode; AutoStartDuration = other.AutoStartDuration; diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index 0013c9e033..92db4d2d07 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -155,7 +155,7 @@ namespace osu.Game.Screens.Menu Room = room; cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet; - if (room.StartDate.Value != null && room.RoomID != lastDailyChallengeRoomID) + if (room.StartDate != null && room.RoomID != lastDailyChallengeRoomID) { lastDailyChallengeRoomID = room.RoomID; @@ -163,7 +163,7 @@ namespace osu.Game.Screens.Menu statics.SetValue(Static.DailyChallengeIntroPlayed, false); // we only want to notify the user if the new challenge just went live. - if (Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 1800) + if (Math.Abs((DateTimeOffset.Now - room.StartDate.Value).TotalSeconds) < 1800) notificationOverlay?.Post(new NewDailyChallengeNotification(room)); } @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Menu if (Room == null) return; - var remaining = (Room.EndDate.Value - DateTimeOffset.Now) ?? TimeSpan.Zero; + var remaining = (Room.EndDate - DateTimeOffset.Now) ?? TimeSpan.Zero; if (remaining <= TimeSpan.Zero) { diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 968345c713..9559826dab 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -228,7 +228,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge Origin = Anchor.Centre, Children = new Drawable[] { - new DailyChallengeTimeRemainingRing(), + new DailyChallengeTimeRemainingRing(room), breakdown = new DailyChallengeScoreBreakdown(), totals = new DailyChallengeTotalsDisplay(), } @@ -301,7 +301,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge Spacing = new Vector2(10), Children = new Drawable[] { - new PlaylistsReadyButton + new PlaylistsReadyButton(room) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs index e86f26ad6b..bffbb664a3 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs @@ -2,6 +2,7 @@ // 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; @@ -10,6 +11,7 @@ using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Rooms; using osu.Game.Overlays; using osuTK; @@ -17,8 +19,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { public partial class DailyChallengeTimeRemainingRing : OnlinePlayComposite { - private CircularProgress progress = null!; - private OsuSpriteText timeText = null!; + private readonly Room room; [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -26,6 +27,14 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge [Resolved] private OsuColour colours { get; set; } = null!; + private CircularProgress progress = null!; + private OsuSpriteText timeText = null!; + + public DailyChallengeTimeRemainingRing(Room room) + { + this.room = room; + } + [BackgroundDependencyLoader] private void load() { @@ -90,12 +99,23 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { base.LoadComplete(); - StartDate.BindValueChanged(_ => Scheduler.AddOnce(updateState)); - EndDate.BindValueChanged(_ => Scheduler.AddOnce(updateState)); + room.PropertyChanged += onRoomPropertyChanged; updateState(); + FinishTransforms(true); } + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + switch (e.PropertyName) + { + case nameof(Room.StartDate): + case nameof(Room.EndDate): + Scheduler.AddOnce(updateState); + break; + } + } + private ScheduledDelegate? scheduledUpdate; private void updateState() @@ -105,7 +125,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge const float transition_duration = 300; - if (StartDate.Value == null || EndDate.Value == null || EndDate.Value < DateTimeOffset.Now) + if (room.StartDate == null || room.EndDate == null || room.EndDate < DateTimeOffset.Now) { timeText.Text = TimeSpan.Zero.ToString(@"hh\:mm\:ss"); progress.Progress = 0; @@ -114,8 +134,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge return; } - var roomDuration = EndDate.Value.Value - StartDate.Value.Value; - var remaining = EndDate.Value.Value - DateTimeOffset.Now; + var roomDuration = room.EndDate.Value - room.StartDate.Value; + var remaining = room.EndDate.Value - DateTimeOffset.Now; timeText.Text = remaining.ToString(@"hh\:mm\:ss"); progress.Progress = remaining.TotalSeconds / roomDuration.TotalSeconds; @@ -138,5 +158,11 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge scheduledUpdate = Scheduler.AddDelayed(updateState, 1000); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 390ea06c1b..7a22bf1a0b 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -169,7 +169,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft }, - endDateInfo = new EndDateInfo + endDateInfo = new EndDateInfo(Room) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs index 844991095e..18c7972c06 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs @@ -2,49 +2,68 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.ComponentModel; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public partial class EndDateInfo : OnlinePlayComposite { - public EndDateInfo() + private readonly Room room; + + public EndDateInfo(Room room) { + this.room = room; AutoSizeAxes = Axes.Both; } [BackgroundDependencyLoader] private void load() { - InternalChild = new EndDatePart + InternalChild = new EndDatePart(room) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12), - EndDate = { BindTarget = EndDate } + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12) }; } private partial class EndDatePart : DrawableDate { - public readonly IBindable EndDate = new Bindable(); + private readonly Room room; - public EndDatePart() + public EndDatePart(Room room) : base(DateTimeOffset.UtcNow) { - EndDate.BindValueChanged(date => - { - // If null, set a very large future date to prevent unnecessary schedules. - Date = date.NewValue ?? DateTimeOffset.Now.AddYears(1); - }, true); + this.room = room; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + room.PropertyChanged += onRoomPropertyChanged; + updateEndDate(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.EndDate)) + updateEndDate(); + } + + private void updateEndDate() + { + // If null, set a very large future date to prevent unnecessary schedules. + Date = room.EndDate ?? DateTimeOffset.Now.AddYears(1); } protected override string Format() { - if (EndDate.Value == null) + if (room.EndDate == null) return string.Empty; var diffToNow = Date.Subtract(DateTimeOffset.Now); @@ -60,6 +79,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components return $"Closing {base.Format()}"; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs index 10d3695d14..b3dc617fd6 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs @@ -34,7 +34,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components TextFlow.Colour = Colour4.Black; Pill.Background.Alpha = 1; - EndDate.BindValueChanged(_ => updateDisplay()); room.PropertyChanged += onRoomPropertyChanged; updateDisplay(); @@ -43,8 +42,13 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.Status)) - updateDisplay(); + switch (e.PropertyName) + { + case nameof(Room.Status): + case nameof(Room.EndDate): + updateDisplay(); + break; + } } private void updateDisplay() diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index a2eba3188d..90288a1067 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -341,8 +341,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge r.RoomID = null; // Null out dates because end date is not supported client-side and the settings overlay will populate a duration. - r.EndDate.Value = null; - r.Duration.Value = null; + r.EndDate = null; + r.Duration = null; Open(r); diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 70418d1a7e..66196d1df6 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; @@ -32,14 +31,5 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] public Bindable UserScore { get; private set; } = null!; - - [Resolved(typeof(Room))] - protected Bindable StartDate { get; private set; } = null!; - - [Resolved(typeof(Room))] - protected Bindable EndDate { get; private set; } = null!; - - [Resolved(typeof(Room))] - protected Bindable Duration { get; private set; } = null!; } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs index 4b00678b01..5f1037bff1 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.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 System.Linq; using osu.Framework.Allocation; @@ -17,20 +15,20 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { public partial class PlaylistsReadyButton : ReadyButton { - [Resolved(typeof(Room), nameof(Room.EndDate))] - private Bindable endDate { get; set; } - [Resolved(typeof(Room), nameof(Room.MaxAttempts))] - private Bindable maxAttempts { get; set; } + private Bindable maxAttempts { get; set; } = null!; [Resolved(typeof(Room), nameof(Room.UserScore))] - private Bindable userScore { get; set; } + private Bindable userScore { get; set; } = null!; [Resolved] - private IBindable gameBeatmap { get; set; } + private IBindable gameBeatmap { get; set; } = null!; - public PlaylistsReadyButton() + private readonly Room room; + + public PlaylistsReadyButton(Room room) { + this.room = room; Text = "Start"; } @@ -80,6 +78,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private bool enoughTimeLeft => // This should probably consider the length of the currently selected item, rather than a constant 30 seconds. - endDate.Value != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < endDate.Value; + room.EndDate != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < room.EndDate; } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs index 5161de5f64..0d837423a6 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs @@ -1,26 +1,25 @@ // 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 osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Online.Rooms; using osuTK; namespace osu.Game.Screens.OnlinePlay.Playlists { public partial class PlaylistsRoomFooter : CompositeDrawable { - public Action OnStart; + public Action? OnStart; - public PlaylistsRoomFooter() + public PlaylistsRoomFooter(Room room) { RelativeSizeAxes = Axes.Both; InternalChildren = new[] { - new PlaylistsReadyButton + new PlaylistsReadyButton(room) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 20fbe42dd5..63bfa8f45d 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -317,7 +317,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists }; MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true); - Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue ?? TimeSpan.FromMinutes(30), true); DurationField.Current.BindValueChanged(duration => { @@ -346,6 +345,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists updateRoomName(); updateRoomAvailability(); updateRoomMaxParticipants(); + updateRoomDuration(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -363,6 +363,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists case nameof(Room.MaxParticipants): updateRoomMaxParticipants(); break; + + case nameof(Room.Duration): + updateRoomDuration(); + break; } } @@ -375,6 +379,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void updateRoomMaxParticipants() => MaxParticipantsField.Text = room.MaxParticipants?.ToString(); + private void updateRoomDuration() + => DurationField.Current.Value = room.Duration ?? TimeSpan.FromMinutes(30); + private void populateDurations(ValueChangedEvent user) { // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427) @@ -431,7 +438,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists else MaxAttempts.Value = null; - Duration.Value = DurationField.Current.Value; + room.Duration = DurationField.Current.Value; loadingLayer.Show(); manager?.CreateRoom(room, onSuccess, onError); diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 9780c48f1f..6d9bf29687 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -234,7 +234,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists } }; - protected override Drawable CreateFooter() => new PlaylistsRoomFooter + protected override Drawable CreateFooter() => new PlaylistsRoomFooter(Room) { OnStart = StartPlay }; diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index 3db9e12ab7..de54ff57e8 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay RoomID = -currentRoomId, Name = $@"Room {currentRoomId}", Host = new APIUser { Username = @"Host" }, - EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, + Duration = TimeSpan.FromSeconds(10), Category = withSpotlightRooms && i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal, }; From 6c84e425f8c71ac1a850c8d2e43e6be6cc1c2d81 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 20:32:34 +0900 Subject: [PATCH 037/139] Make `Room.MaxAttempts` non-bindable --- .../TestScenePlaylistsRoomCreation.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 23 ++++++------ .../Components/RoomLocalUserInfo.cs | 36 +++++++++++++------ .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 -- .../Playlists/PlaylistsReadyButton.cs | 7 ++-- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 19 +++++----- .../Playlists/PlaylistsRoomSubScreen.cs | 19 +++++++--- 7 files changed, 66 insertions(+), 43 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index d0cbee9e50..031f9c272f 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Playlists setupAndCreateRoom(room => { room.Name = "my awesome room"; - room.MaxAttempts.Value = 5; + room.MaxAttempts = 5; room.Host = API.LocalUser.Value; room.RecentParticipants.Add(room.Host); room.EndDate = DateTimeOffset.Now.AddMinutes(5); diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index fd958d8725..cfb3b30ddb 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -146,6 +146,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref type, value); } + /// + /// The maximum number of attempts on the playlist. Only valid for playlist rooms. + /// + public int? MaxAttempts + { + get => maxAttempts; + set => SetField(ref maxAttempts, value); + } + /// /// The playlist queueing mode. Only valid for multiplayer rooms. /// @@ -237,6 +246,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("participant_count")] private int participantCount; + [JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)] + private int? maxAttempts; + [JsonConverter(typeof(SnakeCaseStringEnumConverter))] [JsonProperty("type")] private MatchType type; @@ -276,9 +288,6 @@ namespace osu.Game.Online.Rooms [Cached] public readonly Bindable DifficultyRange = new Bindable(); - [Cached] - public readonly Bindable MaxAttempts = new Bindable(); - [Cached] [JsonProperty("current_user_score")] public readonly Bindable UserScore = new Bindable(); @@ -287,14 +296,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("recent_participants")] public readonly BindableList RecentParticipants = new BindableList(); - // Todo: Find a better way to do this (https://github.com/ppy/osu-framework/issues/1930) - [JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)] - private int? maxAttempts - { - get => MaxAttempts.Value; - set => MaxAttempts.Value = value; - } - /// /// Copies values from another into this one. /// diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs index 0c3b53266c..974c296399 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs @@ -1,26 +1,28 @@ // 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.ComponentModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { public partial class RoomLocalUserInfo : OnlinePlayComposite { - private OsuSpriteText attemptDisplay; + private readonly Room room; + private OsuSpriteText attemptDisplay = null!; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; - public RoomLocalUserInfo() + public RoomLocalUserInfo(Room room) { + this.room = room; AutoSizeAxes = Axes.Both; } @@ -45,19 +47,27 @@ namespace osu.Game.Screens.OnlinePlay.Components { base.LoadComplete(); - MaxAttempts.BindValueChanged(_ => updateAttempts()); - UserScore.BindValueChanged(_ => updateAttempts(), true); + UserScore.BindValueChanged(_ => updateAttempts()); + + room.PropertyChanged += onRoomPropertyChanged; + updateAttempts(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.MaxAttempts)) + updateAttempts(); } private void updateAttempts() { - if (MaxAttempts.Value != null) + if (room.MaxAttempts != null) { - attemptDisplay.Text = $"Maximum attempts: {MaxAttempts.Value:N0}"; + attemptDisplay.Text = $"Maximum attempts: {room.MaxAttempts:N0}"; if (UserScore.Value != null) { - int remaining = MaxAttempts.Value.Value - UserScore.Value.PlaylistItemAttempts.Sum(a => a.Attempts); + int remaining = room.MaxAttempts.Value - UserScore.Value.PlaylistItemAttempts.Sum(a => a.Attempts); attemptDisplay.Text += $" ({remaining} remaining)"; if (remaining == 0) @@ -69,5 +79,11 @@ namespace osu.Game.Screens.OnlinePlay.Components attemptDisplay.Text = string.Empty; } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 66196d1df6..1880fdc974 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -26,9 +26,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected BindableList RecentParticipants { get; private set; } = null!; - [Resolved(typeof(Room))] - protected Bindable MaxAttempts { get; private set; } = null!; - [Resolved(typeof(Room))] public Bindable UserScore { get; private set; } = null!; } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs index 5f1037bff1..ca661b041a 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs @@ -15,9 +15,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { public partial class PlaylistsReadyButton : ReadyButton { - [Resolved(typeof(Room), nameof(Room.MaxAttempts))] - private Bindable maxAttempts { get; set; } = null!; - [Resolved(typeof(Room), nameof(Room.UserScore))] private Bindable userScore { get; set; } = null!; @@ -46,10 +43,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists userScore.BindValueChanged(aggregate => { - if (maxAttempts.Value == null) + if (room.MaxAttempts == null) return; - int remaining = maxAttempts.Value.Value - aggregate.NewValue.PlaylistItemAttempts.Sum(a => a.Attempts); + int remaining = room.MaxAttempts.Value - aggregate.NewValue.PlaylistItemAttempts.Sum(a => a.Attempts); hasRemainingAttempts = remaining > 0; }); diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 63bfa8f45d..6550b4c2f1 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -316,8 +316,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists loadingLayer = new LoadingLayer(true) }; - MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true); - DurationField.Current.BindValueChanged(duration => { if (hasValidDuration) @@ -346,6 +344,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists updateRoomAvailability(); updateRoomMaxParticipants(); updateRoomDuration(); + updateRoomMaxAttempts(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -367,6 +366,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists case nameof(Room.Duration): updateRoomDuration(); break; + + case nameof(Room.MaxAttempts): + updateRoomMaxAttempts(); + break; } } @@ -382,6 +385,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void updateRoomDuration() => DurationField.Current.Value = room.Duration ?? TimeSpan.FromMinutes(30); + private void updateRoomMaxAttempts() + => MaxAttemptsField.Text = room.MaxAttempts?.ToString(); + private void populateDurations(ValueChangedEvent user) { // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427) @@ -431,13 +437,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists room.Name = NameField.Text; room.Availability = AvailabilityPicker.Current.Value; - room.MaxParticipants = int.TryParse(MaxParticipantsField.Text, out int max) ? max : null; - - if (int.TryParse(MaxAttemptsField.Text, out max)) - MaxAttempts.Value = max; - else - MaxAttempts.Value = null; - + room.MaxParticipants = int.TryParse(MaxParticipantsField.Text, out int maxParticipants) ? maxParticipants : null; + room.MaxAttempts = int.TryParse(MaxAttemptsField.Text, out int maxAttempts) ? maxAttempts : null; room.Duration = DurationField.Current.Value; loadingLayer.Show(); diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 6d9bf29687..079e3b03b7 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -60,16 +60,24 @@ namespace osu.Game.Screens.OnlinePlay.Playlists base.LoadComplete(); isIdle.BindValueChanged(_ => updatePollingRate(), true); - Room.MaxAttempts.BindValueChanged(_ => progressSection.Alpha = Room.MaxAttempts.Value != null ? 1 : 0, true); Room.PropertyChanged += onRoomPropertyChanged; updateSetupState(); + updateRoomMaxAttempts(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.RoomID)) - updateSetupState(); + switch (e.PropertyName) + { + case nameof(Room.RoomID): + updateSetupState(); + break; + + case nameof(Room.MaxAttempts): + updateRoomMaxAttempts(); + break; + } } private void updateSetupState() @@ -82,6 +90,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists } } + private void updateRoomMaxAttempts() + => progressSection.Alpha = Room.MaxAttempts != null ? 1 : 0; + protected override Drawable CreateMainContent() => new Container { RelativeSizeAxes = Axes.Both, @@ -193,7 +204,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Children = new Drawable[] { new OverlinedHeader("Progress"), - new RoomLocalUserInfo(), + new RoomLocalUserInfo(Room), } }, }, From 80b3e330a6daf7cfd95a5b1e69148f033826b8b3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 22:23:33 +0900 Subject: [PATCH 038/139] Make `Room.ChannelId` non-bindable --- osu.Game/Online/Rooms/Room.cs | 18 +++++++++++----- .../Match/Components/MatchChatDisplay.cs | 21 ++++++++++++------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index cfb3b30ddb..ea3e8d73fe 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -194,6 +194,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref currentPlaylistItem, value); } + /// + /// The chat channel id for the room. Will be 0 while the room has not yet been created. + /// + public int ChannelId + { + get => channelId; + private set => SetField(ref channelId, value); + } + /// /// The current room status. /// @@ -266,6 +275,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; + [JsonProperty("channel_id")] + private int channelId; + // Not serialised (see: GetRoomsRequest). private RoomStatus status = new RoomStatusOpen(); @@ -276,10 +288,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("playlist")] public readonly BindableList Playlist = new BindableList(); - [Cached] - [JsonProperty("channel_id")] - public readonly Bindable ChannelId = new Bindable(); - [JsonProperty("playlist_item_stats")] [Cached] public readonly Bindable PlaylistItemStats = new Bindable(); @@ -313,7 +321,7 @@ namespace osu.Game.Online.Rooms if (other.Host != null && Host?.Id != other.Host.Id) Host = other.Host; - ChannelId.Value = other.ChannelId.Value; + ChannelId = other.ChannelId; Status = other.Status; Availability = other.Availability; HasPassword = other.HasPassword; diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs index ed4f0ad6d8..a81425102d 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.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.ComponentModel; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Game.Online.Chat; using osu.Game.Online.Rooms; @@ -10,8 +10,6 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components { public partial class MatchChatDisplay : StandAloneChatDisplay { - private readonly IBindable channelId = new Bindable(); - [Resolved] private ChannelManager? channelManager { get; set; } @@ -29,23 +27,30 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components { base.LoadComplete(); - // Required for the time being since this component is created prior to the room being joined. - channelId.BindTo(room.ChannelId); - channelId.BindValueChanged(_ => updateChannel(), true); + room.PropertyChanged += onRoomPropertyChanged; + updateChannel(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.ChannelId)) + updateChannel(); } private void updateChannel() { - if (room.RoomID == null || channelId.Value == 0) + if (room.RoomID == null || room.ChannelId == 0) return; - Channel.Value = channelManager?.JoinChannel(new Channel { Id = channelId.Value, Type = ChannelType.Multiplayer, Name = $"#lazermp_{room.RoomID.Value}" }); + Channel.Value = channelManager?.JoinChannel(new Channel { Id = room.ChannelId, Type = ChannelType.Multiplayer, Name = $"#lazermp_{room.RoomID.Value}" }); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + if (leaveChannelOnDispose) channelManager?.LeaveChannel(Channel.Value); } From 487a010b12eec172c1e5aa8dd2ad9f64a973508e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 22:29:45 +0900 Subject: [PATCH 039/139] Make `Room.PlaylistItemStats` non-bindable --- osu.Game/Online/Rooms/Room.cs | 18 ++++++++--- .../Lounge/Components/DrawableRoom.cs | 2 +- .../Lounge/Components/PlaylistCountPill.cs | 31 ++++++++++++++++--- .../Lounge/Components/RoomsContainer.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 -- .../Visual/OnlinePlay/TestRoomManager.cs | 2 +- 6 files changed, 43 insertions(+), 15 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index ea3e8d73fe..dd3a9b2e73 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -155,6 +155,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref maxAttempts, value); } + /// + /// Describes the items in the playlist. + /// + public RoomPlaylistItemStats? PlaylistItemStats + { + get => playlistItemStats; + set => SetField(ref playlistItemStats, value); + } + /// /// The playlist queueing mode. Only valid for multiplayer rooms. /// @@ -258,6 +267,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)] private int? maxAttempts; + [JsonProperty("playlist_item_stats")] + private RoomPlaylistItemStats? playlistItemStats; + [JsonConverter(typeof(SnakeCaseStringEnumConverter))] [JsonProperty("type")] private MatchType type; @@ -288,10 +300,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("playlist")] public readonly BindableList Playlist = new BindableList(); - [JsonProperty("playlist_item_stats")] - [Cached] - public readonly Bindable PlaylistItemStats = new Bindable(); - [JsonProperty("difficulty_range")] [Cached] public readonly Bindable DifficultyRange = new Bindable(); @@ -333,7 +341,7 @@ namespace osu.Game.Online.Rooms QueueMode = other.QueueMode; AutoStartDuration = other.AutoStartDuration; DifficultyRange.Value = other.DifficultyRange.Value; - PlaylistItemStats.Value = other.PlaylistItemStats.Value; + PlaylistItemStats = other.PlaylistItemStats; CurrentPlaylistItem = other.CurrentPlaylistItem; AutoSkip = other.AutoSkip; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 7a22bf1a0b..ddfbe00227 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -355,7 +355,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components pills.AddRange(new Drawable[] { - new PlaylistCountPill + new PlaylistCountPill(Room) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs index fe5ccb4f09..7e530993dd 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; using System.Linq; using Humanizer; using osu.Framework.Extensions.LocalisationExtensions; using osu.Game.Graphics; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { @@ -13,26 +15,47 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components /// public partial class PlaylistCountPill : OnlinePlayPill { + private readonly Room room; + + public PlaylistCountPill(Room room) + { + this.room = room; + } + protected override void LoadComplete() { base.LoadComplete(); - PlaylistItemStats.BindValueChanged(_ => updateCount()); - Playlist.BindCollectionChanged((_, _) => updateCount(), true); + Playlist.BindCollectionChanged((_, _) => updateCount()); + + room.PropertyChanged += onRoomPropertyChanged; + updateCount(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.PlaylistItemStats)) + updateCount(); } private void updateCount() { - int activeItems = Playlist.Count > 0 || PlaylistItemStats.Value == null + int activeItems = Playlist.Count > 0 || room.PlaylistItemStats == null // For now, use the playlist as the source of truth if it has any items. // This allows the count to display correctly on the room screen (after joining a room). ? Playlist.Count(i => !i.Expired) - : PlaylistItemStats.Value.CountActive; + : room.PlaylistItemStats.CountActive; TextFlow.Clear(); TextFlow.AddText(activeItems.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold)); TextFlow.AddText(" "); TextFlow.AddText("Beatmap".ToQuantity(activeItems, ShowQuantityAs.None)); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 11ad886773..17aed021b2 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -79,7 +79,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { bool matchingFilter = true; - matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats.Value?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false; + matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false; if (!string.IsNullOrEmpty(criteria.SearchString)) { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 1880fdc974..46debb5b9d 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -14,9 +14,6 @@ namespace osu.Game.Screens.OnlinePlay /// public partial class OnlinePlayComposite : CompositeDrawable { - [Resolved(typeof(Room))] - protected Bindable PlaylistItemStats { get; private set; } = null!; - [Resolved(typeof(Room))] protected BindableList Playlist { get; private set; } = null!; diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index de54ff57e8..a384a14441 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay if (ruleset != null) { - room.PlaylistItemStats.Value = new Room.RoomPlaylistItemStats + room.PlaylistItemStats = new Room.RoomPlaylistItemStats { RulesetIDs = new[] { ruleset.OnlineID }, }; From c4f8fd183216d537f3d2ee47d4c54c561cee3f3c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 22:33:49 +0900 Subject: [PATCH 040/139] Make `Room.DifficultyRange` non-bindable --- .../TestSceneStarRatingRangeDisplay.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 18 +++++--- .../Components/StarRatingRangeDisplay.cs | 44 +++++++++++++------ .../Lounge/Components/DrawableRoom.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 -- 5 files changed, 46 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs index b53a61f881..5c11690cd6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { SelectedRoom.Value = new Room(); - Child = new StarRatingRangeDisplay + Child = new StarRatingRangeDisplay(SelectedRoom.Value) { Anchor = Anchor.Centre, Origin = Anchor.Centre diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index dd3a9b2e73..9d69e791c7 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -164,6 +164,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref playlistItemStats, value); } + /// + /// Describes the range of difficulty of the room. + /// + public RoomDifficultyRange? DifficultyRange + { + get => difficultyRange; + set => SetField(ref difficultyRange, value); + } + /// /// The playlist queueing mode. Only valid for multiplayer rooms. /// @@ -270,6 +279,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("playlist_item_stats")] private RoomPlaylistItemStats? playlistItemStats; + [JsonProperty("difficulty_range")] + private RoomDifficultyRange? difficultyRange; + [JsonConverter(typeof(SnakeCaseStringEnumConverter))] [JsonProperty("type")] private MatchType type; @@ -300,10 +312,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("playlist")] public readonly BindableList Playlist = new BindableList(); - [JsonProperty("difficulty_range")] - [Cached] - public readonly Bindable DifficultyRange = new Bindable(); - [Cached] [JsonProperty("current_user_score")] public readonly Bindable UserScore = new Bindable(); @@ -340,7 +348,7 @@ namespace osu.Game.Online.Rooms UserScore.Value = other.UserScore.Value; QueueMode = other.QueueMode; AutoStartDuration = other.AutoStartDuration; - DifficultyRange.Value = other.DifficultyRange.Value; + DifficultyRange = other.DifficultyRange; PlaylistItemStats = other.PlaylistItemStats; CurrentPlaylistItem = other.CurrentPlaylistItem; AutoSkip = other.AutoSkip; diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index 2ee3bb30dd..13e3dc6d7c 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -1,9 +1,8 @@ // 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 System.ComponentModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -13,22 +12,27 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; +using osu.Game.Online.Rooms; using osuTK; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Components { public partial class StarRatingRangeDisplay : OnlinePlayComposite { + private readonly Room room; + [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; - private StarRatingDisplay minDisplay; - private Drawable minBackground; - private StarRatingDisplay maxDisplay; - private Drawable maxBackground; + private StarRatingDisplay minDisplay = null!; + private Drawable minBackground = null!; + private StarRatingDisplay maxDisplay = null!; + private Drawable maxBackground = null!; - public StarRatingRangeDisplay() + public StarRatingRangeDisplay(Room room) { + this.room = room; AutoSizeAxes = Axes.Both; } @@ -76,8 +80,16 @@ namespace osu.Game.Screens.OnlinePlay.Components { base.LoadComplete(); - DifficultyRange.BindValueChanged(_ => updateRange()); - Playlist.BindCollectionChanged((_, _) => updateRange(), true); + Playlist.BindCollectionChanged((_, _) => updateRange()); + + room.PropertyChanged += onRoomPropertyChanged; + updateRange(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.DifficultyRange)) + updateRange(); } private void updateRange() @@ -85,11 +97,11 @@ namespace osu.Game.Screens.OnlinePlay.Components StarDifficulty minDifficulty; StarDifficulty maxDifficulty; - if (DifficultyRange.Value != null && Playlist.Count == 0) + if (room.DifficultyRange != null && Playlist.Count == 0) { // When Playlist is empty (in lounge) we take retrieved range - minDifficulty = new StarDifficulty(DifficultyRange.Value.Min, 0); - maxDifficulty = new StarDifficulty(DifficultyRange.Value.Max, 0); + minDifficulty = new StarDifficulty(room.DifficultyRange.Min, 0); + maxDifficulty = new StarDifficulty(room.DifficultyRange.Max, 0); } else { @@ -107,5 +119,11 @@ namespace osu.Game.Screens.OnlinePlay.Components minBackground.Colour = colours.ForStarDifficulty(minDifficulty.Stars); maxBackground.Colour = colours.ForStarDifficulty(maxDifficulty.Stars); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index ddfbe00227..c368666ac2 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -360,7 +360,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - new StarRatingRangeDisplay + new StarRatingRangeDisplay(Room) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 46debb5b9d..66e43c50b7 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -17,9 +17,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected BindableList Playlist { get; private set; } = null!; - [Resolved(typeof(Room))] - protected Bindable DifficultyRange { get; private set; } = null!; - [Resolved(typeof(Room))] protected BindableList RecentParticipants { get; private set; } = null!; From dc5337d771ef035ae66911a0de85863661103d65 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 13 Nov 2024 22:39:01 +0900 Subject: [PATCH 041/139] Make `Room.UserScore` non-bindable --- osu.Game/Online/Rooms/Room.cs | 18 +++++++--- .../Components/RoomLocalUserInfo.cs | 15 +++++---- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 -- .../Playlists/PlaylistsReadyButton.cs | 33 +++++++++++++------ 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 9d69e791c7..8fb3901a79 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -200,6 +200,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref autoStartDuration, (ushort)value.TotalSeconds); } + /// + /// Provides some extra scoring statistics for the local user in the room. + /// + public PlaylistAggregateScore? UserScore + { + get => userScore; + set => SetField(ref userScore, value); + } + /// /// Represents the current item selected within the room. /// @@ -296,6 +305,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("auto_start_duration")] private ushort autoStartDuration; + [JsonProperty("current_user_score")] + private PlaylistAggregateScore? userScore; + [JsonProperty("current_playlist_item")] private PlaylistItem? currentPlaylistItem; @@ -312,10 +324,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("playlist")] public readonly BindableList Playlist = new BindableList(); - [Cached] - [JsonProperty("current_user_score")] - public readonly Bindable UserScore = new Bindable(); - [Cached] [JsonProperty("recent_participants")] public readonly BindableList RecentParticipants = new BindableList(); @@ -345,7 +353,7 @@ namespace osu.Game.Online.Rooms MaxParticipants = other.MaxParticipants; ParticipantCount = other.ParticipantCount; EndDate = other.EndDate; - UserScore.Value = other.UserScore.Value; + UserScore = other.UserScore; QueueMode = other.QueueMode; AutoStartDuration = other.AutoStartDuration; DifficultyRange = other.DifficultyRange; diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs index 974c296399..b3e60d4318 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs @@ -47,16 +47,19 @@ namespace osu.Game.Screens.OnlinePlay.Components { base.LoadComplete(); - UserScore.BindValueChanged(_ => updateAttempts()); - room.PropertyChanged += onRoomPropertyChanged; updateAttempts(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.MaxAttempts)) - updateAttempts(); + switch (e.PropertyName) + { + case nameof(Room.UserScore): + case nameof(Room.MaxAttempts): + updateAttempts(); + break; + } } private void updateAttempts() @@ -65,9 +68,9 @@ namespace osu.Game.Screens.OnlinePlay.Components { attemptDisplay.Text = $"Maximum attempts: {room.MaxAttempts:N0}"; - if (UserScore.Value != null) + if (room.UserScore != null) { - int remaining = room.MaxAttempts.Value - UserScore.Value.PlaylistItemAttempts.Sum(a => a.Attempts); + int remaining = room.MaxAttempts.Value - room.UserScore.PlaylistItemAttempts.Sum(a => a.Attempts); attemptDisplay.Text += $" ({remaining} remaining)"; if (remaining == 0) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 66e43c50b7..82fc6d535c 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -19,8 +19,5 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected BindableList RecentParticipants { get; private set; } = null!; - - [Resolved(typeof(Room))] - public Bindable UserScore { get; private set; } = null!; } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs index ca661b041a..a460779ea6 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.ComponentModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -15,9 +16,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { public partial class PlaylistsReadyButton : ReadyButton { - [Resolved(typeof(Room), nameof(Room.UserScore))] - private Bindable userScore { get; set; } = null!; - [Resolved] private IBindable gameBeatmap { get; set; } = null!; @@ -41,15 +39,24 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { base.LoadComplete(); - userScore.BindValueChanged(aggregate => - { - if (room.MaxAttempts == null) - return; + room.PropertyChanged += onRoomPropertyChanged; + updateRoomUserScore(); + } - int remaining = room.MaxAttempts.Value - aggregate.NewValue.PlaylistItemAttempts.Sum(a => a.Attempts); + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.UserScore)) + updateRoomUserScore(); + } - hasRemainingAttempts = remaining > 0; - }); + private void updateRoomUserScore() + { + if (room.MaxAttempts == null || room.UserScore == null) + return; + + int remaining = room.MaxAttempts.Value - room.UserScore.PlaylistItemAttempts.Sum(a => a.Attempts); + + hasRemainingAttempts = remaining > 0; } protected override void Update() @@ -76,5 +83,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private bool enoughTimeLeft => // 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; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } From b16edbbf524b8f95cb9a67f2bb17475dc2e34bad Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 14 Nov 2024 15:07:16 +0900 Subject: [PATCH 042/139] Make `Room.RecentParticipants` non-bindable --- .../Multiplayer/TestSceneDrawableRoom.cs | 4 +- .../TestSceneDrawableRoomParticipantsList.cs | 6 +- .../TestScenePlaylistsParticipantsList.cs | 12 ++-- .../TestScenePlaylistsRoomCreation.cs | 4 +- .../Online/Multiplayer/MultiplayerClient.cs | 6 +- osu.Game/Online/Rooms/Room.cs | 32 ++++++--- .../Components/ParticipantsDisplay.cs | 2 +- .../OnlinePlay/Components/ParticipantsList.cs | 41 ++++++++---- .../DrawableRoomParticipantsList.cs | 66 ++++++++----------- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 4 -- .../OnlinePlay/TestRoomRequestsHandler.cs | 2 +- 11 files changed, 100 insertions(+), 79 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 31923b676a..d00ff72599 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -199,11 +199,11 @@ namespace osu.Game.Tests.Visual.Multiplayer if (room.RecentParticipants.Count == 0) { - room.RecentParticipants.AddRange(Enumerable.Range(0, 20).Select(i => new APIUser + room.RecentParticipants = Enumerable.Range(0, 20).Select(i => new APIUser { Id = i, Username = $"User {i}" - })); + }).ToArray(); } return new DrawableLoungeRoom(room) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs index 3d0c1be22c..b909b934b3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs @@ -138,17 +138,17 @@ namespace osu.Game.Tests.Visual.Multiplayer private void addUser(int id) { - SelectedRoom.Value.RecentParticipants.Add(new APIUser + SelectedRoom.Value.RecentParticipants = SelectedRoom.Value.RecentParticipants.Append(new APIUser { Id = id, Username = $"User {id}" - }); + }).ToArray(); SelectedRoom.Value.ParticipantCount++; } private void removeUserAt(int index) { - SelectedRoom.Value.RecentParticipants.RemoveAt(index); + SelectedRoom.Value.RecentParticipants = SelectedRoom.Value.RecentParticipants.Where(u => !u.Equals(SelectedRoom.Value.RecentParticipants[index])).ToArray(); SelectedRoom.Value.ParticipantCount--; } } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index 2368a2068d..f72a2cd655 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.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.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online.API.Requests.Responses; @@ -19,17 +20,16 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("create list", () => { - SelectedRoom.Value = new Room { RoomID = 7 }; - - for (int i = 0; i < 50; i++) + SelectedRoom.Value = new Room { - SelectedRoom.Value.RecentParticipants.Add(new APIUser + RoomID = 7, + RecentParticipants = Enumerable.Range(0, 50).Select(_ => new APIUser { Username = "peppy", Statistics = new UserStatistics { GlobalRank = 1234 }, Id = 2 - }); - } + }).ToArray() + }; }); } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 031f9c272f..f75b3d7b14 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Playlists { room.Name = "my awesome room"; room.Host = API.LocalUser.Value; - room.RecentParticipants.Add(room.Host); + room.RecentParticipants = [room.Host]; room.EndDate = DateTimeOffset.Now.AddMinutes(5); room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual.Playlists room.Name = "my awesome room"; room.MaxAttempts = 5; room.Host = API.LocalUser.Value; - room.RecentParticipants.Add(room.Host); + room.RecentParticipants = [room.Host]; room.EndDate = DateTimeOffset.Now.AddMinutes(5); room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) { diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index fe0b4c6a21..74f89c7a61 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -487,11 +487,11 @@ namespace osu.Game.Online.Multiplayer { Debug.Assert(APIRoom != null); - APIRoom.RecentParticipants.Add(user.User ?? new APIUser + APIRoom.RecentParticipants = APIRoom.RecentParticipants.Append(user.User ?? new APIUser { Id = user.UserID, Username = "[Unresolved]" - }); + }).ToArray(); APIRoom.ParticipantCount++; } @@ -506,7 +506,7 @@ namespace osu.Game.Online.Multiplayer PlayingUserIds.Remove(user.UserID); Debug.Assert(APIRoom != null); - APIRoom.RecentParticipants.RemoveAll(u => u.Id == user.UserID); + APIRoom.RecentParticipants = APIRoom.RecentParticipants.Where(u => u.Id != user.UserID).ToArray(); APIRoom.ParticipantCount--; callback?.Invoke(user); diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 8fb3901a79..f3f8e87e54 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -137,6 +137,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref participantCount, value); } + /// + /// The set of most recent participants in the room. + /// + public IReadOnlyList RecentParticipants + { + get => recentParticipants; + set => SetList(ref recentParticipants, value); + } + /// /// The match type. /// @@ -282,6 +291,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("participant_count")] private int participantCount; + [JsonProperty("recent_participants")] + private IReadOnlyList recentParticipants = []; + [JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)] private int? maxAttempts; @@ -324,10 +336,6 @@ namespace osu.Game.Online.Rooms [JsonProperty("playlist")] public readonly BindableList Playlist = new BindableList(); - [Cached] - [JsonProperty("recent_participants")] - public readonly BindableList RecentParticipants = new BindableList(); - /// /// Copies values from another into this one. /// @@ -369,11 +377,7 @@ namespace osu.Game.Online.Rooms Playlist.AddRange(other.Playlist); } - if (!RecentParticipants.SequenceEqual(other.RecentParticipants)) - { - RecentParticipants.Clear(); - RecentParticipants.AddRange(other.RecentParticipants); - } + RecentParticipants = other.RecentParticipants; } public void RemoveExpiredPlaylistItems() @@ -411,6 +415,16 @@ namespace osu.Game.Online.Rooms protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null!) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + protected bool SetList(ref IReadOnlyList list, IReadOnlyList value, [CallerMemberName] string propertyName = null!) + { + if (list.SequenceEqual(value)) + return false; + + list = value; + OnPropertyChanged(propertyName); + return true; + } + protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null!) { if (EqualityComparer.Default.Equals(field, value)) diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs index 1ae2756514..5a040dcadb 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.OnlinePlay.Components AddInternal(scroll = new OsuScrollContainer(direction) { - Child = list = new ParticipantsList() + Child = list = new ParticipantsList(room) }); switch (direction) diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs index c4aefe4f99..1be23014bd 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs @@ -1,15 +1,14 @@ // 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 System.ComponentModel; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Threading; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Rooms; using osu.Game.Users.Drawables; using osuTK; @@ -57,15 +56,29 @@ namespace osu.Game.Screens.OnlinePlay.Components } } - [BackgroundDependencyLoader] - private void load() + private readonly Room room; + + public ParticipantsList(Room room) { - RecentParticipants.CollectionChanged += (_, _) => updateParticipants(); + this.room = room; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + room.PropertyChanged += onRoomPropertyChanged; updateParticipants(); } - private ScheduledDelegate scheduledUpdate; - private FillFlowContainer tiles; + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.RecentParticipants)) + updateParticipants(); + } + + private ScheduledDelegate? scheduledUpdate; + private FillFlowContainer? tiles; private void updateParticipants() { @@ -83,8 +96,8 @@ namespace osu.Game.Screens.OnlinePlay.Components Spacing = Vector2.One }; - for (int i = 0; i < RecentParticipants.Count; i++) - tiles.Add(new UserTile { User = RecentParticipants[i] }); + for (int i = 0; i < room.RecentParticipants.Count; i++) + tiles.Add(new UserTile { User = room.RecentParticipants[i] }); AddInternal(tiles); @@ -92,9 +105,15 @@ namespace osu.Game.Screens.OnlinePlay.Components }); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } + private partial class UserTile : CompositeDrawable { - public APIUser User + public APIUser? User { get => avatar.User; set => avatar.User = value; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs index 06ae939842..d989bd6c82 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs @@ -1,9 +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.Collections.Specialized; +using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -165,12 +164,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.LoadComplete(); - RecentParticipants.BindCollectionChanged(onParticipantsChanged, true); - room.PropertyChanged += onRoomPropertyChanged; updateRoomHost(); updateRoomParticipantCount(); + updateRoomParticipants(); } private int numberOfCircles = 4; @@ -190,43 +188,38 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components // Reinitialising the list looks janky, but this is unlikely to be used in a setting where it's visible. clearUsers(); - foreach (var u in RecentParticipants) + foreach (var u in room.RecentParticipants) addUser(u); updateHiddenUsers(); } } - private void onParticipantsChanged(object? sender, NotifyCollectionChangedEventArgs e) + private void updateRoomParticipants() { - switch (e.Action) + HashSet newUsers = room.RecentParticipants.ToHashSet(); + + avatarFlow.RemoveAll(a => { - case NotifyCollectionChangedAction.Add: - Debug.Assert(e.NewItems != null); + // Avatar with no user. Really shouldn't ever be the case but asserting it correctly is difficult. + if (a.User == null) + return false; - foreach (var added in e.NewItems.OfType()) - addUser(added); - break; + // User was previously and still is a participant. Keep them around but remove them from the new set. + // This will be useful when we add all remaining users (now just the new participants) to the flow. + if (newUsers.Contains(a.User)) + { + newUsers.Remove(a.User); + return false; + } - case NotifyCollectionChangedAction.Remove: - Debug.Assert(e.OldItems != null); + // User is no longer a participant. Remove them from the flow. + return true; + }, true); - foreach (var removed in e.OldItems.OfType()) - removeUser(removed); - break; - - case NotifyCollectionChangedAction.Reset: - clearUsers(); - break; - - case NotifyCollectionChangedAction.Replace: - case NotifyCollectionChangedAction.Move: - // Easiest is to just reinitialise the whole list. These are unlikely to ever be use cases. - clearUsers(); - foreach (var u in RecentParticipants) - addUser(u); - break; - } + // Add all remaining users to the flow. + foreach (var u in newUsers) + addUser(u); updateHiddenUsers(); } @@ -239,11 +232,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components avatarFlow.Add(new CircularAvatar { User = user }); } - private void removeUser(APIUser user) - { - avatarFlow.RemoveAll(a => a.User == user, true); - } - private void clearUsers() { avatarFlow.Clear(); @@ -253,7 +241,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void updateHiddenUsers() { int hiddenCount = 0; - if (RecentParticipants.Count > NumberOfCircles) + if (room.RecentParticipants.Count > NumberOfCircles) hiddenCount = room.ParticipantCount - NumberOfCircles + 1; hiddenUsers.Count = hiddenCount; @@ -262,7 +250,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components avatarFlow.Remove(avatarFlow.Last(), true); else if (displayedCircles < NumberOfCircles) { - var nextUser = RecentParticipants.FirstOrDefault(u => avatarFlow.All(a => a.User != u)); + var nextUser = room.RecentParticipants.FirstOrDefault(u => avatarFlow.All(a => a.User != u)); if (nextUser != null) addUser(nextUser); } } @@ -278,6 +266,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components case nameof(Room.ParticipantCount): updateRoomParticipantCount(); break; + + case nameof(Room.RecentParticipants): + updateRoomParticipants(); + break; } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 82fc6d535c..ffe36d7609 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay @@ -16,8 +15,5 @@ namespace osu.Game.Screens.OnlinePlay { [Resolved(typeof(Room))] protected BindableList Playlist { get; private set; } = null!; - - [Resolved(typeof(Room))] - protected BindableList RecentParticipants { get; private set; } = null!; } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 0934c4f3dc..37325c2d6b 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -280,7 +280,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay responseRoom.Password = responseRoom.HasPassword ? Guid.NewGuid().ToString() : null; if (!withParticipants) - responseRoom.RecentParticipants.Clear(); + responseRoom.RecentParticipants = []; return responseRoom; } From 34c0f72dd6801da1a45a4e3627a1888f1687cde1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 14 Nov 2024 17:57:32 +0900 Subject: [PATCH 043/139] Make `Room.Playlist` non-bindable --- .../DailyChallenge/TestSceneDailyChallenge.cs | 12 +- .../TestSceneDailyChallengeIntro.cs | 4 +- .../Visual/Menus/TestSceneMainMenu.cs | 4 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 4 +- .../Multiplayer/TestSceneDrawableRoom.cs | 6 +- .../TestSceneMatchBeatmapDetailArea.cs | 7 +- .../Multiplayer/TestSceneMatchStartControl.cs | 2 +- .../Multiplayer/TestSceneMultiplayer.cs | 110 ++++++++--------- .../TestSceneMultiplayerMatchSubScreen.cs | 114 +++++++++++------- .../TestSceneMultiplayerPlaylist.cs | 6 +- .../TestSceneMultiplayerQueueList.cs | 19 +-- .../TestScenePlaylistsSongSelect.cs | 7 +- .../TestSceneStarRatingRangeDisplay.cs | 6 +- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 16 +-- .../TestScenePlaylistsMatchSettingsOverlay.cs | 10 +- .../TestScenePlaylistsRoomCreation.cs | 58 +++++---- .../UserInterface/TestSceneMainMenuButton.cs | 8 +- .../Online/Multiplayer/MultiplayerClient.cs | 34 +----- osu.Game/Online/Rooms/PlaylistExtensions.cs | 5 +- osu.Game/Online/Rooms/PlaylistItem.cs | 25 ++-- osu.Game/Online/Rooms/Room.cs | 26 ++-- .../OnlinePlay/Components/BeatmapTitle.cs | 34 ++++-- .../Components/MatchBeatmapDetailArea.cs | 39 ++++-- .../Components/OverlinedPlaylistHeader.cs | 24 +++- .../Components/StarRatingRangeDisplay.cs | 15 ++- .../Lounge/Components/PlaylistCountPill.cs | 15 ++- .../Lounge/LoungeBackgroundScreen.cs | 32 ++++- .../Match/MultiplayerMatchSettingsOverlay.cs | 19 ++- .../Match/Playlist/MultiplayerPlaylist.cs | 8 +- .../Match/Playlist/MultiplayerQueueList.cs | 52 +++++--- .../Multiplayer/MultiplayerMatchSongSelect.cs | 5 +- .../Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 8 +- .../OnlinePlay/OnlinePlaySongSelect.cs | 3 - .../Playlists/PlaylistsRoomSettingsOverlay.cs | 19 ++- .../Playlists/PlaylistsRoomSubScreen.cs | 13 +- .../Playlists/PlaylistsSongSelect.cs | 37 ++---- .../Multiplayer/MultiplayerTestScene.cs | 4 +- .../Multiplayer/TestMultiplayerClient.cs | 6 +- .../Visual/OnlinePlay/TestRoomManager.cs | 11 +- .../OnlinePlay/TestRoomRequestsHandler.cs | 22 ++-- 41 files changed, 490 insertions(+), 361 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index 1a19c423fc..0742ed5eb9 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -42,13 +42,13 @@ namespace osu.Game.Tests.Visual.DailyChallenge RoomID = 1234, Name = "Daily Challenge: June 4, 2024", Playlist = - { + [ new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) { RequiredMods = [new APIMod(new OsuModTraceable())], AllowedMods = [new APIMod(new OsuModDoubleTime())] } - }, + ], EndDate = DateTimeOffset.Now.AddHours(12), Category = RoomCategory.DailyChallenge }; @@ -65,13 +65,13 @@ namespace osu.Game.Tests.Visual.DailyChallenge RoomID = 1234, Name = "Daily Challenge: June 4, 2024", Playlist = - { + [ new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) { RequiredMods = [new APIMod(new OsuModTraceable())], AllowedMods = [new APIMod(new OsuModDoubleTime())] } - }, + ], EndDate = DateTimeOffset.Now.AddHours(12), Category = RoomCategory.DailyChallenge }; @@ -94,13 +94,13 @@ namespace osu.Game.Tests.Visual.DailyChallenge RoomID = 1234, Name = "Daily Challenge: June 4, 2024", Playlist = - { + [ new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) { RequiredMods = [new APIMod(new OsuModTraceable())], AllowedMods = [new APIMod(new OsuModDoubleTime())] } - }, + ], EndDate = DateTimeOffset.Now.AddHours(12), Category = RoomCategory.DailyChallenge }; diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs index aa311766d5..b0d600c7d5 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs @@ -71,13 +71,13 @@ namespace osu.Game.Tests.Visual.DailyChallenge RoomID = roomId, Name = "Daily Challenge: June 4, 2024", Playlist = - { + [ new PlaylistItem(CreateAPIBeatmap(new OsuRuleset().RulesetInfo)) { RequiredMods = [new APIMod(new OsuModTraceable())], AllowedMods = [new APIMod(new OsuModDoubleTime())] } - }, + ], StartDate = DateTimeOffset.Now, EndDate = DateTimeOffset.Now.AddHours(24), Category = RoomCategory.DailyChallenge diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index bc6c3ed709..f3ea20c1aa 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -45,9 +45,9 @@ namespace osu.Game.Tests.Visual.Menus RoomID = 1234, Name = "Aug 8, 2024", Playlist = - { + [ new PlaylistItem(beatmap) - }, + ], StartDate = DateTimeOffset.Now.AddMinutes(-30), EndDate = DateTimeOffset.Now.AddSeconds(60) }); diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 7dce7daf6d..2b738743ea 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -76,12 +76,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", QueueMode = Mode, Playlist = - { + [ new PlaylistItem(InitialBeatmap) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] })); AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index d00ff72599..e5938a796c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Status = new RoomStatusOpen(), EndDate = DateTimeOffset.Now.AddDays(1), Type = MatchType.HeadToHead, - Playlist = { item1 }, + Playlist = [item1], CurrentPlaylistItem = item1 }), createLoungeRoom(new Room @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Password = "*", EndDate = DateTimeOffset.Now.AddDays(1), Type = MatchType.HeadToHead, - Playlist = { item3 }, + Playlist = [item3], CurrentPlaylistItem = item3 }), createLoungeRoom(new Room @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Playlist room with multiple beatmaps", Status = new RoomStatusPlaying(), EndDate = DateTimeOffset.Now.AddDays(1), - Playlist = { item1, item2 }, + Playlist = [item1, item2], CurrentPlaylistItem = item1 }), createLoungeRoom(new Room diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index 24d1b51ff8..dd39db49c5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.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.Linq; using osu.Framework.Graphics; using osu.Game.Online.API; using osu.Game.Online.Rooms; @@ -23,7 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { SelectedRoom.Value = new Room(); - Child = new MatchBeatmapDetailArea + Child = new MatchBeatmapDetailArea(SelectedRoom.Value) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -35,7 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void createNewItem() { - SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + SelectedRoom.Value.Playlist = SelectedRoom.Value.Playlist.Append(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { ID = SelectedRoom.Value.Playlist.Count, RulesetID = new OsuRuleset().RulesetInfo.OnlineID, @@ -45,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new APIMod(new OsuModDoubleTime()), new APIMod(new OsuModAutoplay()) } - }); + }).ToArray(); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index 4eddac8c7a..6bb5ca254b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual.Multiplayer room.Value = new Room { - Playlist = { item }, + Playlist = [item], CurrentPlaylistItem = item }; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index e625d23779..9213a52c0e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -105,12 +105,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddRepeatStep("random stuff happens", performRandomAction, 30); @@ -240,12 +240,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddUntilStep("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount == 1); @@ -261,12 +261,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }, API.LocalUser.Value); }); @@ -290,12 +290,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }, API.LocalUser.Value); }); @@ -320,12 +320,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", Password = "password", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddUntilStep("room has password", () => multiplayerClient.ClientAPIRoom?.Password == "password"); @@ -341,12 +341,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", Password = "password", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }, API.LocalUser.Value); }); @@ -373,12 +373,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", Password = "password", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddStep("change password", () => multiplayerClient.ChangeSettings(password: "password2")); @@ -403,12 +403,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); pressReadyButton(); @@ -431,7 +431,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return new Room { Name = "Test Room", - Playlist = { item } + Playlist = [item] }; }); @@ -472,7 +472,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return new Room { Name = "Test Room", - Playlist = { item } + Playlist = [item] }; }); @@ -513,7 +513,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return new Room { Name = "Test Room", - Playlist = { item } + Playlist = [item] }; }); @@ -550,12 +550,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddStep("join other user (ready, host)", () => @@ -583,12 +583,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddStep("delete beatmap", () => beatmaps.Delete(importedSet)); @@ -622,12 +622,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddStep("disconnect", () => multiplayerClient.Disconnect()); @@ -641,13 +641,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, AllowedMods = new[] { new APIMod(new OsuModHidden()) } } - } + ] }); AddStep("open mod overlay", () => this.ChildrenOfType().Single().TriggerClick()); @@ -681,12 +681,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }); enterGameplay(); @@ -726,12 +726,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }); enterGameplay(); @@ -756,12 +756,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }); pressReadyButton(); @@ -794,12 +794,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", QueueMode = QueueMode.AllPlayers, Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }, API.LocalUser.Value); }); @@ -811,11 +811,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change server-side settings", () => { roomManager.ServerSideRooms[0].Name = "New name"; - roomManager.ServerSideRooms[0].Playlist.Add(new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) - { - ID = 2, - RulesetID = new OsuRuleset().RulesetInfo.OnlineID, - }); + roomManager.ServerSideRooms[0].Playlist = + [ + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) + { + ID = 2, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + } + ]; }); AddStep("join room", () => InputManager.Key(Key.Enter)); @@ -825,8 +828,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("local room has correct settings", () => { var localRoom = this.ChildrenOfType().Single().Room; - return localRoom.Name == roomManager.ServerSideRooms[0].Name - && localRoom.Playlist.SequenceEqual(roomManager.ServerSideRooms[0].Playlist); + return localRoom.Name == roomManager.ServerSideRooms[0].Name && localRoom.Playlist.Single().ID == 2; }); } @@ -839,12 +841,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", QueueMode = QueueMode.AllPlayers, Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }); AddStep("set spectating state", () => multiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); @@ -875,12 +877,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", QueueMode = QueueMode.AllPlayers, Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }); AddStep("set spectating state", () => multiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); @@ -914,12 +916,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", QueueMode = QueueMode.AllPlayers, Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }); enterGameplay(); @@ -945,12 +947,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", QueueMode = QueueMode.AllPlayers, Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }); enterGameplay(); @@ -978,12 +980,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddStep("join other user and make host", () => @@ -1025,7 +1027,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", QueueMode = QueueMode.AllPlayers, Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, @@ -1036,7 +1038,7 @@ namespace osu.Game.Tests.Visual.Multiplayer RulesetID = new TaikoRuleset().RulesetInfo.OnlineID, AllowedMods = new[] { new APIMod { Acronym = "HD" } }, }, - } + ] }); AddStep("select hidden", () => multiplayerClient.ChangeUserMods(new[] { new APIMod { Acronym = "HD" } })); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 0c2a657c5a..ef5ff25194 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -100,10 +100,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - }); + SelectedRoom.Value.Playlist = + [ + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + } + ]; }); ClickButtonWhenEnabled(); @@ -117,11 +120,14 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo) - { - RulesetID = new TaikoRuleset().RulesetInfo.OnlineID, - AllowedMods = new[] { new APIMod(new TaikoModSwap()) } - }); + SelectedRoom.Value.Playlist = + [ + new PlaylistItem(new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new TaikoRuleset().RulesetInfo.OnlineID, + AllowedMods = new[] { new APIMod(new TaikoModSwap()) } + } + ]; }); ClickButtonWhenEnabled(); @@ -140,10 +146,13 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("set playlist", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - }); + SelectedRoom.Value.Playlist = + [ + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + } + ]; }); AddAssert("create button enabled", () => this.ChildrenOfType().Single().Enabled.Value); @@ -155,10 +164,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set playlist", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - }); + SelectedRoom.Value.Playlist = + [ + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + } + ]; }); ClickButtonWhenEnabled(); @@ -184,11 +196,14 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item with allowed mod", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID, - AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) } - }); + SelectedRoom.Value.Playlist = + [ + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) } + } + ]; }); ClickButtonWhenEnabled(); @@ -211,11 +226,14 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item with allowed mod", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID, - AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) } - }); + SelectedRoom.Value.Playlist = + [ + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) } + } + ]; }); ClickButtonWhenEnabled(); @@ -233,10 +251,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item with no allowed mods", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID, - }); + SelectedRoom.Value.Playlist = + [ + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + } + ]; }); ClickButtonWhenEnabled(); @@ -254,8 +275,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add two playlist items", () => { - SelectedRoom.Value.Playlist.AddRange(new[] - { + SelectedRoom.Value.Playlist = + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID @@ -264,7 +285,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - }); + ]; }); ClickButtonWhenEnabled(); @@ -291,19 +312,22 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item", () => { - SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID, - RequiredMods = new[] + SelectedRoom.Value.Playlist = + [ + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { - new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 2.0 } }), - new APIMod(new OsuModStrictTracking()), - }, - AllowedMods = new[] - { - new APIMod(new OsuModFlashlight()), + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + RequiredMods = new[] + { + new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 2.0 } }), + new APIMod(new OsuModStrictTracking()), + }, + AllowedMods = new[] + { + new APIMod(new OsuModFlashlight()), + } } - }); + ]; }); ClickButtonWhenEnabled(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 92b44d9502..518ea2b511 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create list", () => { - Child = list = new MultiplayerPlaylist + Child = list = new MultiplayerPlaylist(SelectedRoom.Value) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "test name", Playlist = - { + [ new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo) { RulesetID = Ruleset.Value.OnlineID @@ -176,7 +176,7 @@ namespace osu.Game.Tests.Visual.Multiplayer RulesetID = Ruleset.Value.OnlineID, Expired = true } - } + ] }); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 10625d57aa..50f55ebde0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.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 System.Linq; using NUnit.Framework; @@ -27,10 +25,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerQueueList : MultiplayerTestScene { - private MultiplayerQueueList playlist; - private BeatmapManager beatmaps; - private BeatmapSetInfo importedSet; - private BeatmapInfo importedBeatmap; + private MultiplayerQueueList playlist = null!; + private BeatmapManager beatmaps = null!; + private BeatmapSetInfo importedSet = null!; + private BeatmapInfo importedBeatmap = null!; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -46,12 +44,17 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create playlist", () => { - Child = playlist = new MultiplayerQueueList + Child = playlist = new MultiplayerQueueList(SelectedRoom.Value) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(500, 300), - Items = { BindTarget = MultiplayerClient.ClientAPIRoom!.Playlist } + }; + + MultiplayerClient.ClientAPIRoom!.PropertyChanged += (_, e) => + { + if (e.PropertyName == nameof(Room.Playlist)) + playlist.Items.ReplaceRange(0, playlist.Items.Count, MultiplayerClient.ClientAPIRoom.Playlist); }; }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index cc78bed5de..fea6617d3f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -99,12 +99,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddStep("rearrange", () => - { - var item = SelectedRoom.Value.Playlist[0]; - SelectedRoom.Value.Playlist.RemoveAt(0); - SelectedRoom.Value.Playlist.Add(item); - }); + AddStep("rearrange", () => SelectedRoom.Value.Playlist = SelectedRoom.Value.Playlist.Skip(1).Append(SelectedRoom.Value.Playlist[0]).ToArray()); AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); AddAssert("new item has id 2", () => SelectedRoom.Value.Playlist.Last().ID == 2); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs index 5c11690cd6..195b40bd13 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs @@ -33,11 +33,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set playlist", () => { - SelectedRoom.Value.Playlist.AddRange(new[] - { + SelectedRoom.Value.Playlist = + [ new PlaylistItem(new BeatmapInfo { StarRating = min }), new PlaylistItem(new BeatmapInfo { StarRating = max }), - }); + ]; }); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index b68089015e..05136ebee1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -65,12 +65,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", Type = MatchType.TeamVersus, Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddUntilStep("room type is team vs", () => multiplayerClient.ClientRoom?.Settings.MatchType == MatchType.TeamVersus); @@ -85,12 +85,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", Type = MatchType.TeamVersus, Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - } + ] }); AddUntilStep("user on team 0", () => (multiplayerClient.ClientRoom?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); @@ -122,12 +122,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "Test Room", Type = MatchType.HeadToHead, Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }); AddUntilStep("match type head to head", () => multiplayerClient.ClientAPIRoom?.Type == MatchType.HeadToHead); @@ -147,12 +147,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { Name = "Test Room", Playlist = - { + [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID, } - } + ] }); AddUntilStep("room type is head to head", () => multiplayerClient.ClientRoom?.Settings.MatchType == MatchType.HeadToHead); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index b45d1e68cb..321f563140 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("clear name and beatmap", () => { SelectedRoom.Value.Name = ""; - SelectedRoom.Value.Playlist.Clear(); + SelectedRoom.Value.Playlist = []; }); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("set name", () => SelectedRoom.Value.Name = "Room name"); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set beatmap", () => SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo))); + AddStep("set beatmap", () => SelectedRoom.Value.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]); AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value); AddStep("clear name", () => SelectedRoom.Value.Name = ""); @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Playlists { settings.NameField.Current.Value = expected_name; settings.DurationField.Current.Value = expectedDuration; - SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)); + SelectedRoom.Value.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]; RoomManager.CreateRequested = r => { @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Playlists var beatmap = CreateBeatmap(Ruleset.Value).BeatmapInfo; SelectedRoom.Value.Name = "Test Room"; - SelectedRoom.Value.Playlist.Add(new PlaylistItem(beatmap)); + SelectedRoom.Value.Playlist = [new PlaylistItem(beatmap)]; errorMessage = $"{not_found_prefix} {beatmap.OnlineID}"; @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("setup", () => { SelectedRoom.Value.Name = "Test Room"; - SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)); + SelectedRoom.Value.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]; RoomManager.CreateRequested = _ => failText; }); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index f75b3d7b14..6a53019b76 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -64,10 +64,13 @@ namespace osu.Game.Tests.Visual.Playlists room.Host = API.LocalUser.Value; room.RecentParticipants = [room.Host]; room.EndDate = DateTimeOffset.Now.AddMinutes(5); - room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - }); + room.Playlist = + [ + new PlaylistItem(importedBeatmap.Beatmaps.First()) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + } + ]; }); AddUntilStep("Progress details are hidden", () => match.ChildrenOfType().FirstOrDefault()?.Parent!.Alpha == 0); @@ -88,10 +91,13 @@ namespace osu.Game.Tests.Visual.Playlists room.Host = API.LocalUser.Value; room.RecentParticipants = [room.Host]; room.EndDate = DateTimeOffset.Now.AddMinutes(5); - room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - }); + room.Playlist = + [ + new PlaylistItem(importedBeatmap.Beatmaps.First()) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + } + ]; }); AddUntilStep("Progress details are visible", () => match.ChildrenOfType().FirstOrDefault()?.Parent!.Alpha == 1); @@ -104,10 +110,13 @@ namespace osu.Game.Tests.Visual.Playlists { room.Name = "my awesome room"; room.Host = API.LocalUser.Value; - room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - }); + room.Playlist = + [ + new PlaylistItem(importedBeatmap.Beatmaps.First()) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + } + ]; }); AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]); @@ -152,19 +161,22 @@ namespace osu.Game.Tests.Visual.Playlists { room.Name = "my awesome room"; room.Host = API.LocalUser.Value; - room.Playlist.Add(new PlaylistItem(new BeatmapInfo - { - MD5Hash = realHash, - OnlineID = realOnlineId, - Metadata = new BeatmapMetadata(), - BeatmapSet = new BeatmapSetInfo + room.Playlist = + [ + new PlaylistItem(new BeatmapInfo { - OnlineID = realOnlineSetId, + MD5Hash = realHash, + OnlineID = realOnlineId, + Metadata = new BeatmapMetadata(), + BeatmapSet = new BeatmapSetInfo + { + OnlineID = realOnlineSetId, + } + }) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID } - }) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - }); + ]; }); AddAssert("match has default beatmap", () => match.Beatmap.IsDefault); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs index 8b17749afc..923c197c22 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs @@ -55,9 +55,9 @@ namespace osu.Game.Tests.Visual.UserInterface { RoomID = 1234, Playlist = - { + [ new PlaylistItem(beatmap) - }, + ], StartDate = DateTimeOffset.Now.AddMinutes(-5), EndDate = DateTimeOffset.Now.AddSeconds(30) }); @@ -133,9 +133,9 @@ namespace osu.Game.Tests.Visual.UserInterface { RoomID = 1234, Playlist = - { + [ new PlaylistItem(beatmap) - }, + ], StartDate = DateTimeOffset.Now.AddMinutes(-50), EndDate = DateTimeOffset.Now.AddSeconds(30) }); diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 74f89c7a61..998a34931d 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -201,8 +200,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(joinedRoom.Playlist.Count > 0); - APIRoom.Playlist.Clear(); - APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(item => new PlaylistItem(item))); + APIRoom.Playlist = joinedRoom.Playlist.Select(item => new PlaylistItem(item)).ToArray(); APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId); // The server will null out the end date upon the host joining the room, but the null value is never communicated to the client. @@ -734,7 +732,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); Room.Playlist.Add(item); - APIRoom.Playlist.Add(new PlaylistItem(item)); + APIRoom.Playlist = APIRoom.Playlist.Append(new PlaylistItem(item)).ToArray(); ItemAdded?.Invoke(item); RoomUpdated?.Invoke(); @@ -753,7 +751,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); Room.Playlist.Remove(Room.Playlist.Single(existing => existing.ID == playlistItemId)); - APIRoom.Playlist.RemoveAll(existing => existing.ID == playlistItemId); + APIRoom.Playlist = APIRoom.Playlist.Where(i => i.ID != playlistItemId).ToArray(); Debug.Assert(Room.Playlist.Count > 0); @@ -771,30 +769,10 @@ namespace osu.Game.Online.Multiplayer if (Room == null) return; - try - { - Debug.Assert(APIRoom != null); + Debug.Assert(APIRoom != null); - Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item; - - int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID)); - - APIRoom.Playlist.RemoveAt(existingIndex); - APIRoom.Playlist.Insert(existingIndex, new PlaylistItem(item)); - } - catch (Exception ex) - { - // Temporary code to attempt to figure out long-term failing tests. - StringBuilder exceptionText = new StringBuilder(); - - exceptionText.AppendLine("MultiplayerClient test failure investigation"); - exceptionText.AppendLine($"Exception : {ex.ToString()}"); - exceptionText.AppendLine($"Lookup : {item.ID}"); - exceptionText.AppendLine($"Items in Room.Playlist : {string.Join(',', Room.Playlist.Select(i => i.ID))}"); - exceptionText.AppendLine($"Items in APIRoom.Playlist: {string.Join(',', APIRoom!.Playlist.Select(i => i.ID))}"); - - throw new AggregateException(exceptionText.ToString()); - } + Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item; + APIRoom.Playlist = APIRoom.Playlist.Select((pi, i) => pi.ID == item.ID ? new PlaylistItem(item) : APIRoom.Playlist[i]).ToArray(); ItemChanged?.Invoke(item); RoomUpdated?.Invoke(); diff --git a/osu.Game/Online/Rooms/PlaylistExtensions.cs b/osu.Game/Online/Rooms/PlaylistExtensions.cs index 8591b5bb47..8afa7d90f8 100644 --- a/osu.Game/Online/Rooms/PlaylistExtensions.cs +++ b/osu.Game/Online/Rooms/PlaylistExtensions.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using Humanizer; using Humanizer.Localisation; -using osu.Framework.Bindables; using osu.Game.Rulesets; using osu.Game.Utils; @@ -30,7 +29,7 @@ namespace osu.Game.Online.Rooms /// or the last-played if all items are expired, /// or if was empty. /// - public static PlaylistItem? GetCurrentItem(this ICollection playlist) + public static PlaylistItem? GetCurrentItem(this IReadOnlyCollection playlist) { if (playlist.Count == 0) return null; @@ -43,7 +42,7 @@ namespace osu.Game.Online.Rooms /// /// Returns the total duration from the in playlist order from the supplied , /// - public static string GetTotalDuration(this BindableList playlist, RulesetStore rulesetStore) => + public static string GetTotalDuration(this IReadOnlyList playlist, RulesetStore rulesetStore) => playlist.Select(p => { double rate = 1; diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index a900d8f3d7..47d4e163bf 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -120,18 +120,21 @@ namespace osu.Game.Online.Rooms #endregion - public PlaylistItem With(Optional beatmap = default, Optional playlistOrder = default) => new PlaylistItem(beatmap.GetOr(Beatmap)) + public PlaylistItem With(Optional id = default, Optional beatmap = default, Optional playlistOrder = default) { - ID = ID, - OwnerID = OwnerID, - RulesetID = RulesetID, - Expired = Expired, - PlaylistOrder = playlistOrder.GetOr(PlaylistOrder), - PlayedAt = PlayedAt, - AllowedMods = AllowedMods, - RequiredMods = RequiredMods, - valid = { Value = Valid.Value }, - }; + return new PlaylistItem(beatmap.GetOr(Beatmap)) + { + ID = id.GetOr(ID), + OwnerID = OwnerID, + RulesetID = RulesetID, + Expired = Expired, + PlaylistOrder = playlistOrder.GetOr(PlaylistOrder), + PlayedAt = PlayedAt, + AllowedMods = AllowedMods, + RequiredMods = RequiredMods, + valid = { Value = Valid.Value }, + }; + } public bool Equals(PlaylistItem? other) => ID == other?.ID diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index f3f8e87e54..deb5808861 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Runtime.CompilerServices; using Newtonsoft.Json; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Game.IO.Serialization.Converters; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; @@ -164,6 +163,15 @@ namespace osu.Game.Online.Rooms set => SetField(ref maxAttempts, value); } + /// + /// The room playlist. + /// + public IReadOnlyList Playlist + { + get => playlist; + set => SetList(ref playlist, value); + } + /// /// Describes the items in the playlist. /// @@ -297,6 +305,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)] private int? maxAttempts; + [JsonProperty("playlist")] + private IReadOnlyList playlist = []; + [JsonProperty("playlist_item_stats")] private RoomPlaylistItemStats? playlistItemStats; @@ -332,10 +343,6 @@ namespace osu.Game.Online.Rooms // Not yet serialised (not implemented). private RoomAvailability availability; - [Cached] - [JsonProperty("playlist")] - public readonly BindableList Playlist = new BindableList(); - /// /// Copies values from another into this one. /// @@ -371,12 +378,7 @@ namespace osu.Game.Online.Rooms other.RemoveExpiredPlaylistItems(); - if (!Playlist.SequenceEqual(other.Playlist)) - { - Playlist.Clear(); - Playlist.AddRange(other.Playlist); - } - + Playlist = other.Playlist; RecentParticipants = other.RecentParticipants; } @@ -386,7 +388,7 @@ namespace osu.Game.Online.Rooms // and display only the non-expired playlist items while the room is still active. In order to achieve this, all expired items are removed from the source Room. // More refactoring is required before this can be done locally instead - DrawableRoomPlaylist is currently directly bound to the playlist to display items in the room. if (Status is not RoomStatusEnded) - Playlist.RemoveAll(i => i.Expired); + Playlist = Playlist.Where(i => !i.Expired).ToArray(); } [JsonObject(MemberSerialization.OptIn)] diff --git a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs index 7c57f5b4f5..01b71539fe 100644 --- a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.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.ComponentModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -8,25 +9,31 @@ using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.Chat; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { public partial class BeatmapTitle : OnlinePlayComposite { + private readonly Room room; private readonly LinkFlowContainer textFlow; - public BeatmapTitle() - { - AutoSizeAxes = Axes.Both; + [Resolved] + private OsuColour colours { get; set; } = null!; + public BeatmapTitle(Room room) + { + this.room = room; + + AutoSizeAxes = Axes.Both; InternalChild = textFlow = new LinkFlowContainer { AutoSizeAxes = Axes.Both }; } - [BackgroundDependencyLoader] - private void load() + protected override void LoadComplete() { - Playlist.CollectionChanged += (_, _) => updateText(); + base.LoadComplete(); + room.PropertyChanged += onRoomPropertyChanged; updateText(); } @@ -46,8 +53,11 @@ namespace osu.Game.Screens.OnlinePlay.Components } } - [Resolved] - private OsuColour colours { get; set; } = null!; + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Playlist)) + updateText(); + } private void updateText() { @@ -56,7 +66,7 @@ namespace osu.Game.Screens.OnlinePlay.Components textFlow.Clear(); - var beatmap = Playlist.FirstOrDefault()?.Beatmap; + var beatmap = room.Playlist.FirstOrDefault()?.Beatmap; if (beatmap == null) { @@ -78,5 +88,11 @@ namespace osu.Game.Screens.OnlinePlay.Components textFlow.AddLink(title, LinkAction.OpenBeatmap, beatmap.OnlineID.ToString(), "Open beatmap"); } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs index b0ede8d9b5..1f2b2e3fc2 100644 --- a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs +++ b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs @@ -1,12 +1,9 @@ // 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 System.ComponentModel; using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterfaceV2; @@ -14,23 +11,22 @@ using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Select; using osuTK; +using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Components { public partial class MatchBeatmapDetailArea : BeatmapDetailArea { - public Action CreateNewItem; - - public readonly Bindable SelectedItem = new Bindable(); - - [Resolved(typeof(Room))] - protected BindableList Playlist { get; private set; } + public Action? CreateNewItem; + private readonly Room room; private readonly GridContainer playlistArea; private readonly DrawableRoomPlaylist playlist; - public MatchBeatmapDetailArea() + public MatchBeatmapDetailArea(Room room) { + this.room = room; + Add(playlistArea = new GridContainer { RelativeSizeAxes = Axes.Both, @@ -72,10 +68,21 @@ namespace osu.Game.Screens.OnlinePlay.Components { base.LoadComplete(); - playlist.Items.BindTo(Playlist); - playlist.SelectedItem.BindTo(SelectedItem); + playlist.Items.BindCollectionChanged((_, __) => room.Playlist = playlist.Items.ToArray()); + + room.PropertyChanged += onRoomPropertyChanged; + updateRoomPlaylist(); } + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Playlist)) + updateRoomPlaylist(); + } + + private void updateRoomPlaylist() + => playlist.Items.ReplaceRange(0, playlist.Items.Count, room.Playlist); + protected override void OnTabChanged(BeatmapDetailAreaTabItem tab, bool selectedMods) { base.OnTabChanged(tab, selectedMods); @@ -93,5 +100,11 @@ namespace osu.Game.Screens.OnlinePlay.Components } protected override BeatmapDetailAreaTabItem[] CreateTabItems() => base.CreateTabItems().Prepend(new BeatmapDetailAreaPlaylistTabItem()).ToArray(); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs b/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs index dd728e460b..55d9f273e9 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.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.ComponentModel; using osu.Framework.Allocation; using osu.Game.Online.Rooms; using osu.Game.Rulesets; @@ -9,19 +10,38 @@ namespace osu.Game.Screens.OnlinePlay.Components { public partial class OverlinedPlaylistHeader : OverlinedHeader { + private readonly Room room; + [Resolved] private RulesetStore rulesets { get; set; } = null!; - public OverlinedPlaylistHeader() + public OverlinedPlaylistHeader(Room room) : base("Playlist") { + this.room = room; } protected override void LoadComplete() { base.LoadComplete(); - Playlist.BindCollectionChanged((_, _) => Details.Value = Playlist.GetTotalDuration(rulesets), true); + room.PropertyChanged += onRoomPropertyChanged; + updateDuration(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Playlist)) + updateDuration(); + } + + private void updateDuration() + => Details.Value = room.Playlist.GetTotalDuration(rulesets); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index 13e3dc6d7c..ff2eac0f30 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -80,16 +80,19 @@ namespace osu.Game.Screens.OnlinePlay.Components { base.LoadComplete(); - Playlist.BindCollectionChanged((_, _) => updateRange()); - room.PropertyChanged += onRoomPropertyChanged; updateRange(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.DifficultyRange)) - updateRange(); + switch (e.PropertyName) + { + case nameof(Room.Playlist): + case nameof(Room.DifficultyRange): + updateRange(); + break; + } } private void updateRange() @@ -97,7 +100,7 @@ namespace osu.Game.Screens.OnlinePlay.Components StarDifficulty minDifficulty; StarDifficulty maxDifficulty; - if (room.DifficultyRange != null && Playlist.Count == 0) + if (room.DifficultyRange != null && room.Playlist.Count == 0) { // When Playlist is empty (in lounge) we take retrieved range minDifficulty = new StarDifficulty(room.DifficultyRange.Min, 0); @@ -106,7 +109,7 @@ namespace osu.Game.Screens.OnlinePlay.Components else { // When Playlist is not empty (in room) we compute actual range - var orderedDifficulties = Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray(); + var orderedDifficulties = room.Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray(); minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0); maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs index 7e530993dd..70ddf15abf 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs @@ -26,24 +26,27 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.LoadComplete(); - Playlist.BindCollectionChanged((_, _) => updateCount()); - room.PropertyChanged += onRoomPropertyChanged; updateCount(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.PlaylistItemStats)) - updateCount(); + switch (e.PropertyName) + { + case nameof(Room.Playlist): + case nameof(Room.PlaylistItemStats): + updateCount(); + break; + } } private void updateCount() { - int activeItems = Playlist.Count > 0 || room.PlaylistItemStats == null + int activeItems = room.Playlist.Count > 0 || room.PlaylistItemStats == null // For now, use the playlist as the source of truth if it has any items. // This allows the count to display correctly on the room screen (after joining a room). - ? Playlist.Count(i => !i.Expired) + ? room.Playlist.Count(i => !i.Expired) : room.PlaylistItemStats.CountActive; TextFlow.Clear(); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs index b31c351b82..4a3985c386 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.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.ComponentModel; using osu.Framework.Bindables; using osu.Framework.Screens; using osu.Game.Online.Rooms; @@ -19,21 +20,44 @@ namespace osu.Game.Screens.OnlinePlay.Lounge playlist.BindCollectionChanged((_, _) => PlaylistItem = playlist.GetCurrentItem()); } + protected override void LoadComplete() + { + base.LoadComplete(); + SelectedRoom.BindValueChanged(onSelectedRoomChanged, true); + } + private void onSelectedRoomChanged(ValueChangedEvent room) { if (room.OldValue != null) - playlist.UnbindFrom(room.OldValue.Playlist); + room.OldValue.PropertyChanged -= onRoomPropertyChanged; if (room.NewValue != null) - playlist.BindTo(room.NewValue.Playlist); - else - playlist.Clear(); + room.NewValue.PropertyChanged += onRoomPropertyChanged; + + updateCurrentItem(); } + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Playlist)) + updateCurrentItem(); + } + + private void updateCurrentItem() + => PlaylistItem = SelectedRoom.Value?.Playlist.GetCurrentItem(); + public override bool OnExiting(ScreenExitEvent e) { // This screen never exits. return true; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (SelectedRoom.Value != null) + SelectedRoom.Value.PropertyChanged -= onRoomPropertyChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index f883fde600..ec257a3d1b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -4,6 +4,7 @@ using System; using System.ComponentModel; using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -277,7 +278,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match drawablePlaylist = new DrawableRoomPlaylist { RelativeSizeAxes = Axes.X, - Height = DrawableRoomPlaylistItem.HEIGHT + Height = DrawableRoomPlaylistItem.HEIGHT, + SelectedItem = { BindTarget = SelectedItem } }, selectBeatmapButton = new RoundedButton { @@ -365,9 +367,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { base.LoadComplete(); - drawablePlaylist.Items.BindTo(Playlist); - drawablePlaylist.SelectedItem.BindTo(SelectedItem); - room.PropertyChanged += onRoomPropertyChanged; updateRoomName(); @@ -377,6 +376,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match updateRoomAutoSkip(); updateRoomMaxParticipants(); updateRoomAutoStartDuration(); + updateRoomPlaylist(); + + drawablePlaylist.Items.BindCollectionChanged((_, __) => room.Playlist = drawablePlaylist.Items.ToArray()); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -410,6 +412,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match case nameof(Room.AutoStartDuration): updateRoomAutoStartDuration(); break; + + case nameof(Room.Playlist): + updateRoomPlaylist(); + break; } } @@ -434,11 +440,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateRoomAutoStartDuration() => typeLabel.Text = room.AutoStartDuration.GetLocalisableDescription(); + private void updateRoomPlaylist() + => drawablePlaylist.Items.ReplaceRange(0, drawablePlaylist.Items.Count, room.Playlist); + protected override void Update() { base.Update(); - ApplyButton.Enabled.Value = Playlist.Count > 0 && NameField.Text.Length > 0 && !operationInProgress.Value; + ApplyButton.Enabled.Value = room.Playlist.Count > 0 && NameField.Text.Length > 0 && !operationInProgress.Value; playlistContainer.Alpha = room.RoomID == null ? 1 : 0; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs index a44891e934..9feee0ae41 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs @@ -33,12 +33,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist [Resolved] private MultiplayerClient client { get; set; } = null!; + private readonly Room room; private readonly BindableWithCurrent selectedItem = new BindableWithCurrent(); private MultiplayerPlaylistTabControl playlistTabControl = null!; private MultiplayerQueueList queueList = null!; private MultiplayerHistoryList historyList = null!; private bool firstPopulation = true; + public MultiplayerPlaylist(Room room) + { + this.room = room; + } + [BackgroundDependencyLoader] private void load() { @@ -59,7 +65,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist Masking = true, Children = new Drawable[] { - queueList = new MultiplayerQueueList + queueList = new MultiplayerQueueList(room) { RelativeSizeAxes = Axes.Both, SelectedItem = { BindTarget = selectedItem }, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 77d82c4347..04bb9b69e6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -1,12 +1,11 @@ // 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.Collections.Generic; +using System.ComponentModel; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.API; @@ -21,28 +20,49 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist /// public partial class MultiplayerQueueList : DrawableRoomPlaylist { - public MultiplayerQueueList() + private readonly Room room; + + private QueueFillFlowContainer flow = null!; + + public MultiplayerQueueList(Room room) { + this.room = room; ShowItemOwners = true; } - protected override FillFlowContainer> CreateListFillFlowContainer() => new QueueFillFlowContainer + protected override void LoadComplete() + { + base.LoadComplete(); + + room.PropertyChanged += onRoomPropertyChanged; + updateRoomPlaylist(); + } + + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Playlist)) + updateRoomPlaylist(); + } + + private void updateRoomPlaylist() + => flow.InvalidateLayout(); + + protected override FillFlowContainer> CreateListFillFlowContainer() => flow = new QueueFillFlowContainer { Spacing = new Vector2(0, 2) }; protected override DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new QueuePlaylistItem(item); + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + room.PropertyChanged -= onRoomPropertyChanged; + } + private partial class QueueFillFlowContainer : FillFlowContainer> { - [Resolved(typeof(Room), nameof(Room.Playlist))] - private BindableList roomPlaylist { get; set; } - - protected override void LoadComplete() - { - base.LoadComplete(); - roomPlaylist.BindCollectionChanged((_, _) => InvalidateLayout()); - } + public new void InvalidateLayout() => base.InvalidateLayout(); public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderBy(item => item.Model.PlaylistOrder); } @@ -50,10 +70,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist private partial class QueuePlaylistItem : DrawableRoomPlaylistItem { [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; [Resolved] - private MultiplayerClient multiplayerClient { get; set; } + private MultiplayerClient multiplayerClient { get; set; } = null!; public QueuePlaylistItem(PlaylistItem item) : base(item) @@ -91,7 +111,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist { base.Dispose(isDisposing); - if (multiplayerClient != null) + if (multiplayerClient.IsNotNull()) multiplayerClient.RoomUpdated -= onRoomUpdated; } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 873a1b0d50..4e03c19095 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -24,6 +24,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private OngoingOperationTracker operationTracker { get; set; } = null!; + private readonly Room room; private readonly IBindable operationInProgress = new Bindable(); private readonly PlaylistItem? itemToEdit; @@ -38,6 +39,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public MultiplayerMatchSongSelect(Room room, PlaylistItem? itemToEdit = null) : base(room, itemToEdit) { + this.room = room; this.itemToEdit = itemToEdit; } @@ -111,8 +113,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } else { - Playlist.Clear(); - Playlist.Add(item); + room.Playlist = [item]; this.Exit(); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 8f2f3aa678..65355f02e5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -138,7 +138,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer null, new Drawable[] { - new MultiplayerPlaylist + new MultiplayerPlaylist(Room) { RelativeSizeAxes = Axes.Both, RequestEdit = OpenSongSelection, diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index ffe36d7609..3b0e974a90 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.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. -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Online.Rooms; @@ -11,9 +9,5 @@ namespace osu.Game.Screens.OnlinePlay /// /// A that exposes bindables for properties. /// - public partial class OnlinePlayComposite : CompositeDrawable - { - [Resolved(typeof(Room))] - protected BindableList Playlist { get; private set; } = null!; - } + public partial class OnlinePlayComposite : CompositeDrawable; } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index a8dfece916..f6b6dfd3ab 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -32,9 +32,6 @@ namespace osu.Game.Screens.OnlinePlay public override bool AllowEditing => false; - [Resolved(typeof(Room), nameof(Room.Playlist))] - protected BindableList Playlist { get; private set; } = null!; - [Resolved] private RulesetStore rulesets { get; set; } = null!; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 6550b4c2f1..468c284d95 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -329,9 +329,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists localUser = api.LocalUser.GetBoundCopy(); localUser.BindValueChanged(populateDurations, true); - - playlist.Items.BindTo(Playlist); - Playlist.BindCollectionChanged(onPlaylistChanged, true); } protected override void LoadComplete() @@ -345,6 +342,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists updateRoomMaxParticipants(); updateRoomDuration(); updateRoomMaxAttempts(); + updateRoomPlaylist(); + + playlist.Items.BindCollectionChanged((_, __) => room.Playlist = playlist.Items.ToArray()); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -370,6 +370,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists case nameof(Room.MaxAttempts): updateRoomMaxAttempts(); break; + + case nameof(Room.Playlist): + updateRoomPlaylist(); + break; } } @@ -388,6 +392,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void updateRoomMaxAttempts() => MaxAttemptsField.Text = room.MaxAttempts?.ToString(); + private void updateRoomPlaylist() + => playlist.Items.ReplaceRange(0, playlist.Items.Count, room.Playlist); + private void populateDurations(ValueChangedEvent user) { // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427) @@ -421,9 +428,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists public void SelectBeatmap() => editPlaylistButton.TriggerClick(); private void onPlaylistChanged(object? sender, NotifyCollectionChangedEventArgs e) => - playlistLength.Text = $"Length: {Playlist.GetTotalDuration(rulesets)}"; + playlistLength.Text = $"Length: {room.Playlist.GetTotalDuration(rulesets)}"; - private bool hasValidSettings => room.RoomID == null && NameField.Text.Length > 0 && Playlist.Count > 0 + private bool hasValidSettings => room.RoomID == null && NameField.Text.Length > 0 && room.Playlist.Count > 0 && hasValidDuration; private bool hasValidDuration => DurationField.Current.Value <= TimeSpan.FromDays(14) || localUser.Value.IsSupporter; @@ -464,7 +471,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists .Select(int.Parse) .ToArray(); - foreach (var item in Playlist) + foreach (var item in room.Playlist) { if (invalidBeatmapIDs.Contains(item.Beatmap.OnlineID)) item.MarkInvalid(); diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 079e3b03b7..c82317add4 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -38,6 +38,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private MatchLeaderboard leaderboard = null!; private SelectionPollingComponent selectionPollingComponent = null!; private FillFlowContainer progressSection = null!; + private DrawableRoomPlaylist drawablePlaylist = null!; public PlaylistsRoomSubScreen(Room room) : base(room, false) // Editing is temporarily not allowed. @@ -77,6 +78,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists case nameof(Room.MaxAttempts): updateRoomMaxAttempts(); break; + + case nameof(Room.Playlist): + updateRoomPlaylist(); + break; } } @@ -93,6 +98,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void updateRoomMaxAttempts() => progressSection.Alpha = Room.MaxAttempts != null ? 1 : 0; + private void updateRoomPlaylist() + => drawablePlaylist.Items.ReplaceRange(0, drawablePlaylist.Items.Count, Room.Playlist); + protected override Drawable CreateMainContent() => new Container { RelativeSizeAxes = Axes.Both, @@ -122,13 +130,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Padding = new MarginPadding { Right = 5 }, Content = new[] { - new Drawable[] { new OverlinedPlaylistHeader(), }, + new Drawable[] { new OverlinedPlaylistHeader(Room), }, new Drawable[] { - new DrawableRoomPlaylist + drawablePlaylist = new DrawableRoomPlaylist { RelativeSizeAxes = Axes.Both, - Items = { BindTarget = Room.Playlist }, SelectedItem = { BindTarget = SelectedItem }, AllowSelection = true, AllowShowingResults = true, diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs index cedea4af70..23824b6a73 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs @@ -12,45 +12,34 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { public partial class PlaylistsSongSelect : OnlinePlaySongSelect { + private readonly Room room; + public PlaylistsSongSelect(Room room) : base(room) { + this.room = room; } - protected override BeatmapDetailArea CreateBeatmapDetailArea() => new MatchBeatmapDetailArea + protected override BeatmapDetailArea CreateBeatmapDetailArea() => new MatchBeatmapDetailArea(room) { - CreateNewItem = createNewItem + CreateNewItem = () => room.Playlist = room.Playlist.Append(createNewItem()).ToArray() }; protected override bool SelectItem(PlaylistItem item) { - switch (Playlist.Count) - { - case 0: - createNewItem(); - break; - - case 1: - Playlist.Clear(); - createNewItem(); - break; - } + if (room.Playlist.Count <= 1) + room.Playlist = [createNewItem()]; this.Exit(); return true; } - private void createNewItem() + private PlaylistItem createNewItem() => new PlaylistItem(Beatmap.Value.BeatmapInfo) { - PlaylistItem item = new PlaylistItem(Beatmap.Value.BeatmapInfo) - { - ID = Playlist.Count == 0 ? 0 : Playlist.Max(p => p.ID) + 1, - RulesetID = Ruleset.Value.OnlineID, - RequiredMods = Mods.Value.Select(m => new APIMod(m)).ToArray(), - AllowedMods = FreeMods.Value.Select(m => new APIMod(m)).ToArray() - }; - - Playlist.Add(item); - } + ID = room.Playlist.Count == 0 ? 0 : room.Playlist.Max(p => p.ID) + 1, + RulesetID = Ruleset.Value.OnlineID, + RequiredMods = Mods.Value.Select(m => new APIMod(m)).ToArray(), + AllowedMods = FreeMods.Value.Select(m => new APIMod(m)).ToArray() + }; } } diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 774b1d5931..42cf317829 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -38,12 +38,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Name = "test name", Type = MatchType.HeadToHead, Playlist = - { + [ new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo) { RulesetID = Ruleset.Value.OnlineID } - } + ] }; } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 29e62cb590..4d812abf11 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -447,7 +447,7 @@ namespace osu.Game.Tests.Visual.Multiplayer item.PlaylistOrder = existingItem.PlaylistOrder; ServerRoom.Playlist[ServerRoom.Playlist.IndexOf(existingItem)] = item; - ServerAPIRoom.Playlist[ServerAPIRoom.Playlist.IndexOf(ServerAPIRoom.Playlist.Single(i => i.ID == item.ID))] = new PlaylistItem(item); + ServerAPIRoom.Playlist = ServerAPIRoom.Playlist.Select((pi, i) => pi.ID == item.ID ? new PlaylistItem(item) : ServerAPIRoom.Playlist[i]).ToArray(); await ((IMultiplayerClient)this).PlaylistItemChanged(clone(item)).ConfigureAwait(false); } @@ -474,7 +474,7 @@ namespace osu.Game.Tests.Visual.Multiplayer throw new InvalidOperationException("Attempted to remove an item which has already been played."); ServerRoom.Playlist.Remove(item); - ServerAPIRoom.Playlist.RemoveAll(i => i.ID == item.ID); + ServerAPIRoom.Playlist = ServerAPIRoom.Playlist.Where(i => i.ID != item.ID).ToArray(); await ((IMultiplayerClient)this).PlaylistItemRemoved(clone(playlistItemId)).ConfigureAwait(false); await updateCurrentItem(ServerRoom).ConfigureAwait(false); @@ -569,7 +569,7 @@ namespace osu.Game.Tests.Visual.Multiplayer item.ID = ++lastPlaylistItemId; ServerRoom.Playlist.Add(item); - ServerAPIRoom.Playlist.Add(new PlaylistItem(item)); + ServerAPIRoom.Playlist = ServerAPIRoom.Playlist.Append(new PlaylistItem(item)).ToArray(); await ((IMultiplayerClient)this).PlaylistItemAdded(clone(item)).ConfigureAwait(false); await updatePlaylistOrder(ServerRoom).ConfigureAwait(false); diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index a384a14441..e813b49844 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -48,10 +48,13 @@ namespace osu.Game.Tests.Visual.OnlinePlay RulesetIDs = new[] { ruleset.OnlineID }, }; - room.Playlist.Add(new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() }) - { - RulesetID = ruleset.OnlineID, - }); + room.Playlist = + [ + new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() }) + { + RulesetID = ruleset.OnlineID, + } + ]; } CreateRoom(room); diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 37325c2d6b..224bb90c8c 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -290,19 +290,17 @@ namespace osu.Game.Tests.Visual.OnlinePlay var result = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source)); Debug.Assert(result != null); - // Playlist item IDs and beatmaps aren't serialised. - if (source.CurrentPlaylistItem != null) - { - Debug.Assert(result.CurrentPlaylistItem != null); - result.CurrentPlaylistItem = result.CurrentPlaylistItem.With(new Optional(source.CurrentPlaylistItem.Beatmap)); - result.CurrentPlaylistItem.ID = source.CurrentPlaylistItem.ID; - } + // When serialising, only beatmap IDs are sent to the server. + // When deserialising, full beatmaps and IDs are expected to arrive. - for (int i = 0; i < source.Playlist.Count; i++) - { - result.Playlist[i] = result.Playlist[i].With(new Optional(source.Playlist[i].Beatmap)); - result.Playlist[i].ID = source.Playlist[i].ID; - } + PlaylistItem? finalCurrentItem = result.CurrentPlaylistItem?.With(id: source.CurrentPlaylistItem!.ID, beatmap: new Optional(source.CurrentPlaylistItem.Beatmap)); + PlaylistItem[] finalPlaylist = result.Playlist.Select((pi, i) => pi.With(id: source.Playlist[i].ID, beatmap: new Optional(source.Playlist[i].Beatmap))).ToArray(); + + // When setting the properties, we do a clear-then-add, otherwise equality comparers (that only compare by ID) pass early and members don't get replaced. + result.CurrentPlaylistItem = null; + result.CurrentPlaylistItem = finalCurrentItem; + result.Playlist = []; + result.Playlist = finalPlaylist; return result; } From bfbae9458a629e0afa2cec4784fb367883019e45 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 15 Nov 2024 13:20:09 +0900 Subject: [PATCH 044/139] Remove `OnlinePlayComposite` --- .../Screens/OnlinePlay/Components/BeatmapTitle.cs | 3 ++- .../OnlinePlay/Components/OverlinedHeader.cs | 2 +- .../Components/ParticipantCountDisplay.cs | 2 +- .../OnlinePlay/Components/ParticipantsDisplay.cs | 3 ++- .../OnlinePlay/Components/ParticipantsList.cs | 2 +- .../OnlinePlay/Components/RoomLocalUserInfo.cs | 2 +- .../OnlinePlay/Components/StarRatingRangeDisplay.cs | 2 +- .../DailyChallengeTimeRemainingRing.cs | 2 +- .../OnlinePlay/Lounge/Components/DrawableRoom.cs | 4 ++-- .../Components/DrawableRoomParticipantsList.cs | 2 +- .../OnlinePlay/Lounge/Components/EndDateInfo.cs | 3 ++- .../OnlinePlay/Lounge/Components/OnlinePlayPill.cs | 3 ++- .../Match/Components/RoomSettingsOverlay.cs | 4 ++-- .../Match/MultiplayerMatchSettingsOverlay.cs | 4 ++-- osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs | 13 ------------- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 4 ++-- 16 files changed, 23 insertions(+), 32 deletions(-) delete mode 100644 osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs diff --git a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs index 01b71539fe..5c8ac5ce73 100644 --- a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs @@ -5,6 +5,7 @@ using System.ComponentModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -13,7 +14,7 @@ using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { - public partial class BeatmapTitle : OnlinePlayComposite + public partial class BeatmapTitle : CompositeDrawable { private readonly Room room; private readonly LinkFlowContainer textFlow; diff --git a/osu.Game/Screens/OnlinePlay/Components/OverlinedHeader.cs b/osu.Game/Screens/OnlinePlay/Components/OverlinedHeader.cs index 09a3602cdd..d9cdcac7d7 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OverlinedHeader.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OverlinedHeader.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.OnlinePlay.Components /// /// A header used in the multiplayer interface which shows text / details beneath a line. /// - public partial class OverlinedHeader : OnlinePlayComposite + public partial class OverlinedHeader : CompositeDrawable { private bool showLine = true; diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs index f9744717d5..db9cf3f92d 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs @@ -11,7 +11,7 @@ using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { - public partial class ParticipantCountDisplay : OnlinePlayComposite + public partial class ParticipantCountDisplay : CompositeDrawable { private const float text_size = 30; private const float transition_duration = 100; diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs index 5a040dcadb..a12d843b0a 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs @@ -4,12 +4,13 @@ using System.ComponentModel; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { - public partial class ParticipantsDisplay : OnlinePlayComposite + public partial class ParticipantsDisplay : CompositeDrawable { public readonly Bindable Details = new Bindable(); diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs index 1be23014bd..79084a5285 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.Components { - public partial class ParticipantsList : OnlinePlayComposite + public partial class ParticipantsList : CompositeDrawable { public const float TILE_SIZE = 35; diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs index b3e60d4318..39b5edbd26 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs @@ -12,7 +12,7 @@ using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { - public partial class RoomLocalUserInfo : OnlinePlayComposite + public partial class RoomLocalUserInfo : CompositeDrawable { private readonly Room room; private OsuSpriteText attemptDisplay = null!; diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index ff2eac0f30..2bdb41ce12 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -18,7 +18,7 @@ using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Components { - public partial class StarRatingRangeDisplay : OnlinePlayComposite + public partial class StarRatingRangeDisplay : CompositeDrawable { private readonly Room room; diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs index bffbb664a3..bf01ee6b52 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs @@ -17,7 +17,7 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.DailyChallenge { - public partial class DailyChallengeTimeRemainingRing : OnlinePlayComposite + public partial class DailyChallengeTimeRemainingRing : CompositeDrawable { private readonly Room room; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index c368666ac2..2a9bb93d94 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -346,7 +346,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components if (Room.Type != MatchType.Playlists) { - pills.AddRange(new OnlinePlayComposite[] + pills.AddRange(new Drawable[] { new MatchTypePill(Room), new QueueModePill(Room), @@ -377,7 +377,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Room.PropertyChanged -= onRoomPropertyChanged; } - private partial class RoomStatusText : OnlinePlayComposite + private partial class RoomStatusText : CompositeDrawable { public readonly IBindable SelectedItem = new Bindable(); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs index d989bd6c82..5bcc974c26 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs @@ -21,7 +21,7 @@ using Container = osu.Framework.Graphics.Containers.Container; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public partial class DrawableRoomParticipantsList : OnlinePlayComposite + public partial class DrawableRoomParticipantsList : CompositeDrawable { public const float SHEAR_WIDTH = 12f; private const float avatar_size = 36; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs index 18c7972c06..3b03ce61f1 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs @@ -5,12 +5,13 @@ using System; using System.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public partial class EndDateInfo : OnlinePlayComposite + public partial class EndDateInfo : CompositeDrawable { private readonly Room room; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs index 3e6d7a2e54..c65a5e2469 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs @@ -3,13 +3,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public abstract partial class OnlinePlayPill : OnlinePlayComposite + public abstract partial class OnlinePlayPill : CompositeDrawable { protected PillContainer Pill { get; private set; } = null!; protected OsuTextFlowContainer TextFlow { get; private set; } = null!; diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs index 14d506a368..09aed176c4 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected const float TRANSITION_DURATION = 350; protected const float FIELD_PADDING = 25; - protected OnlinePlayComposite Settings { get; set; } = null!; + protected Drawable Settings { get; set; } = null!; protected override bool BlockScrollInput => false; @@ -48,7 +48,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected abstract void SelectBeatmap(); - protected abstract OnlinePlayComposite CreateSettings(Room room); + protected abstract Drawable CreateSettings(Room room); protected override void PopIn() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index ec257a3d1b..1dbef079d4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match protected override void SelectBeatmap() => settings.SelectBeatmap(); - protected override OnlinePlayComposite CreateSettings(Room room) => settings = new MatchSettings(room) + protected override Drawable CreateSettings(Room room) => settings = new MatchSettings(room) { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Y, @@ -59,7 +59,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match SelectedItem = { BindTarget = SelectedItem } }; - protected partial class MatchSettings : OnlinePlayComposite + protected partial class MatchSettings : CompositeDrawable { private const float disabled_alpha = 0.2f; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs deleted file mode 100644 index 3b0e974a90..0000000000 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ /dev/null @@ -1,13 +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.Containers; -using osu.Game.Online.Rooms; - -namespace osu.Game.Screens.OnlinePlay -{ - /// - /// A that exposes bindables for properties. - /// - public partial class OnlinePlayComposite : CompositeDrawable; -} diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 468c284d95..88af161cc8 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -47,14 +47,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected override void SelectBeatmap() => settings.SelectBeatmap(); - protected override OnlinePlayComposite CreateSettings(Room room) => settings = new MatchSettings(room) + protected override Drawable CreateSettings(Room room) => settings = new MatchSettings(room) { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Y, EditPlaylist = () => EditPlaylist?.Invoke() }; - protected partial class MatchSettings : OnlinePlayComposite + protected partial class MatchSettings : CompositeDrawable { private const float disabled_alpha = 0.2f; From 1a656d0ec353897852630bdff3839a8c2105c09d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 15 Nov 2024 13:22:03 +0900 Subject: [PATCH 045/139] Remove `CachedModelDependencyContainer` usages from online play --- .../DailyChallenge/TestSceneDailyChallengeCarousel.cs | 5 ----- .../TestSceneDailyChallengeTimeRemainingRing.cs | 5 ----- .../Visual/Multiplayer/TestSceneMatchStartControl.cs | 3 --- .../Visual/Multiplayer/TestSceneRankRangePill.cs | 5 ----- .../Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 8 -------- .../Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs | 8 -------- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 8 -------- .../Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs | 2 +- 8 files changed, 1 insertion(+), 43 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs index 5854c2e793..b9470f3be4 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs @@ -26,11 +26,6 @@ namespace osu.Game.Tests.Visual.DailyChallenge private readonly Bindable room = new Bindable(new Room()); - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) - { - Model = { BindTarget = room } - }; - [Test] public void TestBasicAppearance() { diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs index 2f522d4d6f..eebbd82190 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs @@ -18,11 +18,6 @@ namespace osu.Game.Tests.Visual.DailyChallenge { private readonly Bindable room = new Bindable(new Room()); - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) - { - Model = { BindTarget = room } - }; - [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index 6bb5ca254b..fb9c801fb4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -43,9 +43,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private OsuButton readyButton => control.ChildrenOfType().Single(); - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => - new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) { Model = { BindTarget = room } }; - [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs index d5f53bc354..9420ddf807 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; -using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Tests.Visual.Multiplayer @@ -18,10 +17,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { private readonly Mock multiplayerClient = new Mock(); - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => - // not used directly in component, but required due to it inheriting from OnlinePlayComposite. - new CachedModelDependencyContainer(base.CreateChildDependencies(parent)); - [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 9559826dab..0dc7e7930a 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -119,14 +119,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge Padding = new MarginPadding { Horizontal = -HORIZONTAL_OVERFLOW_PADDING }; } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - return new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) - { - Model = { Value = room } - }; - } - [BackgroundDependencyLoader] private void load(AudioManager audio) { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 2a9bb93d94..c39ca347c7 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -316,14 +316,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components passwordIcon.Alpha = Room.HasPassword ? 1 : 0; } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - return new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) - { - Model = { Value = Room } - }; - } - private int numberOfAvatars = 7; public int NumberOfAvatars diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 1b7df9c213..4461cf5402 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -287,14 +287,6 @@ namespace osu.Game.Screens.OnlinePlay.Match } } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - return new CachedModelDependencyContainer(base.CreateChildDependencies(parent)) - { - Model = { Value = Room } - }; - } - protected virtual bool IsConnected => api.State.Value == APIState.Online; public override bool OnBackButton() diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs index 64bd27b871..06f8cc40b9 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay UserLookupCache = new TestUserLookupCache(); BeatmapLookupCache = new BeatmapLookupCache(); - dependencies = new DependencyContainer(new CachedModelDependencyContainer(null) { Model = { BindTarget = SelectedRoom } }); + dependencies = new DependencyContainer(); CacheAs(RequestsHandler); CacheAs(SelectedRoom); From db7def9d344be36176a8b7901ed3171b79731b0e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 15 Nov 2024 16:05:06 +0900 Subject: [PATCH 046/139] Remove `IDependencyInjectionCandidate` interface from `Room` --- osu.Game/Online/Rooms/Room.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index deb5808861..a26a3fcb34 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -7,7 +7,6 @@ using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using Newtonsoft.Json; -using osu.Framework.Allocation; using osu.Game.IO.Serialization.Converters; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; @@ -16,7 +15,7 @@ using osu.Game.Online.Rooms.RoomStatuses; namespace osu.Game.Online.Rooms { [JsonObject(MemberSerialization.OptIn)] - public partial class Room : IDependencyInjectionCandidate, INotifyPropertyChanged + public partial class Room : INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; From c852cf9b8e152cba45cddae07f621c7563b30145 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Nov 2024 18:28:54 +0900 Subject: [PATCH 047/139] Remove macOS "borderless" recommendation As of SDL3, this is no longer a thing, and fullscreen should be the preferred execution mode. Probably hold off merging this until we're sure that macOS isn't broken for others in this mode (I had issues locally, such as alt-tabbing being broken sooo...) --- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index ce087f1807..b4b5e12922 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -269,14 +269,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private void updateScreenModeWarning() { if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS) - { - if (windowModeDropdown.Current.Value == WindowMode.Fullscreen) - windowModeDropdown.SetNoticeText(LayoutSettingsStrings.FullscreenMacOSNote, true); - else - windowModeDropdown.ClearNoticeText(); - return; - } if (windowModeDropdown.Current.Value != WindowMode.Fullscreen) { From c2b08beae81bebee05ad1decf9de3cd145a55ee9 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 15 Nov 2024 22:52:27 -0800 Subject: [PATCH 048/139] Add basic searching by source test --- osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 10e0e46f4c..1efcc8542d 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -148,6 +148,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCase("tags too", false)] [TestCase("version", false)] [TestCase("an auteur", true)] + [TestCase("unit", false)] public void TestCriteriaMatchingTerms(string terms, bool filtered) { var exampleBeatmapInfo = getExampleBeatmap(); @@ -175,6 +176,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCase("\"Artist\"!", true)] [TestCase("\"The Artist\"!", false)] [TestCase("\"the artist\"!", false)] + [TestCase("\"unit tests\"!", false)] [TestCase("\"\\\"", true)] // nasty case, covers properly escaping user input in underlying regex. public void TestCriteriaMatchingExactTerms(string terms, bool filtered) { From 7c94973d4a71a9c8841cde154e2c6790341c6970 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 15 Nov 2024 22:51:36 -0800 Subject: [PATCH 049/139] Add failing source filter query test --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index c8f063719d..89743b4ea8 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -501,6 +501,16 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.That(visibleBeatmaps, Is.EqualTo(expectedBeatmapIndexes)); } + [Test] + public void TestApplySourceQueries() + { + const string query = "find me songs with source=\"unit tests\" please"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("find me songs with please", filterCriteria.SearchText.Trim()); + Assert.AreEqual(5, filterCriteria.SearchTerms.Length); + } + private class CustomFilterCriteria : IRulesetFilterCriteria { public string? CustomValue { get; set; } From 465cc716d3d755de90af7355c02885ffd51814e6 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 15 Nov 2024 22:52:43 -0800 Subject: [PATCH 050/139] Add missing source query filter in song select --- osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs | 2 ++ osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 1 + osu.Game/Screens/Select/FilterCriteria.cs | 1 + osu.Game/Screens/Select/FilterQueryParser.cs | 3 +++ 4 files changed, 7 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 89743b4ea8..f4e324d7ba 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -509,6 +509,8 @@ namespace osu.Game.Tests.NonVisual.Filtering FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("find me songs with please", filterCriteria.SearchText.Trim()); Assert.AreEqual(5, filterCriteria.SearchTerms.Length); + Assert.AreEqual("unit tests", filterCriteria.Source.SearchTerm); + Assert.That(filterCriteria.Source.MatchMode, Is.EqualTo(FilterCriteria.MatchMode.IsolatedPhrase)); } private class CustomFilterCriteria : IRulesetFilterCriteria diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 3947cefb91..c007fa29ed 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -81,6 +81,7 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.Title.HasFilter || criteria.Title.Matches(BeatmapInfo.Metadata.Title) || criteria.Title.Matches(BeatmapInfo.Metadata.TitleUnicode); match &= !criteria.DifficultyName.HasFilter || criteria.DifficultyName.Matches(BeatmapInfo.DifficultyName); + match &= !criteria.Source.HasFilter || criteria.Source.Matches(BeatmapInfo.Metadata.Source); match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarRating); if (!match) return false; diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 77d7ff0e9f..76c0f769f0 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -43,6 +43,7 @@ namespace osu.Game.Screens.Select public OptionalTextFilter Artist; public OptionalTextFilter Title; public OptionalTextFilter DifficultyName; + public OptionalTextFilter Source; public OptionalRange UserStarDifficulty = new OptionalRange { diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index ccffd34dc2..78f3bab114 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -113,6 +113,9 @@ namespace osu.Game.Screens.Select case "diff": return TryUpdateCriteriaText(ref criteria.DifficultyName, op, value); + case "source": + return TryUpdateCriteriaText(ref criteria.Source, op, value); + default: return criteria.RulesetCriteria?.TryParseCustomKeywordCriteria(key, op, value) ?? false; } From 706f5b3e5553d14642b1e2a5b341057f0d31cbc2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 8 Oct 2024 11:46:57 -0700 Subject: [PATCH 051/139] Add missing clone keybinding to skin editor --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 42908f7102..7816705489 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -34,11 +34,12 @@ using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Skinning; using osu.Framework.Graphics.Cursor; +using osu.Game.Input.Bindings; namespace osu.Game.Overlays.SkinEditor { [Cached(typeof(SkinEditor))] - public partial class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBindingHandler, IEditorChangeHandler + public partial class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBindingHandler, IKeyBindingHandler, IEditorChangeHandler { public const double TRANSITION_DURATION = 300; @@ -313,6 +314,25 @@ namespace osu.Game.Overlays.SkinEditor { } + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Repeat) + return false; + + switch (e.Action) + { + case GlobalAction.EditorCloneSelection: + Clone(); + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + public void UpdateTargetScreen(Drawable targetScreen) { this.targetScreen = targetScreen; From ad9acc5a0f187080d4caa4deba3a7e365e8200e9 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 8 Oct 2024 11:54:58 -0700 Subject: [PATCH 052/139] Add hotkey hints to skin editor menus --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 7816705489..d0ee2ccd71 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -156,7 +156,7 @@ namespace osu.Game.Overlays.SkinEditor { Items = new OsuMenuItem[] { - new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), + new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()) { Hotkey = new Hotkey(PlatformAction.Save) }, new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, new OsuMenuItemSpacer(), new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))), @@ -168,13 +168,13 @@ namespace osu.Game.Overlays.SkinEditor { Items = new OsuMenuItem[] { - undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), - redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), + undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo) { Hotkey = new Hotkey(PlatformAction.Undo) }, + redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo) { Hotkey = new Hotkey(PlatformAction.Redo) }, new OsuMenuItemSpacer(), - cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), - copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), - pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), - cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), + cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut) { Hotkey = new Hotkey(PlatformAction.Cut) }, + copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy) { Hotkey = new Hotkey(PlatformAction.Copy) }, + pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste) { Hotkey = new Hotkey(PlatformAction.Paste) }, + cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone) { Hotkey = new Hotkey(GlobalAction.EditorCloneSelection) }, } }, } From b014bfea3e96f5195ef0822db094866d0d7aedc0 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 17 Nov 2024 09:56:19 -0500 Subject: [PATCH 053/139] Fix key counter not updating activation state on initial load --- osu.Game/Screens/Play/HUD/InputTrigger.cs | 7 +++++++ osu.Game/Screens/Play/HUD/KeyCounter.cs | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/InputTrigger.cs b/osu.Game/Screens/Play/HUD/InputTrigger.cs index edc61ec142..72dade25f6 100644 --- a/osu.Game/Screens/Play/HUD/InputTrigger.cs +++ b/osu.Game/Screens/Play/HUD/InputTrigger.cs @@ -34,6 +34,11 @@ namespace osu.Game.Screens.Play.HUD /// public IBindable ActivationCount => activationCount; + /// + /// Whether this is currently active. + /// + public bool IsActive { get; private set; } + /// /// Whether any activation or deactivation of this impacts its /// @@ -49,6 +54,7 @@ namespace osu.Game.Screens.Play.HUD if (forwardPlayback && isCounting.Value) activationCount.Value++; + IsActive = true; OnActivate?.Invoke(forwardPlayback); } @@ -57,6 +63,7 @@ namespace osu.Game.Screens.Play.HUD if (!forwardPlayback && isCounting.Value) activationCount.Value--; + IsActive = false; OnDeactivate?.Invoke(forwardPlayback); } } diff --git a/osu.Game/Screens/Play/HUD/KeyCounter.cs b/osu.Game/Screens/Play/HUD/KeyCounter.cs index 66f9dfd6f2..b506694044 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounter.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounter.cs @@ -36,6 +36,14 @@ namespace osu.Game.Screens.Play.HUD Trigger.OnDeactivate += Deactivate; } + protected override void LoadComplete() + { + base.LoadComplete(); + + if (Trigger.IsActive) + Activate(); + } + protected virtual void Activate(bool forwardPlayback = true) { isActive.Value = true; From 4a628287e260e0120adc2dd102fcfc8c81930a78 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 17 Nov 2024 18:13:37 -0500 Subject: [PATCH 054/139] 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 055/139] 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 056/139] 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 057/139] 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 058/139] 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 059/139] 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 e5778eb1fc85ee3f12594e504e4aa001210d95a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Nov 2024 14:38:12 +0900 Subject: [PATCH 060/139] Add scrollbar to manage collections dialog --- osu.Game/Collections/DrawableCollectionList.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 164ec558a4..441b7fabed 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -104,8 +104,8 @@ namespace osu.Game.Collections public Scroll() { - ScrollbarVisible = false; Padding = new MarginPadding(10); + ScrollbarOverlapsContent = false; base.Content.Add(new FillFlowContainer { @@ -187,6 +187,8 @@ namespace osu.Game.Collections { Spacing = new Vector2(0, 5); LayoutEasing = Easing.OutQuint; + + Padding = new MarginPadding { Right = 5 }; } protected override void LoadComplete() From a570863854ba794c4d9c72e84656568ce9c37d1f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Nov 2024 15:12:28 +0900 Subject: [PATCH 061/139] Scroll to new collection in list when adding a new collection --- .../Collections/DrawableCollectionList.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 441b7fabed..5096dce55c 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -46,6 +46,26 @@ namespace osu.Game.Collections realmSubscription = realm.RegisterForNotifications(r => r.All().OrderBy(c => c.Name), collectionsChanged); } + /// + /// When non-null, signifies that a new collection was created and should be presented to the user. + /// + private Guid? lastCreated; + + protected override void OnItemsChanged() + { + base.OnItemsChanged(); + + if (lastCreated != null) + { + var createdItem = flow.Children.SingleOrDefault(item => item.Model.Value.ID == lastCreated); + + if (createdItem != null) + scroll.ScrollTo(createdItem); + + lastCreated = null; + } + } + private void collectionsChanged(IRealmCollection collections, ChangeSet? changes) { if (changes == null) @@ -60,7 +80,11 @@ namespace osu.Game.Collections foreach (int i in changes.InsertedIndices) Items.Insert(i, collections[i].ToLive(realm)); + if (changes.InsertedIndices.Length == 1) + lastCreated = collections[changes.InsertedIndices[0]].ID; + foreach (int i in changes.NewModifiedIndices) + { var updatedItem = collections[i]; From 5ce1f7679be37328fb74b456fa95bfa0540e4271 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Nov 2024 15:57:39 +0900 Subject: [PATCH 062/139] Add search bar to collection management dialog --- .../TestSceneManageCollectionsDialog.cs | 40 +++++++++++++++++-- .../Collections/DrawableCollectionList.cs | 11 +++-- .../Collections/DrawableCollectionListItem.cs | 24 ++++++++++- .../Collections/ManageCollectionsDialog.cs | 37 ++++++++++++++++- 4 files changed, 103 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 56e7b4d39f..0f2f716a07 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -12,6 +12,7 @@ using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Collections; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; @@ -265,7 +266,6 @@ namespace osu.Game.Tests.Visual.Collections } [Test] - [Solo] public void TestCollectionRenamedExternal() { BeatmapCollection first = null!; @@ -338,10 +338,44 @@ namespace osu.Game.Tests.Visual.Collections AddUntilStep("collection has new name", () => first.Name == "First"); } + [Test] + public void TestSearch() + { + BeatmapCollection first = null!; + + AddStep("add two collections", () => + { + Realm.Write(r => + { + r.Add(new[] + { + first = new BeatmapCollection(name: "1"), + new BeatmapCollection(name: "2"), + }); + }); + }); + + assertCollectionName(0, "1"); + assertCollectionName(1, "2"); + + AddStep("search for 1", () => dialog.ChildrenOfType().Single().Current.Value = "1"); + + assertCollectionCount(1); + + AddStep("change first collection name", () => Realm.Write(_ => first.Name = "First")); + + assertCollectionCount(0); + + AddStep("search for first", () => dialog.ChildrenOfType().Single().Current.Value = "firs"); + + assertCollectionCount(1); + } + private void assertCollectionCount(int count) - => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count() == count + 1); // +1 for placeholder + => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count(i => i.IsPresent) == count + 1); // +1 for placeholder private void assertCollectionName(int index, string name) - => AddUntilStep($"item {index + 1} has correct name", () => dialog.ChildrenOfType().Single().OrderedItems.ElementAt(index).ChildrenOfType().First().Text == name); + => AddUntilStep($"item {index + 1} has correct name", + () => dialog.ChildrenOfType().Single().OrderedItems.ElementAt(index).ChildrenOfType().First().Text == name); } } diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 5096dce55c..f00691ba0f 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -34,6 +34,12 @@ namespace osu.Game.Collections public IEnumerable OrderedItems => flow.FlowingChildren; + public string SearchTerm + { + get => flow.SearchTerm; + set => flow.SearchTerm = value; + } + protected override FillFlowContainer>> CreateListFillFlowContainer() => flow = new Flow { DragActive = { BindTarget = DragActive } @@ -128,7 +134,6 @@ namespace osu.Game.Collections public Scroll() { - Padding = new MarginPadding(10); ScrollbarOverlapsContent = false; base.Content.Add(new FillFlowContainer @@ -157,7 +162,7 @@ namespace osu.Game.Collections base.Update(); // AutoSizeAxes cannot be used as the height should represent the post-layout-transform height at all times, so that the placeholder doesn't bounce around. - content.Height = ((Flow)Child).Children.Sum(c => c.DrawHeight + 5); + content.Height = ((Flow)Child).Children.Sum(c => c.IsPresent ? c.DrawHeight + 5 : 0); } /// @@ -203,7 +208,7 @@ namespace osu.Game.Collections /// /// The flow of . Disables layout easing unless a drag is in progress. /// - private partial class Flow : FillFlowContainer>> + private partial class Flow : SearchContainer>> { public readonly IBindable DragActive = new Bindable(); diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index f07ec87353..e86254329f 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -10,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -23,7 +25,7 @@ namespace osu.Game.Collections /// /// Visualises a inside a . /// - public partial class DrawableCollectionListItem : OsuRearrangeableListItem> + public partial class DrawableCollectionListItem : OsuRearrangeableListItem>, IFilterable { private const float item_height = 35; private const float button_width = item_height * 0.75f; @@ -207,5 +209,25 @@ namespace osu.Game.Collections private void deleteCollection() => collection.PerformWrite(c => c.Realm!.Remove(c)); } + + public IEnumerable FilterTerms => [(LocalisableString)Model.Value.Name]; + + private bool matchingFilter = true; + + public bool MatchingFilter + { + get => matchingFilter; + set + { + matchingFilter = value; + + if (matchingFilter) + this.FadeIn(200); + else + Hide(); + } + } + + public bool FilteringActive { get; set; } } } diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 9f8158af53..90b73bc6b2 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; +using osu.Game.Resources.Localisation.Web; using osuTK; namespace osu.Game.Collections @@ -26,6 +27,9 @@ namespace osu.Game.Collections private IDisposable? duckOperation; + private BasicSearchTextBox searchTextBox = null!; + private DrawableCollectionList list = null!; + [Resolved] private MusicController? musicController { get; set; } @@ -104,10 +108,29 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.Both, Colour = colours.GreySeaFoamDarker }, - new DrawableCollectionList + new FillFlowContainer { RelativeSizeAxes = Axes.Both, - } + Direction = FillDirection.Vertical, + Padding = new MarginPadding(10), + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + searchTextBox = new BasicSearchTextBox + { + RelativeSizeAxes = Axes.X, + Y = 10, + Height = 40, + ReleaseFocusOnCommit = false, + HoldFocus = true, + PlaceholderText = HomeStrings.SearchPlaceholder, + }, + list = new DrawableCollectionList + { + RelativeSizeAxes = Axes.Both, + } + } + }, } } }, @@ -117,6 +140,16 @@ namespace osu.Game.Collections }; } + protected override void LoadComplete() + { + base.LoadComplete(); + + searchTextBox.Current.BindValueChanged(_ => + { + list.SearchTerm = searchTextBox.Current.Value; + }); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From ebc2cc35700415a2b242c03f65880d7371278462 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Nov 2024 16:51:34 +0900 Subject: [PATCH 063/139] Ensure cleanup tasks are run even on a cancelled / exceptioned import task --- osu.Game/Database/ModelDownloader.cs | 25 ++-- .../Database/RealmArchiveModelImporter.cs | 108 +++++++++--------- 2 files changed, 72 insertions(+), 61 deletions(-) diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index 8aece748a8..dfeec259fe 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -68,18 +68,23 @@ namespace osu.Game.Database { Task.Factory.StartNew(async () => { - bool importSuccessful; + bool importSuccessful = false; - if (originalModel != null) - importSuccessful = (await importer.ImportAsUpdate(notification, new ImportTask(filename), originalModel).ConfigureAwait(false)) != null; - else - importSuccessful = (await importer.Import(notification, new[] { new ImportTask(filename) }).ConfigureAwait(false)).Any(); + try + { + if (originalModel != null) + importSuccessful = (await importer.ImportAsUpdate(notification, new ImportTask(filename), originalModel).ConfigureAwait(false)) != null; + else + importSuccessful = (await importer.Import(notification, new[] { new ImportTask(filename) }).ConfigureAwait(false)).Any(); + } + finally + { + // for now a failed import will be marked as a failed download for simplicity. + if (!importSuccessful) + DownloadFailed?.Invoke(request); - // for now a failed import will be marked as a failed download for simplicity. - if (!importSuccessful) - DownloadFailed?.Invoke(request); - - CurrentDownloads.Remove(request); + CurrentDownloads.Remove(request); + } }, TaskCreationOptions.LongRunning); }; diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 22bcc622e9..e538530b79 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -117,67 +117,73 @@ namespace osu.Game.Database await pauseIfNecessaryAsync(parameters, notification, notification.CancellationToken).ConfigureAwait(false); - await Parallel.ForEachAsync(tasks, notification.CancellationToken, async (task, cancellation) => + try { - cancellation.ThrowIfCancellationRequested(); - - try + await Parallel.ForEachAsync(tasks, notification.CancellationToken, async (task, cancellation) => { - await pauseIfNecessaryAsync(parameters, notification, cancellation).ConfigureAwait(false); + cancellation.ThrowIfCancellationRequested(); - var model = await Import(task, parameters, cancellation).ConfigureAwait(false); - - lock (imported) + try { - if (model != null) - imported.Add(model); - current++; + await pauseIfNecessaryAsync(parameters, notification, cancellation).ConfigureAwait(false); - notification.Text = $"Imported {current} of {tasks.Length} {HumanisedModelName}s"; - notification.Progress = (float)current / tasks.Length; + var model = await Import(task, parameters, cancellation).ConfigureAwait(false); + + lock (imported) + { + if (model != null) + imported.Add(model); + current++; + + notification.Text = $"Imported {current} of {tasks.Length} {HumanisedModelName}s"; + notification.Progress = (float)current / tasks.Length; + } + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception e) + { + Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database); + } + }).ConfigureAwait(false); + } + finally + { + if (imported.Count == 0) + { + if (notification.CancellationToken.IsCancellationRequested) + { + notification.State = ProgressNotificationState.Cancelled; + } + else + { + notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import failed! Check logs for more information."; + notification.State = ProgressNotificationState.Cancelled; } } - catch (OperationCanceledException) - { - throw; - } - catch (Exception e) - { - Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database); - } - }).ConfigureAwait(false); - - if (imported.Count == 0) - { - if (notification.CancellationToken.IsCancellationRequested) - { - notification.State = ProgressNotificationState.Cancelled; - return imported; - } - - notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import failed! Check logs for more information."; - notification.State = ProgressNotificationState.Cancelled; - } - else - { - if (tasks.Length > imported.Count) - notification.CompletionText = $"Imported {imported.Count} of {tasks.Length} {HumanisedModelName}s."; - else if (imported.Count > 1) - notification.CompletionText = $"Imported {imported.Count} {HumanisedModelName}s!"; else - notification.CompletionText = $"Imported {imported.First().GetDisplayString()}!"; - - if (imported.Count > 0 && PresentImport != null) { - notification.CompletionText += " Click to view."; - notification.CompletionClickAction = () => - { - PresentImport?.Invoke(imported); - return true; - }; - } + if (tasks.Length > imported.Count) + notification.CompletionText = $"Imported {imported.Count} of {tasks.Length} {HumanisedModelName}s."; + else if (imported.Count > 1) + notification.CompletionText = $"Imported {imported.Count} {HumanisedModelName}s!"; + else + notification.CompletionText = $"Imported {imported.First().GetDisplayString()}!"; - notification.State = ProgressNotificationState.Completed; + if (imported.Count > 0 && PresentImport != null) + { + notification.CompletionText += " Click to view."; + notification.CompletionClickAction = () => + { + PresentImport?.Invoke(imported); + return true; + }; + } + + notification.State = ProgressNotificationState.Completed; + } } return imported; From 02deb393351e94e6778281e76bf0a345b0cb70a2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 18 Nov 2024 17:20:11 +0900 Subject: [PATCH 064/139] Make android build on non-maui workflow --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb45447ed5..d75f09f184 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,7 +114,7 @@ jobs: dotnet-version: "8.0.x" - name: Install .NET workloads - run: dotnet workload install maui-android + run: dotnet workload install android - name: Compile run: dotnet build -c Debug osu.Android.slnf From dcf4674c6c6ac567d80ecdd0145fb8080012e89e Mon Sep 17 00:00:00 2001 From: StanR Date: Mon, 18 Nov 2024 14:01:17 +0500 Subject: [PATCH 065/139] 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 4066186b24efaf90333b63298c8b23856b419711 Mon Sep 17 00:00:00 2001 From: StanR Date: Mon, 18 Nov 2024 14:48:51 +0500 Subject: [PATCH 066/139] 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 067/139] 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 068/139] 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 15a8cfe685a7e16381ed458fb5cdbe1b90c37f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Nov 2024 14:30:17 +0100 Subject: [PATCH 069/139] Fix mania notes disappearing on seek to their end time --- osu.Game.Rulesets.Mania/Edit/EditorColumn.cs | 45 +++++++++++++++++++ osu.Game.Rulesets.Mania/Edit/EditorStage.cs | 18 ++++++++ .../Edit/ManiaEditorPlayfield.cs | 3 ++ osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 6 ++- osu.Game.Rulesets.Mania/UI/Stage.cs | 16 ++++--- 5 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Edit/EditorColumn.cs create mode 100644 osu.Game.Rulesets.Mania/Edit/EditorStage.cs diff --git a/osu.Game.Rulesets.Mania/Edit/EditorColumn.cs b/osu.Game.Rulesets.Mania/Edit/EditorColumn.cs new file mode 100644 index 0000000000..11d1848173 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/EditorColumn.cs @@ -0,0 +1,45 @@ +// 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.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public partial class EditorColumn : Column + { + public EditorColumn(int index, bool isSpecial) + : base(index, isSpecial) + { + } + + protected override void OnNewDrawableHitObject(DrawableHitObject drawableHitObject) + { + base.OnNewDrawableHitObject(drawableHitObject); + drawableHitObject.ApplyCustomUpdateState += (dho, state) => + { + switch (dho) + { + // hold note heads are exempt from what follows due to the "freezing" mechanic + // which already ensures they'll never fade away on their own. + case DrawableHoldNoteHead: + break; + + // mania features instantaneous hitobject fade-outs. + // this means that without manual intervention stopping the clock at the precise time of hitting the object + // means the object will fade out. + // this is anti-user in editor contexts, as the user is expecting to continue the see the note on the receptor line. + // therefore, apply a crude workaround to prevent it from going away. + default: + { + if (state == ArmedState.Hit) + dho.FadeTo(1).Delay(1).FadeOut().Expire(); + break; + } + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/EditorStage.cs b/osu.Game.Rulesets.Mania/Edit/EditorStage.cs new file mode 100644 index 0000000000..c5f93f6182 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/EditorStage.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.UI; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public partial class EditorStage : Stage + { + public EditorStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction columnStartAction) + : base(firstColumnIndex, definition, ref columnStartAction) + { + } + + protected override Column CreateColumn(int index, bool isSpecial) => new EditorColumn(index, isSpecial); + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs b/osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs index 77e372d1d6..2dc2b8ae48 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs @@ -13,5 +13,8 @@ namespace osu.Game.Rulesets.Mania.Edit : base(stages) { } + + protected override Stage CreateStage(int firstColumnIndex, StageDefinition stageDefinition, ref ManiaAction columnAction) + => new EditorStage(firstColumnIndex, stageDefinition, ref columnAction); } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 1f388144bd..a4ebb3347a 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System; using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Mania.Beatmaps; @@ -71,7 +72,7 @@ namespace osu.Game.Rulesets.Mania.UI for (int i = 0; i < stageDefinitions.Count; i++) { - var newStage = new Stage(firstColumnIndex, stageDefinitions[i], ref columnAction); + var newStage = CreateStage(firstColumnIndex, stageDefinitions[i], ref columnAction); playfieldGrid.Content[0][i] = newStage; @@ -82,6 +83,9 @@ namespace osu.Game.Rulesets.Mania.UI } } + [Pure] + protected virtual Stage CreateStage(int firstColumnIndex, StageDefinition stageDefinition, ref ManiaAction columnAction) => new Stage(firstColumnIndex, stageDefinition, ref columnAction); + public override void Add(HitObject hitObject) => getStageByColumn(((ManiaHitObject)hitObject).Column).Add(hitObject); public override bool Remove(HitObject hitObject) => getStageByColumn(((ManiaHitObject)hitObject).Column).Remove(hitObject); diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index 86f2243561..9fb77a4995 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; @@ -134,12 +135,14 @@ namespace osu.Game.Rulesets.Mania.UI { bool isSpecial = definition.IsSpecialColumn(i); - var column = new Column(firstColumnIndex + i, isSpecial) + var action = columnStartAction; + columnStartAction++; + var column = CreateColumn(firstColumnIndex + i, isSpecial).With(c => { - RelativeSizeAxes = Axes.Both, - Width = 1, - Action = { Value = columnStartAction++ } - }; + c.RelativeSizeAxes = Axes.Both; + c.Width = 1; + c.Action.Value = action; + }); topLevelContainer.Add(column.TopLevelContainer.CreateProxy()); columnBackgrounds.Add(column.BackgroundContainer.CreateProxy()); @@ -154,6 +157,9 @@ namespace osu.Game.Rulesets.Mania.UI RegisterPool(50, 200); } + [Pure] + protected virtual Column CreateColumn(int index, bool isSpecial) => new Column(index, isSpecial); + [BackgroundDependencyLoader] private void load(ISkinSource skin) { From 259e9ecf65704068e0745afbb6c6d312a90f5339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Nov 2024 15:06:13 +0100 Subject: [PATCH 070/139] Deactivate new combo toggle on deselecting objects Closes https://github.com/ppy/osu/issues/30713. Was a point of discussion doing review: https://github.com/ppy/osu/pull/30214#discussion_r1798833139 Given it got pointed out immediately for something so minor, I'm inclined to believe it's a rather undesirable change. --- .../Screens/Edit/Compose/Components/EditorSelectionHandler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index a9dbfc29a9..6724a1dc4d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -258,8 +258,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void resetTernaryStates() { - if (SelectionNewComboState.Value == TernaryState.Indeterminate) - SelectionNewComboState.Value = TernaryState.False; + SelectionNewComboState.Value = TernaryState.False; AutoSelectionBankEnabled.Value = true; SelectionAdditionBanksEnabled.Value = true; SelectionBankStates[HIT_BANK_AUTO].Value = TernaryState.True; From 22b082d968d7d4179de0d0350867a0c0f187f377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 18 Nov 2024 15:28:30 +0100 Subject: [PATCH 071/139] Fix not being able to scroll to new collection text box when list overflows --- osu.Game/Collections/DrawableCollectionList.cs | 6 ++++++ osu.Game/Collections/ManageCollectionsDialog.cs | 8 +++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index f00691ba0f..85af1d383d 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -21,6 +21,12 @@ namespace osu.Game.Collections /// public partial class DrawableCollectionList : OsuRearrangeableListContainer> { + public new MarginPadding Padding + { + get => base.Padding; + set => base.Padding = value; + } + protected override ScrollContainer CreateScrollContainer() => scroll = new Scroll(); [Resolved] diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 90b73bc6b2..a738ae66cb 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -108,12 +108,10 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.Both, Colour = colours.GreySeaFoamDarker }, - new FillFlowContainer + new Container { RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, Padding = new MarginPadding(10), - Spacing = new Vector2(0, 10), Children = new Drawable[] { searchTextBox = new BasicSearchTextBox @@ -127,6 +125,10 @@ namespace osu.Game.Collections }, list = new DrawableCollectionList { + Padding = new MarginPadding + { + Top = 60, + }, RelativeSizeAxes = Axes.Both, } } From a2af4cbb50f04cd528d01f3c5becee0daeadd080 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Nov 2024 23:33:48 +0900 Subject: [PATCH 072/139] Fix right click scroll at song select not quite matching scrollbar position Closes https://github.com/ppy/osu/issues/30744. --- osu.Game/Graphics/Containers/OsuScrollContainer.cs | 13 +++++++++---- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index ffd28957ef..a3cd5a4902 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -42,8 +42,6 @@ namespace osu.Game.Graphics.Containers /// public double DistanceDecayOnRightMouseScrollbar = 0.02; - private bool shouldPerformRightMouseScroll(MouseButtonEvent e) => RightMouseScrollbar && e.Button == MouseButton.Right; - private bool rightMouseDragging; protected override bool IsDragging => base.IsDragging || rightMouseDragging; @@ -126,8 +124,15 @@ namespace osu.Game.Graphics.Containers return base.OnScroll(e); } - protected virtual void ScrollFromMouseEvent(MouseEvent e) => - ScrollTo(Clamp(ToLocalSpace(e.ScreenSpaceMousePosition)[ScrollDim] / DrawSize[ScrollDim] * Content.DrawSize[ScrollDim]), true, DistanceDecayOnRightMouseScrollbar); + protected virtual void ScrollFromMouseEvent(MouseEvent e) + { + float fromScrollbarPosition = FromScrollbarPosition(ToLocalSpace(e.ScreenSpaceMousePosition)[ScrollDim]); + float scrollbarCentreOffset = FromScrollbarPosition(Scrollbar.DrawHeight) * 0.5f; + + ScrollTo(Clamp(fromScrollbarPosition - scrollbarCentreOffset), true, DistanceDecayOnRightMouseScrollbar); + } + + private bool shouldPerformRightMouseScroll(MouseButtonEvent e) => RightMouseScrollbar && e.Button == MouseButton.Right; protected override ScrollbarContainer CreateScrollbar(Direction direction) => new OsuScrollbar(direction); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 95268c35da..5e1e0ce615 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -245,8 +245,7 @@ namespace osu.Game.Screens.Select config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled); - RightClickScrollingEnabled.ValueChanged += enabled => Scroll.RightMouseScrollbar = enabled.NewValue; - RightClickScrollingEnabled.TriggerChange(); + RightClickScrollingEnabled.BindValueChanged(enabled => Scroll.RightMouseScrollbar = enabled.NewValue, true); if (detachedBeatmapStore != null && detachedBeatmapSets == null) { From 16158710f9567a1641c272dd21f6d70a33d8338e Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Mon, 18 Nov 2024 23:48:26 +0100 Subject: [PATCH 073/139] Made reading of ReadCurrentDistanceSnap public --- osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs | 2 +- osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs | 2 +- osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs b/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs index 6f5b32a41d..ae4025aa2f 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Catch.Edit { public partial class CatchDistanceSnapProvider : ComposerDistanceSnapProvider { - protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after) + public override double ReadCurrentDistanceSnap(HitObject before, HitObject after) { // osu!catch's distance snap implementation is limited, in that a custom spacing cannot be specified. // Therefore this functionality is not currently used. diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs index 522943df7d..4042cfa0e2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuDistanceSnapProvider : ComposerDistanceSnapProvider { - protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after) + public override double ReadCurrentDistanceSnap(HitObject before, HitObject after) { float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime()); float actualDistance = Vector2.Distance(((OsuHitObject)before).EndPosition, ((OsuHitObject)after).Position); diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index cf41c8e108..5bf15aee8b 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -163,7 +163,7 @@ namespace osu.Game.Rulesets.Edit return (lastBefore, firstAfter); } - protected abstract double ReadCurrentDistanceSnap(HitObject before, HitObject after); + public abstract double ReadCurrentDistanceSnap(HitObject before, HitObject after); protected override void Update() { From 111f029ead5d5130904bcae1e463d0a330039eec Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Mon, 18 Nov 2024 23:53:01 +0100 Subject: [PATCH 074/139] Added a custom hitobject inspector for catch additionally show the x value of prev and next --- .../Edit/CatchHitObjectComposer.cs | 2 + .../Edit/CatchHitObjectInspector.cs | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 09d1ff663d..aae3369d40 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -70,6 +70,8 @@ namespace osu.Game.Rulesets.Catch.Edit })); } + protected override Drawable CreateHitObjectInspector() => new CatchHitObjectInspector(DistanceSnapProvider); + protected override IEnumerable CreateTernaryButtons() => base.CreateTernaryButtons() .Concat(DistanceSnapProvider.CreateTernaryButtons()); diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs new file mode 100644 index 0000000000..6ff7e9c6a0 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs @@ -0,0 +1,43 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Catch.Edit +{ + public partial class CatchHitObjectInspector(CatchDistanceSnapProvider snapProvider) : HitObjectInspector + { + protected override void AddInspectorValues(HitObject[] objects) + { + base.AddInspectorValues(objects); + + if (objects.Length > 0) + { + HitObject firstSelectedHitObject = objects.MinBy(ho => ho.StartTime)!; + HitObject lastSelectedHitObject = objects.MaxBy(ho => ho.GetEndTime())!; + + HitObject? precedingObject = EditorBeatmap.HitObjects.LastOrDefault(ho => ho.GetEndTime() < firstSelectedHitObject.StartTime); + HitObject? nextObject = EditorBeatmap.HitObjects.FirstOrDefault(ho => ho.StartTime > lastSelectedHitObject.GetEndTime()); + + if (precedingObject != null || nextObject != null) + { + AddHeader("Snapping"); + } + + if (precedingObject != null) + { + double previousSnap = snapProvider.ReadCurrentDistanceSnap(precedingObject, firstSelectedHitObject); + AddValue($"Previous: {previousSnap:#,0.##}x"); + } + + if (nextObject != null) + { + double nextSnap = snapProvider.ReadCurrentDistanceSnap(lastSelectedHitObject, nextObject); + AddValue($"Next: {nextSnap:#,0.##}x"); + } + } + } + } +} From a5327aa5627c42de00de135a1d1928cd044c44bb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 19 Nov 2024 18:48:30 +0900 Subject: [PATCH 075/139] Use properties instead of fields --- .../Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index 8ddda485b5..be6ca43f4b 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -19,12 +19,12 @@ namespace osu.Game.Beatmaps.Drawables /// /// Delay before the background is loaded while on-screen. /// - public double BackgroundLoadDelay = 500; + public double BackgroundLoadDelay { get; set; } = 500; /// /// Delay before the background is unloaded while off-screen. /// - public double BackgroundUnloadDelay = 10000; + public double BackgroundUnloadDelay { get; set; } = 10000; [Resolved] private BeatmapManager beatmaps { get; set; } = null!; From a8e14b6625552e35687bb6bf347898ad5deb552e Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Tue, 19 Nov 2024 19:54:12 +0100 Subject: [PATCH 076/139] Align inspector info more to how it is shown for osu --- .../Edit/CatchHitObjectInspector.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs index 6ff7e9c6a0..c279354d9a 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs @@ -21,21 +21,18 @@ namespace osu.Game.Rulesets.Catch.Edit HitObject? precedingObject = EditorBeatmap.HitObjects.LastOrDefault(ho => ho.GetEndTime() < firstSelectedHitObject.StartTime); HitObject? nextObject = EditorBeatmap.HitObjects.FirstOrDefault(ho => ho.StartTime > lastSelectedHitObject.GetEndTime()); - if (precedingObject != null || nextObject != null) - { - AddHeader("Snapping"); - } - if (precedingObject != null) { double previousSnap = snapProvider.ReadCurrentDistanceSnap(precedingObject, firstSelectedHitObject); - AddValue($"Previous: {previousSnap:#,0.##}x"); + AddHeader("To previous"); + AddValue($"{previousSnap:#,0.##}x"); } if (nextObject != null) { double nextSnap = snapProvider.ReadCurrentDistanceSnap(lastSelectedHitObject, nextObject); - AddValue($"Next: {nextSnap:#,0.##}x"); + AddHeader("To next"); + AddValue($"{nextSnap:#,0.##}x"); } } } From 9ac877606b29099112c7b5058833bb408474858b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 20 Nov 2024 12:32:42 +0900 Subject: [PATCH 077/139] Fix inspection issues --- .../TestScenePlaylistsSongSelect.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index fea6617d3f..b55102518a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -74,14 +74,14 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestItemAddedWhenCreateNewItemClicked() { - AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); + AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); } [Test] public void TestItemNotAddedIfExistingOnStart() { - AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); + AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddStep("finalise selection", () => songSelect.FinaliseSelection()); AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); } @@ -89,19 +89,19 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestAddSameItemMultipleTimes() { - AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); + AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); + AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddAssert("playlist has 2 items", () => SelectedRoom.Value.Playlist.Count == 2); } [Test] public void TestAddItemAfterRearrangement() { - AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); - AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); + AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); + AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddStep("rearrange", () => SelectedRoom.Value.Playlist = SelectedRoom.Value.Playlist.Skip(1).Append(SelectedRoom.Value.Playlist[0]).ToArray()); - AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem()); + AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddAssert("new item has id 2", () => SelectedRoom.Value.Playlist.Last().ID == 2); } @@ -112,9 +112,9 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestNewItemHasNewModInstances() { AddStep("set dt mod", () => SelectedMods.Value = new[] { new OsuModDoubleTime() }); - AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem()); + AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddStep("change mod rate", () => ((OsuModDoubleTime)SelectedMods.Value[0]).SpeedChange.Value = 2); - AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem()); + AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddAssert("item 1 has rate 1.5", () => { @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Multiplayer mod = (OsuModDoubleTime)SelectedMods.Value[0]; }); - AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem()); + AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddStep("change stored mod rate", () => mod.SpeedChange.Value = 2); AddAssert("item has rate 1.5", () => From b4077fc8a2afa17cccfd43f9f8d8d4b507a1f55e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Nov 2024 16:28:11 +0900 Subject: [PATCH 078/139] Use `!FrameworkEnvironment.UseSDL3` instead of removing warning altogether --- .../Settings/Sections/Graphics/LayoutSettings.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index b4b5e12922..4362f383ee 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -268,8 +268,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private void updateScreenModeWarning() { - if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS) + // Can be removed once we stop supporting SDL2. + if (!FrameworkEnvironment.UseSDL3) + { + if (windowModeDropdown.Current.Value == WindowMode.Fullscreen) + windowModeDropdown.SetNoticeText(LayoutSettingsStrings.FullscreenMacOSNote, true); + else + windowModeDropdown.ClearNoticeText(); + return; + } if (windowModeDropdown.Current.Value != WindowMode.Fullscreen) { From 50089c027eb9d4c1c1f57314d9ff1e252387f616 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Nov 2024 16:40:33 +0900 Subject: [PATCH 079/139] 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 f271bdfaaf..4699beeac0 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 ecb9ae2c8d..ccae4a15ee 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 03de51848437b2fbadb028d2a414de8c936dc6e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Nov 2024 16:56:22 +0900 Subject: [PATCH 080/139] Fix missing `updateRoomPlaylist` call --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index c82317add4..f915676fb1 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -65,6 +65,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Room.PropertyChanged += onRoomPropertyChanged; updateSetupState(); updateRoomMaxAttempts(); + updateRoomPlaylist(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) From c6d08daee84e188f6fe51c95583869e16b42f209 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Nov 2024 17:11:49 +0900 Subject: [PATCH 081/139] Remove `MultiplayerMatchSubScreen` flaky test attribute Didn't really work to fix these tests due to the sticky nature of the failure. Also I can no longer reproduce locally, so the hope is that these are fixed by https://github.com/ppy/osu/pull/30634. --- .../TestSceneMultiplayerMatchSubScreen.cs | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index ef5ff25194..abcb591cf6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -77,25 +77,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] - /* - * Fail rate around 1.5% - * - * TearDown : System.AggregateException : One or more errors occurred. (Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')) - ----> System.ArgumentOutOfRangeException : Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index') - * --TearDown - * at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) - * at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) - * at osu.Framework.Extensions.TaskExtensions.WaitSafely(Task task) - * at osu.Framework.Testing.TestScene.checkForErrors() - * at osu.Framework.Testing.TestScene.RunTestsFromNUnit() - *--ArgumentOutOfRangeException - * at osu.Framework.Bindables.BindableList`1.removeAt(Int32 index, BindableList`1 caller) - * at osu.Framework.Bindables.BindableList`1.removeAt(Int32 index, BindableList`1 caller) - * at osu.Framework.Bindables.BindableList`1.removeAt(Int32 index, BindableList`1 caller) - * at osu.Game.Online.Multiplayer.MultiplayerClient.<>c__DisplayClass106_0.b__0() in C:\BuildAgent\work\ecd860037212ac52\osu.Game\Online\Multiplayer\MultiplayerClient .cs:line 702 - * at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() - */ public void TestCreatedRoom() { AddStep("add playlist item", () => @@ -115,7 +96,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestTaikoOnlyMod() { AddStep("add playlist item", () => @@ -139,7 +119,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestSettingValidity() { AddAssert("create button not enabled", () => !this.ChildrenOfType().Single().Enabled.Value); @@ -159,7 +138,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestStartMatchWhileSpectating() { AddStep("set playlist", () => @@ -191,7 +169,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestFreeModSelectionHasAllowedMods() { AddStep("add playlist item with allowed mod", () => @@ -221,7 +198,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestModSelectKeyWithAllowedMods() { AddStep("add playlist item with allowed mod", () => @@ -246,7 +222,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestModSelectKeyWithNoAllowedMods() { AddStep("add playlist item with no allowed mods", () => @@ -270,7 +245,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestNextPlaylistItemSelectedAfterCompletion() { AddStep("add two playlist items", () => @@ -307,7 +281,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestModSelectOverlay() { AddStep("add playlist item", () => From 7726ca02b093df019fa56424563417507bf2b760 Mon Sep 17 00:00:00 2001 From: Fivoka <42149652+Fivoka@users.noreply.github.com> Date: Wed, 20 Nov 2024 12:29:29 +0100 Subject: [PATCH 082/139] Changed multiplier from 2 to 3.5 --- osu.Game/Screens/Play/BreakOverlay.cs | 2 +- osu.Game/Screens/Play/SkipOverlay.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 1fdb9402bc..550d29965f 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -166,7 +166,7 @@ namespace osu.Game.Screens.Play return; float timeBoxTargetWidth = (float)Math.Max(0, (remainingTimeForCurrentPeriod - timingPoint.BeatLength / currentPeriod.Value.Value.Duration)); - remainingTimeBox.ResizeWidthTo(timeBoxTargetWidth, timingPoint.BeatLength * 2, Easing.OutQuint); + remainingTimeBox.ResizeWidthTo(timeBoxTargetWidth, timingPoint.BeatLength * 3.5, Easing.OutQuint); } private void updateDisplay(ValueChangedEvent period) diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 362677ca5c..be8517d9a0 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -219,7 +219,7 @@ namespace osu.Game.Screens.Play float progress = (float)(gameplayClock.CurrentTime - displayTime) / (float)(fadeOutBeginTime - displayTime); float newWidth = 1 - Math.Clamp(progress, 0, 1); - remainingTimeBox.ResizeWidthTo(newWidth, timingPoint.BeatLength * 2, Easing.OutQuint); + remainingTimeBox.ResizeWidthTo(newWidth, timingPoint.BeatLength * 3.5, Easing.OutQuint); } public partial class FadeContainer : Container, IStateful From dfa32302acf14ab470e0827d5efbf7bdeed09d8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 20 Nov 2024 13:16:37 +0100 Subject: [PATCH 083/139] Add test case covering stability of compatibility export operation This is important as the format will be used more when lazer beatmap submission comes online, and its stability is a useful property for that. Included archive contains an `.osu` with a few fractional-millisecond timing points and objects, as well as a multi-segment-type slider. That should cover the range of all possible modifications that the compatibility exporter currently performs. --- .../Beatmaps/IO/LegacyBeatmapExporterTest.cs | 43 ++++++++++++++++++ .../Archives/legacy-export-stability-test.olz | Bin 0 -> 946 bytes 2 files changed, 43 insertions(+) create mode 100644 osu.Game.Tests/Resources/Archives/legacy-export-stability-test.olz diff --git a/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs b/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs index 9947def06d..8a95d26782 100644 --- a/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.IO; +using System.Text; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions; @@ -9,6 +10,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.IO.Archives; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual; using MemoryStream = System.IO.MemoryStream; @@ -48,6 +50,47 @@ namespace osu.Game.Tests.Beatmaps.IO AddAssert("hit object is snapped", () => beatmap.Beatmap.HitObjects[0].StartTime, () => Is.EqualTo(28519).Within(0.001)); } + [Test] + public void TestExportStability() + { + IWorkingBeatmap beatmap = null!; + MemoryStream firstExport = null!; + MemoryStream secondExport = null!; + + // Ensure importer encoding is correct + AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"legacy-export-stability-test.olz")); + AddStep("export once", () => + { + firstExport = new MemoryStream(); + + new LegacyBeatmapExporter(LocalStorage) + .ExportToStream((BeatmapSetInfo)beatmap.BeatmapInfo.BeatmapSet!, firstExport, null); + }); + + AddStep("import beatmap again", () => beatmap = importBeatmapFromStream(firstExport)); + AddStep("export again", () => + { + secondExport = new MemoryStream(); + + new LegacyBeatmapExporter(LocalStorage) + .ExportToStream((BeatmapSetInfo)beatmap.BeatmapInfo.BeatmapSet!, secondExport, null); + }); + + const string osu_filename = @"legacy export - stability test (spaceman_atlas) [].osu"; + + AddAssert("exports are identical", + () => getStringContentsOf(osu_filename, firstExport.GetBuffer()), + () => Is.EqualTo(getStringContentsOf(osu_filename, secondExport.GetBuffer()))); + + string getStringContentsOf(string filename, byte[] archiveBytes) + { + using var memoryStream = new MemoryStream(archiveBytes); + using var archiveReader = new ZipArchiveReader(memoryStream); + byte[] fileContent = archiveReader.GetStream(filename).ReadAllBytesToArray(); + return Encoding.UTF8.GetString(fileContent); + } + } + private IWorkingBeatmap importBeatmapFromStream(Stream stream) { var imported = beatmaps.Import(new ImportTask(stream, "filename.osz")).GetResultSafely(); diff --git a/osu.Game.Tests/Resources/Archives/legacy-export-stability-test.olz b/osu.Game.Tests/Resources/Archives/legacy-export-stability-test.olz new file mode 100644 index 0000000000000000000000000000000000000000..c6cf33acaf8587cbb040413f5c39d9cc7544d913 GIT binary patch literal 946 zcmWIWW@Zs#-~hsd86}YnP+-Brz+lFpz>t%go|s&zkXliYUsR%?t595$n3S25SyHJ` zl3HA%pix|qn4FrMm=~W|l9O1hsSq8jmtR~O8p6xKzUqQ)It-Uqa5FHnykKTv028T! z(RsHG1pYkNEMaJU@O$BYn=X}xyAq|hn0R}drp;*S)sYl=e5Ez-TEE=N@E4cXB`;xJ zD7tmCwe{v-e?N!Y+lYT(`1NS5e7K&d^eG;v^6C3NU);)F^6O4Z-J{szyP>R(n_rk8 z+<&KS>iRC>u5-Vi>zV%LZV)+pH|y@No&KR8?KW@Cm>2Wcz2@`TCrogcSbRoy=uEWiEqy`#@g*!aY*l22cITd(n3v$tLHi+8cY z3#uCR4o!#&|NZmU)rHk39?UF%sg!f*!tqrCFO1sK{#&1YYKwZk z^B5zt7IM6pwvab7TE%`z?YCs{S94qI4_uy+P!qc4-@&W557ozi|2U!cZ6)`-=J!)t zc@=J~kNJLe={4RLllz@tBraX>Qt8h9_Fp@FZ?o>273(6tYj0uaIp;Nrcdf03?&f^r z&+h+fS-C6j_`R$B?b9l`x%+|}4)5ZXa|_n_mzIBc_T0Rh>20ic68txuiIgw9Kc%p7 zsn*tQGYpy5*FIUQk?HB>Ba;3&=Sfro^OHS~c3XZ}?UrhAJ<6%=@Vm~Z0j(=HPU`9s zZfg~2Yc%%K&8fd5Df7@$>MwJEH#5M?w0;DB?8Hj-a0N_@bl>h($ literal 0 HcmV?d00001 From 1ef02fec062a85737589f9f64f291e094e791ef8 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 20 Nov 2024 12:08:57 -0500 Subject: [PATCH 084/139] Fix "positional hitsounds level" setting not specifying a precision constraint --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index af6fd61a3d..f922981d77 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -132,7 +132,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.Prefer24HourTime, !CultureInfoHelper.SystemCulture.DateTimeFormat.ShortTimePattern.Contains(@"tt")); // Gameplay - SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.2f, 0, 1); + SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.2f, 0, 1, 0.01f); SetDefault(OsuSetting.DimLevel, 0.7, 0, 1, 0.01); SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01); SetDefault(OsuSetting.LightenDuringBreaks, true); From 40b95901e3253bf17f668d46df5c46ca8e469b8e Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 20 Nov 2024 12:11:38 -0500 Subject: [PATCH 085/139] Fix more cases of settings with no defined precision values --- osu.Game/Configuration/OsuConfigManager.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index f922981d77..362c06849d 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -54,7 +54,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Sequential); SetDefault(OsuSetting.ModSelectTextSearchStartsActive, true); - SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f); + SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f, 0.01f); SetDefault(OsuSetting.BeatmapListingCardSize, BeatmapCardSize.Normal); @@ -169,13 +169,13 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.Scaling, ScalingMode.Off); SetDefault(OsuSetting.SafeAreaConsiderations, true); - SetDefault(OsuSetting.ScalingBackgroundDim, 0.9f, 0.5f, 1f); + SetDefault(OsuSetting.ScalingBackgroundDim, 0.9f, 0.5f, 1f, 0.01f); - SetDefault(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f); - SetDefault(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f); + SetDefault(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f, 0.01f); + SetDefault(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f, 0.01f); - SetDefault(OsuSetting.ScalingPositionX, 0.5f, 0f, 1f); - SetDefault(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f); + SetDefault(OsuSetting.ScalingPositionX, 0.5f, 0f, 1f, 0.01f); + SetDefault(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f, 0.01f); SetDefault(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f); From 945139635f99b827e405273dad54e80f2a606e3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2024 02:23:42 +0900 Subject: [PATCH 086/139] Add failing test case --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index cf813cfd51..182193714b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -18,6 +18,7 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; @@ -207,7 +208,25 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestBlockLoadViaFocus() + public void TestLoadNotBlockedViaArbitraryFocus() + { + AddStep("load dummy beatmap", () => resetPlayer(false)); + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); + + AddUntilStep("click settings slider", () => + { + InputManager.MoveMouseTo(loader.ChildrenOfType>().First()); + InputManager.Click(MouseButton.Left); + + return InputManager.FocusedDrawable is OsuSliderBar; + }); + + AddUntilStep("wait for load ready", () => player?.LoadState == LoadState.Ready); + AddUntilStep("loads", () => !loader.IsCurrentScreen()); + } + + [Test] + public void TestBlockLoadViaOverlayFocus() { AddStep("load dummy beatmap", () => resetPlayer(false)); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); From ae98f63b511870c11aad3d4955a1a5454a0aad58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2024 02:23:56 +0900 Subject: [PATCH 087/139] Fix beatmap load not continuing when when settings slider is focused Regressed with recent sliderbar focus changes. Closes #30716. --- osu.Game/Screens/Play/PlayerLoader.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index aedc268d70..3e36c630db 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -122,7 +122,9 @@ namespace osu.Game.Screens.Play // not ready if the user is dragging a slider or otherwise. && (inputManager.DraggedDrawable == null || inputManager.DraggedDrawable is OsuLogo) // not ready if a focused overlay is visible, like settings. - && inputManager.FocusedDrawable == null; + && inputManager.FocusedDrawable is not OsuFocusedOverlayContainer + // or if a child of a focused overlay is focused, like settings' search textbox. + && inputManager.FocusedDrawable?.FindClosestParent() == null; private readonly Func createPlayer; From 37394a5027db31440aa06f208d7563771f79b34d Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 21 Nov 2024 14:04:42 +1300 Subject: [PATCH 088/139] Use consistent decimal places in BeatmapAttributeText --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 79a1ed4d7c..f1c27434fa 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -211,19 +211,19 @@ namespace osu.Game.Skinning.Components return beatmap.Value.BeatmapInfo.Status.GetLocalisableDescription(); case BeatmapAttribute.BPM: - return FormatUtils.RoundBPM(beatmap.Value.BeatmapInfo.BPM, ModUtils.CalculateRateWithMods(mods.Value)).ToLocalisableString(@"F2"); + return FormatUtils.RoundBPM(beatmap.Value.BeatmapInfo.BPM, ModUtils.CalculateRateWithMods(mods.Value)).ToLocalisableString(@"0.##"); case BeatmapAttribute.CircleSize: - return computeDifficulty().CircleSize.ToLocalisableString(@"F2"); + return computeDifficulty().CircleSize.ToLocalisableString(@"0.##"); case BeatmapAttribute.HPDrain: - return computeDifficulty().DrainRate.ToLocalisableString(@"F2"); + return computeDifficulty().DrainRate.ToLocalisableString(@"0.##"); case BeatmapAttribute.Accuracy: - return computeDifficulty().OverallDifficulty.ToLocalisableString(@"F2"); + return computeDifficulty().OverallDifficulty.ToLocalisableString(@"0.##"); case BeatmapAttribute.ApproachRate: - return computeDifficulty().ApproachRate.ToLocalisableString(@"F2"); + return computeDifficulty().ApproachRate.ToLocalisableString(@"0.##"); case BeatmapAttribute.StarRating: return (starDifficulty?.Stars ?? 0).ToLocalisableString(@"F2"); From fe8e9d455a6a91f7e03a3be0c4331c6ab53849f0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Nov 2024 18:04:44 +0900 Subject: [PATCH 089/139] Add failing test --- .../TestScenePlaylistsRoomSubScreen.cs | 41 ++++++++++++++++++ .../Visual/OnlinePlay/TestRoomManager.cs | 42 +++++++------------ 2 files changed, 57 insertions(+), 26 deletions(-) create mode 100644 osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs new file mode 100644 index 0000000000..4ff8123fbb --- /dev/null +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.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 NUnit.Framework; +using osu.Framework.Screens; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Rooms; +using osu.Game.Online.Rooms.RoomStatuses; +using osu.Game.Screens.OnlinePlay.Playlists; +using osu.Game.Tests.Visual.OnlinePlay; + +namespace osu.Game.Tests.Visual.Playlists +{ + public class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene + { + protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; + + [Test] + public void TestStatusUpdateOnEnter() + { + Room room = null!; + PlaylistsRoomSubScreen roomScreen = null!; + + AddStep("create room", () => + { + RoomManager.AddRoom(room = new Room + { + Name = @"Test Room", + Host = new APIUser { Username = @"Host" }, + Category = RoomCategory.Normal, + EndDate = DateTimeOffset.Now.AddMinutes(-1) + }); + }); + + AddStep("push screen", () => LoadScreen(roomScreen = new PlaylistsRoomSubScreen(room))); + AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen()); + AddAssert("status is still ended", () => roomScreen.Room.Status, Is.TypeOf); + } + } +} diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index e813b49844..b1e3eafacc 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -29,38 +29,28 @@ namespace osu.Game.Tests.Visual.OnlinePlay { for (int i = 0; i < count; i++) { - var room = new Room + AddRoom(new Room { - RoomID = -currentRoomId, Name = $@"Room {currentRoomId}", Host = new APIUser { Username = @"Host" }, Duration = TimeSpan.FromSeconds(10), Category = withSpotlightRooms && i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal, - }; - - if (withPassword) - room.Password = @"password"; - - if (ruleset != null) - { - room.PlaylistItemStats = new Room.RoomPlaylistItemStats - { - RulesetIDs = new[] { ruleset.OnlineID }, - }; - - room.Playlist = - [ - new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() }) - { - RulesetID = ruleset.OnlineID, - } - ]; - } - - CreateRoom(room); - - currentRoomId++; + Password = withPassword ? @"password" : null, + PlaylistItemStats = ruleset == null + ? null + : new Room.RoomPlaylistItemStats { RulesetIDs = [ruleset.OnlineID] }, + Playlist = ruleset == null + ? Array.Empty() + : [new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() }) { RulesetID = ruleset.OnlineID }] + }); } } + + public void AddRoom(Room room) + { + room.RoomID = -currentRoomId; + CreateRoom(room); + currentRoomId++; + } } } From 7018672275fd5621d1ceb4ecf9a4411bbdc91fd7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Nov 2024 18:11:41 +0900 Subject: [PATCH 090/139] Fix playlist room status resetting on enter --- osu.Game/Online/Rooms/GetRoomsRequest.cs | 21 --------------------- osu.Game/Online/Rooms/Room.cs | 13 +++++++++++++ 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/osu.Game/Online/Rooms/GetRoomsRequest.cs b/osu.Game/Online/Rooms/GetRoomsRequest.cs index cd797a9668..7feb709acb 100644 --- a/osu.Game/Online/Rooms/GetRoomsRequest.cs +++ b/osu.Game/Online/Rooms/GetRoomsRequest.cs @@ -1,12 +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 osu.Framework.IO.Network; using osu.Game.Extensions; using osu.Game.Online.API; -using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Screens.OnlinePlay.Lounge.Components; namespace osu.Game.Online.Rooms @@ -35,25 +33,6 @@ namespace osu.Game.Online.Rooms return req; } - protected override void PostProcess() - { - base.PostProcess(); - - if (Response != null) - { - // API doesn't populate status so let's do it here. - foreach (var room in Response) - { - if (room.EndDate != null && DateTimeOffset.Now >= room.EndDate) - room.Status = new RoomStatusEnded(); - else if (room.HasPassword) - room.Status = new RoomStatusOpenPrivate(); - else - room.Status = new RoomStatusOpen(); - } - } - } - protected override string Target => "rooms"; } } diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index a26a3fcb34..486f70c0ed 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.Serialization; using Newtonsoft.Json; using osu.Game.IO.Serialization.Converters; using osu.Game.Online.API.Requests.Responses; @@ -264,6 +265,18 @@ namespace osu.Game.Online.Rooms set => SetField(ref availability, value); } + [OnDeserialized] + private void onDeserialised(StreamingContext context) + { + // API doesn't populate status so let's do it here. + if (EndDate != null && DateTimeOffset.Now >= EndDate) + Status = new RoomStatusEnded(); + else if (HasPassword) + Status = new RoomStatusOpenPrivate(); + else + Status = new RoomStatusOpen(); + } + [JsonProperty("id")] private long? roomId; From 033b7c17d556d390a55db99228705cdb16b4b0bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Nov 2024 18:13:00 +0900 Subject: [PATCH 091/139] Add back macOS precheck --- osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 4362f383ee..f40a4c941f 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -269,7 +269,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private void updateScreenModeWarning() { // Can be removed once we stop supporting SDL2. - if (!FrameworkEnvironment.UseSDL3) + if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS && !FrameworkEnvironment.UseSDL3) { if (windowModeDropdown.Current.Value == WindowMode.Fullscreen) windowModeDropdown.SetNoticeText(LayoutSettingsStrings.FullscreenMacOSNote, true); From 8dbf750446c2ccb23079b1923acffe944ee061fd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Nov 2024 18:38:56 +0900 Subject: [PATCH 092/139] Fix inspection --- .../Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index 4ff8123fbb..4306fc1e6a 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -12,7 +12,7 @@ using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Playlists { - public class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene + public partial class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene { protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; From 2138729c02fda9b619a3298a7ec3b63628141073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Nov 2024 12:17:43 +0100 Subject: [PATCH 093/139] Do not show distance to next/previous object if said object is a banana shower The results of such a display were a little bit nonsensical. --- osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs index c279354d9a..bfabcbdb83 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectInspector.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -21,14 +22,14 @@ namespace osu.Game.Rulesets.Catch.Edit HitObject? precedingObject = EditorBeatmap.HitObjects.LastOrDefault(ho => ho.GetEndTime() < firstSelectedHitObject.StartTime); HitObject? nextObject = EditorBeatmap.HitObjects.FirstOrDefault(ho => ho.StartTime > lastSelectedHitObject.GetEndTime()); - if (precedingObject != null) + if (precedingObject != null && precedingObject is not BananaShower) { double previousSnap = snapProvider.ReadCurrentDistanceSnap(precedingObject, firstSelectedHitObject); AddHeader("To previous"); AddValue($"{previousSnap:#,0.##}x"); } - if (nextObject != null) + if (nextObject != null && nextObject is not BananaShower) { double nextSnap = snapProvider.ReadCurrentDistanceSnap(lastSelectedHitObject, nextObject); AddHeader("To next"); From 6870c99eb2f1429500a68258daa647a3bb44f9db Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Nov 2024 20:26:44 +0900 Subject: [PATCH 094/139] Enable NRT for multiplayer and playlists --- .../StatefulMultiplayerClientTest.cs | 2 +- .../OnlinePlay/TestSceneCatchUpSyncManager.cs | 12 +- ...MultiplayerGameplayLeaderboardTestScene.cs | 10 +- .../TestSceneCreateMultiplayerMatchButton.cs | 6 +- .../TestSceneDrawableRoomParticipantsList.cs | 10 +- .../TestSceneDrawableRoomPlaylist.cs | 13 +- .../TestSceneFreeModSelectOverlay.cs | 11 +- .../TestSceneGameplayChatDisplay.cs | 4 +- .../Multiplayer/TestSceneHostOnlyQueueMode.cs | 12 +- .../TestSceneLoungeRoomsContainer.cs | 12 +- .../TestSceneMatchBeatmapDetailArea.cs | 2 +- .../TestSceneMultiSpectatorLeaderboard.cs | 10 +- .../TestSceneMultiSpectatorScreen.cs | 2 +- ...TestSceneMultiplayerGameplayLeaderboard.cs | 4 +- ...ceneMultiplayerGameplayLeaderboardTeams.cs | 2 +- .../TestSceneMultiplayerLoungeSubScreen.cs | 23 +-- .../TestSceneMultiplayerMatchSongSelect.cs | 30 ++- .../TestSceneMultiplayerMatchSubScreen.cs | 33 ++- .../TestSceneMultiplayerPlaylist.cs | 2 +- .../TestSceneMultiplayerQueueList.cs | 2 +- .../TestSceneMultiplayerResults.cs | 4 +- .../TestSceneMultiplayerSpectateButton.cs | 2 +- ...TestSceneMultiplayerSpectatorPlayerGrid.cs | 4 +- .../TestSceneMultiplayerTeamResults.cs | 4 +- .../TestScenePlaylistsRoomSettingsPlaylist.cs | 10 +- .../TestScenePlaylistsSongSelect.cs | 29 ++- .../TestSceneStarRatingRangeDisplay.cs | 2 +- .../TestScenePlaylistsLoungeSubScreen.cs | 6 +- .../TestScenePlaylistsMatchSettingsOverlay.cs | 26 +-- .../TestScenePlaylistsParticipantsList.cs | 4 +- .../TestScenePlaylistsResultsScreen.cs | 15 +- .../TestScenePlaylistsRoomCreation.cs | 8 +- .../Components/ListingPollingComponent.cs | 2 +- .../OnlinePlay/DrawableRoomPlaylist.cs | 12 +- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 192 ++++++++++-------- .../Lounge/Components/FilterCriteria.cs | 8 +- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 49 +++-- .../Match/Components/MatchLeaderboardScore.cs | 4 +- .../Match/Components/MatchTypePicker.cs | 9 +- .../Components/RoomAvailabilityPicker.cs | 4 +- .../OnlinePlay/Match/RoomModSelectOverlay.cs | 2 +- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 57 +++--- .../OnlinePlay/Match/UserModSelectButton.cs | 1 - .../CreateMultiplayerMatchButton.cs | 10 +- .../Multiplayer/GameplayChatDisplay.cs | 6 +- .../Match/MultiplayerCountdownButton.cs | 11 +- .../Match/MultiplayerReadyButton.cs | 28 +-- .../Multiplayer/MultiplayerLoungeSubScreen.cs | 12 +- .../Multiplayer/MultiplayerMatchSubScreen.cs | 4 +- .../Multiplayer/MultiplayerPlayerLoader.cs | 8 +- .../OnlinePlay/OngoingOperationTracker.cs | 4 +- .../Screens/OnlinePlay/OnlinePlayScreen.cs | 12 +- .../Playlists/PlaylistsRoomSubScreen.cs | 9 +- .../IOnlinePlayTestSceneDependencies.cs | 2 +- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 2 +- .../OnlinePlayTestSceneDependencies.cs | 4 +- 56 files changed, 357 insertions(+), 411 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index 2e9170019c..559db16751 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -73,7 +73,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer AddStep("create room initially in gameplay", () => { var newRoom = new Room(); - newRoom.CopyFrom(SelectedRoom.Value); + newRoom.CopyFrom(SelectedRoom.Value!); newRoom.RoomID = null; MultiplayerClient.RoomSetupAction = room => diff --git a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs index 7b0b211899..8f6325c70b 100644 --- a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs +++ b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.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 System.Collections.Generic; using NUnit.Framework; @@ -21,12 +19,12 @@ namespace osu.Game.Tests.OnlinePlay [HeadlessTest] public partial class TestSceneCatchUpSyncManager : OsuTestScene { - private GameplayClockContainer master; - private SpectatorSyncManager syncManager; + private GameplayClockContainer master = null!; + private SpectatorSyncManager syncManager = null!; - private Dictionary clocksById; - private SpectatorPlayerClock player1; - private SpectatorPlayerClock player2; + private Dictionary clocksById = null!; + private SpectatorPlayerClock player1 = null!; + private SpectatorPlayerClock player2 = null!; [SetUp] public void Setup() diff --git a/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs index 906eea9553..1eb08ad3c8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.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 System.Collections.Generic; using System.Collections.Specialized; @@ -31,7 +29,7 @@ namespace osu.Game.Tests.Visual.Multiplayer protected readonly BindableList MultiplayerUsers = new BindableList(); - protected MultiplayerGameplayLeaderboard Leaderboard { get; private set; } + protected MultiplayerGameplayLeaderboard? Leaderboard { get; private set; } protected virtual MultiplayerRoomUser CreateUser(int userId) => new MultiplayerRoomUser(userId); @@ -40,7 +38,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private readonly BindableList multiplayerUserIds = new BindableList(); private readonly BindableDictionary watchedUserStates = new BindableDictionary(); - private OsuConfigManager config; + private OsuConfigManager config = null!; private readonly Mock spectatorClient = new Mock(); private readonly Mock multiplayerClient = new Mock(); @@ -133,7 +131,7 @@ namespace osu.Game.Tests.Visual.Multiplayer LoadComponentAsync(Leaderboard = CreateLeaderboard(), Add); }); - AddUntilStep("wait for load", () => Leaderboard.IsLoaded); + AddUntilStep("wait for load", () => Leaderboard!.IsLoaded); AddStep("check watch requests were sent", () => { @@ -146,7 +144,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestScoreUpdates() { AddRepeatStep("update state", UpdateUserStatesRandomly, 100); - AddToggleStep("switch compact mode", expanded => Leaderboard.Expanded.Value = expanded); + AddToggleStep("switch compact mode", expanded => Leaderboard!.Expanded.Value = expanded); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.cs index 11b0f8b91c..6d6d30d517 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.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.Graphics; @@ -12,7 +10,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneCreateMultiplayerMatchButton : MultiplayerTestScene { - private CreateMultiplayerMatchButton button; + private CreateMultiplayerMatchButton button = null!; public override void SetUpSteps() { @@ -29,7 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestButtonEnableStateChanges() { - IDisposable joiningRoomOperation = null; + IDisposable joiningRoomOperation = null!; assertButtonEnableState(true); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs index b909b934b3..c1662bf944 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("4 circles displayed", () => list.ChildrenOfType().Count() == 4); AddAssert("46 hidden users", () => list.ChildrenOfType().Single().Count == 46); - AddStep("remove from end", () => removeUserAt(SelectedRoom.Value.RecentParticipants.Count - 1)); + AddStep("remove from end", () => removeUserAt(SelectedRoom.Value!.RecentParticipants.Count - 1)); AddAssert("4 circles displayed", () => list.ChildrenOfType().Count() == 4); AddAssert("45 hidden users", () => list.ChildrenOfType().Single().Count == 45); @@ -138,18 +138,18 @@ namespace osu.Game.Tests.Visual.Multiplayer private void addUser(int id) { - SelectedRoom.Value.RecentParticipants = SelectedRoom.Value.RecentParticipants.Append(new APIUser + SelectedRoom.Value!.RecentParticipants = SelectedRoom.Value!.RecentParticipants.Append(new APIUser { Id = id, Username = $"User {id}" }).ToArray(); - SelectedRoom.Value.ParticipantCount++; + SelectedRoom.Value!.ParticipantCount++; } private void removeUserAt(int index) { - SelectedRoom.Value.RecentParticipants = SelectedRoom.Value.RecentParticipants.Where(u => !u.Equals(SelectedRoom.Value.RecentParticipants[index])).ToArray(); - SelectedRoom.Value.ParticipantCount--; + SelectedRoom.Value!.RecentParticipants = SelectedRoom.Value!.RecentParticipants.Where(u => !u.Equals(SelectedRoom.Value!.RecentParticipants[index])).ToArray(); + SelectedRoom.Value!.ParticipantCount--; } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 2ef56bd54e..18cd720bf2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.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 System.Collections.Generic; using System.Diagnostics; @@ -39,9 +37,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneDrawableRoomPlaylist : MultiplayerTestScene { - private TestPlaylist playlist; - - private BeatmapManager manager; + private TestPlaylist playlist = null!; + private BeatmapManager manager = null!; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -199,14 +196,14 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestDownloadButtonHiddenWhenBeatmapExists() { - Live imported = null; + Live imported = null!; AddStep("import beatmap", () => { var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; Debug.Assert(beatmap.BeatmapSet != null); - imported = manager.Import(beatmap.BeatmapSet); + imported = manager.Import(beatmap.BeatmapSet)!; }); createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach())); @@ -378,7 +375,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - private void createPlaylist(Action setupPlaylist = null) + private void createPlaylist(Action? setupPlaylist = null) { AddStep("create playlist", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 4316653dde..fb54b89a4b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.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 System.Collections.Generic; using System.Linq; @@ -26,9 +24,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneFreeModSelectOverlay : MultiplayerTestScene { - private FreeModSelectOverlay freeModSelectOverlay; - private FooterButtonFreeMods footerButtonFreeMods; - private ScreenFooter footer; + private FreeModSelectOverlay freeModSelectOverlay = null!; + private FooterButtonFreeMods footerButtonFreeMods = null!; + private ScreenFooter footer = null!; private readonly Bindable>> availableMods = new Bindable>>(); [BackgroundDependencyLoader] @@ -49,8 +47,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddToggleStep("toggle visibility", visible => { - if (freeModSelectOverlay != null) - freeModSelectOverlay.State.Value = visible ? Visibility.Visible : Visibility.Hidden; + freeModSelectOverlay.State.Value = visible ? Visibility.Visible : Visibility.Hidden; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs index 6a500bbe55..235d142820 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneGameplayChatDisplay.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.Linq; using Moq; using NUnit.Framework; @@ -20,7 +18,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneGameplayChatDisplay : OsuManualInputManagerTestScene { - private GameplayChatDisplay chatDisplay; + private GameplayChatDisplay chatDisplay = null!; [Cached(typeof(ILocalUserPlayInfo))] private ILocalUserPlayInfo localUserInfo; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index e0364fd65d..55c9e8142f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.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 System.Linq; using NUnit.Framework; @@ -70,13 +68,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { RunGameplay(); - IBeatmapInfo firstBeatmap = null; - AddStep("get first playlist item beatmap", () => firstBeatmap = MultiplayerClient.ServerAPIRoom?.Playlist[0].Beatmap); + IBeatmapInfo firstBeatmap = null!; + AddStep("get first playlist item beatmap", () => firstBeatmap = MultiplayerClient.ServerAPIRoom!.Playlist[0].Beatmap); selectNewItem(() => OtherBeatmap); - AddUntilStep("first playlist item hasn't changed", () => MultiplayerClient.ServerAPIRoom?.Playlist[0].Beatmap == firstBeatmap); - AddUntilStep("second playlist item changed", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Beatmap != firstBeatmap); + AddUntilStep("first playlist item hasn't changed", () => MultiplayerClient.ServerAPIRoom!.Playlist[0].Beatmap == firstBeatmap); + AddUntilStep("second playlist item changed", () => MultiplayerClient.ClientAPIRoom!.Playlist[1].Beatmap != firstBeatmap); } [Test] @@ -103,7 +101,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); - BeatmapInfo otherBeatmap = null; + BeatmapInfo otherBeatmap = null!; AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index e28790f2ed..797b69ec72 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.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.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -23,7 +21,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; - private RoomsContainer container; + private RoomsContainer container = null!; public override void SetUpSteps() { @@ -65,7 +63,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("select first room", () => container.Rooms.First().TriggerClick()); AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category == RoomCategory.Spotlight))); - AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID))); + AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID)!)); AddAssert("first spotlight still selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category == RoomCategory.Spotlight))); AddStep("remove spotlight room", () => RoomManager.RemoveRoom(RoomManager.Rooms.Single(r => r.Category == RoomCategory.Spotlight))); @@ -157,7 +155,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add rooms", () => RoomManager.AddRooms(3, new CatchRuleset().RulesetInfo)); // Todo: What even is this case...? - AddStep("set empty filter criteria", () => container.Filter.Value = null); + AddStep("set empty filter criteria", () => container.Filter.Value = new FilterCriteria()); AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5); AddStep("filter osu! rooms", () => container.Filter.Value = new FilterCriteria { Ruleset = new OsuRuleset().RulesetInfo }); @@ -195,9 +193,9 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add rooms", () => RoomManager.AddRooms(3, withPassword: true)); } - private bool checkRoomSelected(Room room) => SelectedRoom.Value == room; + private bool checkRoomSelected(Room? room) => SelectedRoom.Value == room; - private Room getRoomInFlow(int index) => + private Room? getRoomInFlow(int index) => (container.ChildrenOfType>().First().FlowingChildren.ElementAt(index) as DrawableRoom)?.Room; } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index dd39db49c5..813a420cbd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void createNewItem() { - SelectedRoom.Value.Playlist = SelectedRoom.Value.Playlist.Append(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + SelectedRoom.Value!.Playlist = SelectedRoom.Value.Playlist.Append(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { ID = SelectedRoom.Value.Playlist.Count, RulesetID = new OsuRuleset().RulesetInfo.OnlineID, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 4bf2ebc1a4..3245b3c6a9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.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.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -18,8 +16,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene { - private Dictionary clocks; - private MultiSpectatorLeaderboard leaderboard; + private Dictionary clocks = null!; + private MultiSpectatorLeaderboard? leaderboard; [SetUpSteps] public override void SetUpSteps() @@ -55,13 +53,13 @@ namespace osu.Game.Tests.Visual.Multiplayer }, Add); }); - AddUntilStep("wait for load", () => leaderboard.IsLoaded); + AddUntilStep("wait for load", () => leaderboard!.IsLoaded); AddUntilStep("wait for user population", () => leaderboard.ChildrenOfType().Count() == 2); AddStep("add clock sources", () => { foreach ((int userId, var clock) in clocks) - leaderboard.AddClock(userId, clock); + leaderboard!.AddClock(userId, clock); }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 2b17f91e68..85352ada3c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -455,7 +455,7 @@ namespace osu.Game.Tests.Visual.Multiplayer applyToBeatmap?.Invoke(Beatmap.Value); - LoadScreen(spectatorScreen = new MultiSpectatorScreen(SelectedRoom.Value, playingUsers.ToArray())); + LoadScreen(spectatorScreen = new MultiSpectatorScreen(SelectedRoom.Value!, playingUsers.ToArray())); }); AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded)); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index bafe373d57..2f232a6164 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -37,10 +37,10 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestPerUserMods() { - AddStep("first user has no mods", () => Assert.That(((TestLeaderboard)Leaderboard).UserMods[0], Is.Empty)); + AddStep("first user has no mods", () => Assert.That(((TestLeaderboard)Leaderboard!).UserMods[0], Is.Empty)); AddStep("last user has NF mod", () => { - Assert.That(((TestLeaderboard)Leaderboard).UserMods[TOTAL_USERS - 1], Has.One.Items); + Assert.That(((TestLeaderboard)Leaderboard!).UserMods[TOTAL_USERS - 1], Has.One.Items); Assert.That(((TestLeaderboard)Leaderboard).UserMods[TOTAL_USERS - 1].Single(), Is.TypeOf()); }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index 37662ffce8..3f1db308c0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { LoadComponentAsync(new MatchScoreDisplay { - Team1Score = { BindTarget = Leaderboard.TeamScores[0] }, + Team1Score = { BindTarget = Leaderboard!.TeamScores[0] }, Team2Score = { BindTarget = Leaderboard.TeamScores[1] } }, Add); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs index cf25e06799..9951f62c77 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.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.Linq; using NUnit.Framework; using osu.Framework.Graphics.Containers; @@ -22,10 +20,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; - private LoungeSubScreen loungeScreen; - - private Room lastJoinedRoom; - private string lastJoinedPassword; + private LoungeSubScreen loungeScreen = null!; + private Room? lastJoinedRoom; + private string? lastJoinedPassword; public override void SetUpSteps() { @@ -87,7 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestJoinRoomWithIncorrectPasswordViaButton() { - DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null; + DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null; AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); AddStep("select room", () => InputManager.Key(Key.Down)); @@ -97,14 +94,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType().First().TriggerClick()); AddAssert("room not joined", () => loungeScreen.IsCurrentScreen()); - AddUntilStep("password prompt still visible", () => passwordEntryPopover.State.Value == Visibility.Visible); + AddUntilStep("password prompt still visible", () => passwordEntryPopover!.State.Value == Visibility.Visible); AddAssert("textbox still focused", () => InputManager.FocusedDrawable is OsuPasswordTextBox); } [Test] public void TestJoinRoomWithIncorrectPasswordViaEnter() { - DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null; + DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null; AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); AddStep("select room", () => InputManager.Key(Key.Down)); @@ -114,14 +111,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("press enter", () => InputManager.Key(Key.Enter)); AddAssert("room not joined", () => loungeScreen.IsCurrentScreen()); - AddUntilStep("password prompt still visible", () => passwordEntryPopover.State.Value == Visibility.Visible); + AddUntilStep("password prompt still visible", () => passwordEntryPopover!.State.Value == Visibility.Visible); AddAssert("textbox still focused", () => InputManager.FocusedDrawable is OsuPasswordTextBox); } [Test] public void TestJoinRoomWithCorrectPassword() { - DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null; + DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null; AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); AddStep("select room", () => InputManager.Key(Key.Down)); @@ -137,7 +134,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestJoinRoomWithPasswordViaKeyboardOnly() { - DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null; + DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null; AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); AddStep("select room", () => InputManager.Key(Key.Down)); @@ -150,7 +147,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("room join password correct", () => lastJoinedPassword == "password"); } - private void onRoomJoined(Room room, string password) + private void onRoomJoined(Room room, string? password) { lastJoinedRoom = room; lastJoinedPassword = password; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index bd635b1669..2a5f16d091 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -1,12 +1,9 @@ // 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 System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -35,17 +32,16 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerMatchSongSelect : MultiplayerTestScene { - private BeatmapManager manager; - private RulesetStore rulesets; + private BeatmapManager manager = null!; + private RulesetStore rulesets = null!; - private IList beatmaps => importedBeatmapSet?.PerformRead(s => s.Beatmaps) ?? new List(); + private IList beatmaps => importedBeatmapSet.PerformRead(s => s.Beatmaps); - private TestMultiplayerMatchSongSelect songSelect; - - private Live importedBeatmapSet; + private TestMultiplayerMatchSongSelect songSelect = null!; + private Live importedBeatmapSet = null!; [Resolved] - private OsuConfigManager configManager { get; set; } + private OsuConfigManager configManager { get; set; } = null!; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -57,7 +53,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); Dependencies.Cache(Realm); - importedBeatmapSet = manager.Import(TestResources.CreateTestBeatmapSetInfo(8, rulesets.AvailableRulesets.ToArray())); + importedBeatmapSet = manager.Import(TestResources.CreateTestBeatmapSetInfo(8, rulesets.AvailableRulesets.ToArray()))!; Add(detachedBeatmapStore); } @@ -71,7 +67,7 @@ namespace osu.Game.Tests.Visual.Multiplayer SelectedMods.SetDefault(); }); - AddStep("create song select", () => LoadScreen(songSelect = new TestMultiplayerMatchSongSelect(SelectedRoom.Value))); + AddStep("create song select", () => LoadScreen(songSelect = new TestMultiplayerMatchSongSelect(SelectedRoom.Value!))); AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded); } @@ -88,7 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestBeatmapConfirmed() { - BeatmapInfo selectedBeatmap = null; + BeatmapInfo selectedBeatmap = null!; setUp(); @@ -117,8 +113,8 @@ namespace osu.Game.Tests.Visual.Multiplayer setUp(); AddStep("change ruleset", () => Ruleset.Value = new OsuRuleset().RulesetInfo); - AddStep($"select {allowedMod.ReadableName()} as allowed", () => songSelect.FreeMods.Value = new[] { (Mod)Activator.CreateInstance(allowedMod) }); - AddStep($"select {requiredMod.ReadableName()} as required", () => songSelect.Mods.Value = new[] { (Mod)Activator.CreateInstance(requiredMod) }); + AddStep($"select {allowedMod.ReadableName()} as allowed", () => songSelect.FreeMods.Value = new[] { (Mod)Activator.CreateInstance(allowedMod)! }); + AddStep($"select {requiredMod.ReadableName()} as required", () => songSelect.Mods.Value = new[] { (Mod)Activator.CreateInstance(requiredMod)! }); AddAssert("freemods empty", () => songSelect.FreeMods.Value.Count == 0); @@ -141,7 +137,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create song select", () => { - SelectedRoom.Value.Playlist.Single().RulesetID = 2; + SelectedRoom.Value!.Playlist.Single().RulesetID = 2; songSelect = new TestMultiplayerMatchSongSelect(SelectedRoom.Value, SelectedRoom.Value.Playlist.Single()); songSelect.OnLoadComplete += _ => Ruleset.Value = new TaikoRuleset().RulesetInfo; LoadScreen(songSelect); @@ -171,7 +167,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public new BeatmapCarousel Carousel => base.Carousel; - public TestMultiplayerMatchSongSelect(Room room, [CanBeNull] PlaylistItem itemToEdit = null) + public TestMultiplayerMatchSongSelect(Room room, PlaylistItem? itemToEdit = null) : base(room, itemToEdit) { } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index abcb591cf6..8ea52f8099 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -1,10 +1,7 @@ // 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.Linq; -using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -42,10 +39,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerMatchSubScreen : MultiplayerTestScene { - private MultiplayerMatchSubScreen screen; - - private BeatmapManager beatmaps; - private BeatmapSetInfo importedSet; + private MultiplayerMatchSubScreen screen = null!; + private BeatmapManager beatmaps = null!; + private BeatmapSetInfo importedSet = null!; public TestSceneMultiplayerMatchSubScreen() : base(false) @@ -70,7 +66,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("load match", () => { SelectedRoom.Value = new Room { Name = "Test Room" }; - LoadScreen(screen = new TestMultiplayerMatchSubScreen(SelectedRoom.Value)); + LoadScreen(screen = new TestMultiplayerMatchSubScreen(SelectedRoom.Value!)); }); AddUntilStep("wait for load", () => screen.IsCurrentScreen()); @@ -81,7 +77,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { @@ -100,7 +96,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo) { @@ -125,7 +121,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("set playlist", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { @@ -142,7 +138,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set playlist", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo) { @@ -173,7 +169,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item with allowed mod", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { @@ -202,7 +198,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item with allowed mod", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { @@ -226,7 +222,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item with no allowed mods", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { @@ -249,7 +245,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add two playlist items", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo) { @@ -285,7 +281,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("add playlist item", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { @@ -320,8 +316,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen { [Resolved(canBeNull: true)] - [CanBeNull] - private IDialogOverlay dialogOverlay { get; set; } + private IDialogOverlay? dialogOverlay { get; set; } public TestMultiplayerMatchSubScreen(Room room) : base(room) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 518ea2b511..36f5bba384 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create list", () => { - Child = list = new MultiplayerPlaylist(SelectedRoom.Value) + Child = list = new MultiplayerPlaylist(SelectedRoom.Value!) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 50f55ebde0..3ef2e4ecf4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create playlist", () => { - Child = playlist = new MultiplayerQueueList(SelectedRoom.Value) + Child = playlist = new MultiplayerQueueList(SelectedRoom.Value!) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs index f030466fff..076c2c3cdd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.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 NUnit.Framework; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu; @@ -16,7 +14,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestDisplayResults() { - MultiplayerResultsScreen screen = null; + MultiplayerResultsScreen screen = null!; AddStep("show results screen", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index b4f55bb6a3..1429f86164 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create button", () => { - PlaylistItem item = SelectedRoom.Value.Playlist.First(); + PlaylistItem item = SelectedRoom.Value!.Playlist.First(); AvailabilityTracker.SelectedItem.Value = item; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorPlayerGrid.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorPlayerGrid.cs index 8fd05dcaa9..2f461ad706 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorPlayerGrid.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorPlayerGrid.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.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -16,7 +14,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestSceneMultiplayerSpectatorPlayerGrid : OsuManualInputManagerTestScene { - private PlayerGrid grid; + private PlayerGrid grid = null!; [SetUp] public void Setup() => Schedule(() => diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs index 68fd39a066..f77b6e8c68 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.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.Collections.Generic; using NUnit.Framework; using osu.Framework.Bindables; @@ -32,7 +30,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [TestCase(1048576, 1048576)] public void TestDisplayTeamResults(int team1Score, int team2Score) { - MultiplayerResultsScreen screen = null; + MultiplayerResultsScreen screen = null!; AddStep("show results screen", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs index ae27db0dd1..cd41884ba7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.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 System.Collections.Generic; using System.Linq; @@ -28,18 +26,18 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestScenePlaylistsRoomSettingsPlaylist : OnlinePlayTestScene { - private TestPlaylist playlist; + private TestPlaylist playlist = null!; [Test] public void TestItemRemovedOnDeletion() { - PlaylistItem selectedItem = null; + PlaylistItem selectedItem = null!; createPlaylist(); moveToItem(0); AddStep("click", () => InputManager.Click(MouseButton.Left)); - AddStep("retrieve selection", () => selectedItem = playlist.SelectedItem.Value); + AddStep("retrieve selection", () => selectedItem = playlist.SelectedItem.Value!); moveToDeleteButton(0); AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); @@ -122,7 +120,7 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset); }); - private void createPlaylist(Action setupPlaylist = null) + private void createPlaylist(Action? setupPlaylist = null) { AddStep("create playlist", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index b55102518a..fa1909254a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.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 System.Linq; using NUnit.Framework; @@ -27,9 +25,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { public partial class TestScenePlaylistsSongSelect : OnlinePlayTestScene { - private BeatmapManager manager; - - private TestPlaylistsSongSelect songSelect; + private BeatmapManager manager = null!; + private TestPlaylistsSongSelect songSelect = null!; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -60,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer SelectedMods.Value = Array.Empty(); }); - AddStep("create song select", () => LoadScreen(songSelect = new TestPlaylistsSongSelect(SelectedRoom.Value))); + AddStep("create song select", () => LoadScreen(songSelect = new TestPlaylistsSongSelect(SelectedRoom.Value!))); AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded); } @@ -68,14 +65,14 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestItemAddedIfEmptyOnStart() { AddStep("finalise selection", () => songSelect.FinaliseSelection()); - AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); + AddAssert("playlist has 1 item", () => SelectedRoom.Value!.Playlist.Count == 1); } [Test] public void TestItemAddedWhenCreateNewItemClicked() { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); - AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); + AddAssert("playlist has 1 item", () => SelectedRoom.Value!.Playlist.Count == 1); } [Test] @@ -83,7 +80,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddStep("finalise selection", () => songSelect.FinaliseSelection()); - AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1); + AddAssert("playlist has 1 item", () => SelectedRoom.Value!.Playlist.Count == 1); } [Test] @@ -91,7 +88,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); - AddAssert("playlist has 2 items", () => SelectedRoom.Value.Playlist.Count == 2); + AddAssert("playlist has 2 items", () => SelectedRoom.Value!.Playlist.Count == 2); } [Test] @@ -99,10 +96,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); - AddStep("rearrange", () => SelectedRoom.Value.Playlist = SelectedRoom.Value.Playlist.Skip(1).Append(SelectedRoom.Value.Playlist[0]).ToArray()); + AddStep("rearrange", () => SelectedRoom.Value!.Playlist = SelectedRoom.Value!.Playlist.Skip(1).Append(SelectedRoom.Value!.Playlist[0]).ToArray()); AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!()); - AddAssert("new item has id 2", () => SelectedRoom.Value.Playlist.Last().ID == 2); + AddAssert("new item has id 2", () => SelectedRoom.Value!.Playlist.Last().ID == 2); } /// @@ -118,13 +115,13 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("item 1 has rate 1.5", () => { - var mod = (OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0].ToMod(new OsuRuleset()); + var mod = (OsuModDoubleTime)SelectedRoom.Value!.Playlist.First().RequiredMods[0].ToMod(new OsuRuleset()); return Precision.AlmostEquals(1.5, mod.SpeedChange.Value); }); AddAssert("item 2 has rate 2", () => { - var mod = (OsuModDoubleTime)SelectedRoom.Value.Playlist.Last().RequiredMods[0].ToMod(new OsuRuleset()); + var mod = (OsuModDoubleTime)SelectedRoom.Value!.Playlist.Last().RequiredMods[0].ToMod(new OsuRuleset()); return Precision.AlmostEquals(2, mod.SpeedChange.Value); }); } @@ -135,7 +132,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestGlobalModInstancesNotRetained() { - OsuModDoubleTime mod = null; + OsuModDoubleTime mod = null!; AddStep("set dt mod and store", () => { @@ -150,7 +147,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change stored mod rate", () => mod.SpeedChange.Value = 2); AddAssert("item has rate 1.5", () => { - var m = (OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0].ToMod(new OsuRuleset()); + var m = (OsuModDoubleTime)SelectedRoom.Value!.Playlist.First().RequiredMods[0].ToMod(new OsuRuleset()); return Precision.AlmostEquals(1.5, m.SpeedChange.Value); }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs index 195b40bd13..88afef7de2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set playlist", () => { - SelectedRoom.Value.Playlist = + SelectedRoom.Value!.Playlist = [ new PlaylistItem(new BeatmapInfo { StarRating = min }), new PlaylistItem(new BeatmapInfo { StarRating = max }), diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs index 0c536cb1d4..8c8dc8d69a 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.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.Linq; using NUnit.Framework; using osu.Framework.Bindables; @@ -21,7 +19,7 @@ namespace osu.Game.Tests.Visual.Playlists { protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; - private TestLoungeSubScreen loungeScreen; + private TestLoungeSubScreen loungeScreen = null!; public override void SetUpSteps() { @@ -97,7 +95,7 @@ namespace osu.Game.Tests.Visual.Playlists private partial class TestLoungeSubScreen : PlaylistsLoungeSubScreen { - public new Bindable SelectedRoom => base.SelectedRoom; + public new Bindable SelectedRoom => base.SelectedRoom; } } } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index 321f563140..5868331451 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Playlists { SelectedRoom.Value = new Room(); - Child = settings = new TestRoomSettings(SelectedRoom.Value) + Child = settings = new TestRoomSettings(SelectedRoom.Value!) { RelativeSizeAxes = Axes.Both, State = { Value = Visibility.Visible } @@ -45,19 +45,19 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("clear name and beatmap", () => { - SelectedRoom.Value.Name = ""; - SelectedRoom.Value.Playlist = []; + SelectedRoom.Value!.Name = ""; + SelectedRoom.Value!.Playlist = []; }); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set name", () => SelectedRoom.Value.Name = "Room name"); + AddStep("set name", () => SelectedRoom.Value!.Name = "Room name"); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set beatmap", () => SelectedRoom.Value.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]); + AddStep("set beatmap", () => SelectedRoom.Value!.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]); AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value); - AddStep("clear name", () => SelectedRoom.Value.Name = ""); + AddStep("clear name", () => SelectedRoom.Value!.Name = ""); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); } @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Playlists { settings.NameField.Current.Value = expected_name; settings.DurationField.Current.Value = expectedDuration; - SelectedRoom.Value.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]; + SelectedRoom.Value!.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]; RoomManager.CreateRequested = r => { @@ -98,8 +98,8 @@ namespace osu.Game.Tests.Visual.Playlists { var beatmap = CreateBeatmap(Ruleset.Value).BeatmapInfo; - SelectedRoom.Value.Name = "Test Room"; - SelectedRoom.Value.Playlist = [new PlaylistItem(beatmap)]; + SelectedRoom.Value!.Name = "Test Room"; + SelectedRoom.Value!.Playlist = [new PlaylistItem(beatmap)]; errorMessage = $"{not_found_prefix} {beatmap.OnlineID}"; @@ -107,13 +107,13 @@ namespace osu.Game.Tests.Visual.Playlists }); AddAssert("error not displayed", () => !settings.ErrorText.IsPresent); - AddAssert("playlist item valid", () => SelectedRoom.Value.Playlist[0].Valid.Value); + AddAssert("playlist item valid", () => SelectedRoom.Value!.Playlist[0].Valid.Value); AddStep("create room", () => settings.ApplyButton.Action.Invoke()); AddAssert("error displayed", () => settings.ErrorText.IsPresent); AddAssert("error has custom text", () => settings.ErrorText.Text != errorMessage); - AddAssert("playlist item marked invalid", () => !SelectedRoom.Value.Playlist[0].Valid.Value); + AddAssert("playlist item marked invalid", () => !SelectedRoom.Value!.Playlist[0].Valid.Value); } [Test] @@ -125,8 +125,8 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("setup", () => { - SelectedRoom.Value.Name = "Test Room"; - SelectedRoom.Value.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]; + SelectedRoom.Value!.Name = "Test Room"; + SelectedRoom.Value!.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]; RoomManager.CreateRequested = _ => failText; }); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index f72a2cd655..c60b208ffc 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - Child = new ParticipantsDisplay(SelectedRoom.Value, Direction.Horizontal) + Child = new ParticipantsDisplay(SelectedRoom.Value!, Direction.Horizontal) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("create component", () => { - Child = new ParticipantsDisplay(SelectedRoom.Value, Direction.Vertical) + Child = new ParticipantsDisplay(SelectedRoom.Value!, Direction.Vertical) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 7527647b9c..5977e67b0e 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -1,13 +1,10 @@ // 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 System.Collections.Generic; using System.Linq; using System.Net; -using JetBrains.Annotations; using Newtonsoft.Json.Linq; using NUnit.Framework; using osu.Framework.Graphics.Containers; @@ -34,14 +31,14 @@ namespace osu.Game.Tests.Visual.Playlists private const int scores_per_result = 10; private const int real_user_position = 200; - private TestResultsScreen resultsScreen; + private TestResultsScreen 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. private bool requestComplete; private int totalCount; - private ScoreInfo userScore; + private ScoreInfo userScore = null!; [SetUpSteps] public override void SetUpSteps() @@ -205,7 +202,7 @@ namespace osu.Game.Tests.Visual.Playlists AddAssert("placeholder shown", () => this.ChildrenOfType().Count(), () => Is.EqualTo(1)); } - private void createResults(Func getScore = null) + private void createResults(Func? getScore = null) { AddStep("load results", () => { @@ -229,7 +226,7 @@ namespace osu.Game.Tests.Visual.Playlists AddWaitStep("wait for display", 5); } - private void bindHandler(bool delayed = false, ScoreInfo userScore = null, bool failRequests = false, bool noScores = false) => ((DummyAPIAccess)API).HandleRequest = request => + private void bindHandler(bool delayed = false, ScoreInfo? userScore = null, bool failRequests = false, bool noScores = false) => ((DummyAPIAccess)API).HandleRequest = request => { // pre-check for requests we should be handling (as they are scheduled below). switch (request) @@ -286,7 +283,7 @@ namespace osu.Game.Tests.Visual.Playlists req.TriggerFailure(new WebException("Failed.")); } - private MultiplayerScore createUserResponse([NotNull] ScoreInfo userScore) + private MultiplayerScore createUserResponse(ScoreInfo userScore) { var multiplayerUserScore = new MultiplayerScore { @@ -420,7 +417,7 @@ namespace osu.Game.Tests.Visual.Playlists public new LoadingSpinner RightSpinner => base.RightSpinner; public new ScorePanelList ScorePanelList => base.ScorePanelList; - public TestResultsScreen([CanBeNull] ScoreInfo score, int roomId, PlaylistItem playlistItem) + public TestResultsScreen(ScoreInfo? score, int roomId, PlaylistItem playlistItem) : base(score, roomId, playlistItem) { AllowRetry = true; diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 6a53019b76..0270840597 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Playlists importBeatmap(); - AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom.Value))); + AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom.Value!))); AddUntilStep("wait for load", () => match.IsCurrentScreen()); } @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual.Playlists ]; }); - AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]); + AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value!.Playlist[0]); } [Test] @@ -199,7 +199,7 @@ namespace osu.Game.Tests.Visual.Playlists private void setupAndCreateRoom(Action room) { - AddStep("setup room", () => room(SelectedRoom.Value)); + AddStep("setup room", () => room(SelectedRoom.Value!)); AddStep("click create button", () => { @@ -218,7 +218,7 @@ namespace osu.Game.Tests.Visual.Playlists private partial class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen { - public new Bindable SelectedItem => base.SelectedItem; + public new Bindable SelectedItem => base.SelectedItem; public new Bindable Beatmap => base.Beatmap; diff --git a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs index 67c3586a35..b10ce8ed1b 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.OnlinePlay.Components public IBindable InitialRoomsReceived => initialRoomsReceived; private readonly Bindable initialRoomsReceived = new Bindable(); - public readonly Bindable Filter = new Bindable(); + public readonly Bindable Filter = new Bindable(); [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 5a1648c91f..207e0bdf55 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.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 System.Linq; using osu.Framework.Bindables; @@ -26,22 +24,22 @@ namespace osu.Game.Screens.OnlinePlay /// The currently-selected item. Selection is visually represented with a border. /// May be updated by clicking playlist items if is true. /// - public readonly Bindable SelectedItem = new Bindable(); + public readonly Bindable SelectedItem = new Bindable(); /// /// Invoked when an item is requested to be deleted. /// - public Action RequestDeletion; + public Action? RequestDeletion; /// /// Invoked when an item requests its results to be shown. /// - public Action RequestResults; + public Action? RequestResults; /// /// Invoked when an item requests to be edited. /// - public Action RequestEdit; + public Action? RequestEdit; private bool allowReordering; @@ -235,7 +233,7 @@ namespace osu.Game.Screens.OnlinePlay { var visibleItems = ListContainer.AsEnumerable().Where(r => r.IsPresent); - PlaylistItem item; + PlaylistItem? item; if (SelectedItem.Value == null) item = visibleItems.FirstOrDefault()?.Model; diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 43ffaf947e..7a773bb116 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.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 System.Collections.Generic; using System.Linq; @@ -54,23 +52,23 @@ namespace osu.Game.Screens.OnlinePlay /// /// Invoked when this item requests to be deleted. /// - public Action RequestDeletion; + public Action? RequestDeletion; /// /// Invoked when this item requests its results to be shown. /// - public Action RequestResults; + public Action? RequestResults; /// /// Invoked when this item requests to be edited. /// - public Action RequestEdit; + public Action? RequestEdit; /// /// The currently-selected item, used to show a border around this item. /// May be updated by this item if is true. /// - public readonly Bindable SelectedItem = new Bindable(); + public readonly Bindable SelectedItem = new Bindable(); public readonly PlaylistItem Item; @@ -79,48 +77,48 @@ namespace osu.Game.Screens.OnlinePlay private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; private readonly IBindable valid = new Bindable(); - private IBeatmapInfo beatmap; - private IRulesetInfo ruleset; + private IBeatmapInfo? beatmap; + private IRulesetInfo? ruleset; private Mod[] requiredMods = Array.Empty(); - private Container borderContainer; - private FillFlowContainer difficultyIconContainer; - private LinkFlowContainer beatmapText; - private LinkFlowContainer authorText; - private ExplicitContentBeatmapBadge explicitContent; - private ModDisplay modDisplay; - private FillFlowContainer buttonsFlow; - private UpdateableAvatar ownerAvatar; - private Drawable showResultsButton; - private Drawable editButton; - private Drawable removeButton; - private PanelBackground panelBackground; - private FillFlowContainer mainFillFlow; - private BeatmapCardThumbnail thumbnail; + private Container? borderContainer; + private FillFlowContainer? difficultyIconContainer; + private LinkFlowContainer? beatmapText; + private LinkFlowContainer? authorText; + private ExplicitContentBeatmapBadge? explicitContent; + private ModDisplay? modDisplay; + private FillFlowContainer? buttonsFlow; + private UpdateableAvatar? ownerAvatar; + private Drawable? showResultsButton; + private Drawable? editButton; + private Drawable? removeButton; + private PanelBackground? panelBackground; + private FillFlowContainer? mainFillFlow; + private BeatmapCardThumbnail? thumbnail; [Resolved] - private RealmAccess realm { get; set; } + private RealmAccess realm { get; set; } = null!; [Resolved] - private RulesetStore rulesets { get; set; } + private RulesetStore rulesets { get; set; } = null!; [Resolved] - private BeatmapManager beatmaps { get; set; } + private BeatmapManager beatmaps { get; set; } = null!; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; [Resolved] - private UserLookupCache userLookupCache { get; set; } + private UserLookupCache userLookupCache { get; set; } = null!; [Resolved] - private BeatmapLookupCache beatmapLookupCache { get; set; } + private BeatmapLookupCache beatmapLookupCache { get; set; } = null!; [Resolved(CanBeNull = true)] - private BeatmapSetOverlay beatmapOverlay { get; set; } + private BeatmapSetOverlay? beatmapOverlay { get; set; } [Resolved(CanBeNull = true)] - private ManageCollectionsDialog manageCollectionsDialog { get; set; } + private ManageCollectionsDialog? manageCollectionsDialog { get; set; } public DrawableRoomPlaylistItem(PlaylistItem item) : base(item) @@ -136,7 +134,8 @@ namespace osu.Game.Screens.OnlinePlay [BackgroundDependencyLoader] private void load() { - borderContainer.BorderColour = colours.Yellow; + if (borderContainer != null) + borderContainer.BorderColour = colours.Yellow; ruleset = rulesets.GetRuleset(Item.RulesetID); var rulesetInstance = ruleset?.CreateInstance(); @@ -163,7 +162,8 @@ namespace osu.Game.Screens.OnlinePlay return; } - borderContainer.BorderThickness = IsSelectedItem ? border_thickness : 0; + if (borderContainer != null) + borderContainer.BorderThickness = IsSelectedItem ? border_thickness : 0; }, true); valid.BindValueChanged(_ => Scheduler.AddOnce(refresh)); @@ -177,7 +177,11 @@ namespace osu.Game.Screens.OnlinePlay if (showItemOwner) { var foundUser = await userLookupCache.GetUserAsync(Item.OwnerID).ConfigureAwait(false); - Schedule(() => ownerAvatar.User = foundUser); + Schedule(() => + { + if (ownerAvatar != null) + ownerAvatar.User = foundUser; + }); } beatmap = await beatmapLookupCache.GetBeatmapAsync(Item.Beatmap.OnlineID).ConfigureAwait(false); @@ -278,69 +282,89 @@ namespace osu.Game.Screens.OnlinePlay private void refresh() { - if (!valid.Value) + if (borderContainer != null) { - borderContainer.BorderThickness = border_thickness; - borderContainer.BorderColour = colours.Red; - } - - if (beatmap != null) - { - difficultyIconContainer.Children = new Drawable[] + if (!valid.Value) { - thumbnail = new BeatmapCardThumbnail(beatmap.BeatmapSet!, (IBeatmapSetOnlineInfo)beatmap.BeatmapSet!) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Width = 60, - Masking = true, - CornerRadius = 10, - RelativeSizeAxes = Axes.Y, - Dimmed = { Value = IsHovered } - }, - new DifficultyIcon(beatmap, ruleset, requiredMods) - { - Size = new Vector2(24), - TooltipType = DifficultyIconTooltipType.Extended, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - }; + borderContainer.BorderThickness = border_thickness; + borderContainer.BorderColour = colours.Red; + } } - else - difficultyIconContainer.Clear(); - panelBackground.Beatmap.Value = beatmap; - - beatmapText.Clear(); - - if (beatmap != null) + if (difficultyIconContainer != null) { - beatmapText.AddLink(beatmap.GetDisplayTitleRomanisable(includeCreator: false), - LinkAction.OpenBeatmap, - beatmap.OnlineID.ToString(), - null, - text => + if (beatmap != null) + { + difficultyIconContainer.Children = new Drawable[] { - text.Truncate = true; - }); + thumbnail = new BeatmapCardThumbnail(beatmap.BeatmapSet!, (IBeatmapSetOnlineInfo)beatmap.BeatmapSet!) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 60, + Masking = true, + CornerRadius = 10, + RelativeSizeAxes = Axes.Y, + Dimmed = { Value = IsHovered } + }, + new DifficultyIcon(beatmap, ruleset, requiredMods) + { + Size = new Vector2(24), + TooltipType = DifficultyIconTooltipType.Extended, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + }; + } + else + difficultyIconContainer.Clear(); } - authorText.Clear(); + if (panelBackground != null) + panelBackground.Beatmap.Value = beatmap; - if (!string.IsNullOrEmpty(beatmap?.Metadata.Author.Username)) + if (beatmapText != null) { - authorText.AddText("mapped by "); - authorText.AddUserLink(beatmap.Metadata.Author); + beatmapText.Clear(); + + if (beatmap != null) + { + beatmapText.AddLink(beatmap.GetDisplayTitleRomanisable(includeCreator: false), + LinkAction.OpenBeatmap, + beatmap.OnlineID.ToString(), + null, + text => + { + text.Truncate = true; + }); + } } - bool hasExplicitContent = (beatmap?.BeatmapSet as IBeatmapSetOnlineInfo)?.HasExplicitContent == true; - explicitContent.Alpha = hasExplicitContent ? 1 : 0; + if (authorText != null) + { + authorText.Clear(); - modDisplay.Current.Value = requiredMods.ToArray(); + if (!string.IsNullOrEmpty(beatmap?.Metadata.Author.Username)) + { + authorText.AddText("mapped by "); + authorText.AddUserLink(beatmap.Metadata.Author); + } + } - buttonsFlow.Clear(); - buttonsFlow.ChildrenEnumerable = createButtons(); + if (explicitContent != null) + { + bool hasExplicitContent = (beatmap?.BeatmapSet as IBeatmapSetOnlineInfo)?.HasExplicitContent == true; + explicitContent.Alpha = hasExplicitContent ? 1 : 0; + } + + if (modDisplay != null) + modDisplay.Current.Value = requiredMods.ToArray(); + + if (buttonsFlow != null) + { + buttonsFlow.Clear(); + buttonsFlow.ChildrenEnumerable = createButtons(); + } difficultyIconContainer.FadeInFromZero(500, Easing.OutQuint); mainFillFlow.FadeInFromZero(500, Easing.OutQuint); @@ -601,7 +625,7 @@ namespace osu.Game.Screens.OnlinePlay private readonly IBeatmapInfo beatmap; [Resolved] - private BeatmapManager beatmapManager { get; set; } + private BeatmapManager beatmapManager { get; set; } = null!; // required for download tracking, as this button hides itself. can probably be removed with a bit of consideration. public override bool IsPresent => true; @@ -656,7 +680,7 @@ namespace osu.Game.Screens.OnlinePlay // For now, this is the same implementation as in PanelBackground, but supports a beatmap info rather than a working beatmap private partial class PanelBackground : Container // todo: should be a buffered container (https://github.com/ppy/osu-framework/issues/3222) { - public readonly Bindable Beatmap = new Bindable(); + public readonly Bindable Beatmap = new Bindable(); public PanelBackground() { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs index 3a687ad351..0f63718355 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs @@ -1,18 +1,16 @@ // 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.Game.Rulesets; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public class FilterCriteria { - public string SearchString; + public string SearchString = string.Empty; public RoomStatusFilter Status; - public string Category; - public RulesetInfo Ruleset; + public string Category = string.Empty; + public RulesetInfo? Ruleset; public RoomPermissionsFilter Permissions; } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index 90288a1067..ac8caa6b88 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -1,13 +1,10 @@ // 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 System.Collections.Generic; using System.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -54,42 +51,42 @@ namespace osu.Game.Screens.OnlinePlay.Lounge AutoSizeAxes = Axes.Both }; - protected ListingPollingComponent ListingPollingComponent { get; private set; } + protected ListingPollingComponent ListingPollingComponent { get; private set; } = null!; - protected readonly Bindable SelectedRoom = new Bindable(); + protected readonly Bindable SelectedRoom = new Bindable(); [Resolved] - private MusicController music { get; set; } + private MusicController music { get; set; } = null!; [Resolved(CanBeNull = true)] - private OngoingOperationTracker ongoingOperationTracker { get; set; } + private OngoingOperationTracker? ongoingOperationTracker { get; set; } [Resolved] - private IBindable ruleset { get; set; } + private IBindable ruleset { get; set; } = null!; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; - [CanBeNull] - private IDisposable joiningRoomOperation { get; set; } - - [CanBeNull] - private LeasedBindable selectionLease; + [Resolved(CanBeNull = true)] + private IdleTracker? idleTracker { get; set; } [Resolved] - protected OsuConfigManager Config { get; private set; } + protected OsuConfigManager Config { get; private set; } = null!; - private readonly Bindable filter = new Bindable(new FilterCriteria()); + private IDisposable? joiningRoomOperation { get; set; } + private LeasedBindable? selectionLease; + + private readonly Bindable filter = new Bindable(); private readonly IBindable operationInProgress = new Bindable(); private readonly IBindable isIdle = new BindableBool(); - private PopoverContainer popoverContainer; - private LoadingLayer loadingLayer; - private RoomsContainer roomsContainer; - private SearchTextBox searchTextBox; - private Dropdown statusDropdown; + private PopoverContainer popoverContainer = null!; + private LoadingLayer loadingLayer = null!; + private RoomsContainer roomsContainer = null!; + private SearchTextBox searchTextBox = null!; + private Dropdown statusDropdown = null!; [BackgroundDependencyLoader(true)] - private void load([CanBeNull] IdleTracker idleTracker) + private void load() { const float controls_area_height = 25f; @@ -208,7 +205,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge public void UpdateFilter() => Scheduler.AddOnce(updateFilter); - private ScheduledDelegate scheduledFilterUpdate; + private ScheduledDelegate? scheduledFilterUpdate; private void updateFilterDebounced() { @@ -262,7 +259,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge if (SelectedRoom.Value?.RoomID == null) SelectedRoom.Value = new Room(); - music?.EnsurePlayingSomething(); + music.EnsurePlayingSomething(); onReturning(); } @@ -299,7 +296,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge popoverContainer.HidePopover(); } - public virtual void Join(Room room, string password, Action onSuccess = null, Action onFailure = null) => Schedule(() => + public virtual void Join(Room room, string? password, Action? onSuccess = null, Action? onFailure = null) => Schedule(() => { if (joiningRoomOperation != null) return; @@ -364,7 +361,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge /// Push a room as a new subscreen. /// /// An optional template to use when creating the room. - public void Open(Room room = null) => Schedule(() => + public void Open(Room? room = null) => Schedule(() => { // Handles the case where a room is clicked 3 times in quick succession if (!this.IsCurrentScreen()) diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs index fabebc3859..2ea0f9eb84 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboardScore.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.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; @@ -17,7 +15,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components { private readonly APIUserScoreAggregate score; - public override ScoreInfo TooltipContent => null; // match aggregate scores can't show statistics that the custom tooltip displays. + public override ScoreInfo? TooltipContent => null; // match aggregate scores can't show statistics that the custom tooltip displays. public MatchLeaderboardScore(APIUserScoreAggregate score, int? rank, bool isOnlineScope = true) : base(score.CreateScoreInfo(), rank, isOnlineScope) diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchTypePicker.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchTypePicker.cs index 477336e8ea..51bd7a2801 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchTypePicker.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchTypePicker.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 osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -26,7 +24,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected override TabItem CreateTabItem(MatchType value) => new GameTypePickerItem(value); - protected override Dropdown CreateDropdown() => null; + protected override Dropdown? CreateDropdown() => null; public MatchTypePicker() { @@ -41,7 +39,8 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components { private const float transition_duration = 200; - private readonly CircularContainer hover, selection; + private readonly CircularContainer hover; + private readonly CircularContainer selection; public GameTypePickerItem(MatchType value) : base(value) @@ -84,7 +83,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components }; } - private Sample selectSample; + private Sample selectSample = null!; [BackgroundDependencyLoader] private void load(OsuColour colours, AudioManager audio) diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/RoomAvailabilityPicker.cs b/osu.Game/Screens/OnlinePlay/Match/Components/RoomAvailabilityPicker.cs index 85fac9228b..56f02ba633 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/RoomAvailabilityPicker.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/RoomAvailabilityPicker.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 osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; @@ -22,7 +20,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components public partial class RoomAvailabilityPicker : DisableableTabControl { protected override TabItem CreateTabItem(RoomAvailability value) => new RoomAvailabilityPickerItem(value); - protected override Dropdown CreateDropdown() => null; + protected override Dropdown? CreateDropdown() => null; public RoomAvailabilityPicker() { diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs index 6a856d8d72..ffa4235167 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomModSelectOverlay.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.OnlinePlay.Match { public partial class RoomModSelectOverlay : UserModSelectOverlay { - public Bindable SelectedItem { get; } = new Bindable(); + public Bindable SelectedItem { get; } = new Bindable(); [Resolved] private RulesetStore rulesets { get; set; } = null!; diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 4461cf5402..0d9c210149 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -1,14 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -37,7 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Match [Cached(typeof(IPreviewTrackOwner))] public abstract partial class RoomSubScreen : OnlinePlaySubScreen, IPreviewTrackOwner { - public readonly Bindable SelectedItem = new Bindable(); + public readonly Bindable SelectedItem = new Bindable(); public override bool? ApplyModTrackAdjustments => true; @@ -52,9 +49,9 @@ namespace osu.Game.Screens.OnlinePlay.Match /// A container that provides controls for selection of user mods. /// This will be shown/hidden automatically when applicable. /// - protected Drawable UserModsSection; + protected Drawable? UserModsSection; - private Sample sampleStart; + private Sample? sampleStart; /// /// Any mods applied by/to the local user. @@ -62,28 +59,28 @@ namespace osu.Game.Screens.OnlinePlay.Match protected readonly Bindable> UserMods = new Bindable>(Array.Empty()); [Resolved(CanBeNull = true)] - private IOverlayManager overlayManager { get; set; } + private IOverlayManager? overlayManager { get; set; } [Resolved] - private MusicController music { get; set; } + private MusicController music { get; set; } = null!; [Resolved] - private BeatmapManager beatmapManager { get; set; } + private BeatmapManager beatmapManager { get; set; } = null!; [Resolved] - protected RulesetStore Rulesets { get; private set; } + protected RulesetStore Rulesets { get; private set; } = null!; [Resolved] private IAPIProvider api { get; set; } = null!; [Resolved(canBeNull: true)] - protected OnlinePlayScreen ParentScreen { get; private set; } + protected OnlinePlayScreen? ParentScreen { get; private set; } [Resolved] private PreviewTrackManager previewTrackManager { get; set; } = null!; [Resolved(canBeNull: true)] - private IDialogOverlay dialogOverlay { get; set; } + private IDialogOverlay? dialogOverlay { get; set; } [Cached] private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); @@ -93,13 +90,11 @@ namespace osu.Game.Screens.OnlinePlay.Match public readonly Room Room; private readonly bool allowEdit; - internal ModSelectOverlay UserModsSelectOverlay { get; private set; } + internal ModSelectOverlay UserModsSelectOverlay { get; private set; } = null!; - [CanBeNull] - private IDisposable userModsSelectOverlayRegistration; - - private RoomSettingsOverlay settingsOverlay; - private Drawable mainContent; + private IDisposable? userModsSelectOverlayRegistration; + private RoomSettingsOverlay settingsOverlay = null!; + private Drawable mainContent = null!; /// /// Creates a new . @@ -265,7 +260,7 @@ namespace osu.Game.Screens.OnlinePlay.Match updateSetupState(); } - private void onRoomPropertyChanged(object sender, PropertyChangedEventArgs e) + private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(Room.RoomID)) updateSetupState(); @@ -388,6 +383,9 @@ namespace osu.Game.Screens.OnlinePlay.Match protected void StartPlay() { + if (SelectedItem.Value == null) + return; + // User may be at song select or otherwise when the host starts gameplay. // Ensure that they first return to this screen, else global bindables (beatmap etc.) may be in a bad state. if (!this.IsCurrentScreen()) @@ -401,29 +399,28 @@ namespace osu.Game.Screens.OnlinePlay.Match sampleStart?.Play(); // fallback is to allow this class to operate when there is no parent OnlineScreen (testing purposes). - var targetScreen = (Screen)ParentScreen ?? this; + var targetScreen = (Screen?)ParentScreen ?? this; - targetScreen.Push(CreateGameplayScreen()); + targetScreen.Push(CreateGameplayScreen(SelectedItem.Value)); } /// /// Creates the gameplay screen to be entered. /// + /// /// The screen to enter. - protected abstract Screen CreateGameplayScreen(); + protected abstract Screen CreateGameplayScreen(PlaylistItem selectedItem); private void selectedItemChanged() { updateWorkingBeatmap(); - var selected = SelectedItem.Value; - - if (selected == null) + if (SelectedItem.Value is not PlaylistItem selected) return; - var rulesetInstance = Rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance(); + var rulesetInstance = Rulesets.GetRuleset(selected.RulesetID)?.CreateInstance(); Debug.Assert(rulesetInstance != null); - var allowedMods = SelectedItem.Value.AllowedMods.Select(m => m.ToMod(rulesetInstance)); + var allowedMods = selected.AllowedMods.Select(m => m.ToMod(rulesetInstance)); // Remove any user mods that are no longer allowed. UserMods.Value = UserMods.Value.Where(m => allowedMods.Any(a => m.GetType() == a.GetType())).ToList(); @@ -494,7 +491,7 @@ namespace osu.Game.Screens.OnlinePlay.Match cancelTrackLooping(); } - private void applyLoopingToTrack(ValueChangedEvent _ = null) + private void applyLoopingToTrack(ValueChangedEvent? _ = null) { if (!this.IsCurrentScreen()) return; @@ -503,8 +500,8 @@ namespace osu.Game.Screens.OnlinePlay.Match if (track != null) { - Beatmap.Value.PrepareTrackForPreview(true); - music?.EnsurePlayingSomething(); + Beatmap.Value!.PrepareTrackForPreview(true); + music.EnsurePlayingSomething(); } } diff --git a/osu.Game/Screens/OnlinePlay/Match/UserModSelectButton.cs b/osu.Game/Screens/OnlinePlay/Match/UserModSelectButton.cs index f3ea82be99..219bb6a2e3 100644 --- a/osu.Game/Screens/OnlinePlay/Match/UserModSelectButton.cs +++ b/osu.Game/Screens/OnlinePlay/Match/UserModSelectButton.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. -#nullable disable using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs index 7975597beb..9de32267a2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Online.Multiplayer; @@ -12,14 +10,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { public partial class CreateMultiplayerMatchButton : CreateRoomButton { - private IBindable isConnected; - private IBindable operationInProgress; + private IBindable isConnected = null!; + private IBindable operationInProgress = null!; [Resolved] - private MultiplayerClient multiplayerClient { get; set; } + private MultiplayerClient multiplayerClient { get; set; } = null!; [Resolved] - private OngoingOperationTracker ongoingOperationTracker { get; set; } + private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!; [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs index d4483044e0..9a03a131b4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs @@ -1,9 +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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -20,8 +17,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public partial class GameplayChatDisplay : MatchChatDisplay, IKeyBindingHandler { [Resolved(CanBeNull = true)] - [CanBeNull] - private ILocalUserPlayInfo localUserInfo { get; set; } + private ILocalUserPlayInfo? localUserInfo { get; set; } private readonly IBindable localUserPlaying = new Bindable(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.cs index e1543eaceb..50e996d266 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.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 System.Linq; using Humanizer; @@ -33,15 +31,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match TimeSpan.FromMinutes(2) }; - public new Action Action; - - public Action CancelAction; + public new required Action Action; + public required Action CancelAction; [Resolved] - private MultiplayerClient multiplayerClient { get; set; } + private MultiplayerClient multiplayerClient { get; set; } = null!; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; private readonly Drawable background; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 7ce3dde7c2..ca8bc0b262 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -1,14 +1,13 @@ // 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 System.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Game.Graphics; @@ -20,17 +19,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match public partial class MultiplayerReadyButton : ReadyButton { [Resolved] - private MultiplayerClient multiplayerClient { get; set; } + private MultiplayerClient multiplayerClient { get; set; } = null!; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; - [CanBeNull] - private MultiplayerRoom room => multiplayerClient.Room; + private MultiplayerRoom? room => multiplayerClient.Room; - private Sample countdownTickSample; - private Sample countdownWarnSample; - private Sample countdownWarnFinalSample; + private Sample? countdownTickSample; + private Sample? countdownWarnSample; + private Sample? countdownWarnFinalSample; [BackgroundDependencyLoader] private void load(AudioManager audio) @@ -48,13 +46,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match onRoomUpdated(); } - private MultiplayerCountdown countdown; + private MultiplayerCountdown? countdown; private double countdownChangeTime; - private ScheduledDelegate countdownUpdateDelegate; + private ScheduledDelegate? countdownUpdateDelegate; private void onRoomUpdated() => Scheduler.AddOnce(() => { - MultiplayerCountdown newCountdown = room?.ActiveCountdowns.SingleOrDefault(c => c is MatchStartCountdown); + MultiplayerCountdown? newCountdown = room?.ActiveCountdowns.SingleOrDefault(c => c is MatchStartCountdown); if (newCountdown != countdown) { @@ -171,6 +169,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { get { + Debug.Assert(countdown != null); + double timeElapsed = Time.Current - countdownChangeTime; TimeSpan remaining; @@ -224,7 +224,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { base.Dispose(isDisposing); - if (multiplayerClient != null) + if (multiplayerClient.IsNotNull()) multiplayerClient.RoomUpdated -= onRoomUpdated; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index a5cc267569..50358ea9d3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.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.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -27,12 +25,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public partial class MultiplayerLoungeSubScreen : LoungeSubScreen { [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; [Resolved] - private MultiplayerClient client { get; set; } + private MultiplayerClient client { get; set; } = null!; - private Dropdown roomAccessTypeDropdown; + private Dropdown roomAccessTypeDropdown = null!; public override void OnResuming(ScreenTransitionEvent e) { @@ -83,7 +81,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override void OpenNewRoom(Room room) { - if (client?.IsConnected.Value != true) + if (!client.IsConnected.Value) { Logger.Log("Not currently connected to the multiplayer server.", LoggingTarget.Runtime, LogLevel.Important); return; @@ -95,7 +93,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private partial class MultiplayerListingPollingComponent : ListingPollingComponent { [Resolved] - private MultiplayerClient client { get; set; } + private MultiplayerClient client { get; set; } = null!; private readonly IBindable isConnected = new Bindable(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 65355f02e5..edc45dbf7c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -401,7 +401,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer StartPlay(); } - protected override Screen CreateGameplayScreen() + protected override Screen CreateGameplayScreen(PlaylistItem selectedItem) { Debug.Assert(client.LocalUser != null); Debug.Assert(client.Room != null); @@ -415,7 +415,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return new MultiSpectatorScreen(Room, users.Take(PlayerGrid.MAX_PLAYERS).ToArray()); default: - return new MultiplayerPlayerLoader(() => new MultiplayerPlayer(Room, SelectedItem.Value, users)); + return new MultiplayerPlayerLoader(() => new MultiplayerPlayer(Room, selectedItem, users)); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs index f682508319..7eb7f6610e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.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 System.Threading.Tasks; using osu.Framework.Allocation; @@ -18,9 +16,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public bool GameplayPassed => player?.GameplayState.HasPassed == true; [Resolved] - private MultiplayerClient multiplayerClient { get; set; } + private MultiplayerClient multiplayerClient { get; set; } = null!; - private Player player; + private Player? player; public MultiplayerPlayerLoader(Func createPlayer) : base(createPlayer) @@ -45,7 +43,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer .ContinueWith(task => failAndBail(task.Exception?.Message ?? "Server error"), TaskContinuationOptions.NotOnRanToCompletion); } - private void failAndBail(string message = null) + private void failAndBail(string? message = null) { if (!string.IsNullOrEmpty(message)) Logger.Log(message, LoggingTarget.Runtime, LogLevel.Important); diff --git a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs index 7f73d6655f..0c761dba44 100644 --- a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs +++ b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.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 osu.Framework.Bindables; using osu.Framework.Graphics; @@ -22,7 +20,7 @@ namespace osu.Game.Screens.OnlinePlay private readonly Bindable inProgress = new BindableBool(); - private LeasedBindable leasedInProgress; + private LeasedBindable? leasedInProgress; public OngoingOperationTracker() { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 1b7041c9bb..13ffb61d57 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.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.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -27,16 +25,16 @@ namespace osu.Game.Screens.OnlinePlay public IScreen CurrentSubScreen => screenStack.CurrentScreen; - public override bool CursorVisible => (screenStack?.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true; + public override bool CursorVisible => (screenStack.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true; // this is required due to PlayerLoader eventually being pushed to the main stack // while leases may be taken out by a subscreen. public override bool DisallowExternalBeatmapRulesetChanges => true; - protected LoungeSubScreen Lounge { get; private set; } + protected LoungeSubScreen Lounge { get; private set; } = null!; - private OnlinePlayScreenWaveContainer waves; - private ScreenStack screenStack; + private OnlinePlayScreenWaveContainer waves = null!; + private ScreenStack screenStack = null!; [Cached(Type = typeof(IRoomManager))] protected RoomManager RoomManager { get; private set; } @@ -45,7 +43,7 @@ namespace osu.Game.Screens.OnlinePlay private readonly OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker(); [Resolved] - protected IAPIProvider API { get; private set; } + protected IAPIProvider API { get; private set; } = null!; protected OnlinePlayScreen() { diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index f915676fb1..44d1841fb8 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -273,10 +273,13 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Logger.Log($"Polling adjusted (selection: {selectionPollingComponent.TimeBetweenPolls.Value})"); } - protected override Screen CreateGameplayScreen() => new PlayerLoader(() => new PlaylistsPlayer(Room, SelectedItem.Value) + protected override Screen CreateGameplayScreen(PlaylistItem selectedItem) { - Exited = () => leaderboard.RefetchScores() - }); + return new PlayerLoader(() => new PlaylistsPlayer(Room, selectedItem) + { + Exited = () => leaderboard.RefetchScores() + }); + } protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs index 3509519113..8ddc5325db 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// /// The cached . /// - Bindable SelectedRoom { get; } + Bindable SelectedRoom { get; } /// /// The cached diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index eebc3503bc..3f6c175fbd 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// public abstract partial class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestSceneDependencies { - public Bindable SelectedRoom => OnlinePlayDependencies.SelectedRoom; + public Bindable SelectedRoom => OnlinePlayDependencies.SelectedRoom; public IRoomManager RoomManager => OnlinePlayDependencies.RoomManager; public OngoingOperationTracker OngoingOperationTracker => OnlinePlayDependencies.OngoingOperationTracker; public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => OnlinePlayDependencies.AvailabilityTracker; diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs index 06f8cc40b9..e2670c9ad8 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// public class OnlinePlayTestSceneDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestSceneDependencies { - public Bindable SelectedRoom { get; } + public Bindable SelectedRoom { get; } public IRoomManager RoomManager { get; } public OngoingOperationTracker OngoingOperationTracker { get; } public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; } @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay public OnlinePlayTestSceneDependencies() { - SelectedRoom = new Bindable(); + SelectedRoom = new Bindable(); RequestsHandler = new TestRoomRequestsHandler(); OngoingOperationTracker = new OngoingOperationTracker(); AvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); From dc4581656602c5c1aca05f79902f584e412010a0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Nov 2024 20:41:14 +0900 Subject: [PATCH 095/139] Fix inspection --- .../OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs index 905a2bf31b..3186cf89a4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match client.ToggleSpectate().ContinueWith(_ => endOperation()); - void endOperation() => clickOperation?.Dispose(); + void endOperation() => clickOperation.Dispose(); } [BackgroundDependencyLoader] From cc59434ea44d42726468f63668db0e4091e1116f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Nov 2024 20:52:34 +0900 Subject: [PATCH 096/139] Fix crash due to being too forgiving of nulls This one is super duper annoying to test, because we already have e.g. `TestScenePlaylistsScreen`. The only way to test it would be to use an `OsuGameTestScene`. Maybe this is okay? --- osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 13ffb61d57..cc6a4e09e1 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -33,8 +33,8 @@ namespace osu.Game.Screens.OnlinePlay protected LoungeSubScreen Lounge { get; private set; } = null!; + private readonly ScreenStack screenStack = new OnlinePlaySubScreenStack { RelativeSizeAxes = Axes.Both }; private OnlinePlayScreenWaveContainer waves = null!; - private ScreenStack screenStack = null!; [Cached(Type = typeof(IRoomManager))] protected RoomManager RoomManager { get; private set; } @@ -65,7 +65,7 @@ namespace osu.Game.Screens.OnlinePlay RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - screenStack = new OnlinePlaySubScreenStack { RelativeSizeAxes = Axes.Both }, + screenStack, new Header(ScreenTitle, screenStack), RoomManager, ongoingOperationTracker, From 209380cbac77939bc34e03459f376a5886f4fc30 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Nov 2024 21:03:12 +0900 Subject: [PATCH 097/139] Enable NRT in `TestMultiplayerRoomManager` --- .../Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index 8d04c808fd..b998a638e5 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.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 System.Collections.Generic; using osu.Game.Online.API.Requests.Responses; @@ -28,10 +26,10 @@ namespace osu.Game.Tests.Visual.Multiplayer public IReadOnlyList ServerSideRooms => requestsHandler.ServerSideRooms; - public override void CreateRoom(Room room, Action onSuccess = null, Action onError = null) + public override void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null) => base.CreateRoom(room, r => onSuccess?.Invoke(r), onError); - public override void JoinRoom(Room room, string password = null, Action onSuccess = null, Action onError = null) + public override void JoinRoom(Room room, string? password = null, Action? onSuccess = null, Action? onError = null) => base.JoinRoom(room, password, r => onSuccess?.Invoke(r), onError); /// From 42f2ad2423df7b9cab73e387241279804db4710c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Nov 2024 13:06:01 +0100 Subject: [PATCH 098/139] Fix tests --- .../TestSceneBeatmapAttributeText.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs index e3a6fca319..5acd6cb084 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs @@ -73,10 +73,10 @@ namespace osu.Game.Tests.Visual.UserInterface }); }); - [TestCase(BeatmapAttribute.CircleSize, "Circle Size: 1.00")] - [TestCase(BeatmapAttribute.HPDrain, "HP Drain: 2.00")] - [TestCase(BeatmapAttribute.Accuracy, "Accuracy: 3.00")] - [TestCase(BeatmapAttribute.ApproachRate, "Approach Rate: 4.00")] + [TestCase(BeatmapAttribute.CircleSize, "Circle Size: 1")] + [TestCase(BeatmapAttribute.HPDrain, "HP Drain: 2")] + [TestCase(BeatmapAttribute.Accuracy, "Accuracy: 3")] + [TestCase(BeatmapAttribute.ApproachRate, "Approach Rate: 4")] [TestCase(BeatmapAttribute.Title, "Title: _Title")] [TestCase(BeatmapAttribute.Artist, "Artist: _Artist")] [TestCase(BeatmapAttribute.Creator, "Creator: _Creator")] @@ -121,15 +121,15 @@ namespace osu.Game.Tests.Visual.UserInterface Difficulty = { ApproachRate = 10, - CircleSize = 9 + CircleSize = 9.5f } } })); - test(BeatmapAttribute.BPM, new OsuModDoubleTime(), "BPM: 100.00", "BPM: 150.00"); + test(BeatmapAttribute.BPM, new OsuModDoubleTime(), "BPM: 100", "BPM: 150"); test(BeatmapAttribute.Length, new OsuModDoubleTime(), "Length: 00:30", "Length: 00:20"); - test(BeatmapAttribute.ApproachRate, new OsuModDoubleTime(), "Approach Rate: 10.00", "Approach Rate: 11.00"); - test(BeatmapAttribute.CircleSize, new OsuModHardRock(), "Circle Size: 9.00", "Circle Size: 10.00"); + test(BeatmapAttribute.ApproachRate, new OsuModDoubleTime(), "Approach Rate: 10", "Approach Rate: 11"); + test(BeatmapAttribute.CircleSize, new OsuModHardRock(), "Circle Size: 9.5", "Circle Size: 10"); void test(BeatmapAttribute attribute, Mod mod, string before, string after) { From 84ac3097c23dfad41d305cee13586fd09e4a72e1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Nov 2024 22:47:46 +0900 Subject: [PATCH 099/139] Populate parameter description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 0d9c210149..ffea3878fa 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -407,7 +407,7 @@ namespace osu.Game.Screens.OnlinePlay.Match /// /// Creates the gameplay screen to be entered. /// - /// + /// The playlist item about to be played. /// The screen to enter. protected abstract Screen CreateGameplayScreen(PlaylistItem selectedItem); From f738fb2a89e2ceffa9b1cb6b5cde67b3d2f33202 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Nov 2024 23:48:04 +0900 Subject: [PATCH 100/139] Populate rooms as soon as they're joined --- osu.Game/Online/Rooms/JoinRoomRequest.cs | 2 +- osu.Game/Screens/OnlinePlay/Components/RoomManager.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Rooms/JoinRoomRequest.cs b/osu.Game/Online/Rooms/JoinRoomRequest.cs index 2ceb37fc01..dfc7a53fb2 100644 --- a/osu.Game/Online/Rooms/JoinRoomRequest.cs +++ b/osu.Game/Online/Rooms/JoinRoomRequest.cs @@ -7,7 +7,7 @@ using osu.Game.Online.API; namespace osu.Game.Online.Rooms { - public class JoinRoomRequest : APIRequest + public class JoinRoomRequest : APIRequest { public readonly Room Room; public readonly string? Password; diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index ca42e98e3c..73f980f0a3 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -72,9 +72,13 @@ namespace osu.Game.Screens.OnlinePlay.Components currentJoinRoomRequest?.Cancel(); currentJoinRoomRequest = new JoinRoomRequest(room, password); - currentJoinRoomRequest.Success += () => + currentJoinRoomRequest.Success += result => { joinedRoom.Value = room; + + AddOrUpdateRoom(result); + room.CopyFrom(result); // Also copy back to the source model, since this is likely to have been stored elsewhere. + onSuccess?.Invoke(room); }; From 2a7266cb233343ee71ebb4a1104dad4edac74ab5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Nov 2024 01:26:35 +0900 Subject: [PATCH 101/139] Fix tests --- osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 224bb90c8c..c9149bda22 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -71,7 +71,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay return true; } - joinRoomRequest.TriggerSuccess(); + joinRoomRequest.TriggerSuccess(createResponseRoom(room, true)); return true; } From 5dffc322aff5485771de4c3f72cb1df7f8c522a8 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 21 Nov 2024 17:48:12 -0500 Subject: [PATCH 102/139] Recreate beatmap every test run in `ModTestScene` --- .../Mods/TestSceneCatchModNoScope.cs | 6 +- .../Mods/TestSceneCatchModRelax.cs | 2 +- .../TestSceneCatchModHidden.cs | 2 +- .../Mods/TestSceneManiaModAutoplay.cs | 2 +- .../Mods/TestSceneManiaModDoubleTime.cs | 4 +- .../Mods/TestSceneManiaModFadeIn.cs | 2 +- .../Mods/TestSceneManiaModHidden.cs | 2 +- .../Mods/TestSceneManiaModPerfect.cs | 4 +- .../Mods/TestSceneManiaModSuddenDeath.cs | 4 +- .../Mods/TestSceneOsuModAlternate.cs | 8 +-- .../Mods/TestSceneOsuModAutoplay.cs | 4 +- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 2 +- .../Mods/TestSceneOsuModFlashlight.cs | 6 +- .../Mods/TestSceneOsuModHidden.cs | 8 +-- .../Mods/TestSceneOsuModMirror.cs | 2 +- .../Mods/TestSceneOsuModNoScope.cs | 6 +- .../Mods/TestSceneOsuModPerfect.cs | 2 +- .../Mods/TestSceneOsuModRandom.cs | 4 +- .../Mods/TestSceneOsuModSingleTap.cs | 8 +-- .../Mods/TestSceneOsuModSpunOut.cs | 2 +- .../Mods/TestSceneOsuModStrictTracking.cs | 2 +- .../Mods/TestSceneOsuModSuddenDeath.cs | 4 +- .../TestSceneMissHitWindowJudgements.cs | 14 ++--- .../Mods/TestSceneTaikoModHidden.cs | 60 ++++++++++--------- .../Mods/TestSceneTaikoModRelax.cs | 37 ++++++------ .../Mods/TestSceneTaikoModSingleTap.cs | 10 ++-- .../Mods/TestSceneModAccuracyChallenge.cs | 4 +- .../Tests/Visual/ModFailConditionTestScene.cs | 2 +- osu.Game/Tests/Visual/ModTestScene.cs | 4 +- 29 files changed, 110 insertions(+), 107 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs index c8f7da1aae..d185293093 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods }, Autoplay = true, PassCondition = () => Player.ScoreProcessor.Combo.Value == 2, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs index a161615579..6114c9ce6f 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods Mod = new CatchModRelax(), Autoplay = false, PassCondition = passCondition, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs index 825e8c697c..1f32a4531b 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Tests { CreateModTest(new ModTestData { - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs index f653f209c1..4871f5b585 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods CreateModTest(new ModTestData { Autoplay = true, - Beatmap = new ManiaBeatmap(new StageDefinition(1)) + Beatmap = () => new ManiaBeatmap(new StageDefinition(1)) { HitObjects = new List { diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs index 975e43ec08..a492c53456 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods && Precision.AlmostEquals(Player.ScoreProcessor.Accuracy.Value, 0.9836, 0.01) && Player.ScoreProcessor.TotalScore.Value == 946_049, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, Difficulty = { OverallDifficulty = 10 }, @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods && Player.ScoreProcessor.Accuracy.Value == 1 && Player.ScoreProcessor.TotalScore.Value == (long)(1_000_000 * doubleTime.ScoreMultiplier), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, Difficulty = { OverallDifficulty = 10 }, diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs index 9620897983..c425bb05c7 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods CreateModTest(new ModTestData { Mod = new ManiaModHidden(), - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = Enumerable.Range(1, 100).Select(i => (HitObject)new Note { StartTime = 1000 + 200 * i }).ToList(), Breaks = { new BreakPeriod(2000, 28000) } diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs index ae23c4573c..98cceefaf5 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods CreateModTest(new ModTestData { Mod = new ManiaModHidden(), - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = Enumerable.Range(1, 100).Select(i => (HitObject)new Note { StartTime = 1000 + 200 * i }).ToList(), Breaks = { new BreakPeriod(2000, 28000) } diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs index 51730e2b43..7e2a0a4dd7 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Mod = new ManiaModPerfect(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Mod = new ManiaModPerfect(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs index 619816a815..c220578833 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Mod = new ManiaModSuddenDeath(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Mod = new ManiaModSuddenDeath(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs index 7375617aa8..2192184eef 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModAlternate(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 4, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModAlternate(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModAlternate(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 1, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModAlternate(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { Breaks = { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs index ace7f23989..5baafb4f79 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods CreateModTest(new ModTestData { Autoplay = true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Autoplay = true, Mod = mod, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 472c341bdd..0a97933df6 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods public void TestNoAdjustment() => CreateModTest(new ModTestData { Mod = new OsuModDifficultyAdjust(), - Beatmap = new Beatmap + Beatmap = () => new Beatmap { BeatmapInfo = new BeatmapInfo { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs index 075fdd88ca..e413606a3d 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Player.GameplayClockContainer.CurrentTime < 1000 && Player.ChildrenOfType.Flashlight>().Single().FlashlightDim > 0; return Player.GameplayState.HasPassed && !sliderDimmedBeforeStartTime; }, - Beatmap = new OsuBeatmap + Beatmap = () => new OsuBeatmap { HitObjects = new List { @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Player.GameplayClockContainer.CurrentTime >= 1000 && Player.ChildrenOfType.Flashlight>().Single().FlashlightDim > 0; return Player.GameplayState.HasPassed && sliderDimmed; }, - Beatmap = new OsuBeatmap + Beatmap = () => new OsuBeatmap { HitObjects = new List { @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Player.GameplayClockContainer.CurrentTime >= 1000 && Player.ChildrenOfType.Flashlight>().Single().FlashlightDim > 0; return Player.GameplayState.HasPassed && sliderDimmed; }, - Beatmap = new OsuBeatmap + Beatmap = () => new OsuBeatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs index 58bdd805c1..fec4a748d3 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new TestOsuModHidden(), Autoplay = true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new TestOsuModHidden(), Autoplay = true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new TestOsuModHidden(), Autoplay = true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModHidden { OnlyFadeApproachCircles = { Value = true } }, Autoplay = true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs index 0b3496ba68..c8e7061416 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods public void TestCorrectReflections([Values] OsuModMirror.MirrorType type, [Values] bool withStrictTracking) => CreateModTest(new ModTestData { Autoplay = true, - Beatmap = new OsuBeatmap + Beatmap = () => new OsuBeatmap { HitObjects = { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs index d3996ebc3b..d7922f8ba7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs index b01bbbfca1..a9a94cfd72 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModPerfect(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs index 060a845137..81d753fd2d 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs @@ -55,10 +55,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods PassCondition = () => true }); - private OsuBeatmap jumpBeatmap => + private OsuBeatmap jumpBeatmap() => createHitCircleBeatmap(new[] { 100, 200, 300, 400 }, 8, 300, 2 * 300); - private OsuBeatmap streamBeatmap => + private OsuBeatmap streamBeatmap() => createHitCircleBeatmap(new[] { 10, 20, 30, 40, 50, 60, 70, 80 }, 16, 150, 4 * 150); private OsuBeatmap createHitCircleBeatmap(IEnumerable spacings, int objectsPerSpacing, int interval, int beatLength) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs index bd2b205ac8..3d553fa051 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSingleTap(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 2, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSingleTap(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSingleTap(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 1, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSingleTap(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { Breaks = { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index de3ea5f148..ced69d59de 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }); } - private Beatmap singleSpinnerBeatmap => new Beatmap + private Beatmap singleSpinnerBeatmap() => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs index 726b415977..d295b3d891 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModStrictTracking(), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs index ea048aaa6e..25edda34d4 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSuddenDeath(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSuddenDeath(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs index c37660831b..5164d7d01b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Autoplay = false, Mod = new TestAutoMod(), - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = { new HitCircle { Position = new Vector2(256, 192) } } }, @@ -47,18 +47,16 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestMissViaNotHitting() { - var beatmap = new Beatmap - { - HitObjects = { new HitCircle { Position = new Vector2(256, 192) } } - }; - var hitWindows = new OsuHitWindows(); - hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); + hitWindows.SetDifficulty(IBeatmapDifficultyInfo.DEFAULT_DIFFICULTY); CreateModTest(new ModTestData { Autoplay = false, - Beatmap = beatmap, + Beatmap = () => new Beatmap + { + HitObjects = { new HitCircle { Position = new Vector2(256, 192) } } + }, PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset >= hitWindows.WindowFor(HitResult.Meh) && !Player.Results[0].IsHit }); } diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs index 6e6be26e43..cf199f062a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs @@ -31,40 +31,42 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { const double hit_time = 1; - var beatmap = new Beatmap - { - HitObjects = new List - { - new Hit - { - Type = HitType.Rim, - StartTime = hit_time, - }, - new Hit - { - Type = HitType.Centre, - StartTime = hit_time * 2, - }, - }, - BeatmapInfo = - { - Difficulty = new BeatmapDifficulty - { - SliderTickRate = 4, - OverallDifficulty = 0, - }, - Ruleset = new TaikoRuleset().RulesetInfo - }, - }; - - beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f }); - CreateModTest(new ModTestData { Mod = new TaikoModHidden(), Autoplay = true, PassCondition = checkAllMaxResultJudgements(2), - Beatmap = beatmap, + Beatmap = () => + { + var beatmap = new Beatmap + { + HitObjects = new List + { + new Hit + { + Type = HitType.Rim, + StartTime = hit_time, + }, + new Hit + { + Type = HitType.Centre, + StartTime = hit_time * 2, + }, + }, + BeatmapInfo = + { + Difficulty = new BeatmapDifficulty + { + SliderTickRate = 4, + OverallDifficulty = 0, + }, + Ruleset = new TaikoRuleset().RulesetInfo + }, + }; + + beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f }); + return beatmap; + }, }); } } diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs index caf8aa8e76..6894a19a1f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs @@ -15,7 +15,26 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods [Test] public void TestRelax() { - var beatmap = new TaikoBeatmap + var beatmapForReplay = createBeatmap(); + + foreach (var ho in beatmapForReplay.HitObjects) + ho.ApplyDefaults(beatmapForReplay.ControlPointInfo, beatmapForReplay.Difficulty); + + var replay = new TaikoAutoGenerator(beatmapForReplay).Generate(); + + foreach (var frame in replay.Frames.OfType().Where(r => r.Actions.Any())) + frame.Actions = [TaikoAction.LeftCentre]; + + CreateModTest(new ModTestData + { + Mod = new TaikoModRelax(), + Beatmap = createBeatmap, + ReplayFrames = replay.Frames, + Autoplay = false, + PassCondition = () => Player.ScoreProcessor.HasCompleted.Value && Player.ScoreProcessor.Accuracy.Value == 1, + }); + + TaikoBeatmap createBeatmap() => new TaikoBeatmap { HitObjects = { @@ -25,22 +44,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods new Swell { StartTime = 1250, Duration = 500 }, } }; - foreach (var ho in beatmap.HitObjects) - ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); - - var replay = new TaikoAutoGenerator(beatmap).Generate(); - - foreach (var frame in replay.Frames.OfType().Where(r => r.Actions.Any())) - frame.Actions = [TaikoAction.LeftCentre]; - - CreateModTest(new ModTestData - { - Mod = new TaikoModRelax(), - Beatmap = beatmap, - ReplayFrames = replay.Frames, - Autoplay = false, - PassCondition = () => Player.ScoreProcessor.HasCompleted.Value && Player.ScoreProcessor.Accuracy.Value == 1, - }); } } } diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs index 3a11a91f82..840bf78ffe 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = new List { @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { Breaks = { diff --git a/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs b/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs index c5e56c6453..7011303ccd 100644 --- a/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs +++ b/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Mods MinimumAccuracy = { Value = 0.6 } }, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = Enumerable.Range(0, 5).Select(i => new HitCircle { @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual.Mods AccuracyJudgeMode = { Value = ModAccuracyChallenge.AccuracyMode.Standard } }, Autoplay = false, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { HitObjects = Enumerable.Range(0, 5).Select(i => new HitCircle { diff --git a/osu.Game/Tests/Visual/ModFailConditionTestScene.cs b/osu.Game/Tests/Visual/ModFailConditionTestScene.cs index 8f0dff055d..364145160d 100644 --- a/osu.Game/Tests/Visual/ModFailConditionTestScene.cs +++ b/osu.Game/Tests/Visual/ModFailConditionTestScene.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual protected void CreateHitObjectTest(HitObjectTestData testData, bool shouldMiss) => CreateModTest(new ModTestData { Mod = mod, - Beatmap = new Beatmap + Beatmap = () => new Beatmap { BeatmapInfo = { Ruleset = CreatePlayerRuleset().RulesetInfo }, HitObjects = { testData.HitObject } diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index c2ebcdefac..3265e5c257 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual base.TearDownSteps(); } - protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => CurrentTestData?.Beatmap ?? base.CreateBeatmap(ruleset); + protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => CurrentTestData?.Beatmap?.Invoke() ?? base.CreateBeatmap(ruleset); protected sealed override TestPlayer CreatePlayer(Ruleset ruleset) { @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual /// The beatmap for this test case. /// [CanBeNull] - public IBeatmap Beatmap; + public Func Beatmap; /// /// The conditions that cause this test case to pass. From 41addaae9a26575482f6682ce3b450d9420c1c3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2024 13:11:32 +0900 Subject: [PATCH 103/139] Rename variable to imply it is now a function --- .../Mods/TestSceneCatchModNoScope.cs | 6 +++--- .../Mods/TestSceneCatchModRelax.cs | 2 +- .../TestSceneCatchModHidden.cs | 2 +- .../Mods/TestSceneManiaModAutoplay.cs | 2 +- .../Mods/TestSceneManiaModDoubleTime.cs | 4 ++-- .../Mods/TestSceneManiaModFadeIn.cs | 2 +- .../Mods/TestSceneManiaModHidden.cs | 2 +- .../Mods/TestSceneManiaModPerfect.cs | 4 ++-- .../Mods/TestSceneManiaModSuddenDeath.cs | 4 ++-- .../Mods/TestSceneOsuModAlternate.cs | 8 ++++---- .../Mods/TestSceneOsuModAutoplay.cs | 4 ++-- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 2 +- .../Mods/TestSceneOsuModFlashlight.cs | 6 +++--- .../Mods/TestSceneOsuModHidden.cs | 8 ++++---- .../Mods/TestSceneOsuModMirror.cs | 2 +- .../Mods/TestSceneOsuModNoScope.cs | 6 +++--- .../Mods/TestSceneOsuModPerfect.cs | 2 +- .../Mods/TestSceneOsuModRandom.cs | 4 ++-- .../Mods/TestSceneOsuModSingleTap.cs | 8 ++++---- .../Mods/TestSceneOsuModSpunOut.cs | 6 +++--- .../Mods/TestSceneOsuModStrictTracking.cs | 2 +- .../Mods/TestSceneOsuModSuddenDeath.cs | 4 ++-- .../TestSceneMissHitWindowJudgements.cs | 4 ++-- .../Mods/TestSceneTaikoModHidden.cs | 2 +- .../Mods/TestSceneTaikoModRelax.cs | 2 +- .../Mods/TestSceneTaikoModSingleTap.cs | 10 +++++----- .../Visual/Mods/TestSceneModAccuracyChallenge.cs | 4 ++-- osu.Game/Tests/Visual/ModFailConditionTestScene.cs | 2 +- osu.Game/Tests/Visual/ModTestScene.cs | 9 +++++---- 29 files changed, 62 insertions(+), 61 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs index d185293093..828e47e187 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods }, Autoplay = true, PassCondition = () => Player.ScoreProcessor.Combo.Value == 2, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs index 6114c9ce6f..92f7960183 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods Mod = new CatchModRelax(), Autoplay = false, PassCondition = passCondition, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs index 1f32a4531b..9365659f79 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Tests { CreateModTest(new ModTestData { - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs index 4871f5b585..d644ef359d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModAutoplay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods CreateModTest(new ModTestData { Autoplay = true, - Beatmap = () => new ManiaBeatmap(new StageDefinition(1)) + CreateBeatmap = () => new ManiaBeatmap(new StageDefinition(1)) { HitObjects = new List { diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs index a492c53456..50ec119e4d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModDoubleTime.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods && Precision.AlmostEquals(Player.ScoreProcessor.Accuracy.Value, 0.9836, 0.01) && Player.ScoreProcessor.TotalScore.Value == 946_049, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, Difficulty = { OverallDifficulty = 10 }, @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods && Player.ScoreProcessor.Accuracy.Value == 1 && Player.ScoreProcessor.TotalScore.Value == (long)(1_000_000 * doubleTime.ScoreMultiplier), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, Difficulty = { OverallDifficulty = 10 }, diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs index c425bb05c7..f403d67377 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods CreateModTest(new ModTestData { Mod = new ManiaModHidden(), - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = Enumerable.Range(1, 100).Select(i => (HitObject)new Note { StartTime = 1000 + 200 * i }).ToList(), Breaks = { new BreakPeriod(2000, 28000) } diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs index 98cceefaf5..e7e1cf3c7c 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods CreateModTest(new ModTestData { Mod = new ManiaModHidden(), - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = Enumerable.Range(1, 100).Select(i => (HitObject)new Note { StartTime = 1000 + 200 * i }).ToList(), Breaks = { new BreakPeriod(2000, 28000) } diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs index 7e2a0a4dd7..b7a9b31dcc 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Mod = new ManiaModPerfect(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Mod = new ManiaModPerfect(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs index c220578833..15c49ab0a0 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Mod = new ManiaModSuddenDeath(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods Mod = new ManiaModSuddenDeath(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs index 2192184eef..27ff26b438 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModAlternate(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 4, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModAlternate(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModAlternate(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 1, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModAlternate(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { Breaks = { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs index 5baafb4f79..8786b17b92 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods CreateModTest(new ModTestData { Autoplay = true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Autoplay = true, Mod = mod, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 0a97933df6..ca752fe918 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods public void TestNoAdjustment() => CreateModTest(new ModTestData { Mod = new OsuModDifficultyAdjust(), - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { BeatmapInfo = new BeatmapInfo { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs index e413606a3d..6bd3f25bdb 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Player.GameplayClockContainer.CurrentTime < 1000 && Player.ChildrenOfType.Flashlight>().Single().FlashlightDim > 0; return Player.GameplayState.HasPassed && !sliderDimmedBeforeStartTime; }, - Beatmap = () => new OsuBeatmap + CreateBeatmap = () => new OsuBeatmap { HitObjects = new List { @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Player.GameplayClockContainer.CurrentTime >= 1000 && Player.ChildrenOfType.Flashlight>().Single().FlashlightDim > 0; return Player.GameplayState.HasPassed && sliderDimmed; }, - Beatmap = () => new OsuBeatmap + CreateBeatmap = () => new OsuBeatmap { HitObjects = new List { @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Player.GameplayClockContainer.CurrentTime >= 1000 && Player.ChildrenOfType.Flashlight>().Single().FlashlightDim > 0; return Player.GameplayState.HasPassed && sliderDimmed; }, - Beatmap = () => new OsuBeatmap + CreateBeatmap = () => new OsuBeatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs index fec4a748d3..c513f98f21 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new TestOsuModHidden(), Autoplay = true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new TestOsuModHidden(), Autoplay = true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new TestOsuModHidden(), Autoplay = true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModHidden { OnlyFadeApproachCircles = { Value = true } }, Autoplay = true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs index c8e7061416..076cb9ae15 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMirror.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods public void TestCorrectReflections([Values] OsuModMirror.MirrorType type, [Values] bool withStrictTracking) => CreateModTest(new ModTestData { Autoplay = true, - Beatmap = () => new OsuBeatmap + CreateBeatmap = () => new OsuBeatmap { HitObjects = { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs index d7922f8ba7..b9559aeba3 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, Autoplay = true, PassCondition = () => true, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs index a9a94cfd72..8498e53bf0 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModPerfect(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs index 81d753fd2d..3456fcbe84 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { AngleSharpness = { Value = angleSharpness } }, - Beatmap = jumpBeatmap, + CreateBeatmap = jumpBeatmap, Autoplay = true, PassCondition = () => true }); @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { AngleSharpness = { Value = angleSharpness } }, - Beatmap = streamBeatmap, + CreateBeatmap = streamBeatmap, Autoplay = true, PassCondition = () => true }); diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs index 3d553fa051..b0be70e85e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSingleTap(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 2, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSingleTap(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSingleTap(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 1, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSingleTap(), PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { Breaks = { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index ced69d59de..3706b9ac07 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModSpunOut(), Autoplay = false, - Beatmap = singleSpinnerBeatmap, + CreateBeatmap = singleSpinnerBeatmap, PassCondition = () => { // Bind to the first spinner's results for further tracking. @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mods = mods, Autoplay = false, - Beatmap = singleSpinnerBeatmap, + CreateBeatmap = singleSpinnerBeatmap, PassCondition = () => { var counter = Player.ChildrenOfType().SingleOrDefault(); @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModSpunOut(), Autoplay = false, - Beatmap = singleSpinnerBeatmap, + CreateBeatmap = singleSpinnerBeatmap, PassCondition = () => { // Bind to the first spinner's results for further tracking. diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs index d295b3d891..66a60e3542 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModStrictTracking(), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs index 25edda34d4..688cf70f71 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSuddenDeath(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Mod = new OsuModSuddenDeath(), PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs index 5164d7d01b..7a89140fc4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Autoplay = false, Mod = new TestAutoMod(), - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = { new HitCircle { Position = new Vector2(256, 192) } } }, @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Tests CreateModTest(new ModTestData { Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = { new HitCircle { Position = new Vector2(256, 192) } } }, diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs index cf199f062a..e6d5c51902 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods Mod = new TaikoModHidden(), Autoplay = true, PassCondition = checkAllMaxResultJudgements(2), - Beatmap = () => + CreateBeatmap = () => { var beatmap = new Beatmap { diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs index 6894a19a1f..fffe42f1f8 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModRelax.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods CreateModTest(new ModTestData { Mod = new TaikoModRelax(), - Beatmap = createBeatmap, + CreateBeatmap = createBeatmap, ReplayFrames = replay.Frames, Autoplay = false, PassCondition = () => Player.ScoreProcessor.HasCompleted.Value && Player.ScoreProcessor.Accuracy.Value == 1, diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs index 840bf78ffe..b12ac10d2d 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = new List { @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { Mod = new TaikoModSingleTap(), Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { Breaks = { diff --git a/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs b/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs index 7011303ccd..e1c15863ad 100644 --- a/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs +++ b/osu.Game.Tests/Visual/Mods/TestSceneModAccuracyChallenge.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Mods MinimumAccuracy = { Value = 0.6 } }, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = Enumerable.Range(0, 5).Select(i => new HitCircle { @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual.Mods AccuracyJudgeMode = { Value = ModAccuracyChallenge.AccuracyMode.Standard } }, Autoplay = false, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { HitObjects = Enumerable.Range(0, 5).Select(i => new HitCircle { diff --git a/osu.Game/Tests/Visual/ModFailConditionTestScene.cs b/osu.Game/Tests/Visual/ModFailConditionTestScene.cs index 364145160d..72bdd54c51 100644 --- a/osu.Game/Tests/Visual/ModFailConditionTestScene.cs +++ b/osu.Game/Tests/Visual/ModFailConditionTestScene.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual protected void CreateHitObjectTest(HitObjectTestData testData, bool shouldMiss) => CreateModTest(new ModTestData { Mod = mod, - Beatmap = () => new Beatmap + CreateBeatmap = () => new Beatmap { BeatmapInfo = { Ruleset = CreatePlayerRuleset().RulesetInfo }, HitObjects = { testData.HitObject } diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 3265e5c257..eb61518d35 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual base.TearDownSteps(); } - protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => CurrentTestData?.Beatmap?.Invoke() ?? base.CreateBeatmap(ruleset); + protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => CurrentTestData?.CreateBeatmap?.Invoke() ?? base.CreateBeatmap(ruleset); protected sealed override TestPlayer CreatePlayer(Ruleset ruleset) { @@ -93,7 +93,7 @@ namespace osu.Game.Tests.Visual protected class ModTestData { /// - /// Whether to use a replay to simulate an auto-play. True by default. + /// Whether to use a replay to simulate an autoplay. True by default. /// public bool Autoplay = true; @@ -104,10 +104,11 @@ namespace osu.Game.Tests.Visual public List ReplayFrames; /// - /// The beatmap for this test case. + /// A function which should create a new instance of a beatmap containing relevant + /// content to the test. /// [CanBeNull] - public Func Beatmap; + public Func CreateBeatmap; /// /// The conditions that cause this test case to pass. From a76b4418b9159ad25aaa67ec9b1ced2c7e588c46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2024 16:55:37 +0900 Subject: [PATCH 104/139] 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 e59ac9e7c8e7af67e955c82865c5b422e21c8964 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Nov 2024 17:19:26 +0900 Subject: [PATCH 105/139] No longer remove expired playlist items from `Room` model --- osu.Game/Online/Rooms/Room.cs | 11 ----------- .../OnlinePlay/Components/ListingPollingComponent.cs | 3 --- .../Components/SelectionPollingComponent.cs | 1 - 3 files changed, 15 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 486f70c0ed..729074c5b6 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -388,21 +388,10 @@ namespace osu.Game.Online.Rooms CurrentPlaylistItem = other.CurrentPlaylistItem; AutoSkip = other.AutoSkip; - other.RemoveExpiredPlaylistItems(); - Playlist = other.Playlist; RecentParticipants = other.RecentParticipants; } - public void RemoveExpiredPlaylistItems() - { - // Todo: This is not the best way/place to do this, but the intention is to display all playlist items when the room has ended, - // and display only the non-expired playlist items while the room is still active. In order to achieve this, all expired items are removed from the source Room. - // More refactoring is required before this can be done locally instead - DrawableRoomPlaylist is currently directly bound to the playlist to display items in the room. - if (Status is not RoomStatusEnded) - Playlist = Playlist.Where(i => !i.Expired).ToArray(); - } - [JsonObject(MemberSerialization.OptIn)] public class RoomPlaylistItemStats { diff --git a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs index b10ce8ed1b..b213d424df 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs @@ -60,10 +60,7 @@ namespace osu.Game.Screens.OnlinePlay.Components } foreach (var incoming in result) - { - incoming.RemoveExpiredPlaylistItems(); RoomManager.AddOrUpdateRoom(incoming); - } initialRoomsReceived.Value = true; tcs.SetResult(true); diff --git a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs index 8b80228ae1..7cee8b3546 100644 --- a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs @@ -36,7 +36,6 @@ namespace osu.Game.Screens.OnlinePlay.Components req.Success += result => { - result.RemoveExpiredPlaylistItems(); RoomManager.AddOrUpdateRoom(result); tcs.SetResult(true); }; From 39504c348dded475b64b6dbdb88b7abd6a7a1941 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Nov 2024 17:22:30 +0900 Subject: [PATCH 106/139] Cleanup `CopyFrom()` method Though the code appears slightly different, it should be semantically equivalent. APIUser equality is implemented on `Id` and `Host` should never transition from non-null to null. --- osu.Game/Online/Rooms/Room.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 729074c5b6..e1813c7e4e 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -366,12 +366,8 @@ namespace osu.Game.Online.Rooms { RoomID = other.RoomID; Name = other.Name; - Category = other.Category; - - if (other.Host != null && Host?.Id != other.Host.Id) - Host = other.Host; - + Host = other.Host; ChannelId = other.ChannelId; Status = other.Status; Availability = other.Availability; @@ -387,7 +383,6 @@ namespace osu.Game.Online.Rooms PlaylistItemStats = other.PlaylistItemStats; CurrentPlaylistItem = other.CurrentPlaylistItem; AutoSkip = other.AutoSkip; - Playlist = other.Playlist; RecentParticipants = other.RecentParticipants; } From 29757ffdf2260a22f736e11dd1d2100a26ac11ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2024 17:36:28 +0900 Subject: [PATCH 107/139] 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 04ed954387ce19c909940043861ef6bb83bfef27 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2024 18:17:53 +0900 Subject: [PATCH 108/139] Fix song ticker having very bad contrast against bright backgrounds Closes #30814. --- osu.Game/Screens/Menu/SongTicker.cs | 70 ++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Menu/SongTicker.cs b/osu.Game/Screens/Menu/SongTicker.cs index 3bdc0efe19..24d3e1a92c 100644 --- a/osu.Game/Screens/Menu/SongTicker.cs +++ b/osu.Game/Screens/Menu/SongTicker.cs @@ -8,8 +8,12 @@ using osu.Game.Graphics.Sprites; using osuTK; using osu.Game.Graphics; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Beatmaps; +using osuTK.Graphics; namespace osu.Game.Screens.Menu { @@ -27,26 +31,60 @@ namespace osu.Game.Screens.Menu public SongTicker() { AutoSizeAxes = Axes.Both; - Child = new FillFlowContainer + InternalChildren = new Drawable[] { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 3), - Children = new Drawable[] + new Container { - title = new OsuSpriteText + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Position = new Vector2(5, -5), + Padding = new MarginPadding(-5), + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light, italics: true) - }, - artist = new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Font = OsuFont.GetFont(size: 16) + new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Radius = 75, + Type = EdgeEffectType.Shadow, + Colour = OsuColour.Gray(0.04f).Opacity(0.3f), + }, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0, + }, + } + }, } - } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 3), + Children = new Drawable[] + { + title = new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light, italics: true) + }, + artist = new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Font = OsuFont.GetFont(size: 16) + } + } + }, }; } From c590bef4c3ce5550e0b1af2ad6ab1625348e58d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2024 19:05:29 +0900 Subject: [PATCH 109/139] 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 2f45ebeec84ad365e8a09e27c329746171575731 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sat, 23 Nov 2024 20:13:57 -0500 Subject: [PATCH 110/139] Remove using directive --- osu.Game/Screens/Menu/SongTicker.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Menu/SongTicker.cs b/osu.Game/Screens/Menu/SongTicker.cs index 24d3e1a92c..3aac365eee 100644 --- a/osu.Game/Screens/Menu/SongTicker.cs +++ b/osu.Game/Screens/Menu/SongTicker.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Beatmaps; -using osuTK.Graphics; namespace osu.Game.Screens.Menu { From 2f096f71d3b824d81148b1ed04a28c6c2ece217f Mon Sep 17 00:00:00 2001 From: SupDos <6813986+SupDos@users.noreply.github.com> Date: Sun, 24 Nov 2024 02:34:30 +0100 Subject: [PATCH 111/139] Remove FPS shortcut tip --- osu.Game/Screens/Menu/MenuTip.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Menu/MenuTip.cs b/osu.Game/Screens/Menu/MenuTip.cs index da349373c3..58eeb7e82d 100644 --- a/osu.Game/Screens/Menu/MenuTip.cs +++ b/osu.Game/Screens/Menu/MenuTip.cs @@ -118,7 +118,6 @@ namespace osu.Game.Screens.Menu "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!", - "Press Ctrl-Shift-F to toggle the FPS Counter. But make sure not to pay too much attention to it!", "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!" From 8f5d513d461affec8f3812beb18136d3228d08d4 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sat, 23 Nov 2024 22:16:11 -0500 Subject: [PATCH 112/139] Fix room auto start duration setting applied to the wrong component --- .../Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 1dbef079d4..79617f172c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -438,7 +438,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match => MaxParticipantsField.Text = room.MaxParticipants?.ToString(); private void updateRoomAutoStartDuration() - => typeLabel.Text = room.AutoStartDuration.GetLocalisableDescription(); + => startModeDropdown.Current.Value = (StartMode)room.AutoStartDuration.TotalSeconds; private void updateRoomPlaylist() => drawablePlaylist.Items.ReplaceRange(0, drawablePlaylist.Items.Count, room.Playlist); From cab26c70c1f2fca14b3feaa2abff5385963a401b Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sat, 23 Nov 2024 22:27:56 -0500 Subject: [PATCH 113/139] 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 631bfadd68f2a58ef51f7d17c10271bfddc34de5 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 04:10:01 -0500 Subject: [PATCH 114/139] 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 f3155bfc7d83029e9170562598c0dd6ec342f54c Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 04:24:31 -0500 Subject: [PATCH 115/139] Fix pause shortcut on multiplayer not delayed --- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 806985e19d..5d3d5774d0 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -299,7 +299,13 @@ namespace osu.Game.Screens.Play.HUD { case GlobalAction.Back: if (!pendingAnimation) - Confirm(); + { + if (IsDangerousAction) + BeginConfirm(); + else + Confirm(); + } + return true; case GlobalAction.PauseGameplay: @@ -307,7 +313,13 @@ namespace osu.Game.Screens.Play.HUD if (ReplayLoaded.Value) return false; if (!pendingAnimation) - Confirm(); + { + if (IsDangerousAction) + BeginConfirm(); + else + Confirm(); + } + return true; } From aa1358b2b4576b2d9d08365503cb610328068255 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 04:33:03 -0500 Subject: [PATCH 116/139] 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 6d0d7f3e759fac5d5468089d79f0832730ed1088 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 04:45:48 -0500 Subject: [PATCH 117/139] Don't play fail animation if restarting on failure --- osu.Game/Screens/Play/Player.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e9722350bd..2d1f602832 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -976,7 +976,9 @@ namespace osu.Game.Screens.Play if (PauseOverlay.State.Value == Visibility.Visible) PauseOverlay.Hide(); - failAnimationContainer.Start(); + bool restartOnFail = GameplayState.Mods.OfType().Any(m => m.RestartOnFail); + if (!restartOnFail) + failAnimationContainer.Start(); // Failures can be triggered either by a judgement, or by a mod. // @@ -990,7 +992,7 @@ namespace osu.Game.Screens.Play ScoreProcessor.FailScore(Score.ScoreInfo); OnFail(); - if (GameplayState.Mods.OfType().Any(m => m.RestartOnFail)) + if (restartOnFail) Restart(true); }); } From 53b390667a844ca98dd0d7f1d1be0ebffd6c2133 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 06:04:36 -0500 Subject: [PATCH 118/139] 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 119/139] 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 a1916d12db3be7e55fd7ac1d184a1725cca4266d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Nov 2024 18:41:50 +0900 Subject: [PATCH 120/139] 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 121/139] 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 122/139] 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 123/139] 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 124/139] 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 125/139] 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 126/139] 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 127/139] 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 128/139] 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 129/139] 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 e3ea38a366601df01f045f4f111765c34d041145 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 15:12:38 +0900 Subject: [PATCH 130/139] 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 131/139] 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 132/139] 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 e0199386a38ef6581a8e78168e7080b1b34f9b0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 17:33:39 +0900 Subject: [PATCH 133/139] 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 134/139] 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 135/139] 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 312336de24e730247b64af3b601bbe114d2d563f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 18:12:26 +0900 Subject: [PATCH 136/139] 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 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 137/139] 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 138/139] 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 139/139] 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!"