From bebff61a9dde4ea61b27094ae9c4c4c214dababa Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Feb 2021 21:13:00 +0300 Subject: [PATCH 01/90] Add method for retrieving condensed user statistics --- osu.Game/Users/User.cs | 62 +++++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 518236755d..2f8c6823c7 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -2,10 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using JetBrains.Annotations; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using osu.Framework.Bindables; +using osu.Game.IO.Serialization; +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; namespace osu.Game.Users { @@ -178,6 +184,11 @@ namespace osu.Game.Users private UserStatistics statistics; + /// + /// The user statistics of the ruleset specified within the API request. + /// If the user is fetched from a or similar + /// (i.e. is a user compact instance), use instead. + /// [JsonProperty(@"statistics")] public UserStatistics Statistics { @@ -228,13 +239,35 @@ namespace osu.Game.Users [JsonProperty("replays_watched_counts")] public UserHistoryCount[] ReplaysWatchedCounts; - public class UserHistoryCount - { - [JsonProperty("start_date")] - public DateTime Date; + [UsedImplicitly] + [JsonExtensionData] + private readonly IDictionary otherProperties = new Dictionary(); - [JsonProperty("count")] - public long Count; + private readonly Dictionary statisticsCache = new Dictionary(); + + /// + /// Retrieves the user statistics for a certain ruleset. + /// If user is fetched from a , + /// this will always return null, use instead. + /// + /// The ruleset to retrieve statistics for. + // todo: this should likely be moved to a separate UserCompact class at some point. + public UserStatistics GetStatisticsFor(RulesetInfo ruleset) + { + if (statisticsCache.TryGetValue(ruleset, out var existing)) + return existing; + + return statisticsCache[ruleset] = parseStatisticsFor(ruleset); + } + + private UserStatistics parseStatisticsFor(RulesetInfo ruleset) + { + if (!(otherProperties.TryGetValue($"statistics_{ruleset.ShortName}", out var token))) + return null; + + var settings = JsonSerializableExtensions.CreateGlobalSettings(); + settings.DefaultValueHandling = DefaultValueHandling.Include; + return token.ToObject(JsonSerializer.Create(settings)); } public override string ToString() => Username; @@ -249,6 +282,14 @@ namespace osu.Game.Users Id = 0 }; + public bool Equals(User other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return Id == other.Id; + } + public enum PlayStyle { [Description("Keyboard")] @@ -264,12 +305,13 @@ namespace osu.Game.Users Touch, } - public bool Equals(User other) + public class UserHistoryCount { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; + [JsonProperty("start_date")] + public DateTime Date; - return Id == other.Id; + [JsonProperty("count")] + public long Count; } } } From d101add1591599c53f860c21e31901cbed220060 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Feb 2021 21:14:12 +0300 Subject: [PATCH 02/90] Display user global rank for selected ruleset in participants panel --- .../Participants/ParticipantPanel.cs | 21 ++++++++++++------- osu.Game/Users/UserStatistics.cs | 8 +++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 0ee1b6d684..f69a21918a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -35,9 +35,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants [Resolved] private RulesetStore rulesets { get; set; } + private SpriteIcon crown; + private OsuSpriteText userRankText; private ModDisplay userModsDisplay; private StateDisplay userStateDisplay; - private SpriteIcon crown; public ParticipantPanel(MultiplayerRoomUser user) { @@ -119,12 +120,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 18), Text = user?.Username }, - new OsuSpriteText + userRankText = new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: 14), - Text = user?.CurrentModeRank != null ? $"#{user.CurrentModeRank}" : string.Empty } } }, @@ -162,6 +162,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants const double fade_time = 50; + var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID); + + var currentModeRank = User.User?.GetStatisticsFor(ruleset)?.GlobalRank; + + // fallback to current mode rank for testing purposes. + currentModeRank ??= User.User?.CurrentModeRank; + + userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; + userStateDisplay.Status = User.State; if (Room.Host?.Equals(User) == true) @@ -171,11 +180,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants // If the mods are updated at the end of the frame, the flow container will skip a reflow cycle: https://github.com/ppy/osu-framework/issues/4187 // This looks particularly jarring here, so re-schedule the update to that start of our frame as a fix. - Schedule(() => - { - var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance(); - userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList(); - }); + Schedule(() => userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset.CreateInstance())).ToList()); } public MenuItem[] ContextMenuItems diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 8b7699d0ad..6c069f674e 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -3,6 +3,7 @@ using System; using Newtonsoft.Json; +using osu.Game.Online.API.Requests; using osu.Game.Scoring; using osu.Game.Utils; using static osu.Game.Users.User; @@ -26,6 +27,13 @@ namespace osu.Game.Users public int Progress; } + /// + /// This must only be used when coming from condensed user responses (e.g. from ), otherwise use Ranks.Global. + /// + // todo: this should likely be moved to a separate UserStatisticsCompact class at some point. + [JsonProperty(@"global_rank")] + public int? GlobalRank; + [JsonProperty(@"pp")] public decimal? PP; From cca1bac67d56d9d261149eb6f497fee824a69811 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Feb 2021 22:00:01 +0300 Subject: [PATCH 03/90] Pass empty user statistics for consistency Realized `Statistics` was never giving null user statistics, so decided to pass an empty one here as well. --- osu.Game/Users/User.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 2f8c6823c7..467f00e409 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -248,7 +248,7 @@ namespace osu.Game.Users /// /// Retrieves the user statistics for a certain ruleset. /// If user is fetched from a , - /// this will always return null, use instead. + /// this will always return empty instance, use instead. /// /// The ruleset to retrieve statistics for. // todo: this should likely be moved to a separate UserCompact class at some point. @@ -263,7 +263,7 @@ namespace osu.Game.Users private UserStatistics parseStatisticsFor(RulesetInfo ruleset) { if (!(otherProperties.TryGetValue($"statistics_{ruleset.ShortName}", out var token))) - return null; + return new UserStatistics(); var settings = JsonSerializableExtensions.CreateGlobalSettings(); settings.DefaultValueHandling = DefaultValueHandling.Include; From 5bd4f74ddf752f3ddc83d67d8ea48708a0248b13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 16:24:29 +0900 Subject: [PATCH 04/90] Fix a potential crash when exiting play during the results screen transition --- osu.Game/Screens/Play/Player.cs | 48 +++++++++++++++++---------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 5d06ac5b3a..dbee49b5dd 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -339,7 +339,7 @@ namespace osu.Game.Screens.Play { HoldToQuit = { - Action = performUserRequestedExit, + Action = () => PerformExit(true), IsPaused = { BindTarget = GameplayClockContainer.IsPaused } }, PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, @@ -363,14 +363,14 @@ namespace osu.Game.Screens.Play FailOverlay = new FailOverlay { OnRetry = Restart, - OnQuit = performUserRequestedExit, + OnQuit = () => PerformExit(true), }, PauseOverlay = new PauseOverlay { OnResume = Resume, Retries = RestartCount, OnRetry = Restart, - OnQuit = performUserRequestedExit, + OnQuit = () => PerformExit(true), }, new HotkeyExitOverlay { @@ -487,14 +487,30 @@ namespace osu.Game.Screens.Play // if a restart has been requested, cancel any pending completion (user has shown intent to restart). completionProgressDelegate?.Cancel(); - ValidForResume = false; - - if (!this.IsCurrentScreen()) return; + if (!this.IsCurrentScreen()) + { + // there is a chance that the exit was performed after the transition to results has started. + // we want to give the user what they want, so forcefully return to this screen (to proceed with the upwards exit process). + ValidForResume = false; + this.MakeCurrent(); + } if (userRequested) - performUserRequestedExit(); - else - this.Exit(); + { + if (ValidForResume && HasFailed && !FailOverlay.IsPresent) + { + failAnimation.FinishTransforms(true); + return; + } + + if (canPause) + { + Pause(); + return; + } + } + + this.Exit(); } private void performUserRequestedSkip() @@ -508,20 +524,6 @@ namespace osu.Game.Screens.Play updateSampleDisabledState(); } - private void performUserRequestedExit() - { - if (ValidForResume && HasFailed && !FailOverlay.IsPresent) - { - failAnimation.FinishTransforms(true); - return; - } - - if (canPause) - Pause(); - else - this.Exit(); - } - /// /// Restart gameplay via a parent . /// This can be called from a child screen in order to trigger the restart process. From 61b9539864289b9ded799cac93187fc641c3db35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 17:14:16 +0900 Subject: [PATCH 05/90] Fix regression in quick exit logic --- osu.Game/Screens/Play/Player.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index dbee49b5dd..3f8651761e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -478,11 +478,11 @@ namespace osu.Game.Screens.Play /// /// Exits the . /// - /// - /// Whether the exit is requested by the user, or a higher-level game component. - /// Pausing is allowed only in the former case. + /// + /// Whether the pause or fail dialog should be shown before performing an exit. + /// If true and a dialog is not yet displayed, the exit will be blocked the the relevant dialog will display instead. /// - protected void PerformExit(bool userRequested) + protected void PerformExit(bool showDialogFirst) { // if a restart has been requested, cancel any pending completion (user has shown intent to restart). completionProgressDelegate?.Cancel(); @@ -495,7 +495,7 @@ namespace osu.Game.Screens.Play this.MakeCurrent(); } - if (userRequested) + if (showDialogFirst) { if (ValidForResume && HasFailed && !FailOverlay.IsPresent) { @@ -503,7 +503,7 @@ namespace osu.Game.Screens.Play return; } - if (canPause) + if (canPause && !GameplayClockContainer.IsPaused.Value) { Pause(); return; @@ -540,10 +540,7 @@ namespace osu.Game.Screens.Play sampleRestart?.Play(); RestartRequested?.Invoke(); - if (this.IsCurrentScreen()) - PerformExit(true); - else - this.MakeCurrent(); + PerformExit(false); } private ScheduledDelegate completionProgressDelegate; From cba116ff090c650cbc4812f16e15ddfd13747e3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 17:28:57 +0900 Subject: [PATCH 06/90] Fix incorrect call parameter for quick exit --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3f8651761e..8a977b0498 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -379,7 +379,7 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; fadeOut(true); - PerformExit(true); + PerformExit(false); }, }, failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }, From 2c052d70e8668bc9f64354fc14d3425b5f0e6552 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 17:29:18 +0900 Subject: [PATCH 07/90] Only trigger pause cooldown on pause (not exit) --- osu.Game/Screens/Play/Player.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8a977b0498..dda52f4dae 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -505,6 +505,10 @@ namespace osu.Game.Screens.Play if (canPause && !GameplayClockContainer.IsPaused.Value) { + if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value) + // still want to block if we are within the cooldown period and not already paused. + return; + Pause(); return; } @@ -808,14 +812,6 @@ namespace osu.Game.Screens.Play return true; } - // ValidForResume is false when restarting - if (ValidForResume) - { - if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value) - // still want to block if we are within the cooldown period and not already paused. - return true; - } - // GameplayClockContainer performs seeks / start / stop operations on the beatmap's track. // as we are no longer the current screen, we cannot guarantee the track is still usable. GameplayClockContainer?.StopUsingBeatmapClock(); From 94f35825ddb28b8bf2d3e86562afc47dbd30e4a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 17:29:27 +0900 Subject: [PATCH 08/90] Update test to cover changed exit/pause logic I think this makes more sense? --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 46dd91710a..ae806883b0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -108,19 +108,19 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestExitTooSoon() + public void TestExitSoonAfterResumeSucceeds() { AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000)); pauseAndConfirm(); resume(); - AddStep("exit too soon", () => Player.Exit()); + AddStep("exit quick", () => Player.Exit()); confirmClockRunning(true); confirmPauseOverlayShown(false); - AddAssert("not exited", () => Player.IsCurrentScreen()); + AddAssert("exited", () => !Player.IsCurrentScreen()); } [Test] From a4dc54423531c77a0fb34023f8a634a9faaf108d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 14:23:59 +0900 Subject: [PATCH 09/90] Refactor some shared code in TestScenePause --- .../Visual/Gameplay/TestScenePause.cs | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index ae806883b0..1a0b594bb7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -56,9 +56,7 @@ namespace osu.Game.Tests.Visual.Gameplay pauseAndConfirm(); resume(); - confirmClockRunning(false); - confirmPauseOverlayShown(false); - + confirmPausedWithNoOverlay(); AddStep("click to resume", () => InputManager.Click(MouseButton.Left)); confirmClockRunning(true); @@ -73,9 +71,7 @@ namespace osu.Game.Tests.Visual.Gameplay pauseAndConfirm(); resume(); - confirmClockRunning(false); - confirmPauseOverlayShown(false); - + confirmPausedWithNoOverlay(); pauseAndConfirm(); AddUntilStep("resume overlay is not active", () => Player.DrawableRuleset.ResumeOverlay.State.Value == Visibility.Hidden); @@ -94,7 +90,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestPauseTooSoon() + public void TestPauseDuringCooldownTooSoon() { AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); @@ -103,8 +99,8 @@ namespace osu.Game.Tests.Visual.Gameplay resume(); pause(); - confirmClockRunning(true); - confirmPauseOverlayShown(false); + confirmResumed(); + AddAssert("not exited", () => Player.IsCurrentScreen()); } [Test] @@ -117,9 +113,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("exit quick", () => Player.Exit()); - confirmClockRunning(true); - confirmPauseOverlayShown(false); - + confirmResumed(); AddAssert("exited", () => !Player.IsCurrentScreen()); } @@ -133,9 +127,7 @@ namespace osu.Game.Tests.Visual.Gameplay pause(); - confirmClockRunning(false); - confirmPauseOverlayShown(false); - + confirmPausedWithNoOverlay(); AddAssert("fail overlay still shown", () => Player.FailOverlayVisible); exitAndConfirm(); @@ -277,6 +269,12 @@ namespace osu.Game.Tests.Visual.Gameplay confirmPauseOverlayShown(false); } + private void confirmPausedWithNoOverlay() + { + confirmClockRunning(false); + confirmPauseOverlayShown(false); + } + private void confirmExited() { AddUntilStep("player exited", () => !Player.IsCurrentScreen()); From 2b69c7b32530c4ab9ab712610b94664a2a9b9d43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 14:00:30 +0900 Subject: [PATCH 10/90] Fix incorrect order of operation in pause blocking logic --- osu.Game/Screens/Play/Player.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index dda52f4dae..c462786916 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -503,14 +503,17 @@ namespace osu.Game.Screens.Play return; } - if (canPause && !GameplayClockContainer.IsPaused.Value) + if (!GameplayClockContainer.IsPaused.Value) { - if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value) - // still want to block if we are within the cooldown period and not already paused. + // if we are within the cooldown period and not already paused, the operation should block completely. + if (pauseCooldownActive) return; - Pause(); - return; + if (canPause) + { + Pause(); + return; + } } } From 25f5120fdf51be4ca58c208f55198cdcbcf41d0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 14:36:14 +0900 Subject: [PATCH 11/90] Add failing test coverage of user pausing or quick exiting during cooldown --- .../Visual/Gameplay/TestScenePause.cs | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 1a0b594bb7..8246e2c028 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -90,19 +90,47 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestPauseDuringCooldownTooSoon() + public void TestExternalPauseDuringCooldownTooSoon() { AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); pauseAndConfirm(); resume(); - pause(); + pauseExternally(); confirmResumed(); AddAssert("not exited", () => Player.IsCurrentScreen()); } + [Test] + public void TestUserPauseDuringCooldownTooSoon() + { + AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); + + pauseAndConfirm(); + + resume(); + AddStep("pause via exit key", () => Player.ExitViaPause()); + + confirmResumed(); + AddAssert("not exited", () => Player.IsCurrentScreen()); + } + + [Test] + public void TestQuickExitDuringCooldownTooSoon() + { + AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); + + pauseAndConfirm(); + + resume(); + AddStep("pause via exit key", () => Player.ExitViaQuickExit()); + + confirmResumed(); + AddAssert("exited", () => !Player.IsCurrentScreen()); + } + [Test] public void TestExitSoonAfterResumeSucceeds() { @@ -125,7 +153,7 @@ namespace osu.Game.Tests.Visual.Gameplay confirmClockRunning(false); - pause(); + pauseExternally(); confirmPausedWithNoOverlay(); AddAssert("fail overlay still shown", () => Player.FailOverlayVisible); @@ -237,7 +265,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void pauseAndConfirm() { - pause(); + pauseExternally(); confirmPaused(); } @@ -286,7 +314,7 @@ namespace osu.Game.Tests.Visual.Gameplay } private void restart() => AddStep("restart", () => Player.Restart()); - private void pause() => AddStep("pause", () => Player.Pause()); + private void pauseExternally() => AddStep("pause", () => Player.Pause()); private void resume() => AddStep("resume", () => Player.Resume()); private void confirmPauseOverlayShown(bool isShown) => @@ -305,6 +333,10 @@ namespace osu.Game.Tests.Visual.Gameplay public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible; + public void ExitViaPause() => PerformExit(true); + + public void ExitViaQuickExit() => PerformExit(false); + public override void OnEntering(IScreen last) { base.OnEntering(last); From ec37e1602d566920601a1c197757991099b2447f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 15:02:58 +0900 Subject: [PATCH 12/90] Add failing test coverage of retrying from the results screen --- .../Visual/Navigation/OsuGameTestScene.cs | 3 +++ .../Navigation/TestSceneScreenNavigation.cs | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index c5038068ec..96393cc4c3 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -17,6 +17,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Menu; @@ -115,6 +116,8 @@ namespace osu.Game.Tests.Visual.Navigation public new Bindable Ruleset => base.Ruleset; + public new Bindable> SelectedMods => base.SelectedMods; + // if we don't do this, when running under nUnit the version that gets populated is that of nUnit. public override string Version => "test game"; diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 8480e6eaaa..d8380b2dd3 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -11,7 +11,9 @@ using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Toolbar; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Play; +using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Options; using osu.Game.Tests.Beatmaps.IO; @@ -41,6 +43,30 @@ namespace osu.Game.Tests.Visual.Navigation exitViaEscapeAndConfirm(); } + [Test] + public void TestRetryFromResults() + { + Player player = null; + ResultsScreen results = null; + + WorkingBeatmap beatmap() => Game.Beatmap.Value; + + PushAndConfirm(() => new TestSongSelect()); + + AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait()); + + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + AddStep("set autoplay", () => Game.SelectedMods.Value = new[] { new OsuModAutoplay() }); + + AddStep("press enter", () => InputManager.Key(Key.Enter)); + AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); + AddStep("seek to end", () => beatmap().Track.Seek(beatmap().Track.Length)); + AddUntilStep("wait for pass", () => (results = Game.ScreenStack.CurrentScreen as ResultsScreen) != null && results.IsLoaded); + AddStep("attempt to retry", () => results.ChildrenOfType().First().Action()); + AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != player && Game.ScreenStack.CurrentScreen is Player); + } + [TestCase(true)] [TestCase(false)] public void TestSongContinuesAfterExitPlayer(bool withUserPause) From 1aea840504aa337e24173ca701188e6fd88a6d1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 14:03:41 +0900 Subject: [PATCH 13/90] Add missing return in early exit scenario (MakeCurrent isn't compatible with the following Exit) --- osu.Game/Screens/Play/Player.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c462786916..88ca516440 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -493,6 +493,7 @@ namespace osu.Game.Screens.Play // we want to give the user what they want, so forcefully return to this screen (to proceed with the upwards exit process). ValidForResume = false; this.MakeCurrent(); + return; } if (showDialogFirst) From 4f264758a499fea09585cdd9403cba15d9a7777c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 15:57:34 +0900 Subject: [PATCH 14/90] Add test coverage of pause from resume overlay --- .../Visual/Gameplay/TestScenePause.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 8246e2c028..1ad1479cd4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -69,13 +69,14 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for hitobjects", () => Player.HealthProcessor.Health.Value < 1); pauseAndConfirm(); - resume(); + confirmPausedWithNoOverlay(); pauseAndConfirm(); AddUntilStep("resume overlay is not active", () => Player.DrawableRuleset.ResumeOverlay.State.Value == Visibility.Hidden); confirmPaused(); + confirmNotExited(); } [Test] @@ -100,7 +101,7 @@ namespace osu.Game.Tests.Visual.Gameplay pauseExternally(); confirmResumed(); - AddAssert("not exited", () => Player.IsCurrentScreen()); + confirmNotExited(); } [Test] @@ -114,7 +115,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("pause via exit key", () => Player.ExitViaPause()); confirmResumed(); - AddAssert("not exited", () => Player.IsCurrentScreen()); + confirmNotExited(); } [Test] @@ -277,7 +278,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void exitAndConfirm() { - AddUntilStep("player not exited", () => Player.IsCurrentScreen()); + confirmNotExited(); AddStep("exit", () => Player.Exit()); confirmExited(); confirmNoTrackAdjustments(); @@ -286,7 +287,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void confirmPaused() { confirmClockRunning(false); - AddAssert("player not exited", () => Player.IsCurrentScreen()); + confirmNotExited(); AddAssert("player not failed", () => !Player.HasFailed); AddAssert("pause overlay shown", () => Player.PauseOverlayVisible); } @@ -303,10 +304,8 @@ namespace osu.Game.Tests.Visual.Gameplay confirmPauseOverlayShown(false); } - private void confirmExited() - { - AddUntilStep("player exited", () => !Player.IsCurrentScreen()); - } + private void confirmExited() => AddUntilStep("player exited", () => !Player.IsCurrentScreen()); + private void confirmNotExited() => AddAssert("player not exited", () => Player.IsCurrentScreen()); private void confirmNoTrackAdjustments() { From 9cba350337484a3e1466063411daeb489c18abdc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 15:57:21 +0900 Subject: [PATCH 15/90] Refactor again to better cover cases where the pause dialog should definitely be shown --- osu.Game/Screens/Play/Player.cs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 88ca516440..a844d3bcf7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -487,35 +487,30 @@ namespace osu.Game.Screens.Play // if a restart has been requested, cancel any pending completion (user has shown intent to restart). completionProgressDelegate?.Cancel(); + // there is a chance that the exit was performed after the transition to results has started. + // we want to give the user what they want, so forcefully return to this screen (to proceed with the upwards exit process). if (!this.IsCurrentScreen()) { - // there is a chance that the exit was performed after the transition to results has started. - // we want to give the user what they want, so forcefully return to this screen (to proceed with the upwards exit process). ValidForResume = false; this.MakeCurrent(); return; } - if (showDialogFirst) + bool pauseDialogShown = PauseOverlay.State.Value == Visibility.Visible; + + if (showDialogFirst && !pauseDialogShown) { + // if the fail animation is currently in progress, accelerate it (it will show the pause dialog on completion). if (ValidForResume && HasFailed && !FailOverlay.IsPresent) { failAnimation.FinishTransforms(true); return; } - if (!GameplayClockContainer.IsPaused.Value) - { - // if we are within the cooldown period and not already paused, the operation should block completely. - if (pauseCooldownActive) - return; - - if (canPause) - { - Pause(); - return; - } - } + // in the case a dialog needs to be shown, attempt to pause and show it. + // this may fail (see internal checks in Pause()) at which point the exit attempt will be aborted. + Pause(); + return; } this.Exit(); From f664fca0ddf2ac466af1d27048f0d370c74ecb94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 16:11:17 +0900 Subject: [PATCH 16/90] Tidy up tests (and remove duplicate with new call logic) --- .../Visual/Gameplay/TestScenePause.cs | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 1ad1479cd4..aa56c636ab 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -90,20 +90,6 @@ namespace osu.Game.Tests.Visual.Gameplay resumeAndConfirm(); } - [Test] - public void TestExternalPauseDuringCooldownTooSoon() - { - AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); - - pauseAndConfirm(); - - resume(); - pauseExternally(); - - confirmResumed(); - confirmNotExited(); - } - [Test] public void TestUserPauseDuringCooldownTooSoon() { @@ -112,7 +98,7 @@ namespace osu.Game.Tests.Visual.Gameplay pauseAndConfirm(); resume(); - AddStep("pause via exit key", () => Player.ExitViaPause()); + pauseFromUserExitKey(); confirmResumed(); confirmNotExited(); @@ -154,7 +140,7 @@ namespace osu.Game.Tests.Visual.Gameplay confirmClockRunning(false); - pauseExternally(); + pauseFromUserExitKey(); confirmPausedWithNoOverlay(); AddAssert("fail overlay still shown", () => Player.FailOverlayVisible); @@ -266,7 +252,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void pauseAndConfirm() { - pauseExternally(); + pauseFromUserExitKey(); confirmPaused(); } @@ -313,7 +299,7 @@ namespace osu.Game.Tests.Visual.Gameplay } private void restart() => AddStep("restart", () => Player.Restart()); - private void pauseExternally() => AddStep("pause", () => Player.Pause()); + private void pauseFromUserExitKey() => AddStep("user pause", () => Player.ExitViaPause()); private void resume() => AddStep("resume", () => Player.Resume()); private void confirmPauseOverlayShown(bool isShown) => From a1496cd8f3afed32bd9c126a18dc8a3714ca6212 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 9 Feb 2021 08:28:09 +0300 Subject: [PATCH 17/90] Remove necessity of using `CurrentModeRank` as a fallback --- .../TestSceneMultiplayerParticipantsList.cs | 10 ++++++++-- .../Multiplayer/Participants/ParticipantPanel.cs | 4 ---- osu.Game/Users/User.cs | 9 ++++++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 0f7a9b442d..5a0234e379 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -155,7 +155,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { Id = i, Username = $"User {i}", - CurrentModeRank = RNG.Next(1, 100000), + AllStatistics = + { + { Ruleset.Value, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } + }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); @@ -193,7 +196,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { Id = 0, Username = "User 0", - CurrentModeRank = RNG.Next(1, 100000), + AllStatistics = + { + { Ruleset.Value, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } + }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 74bc86f279..e78264223e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -165,10 +165,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID); var currentModeRank = User.User?.GetStatisticsFor(ruleset)?.GlobalRank; - - // fallback to current mode rank for testing purposes. - currentModeRank ??= User.User?.CurrentModeRank; - userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability); diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 467f00e409..621d70301d 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -243,7 +243,10 @@ namespace osu.Game.Users [JsonExtensionData] private readonly IDictionary otherProperties = new Dictionary(); - private readonly Dictionary statisticsCache = new Dictionary(); + /// + /// Map for ruleset with their associated user statistics, can be altered for testing purposes. + /// + internal readonly Dictionary AllStatistics = new Dictionary(); /// /// Retrieves the user statistics for a certain ruleset. @@ -254,10 +257,10 @@ namespace osu.Game.Users // todo: this should likely be moved to a separate UserCompact class at some point. public UserStatistics GetStatisticsFor(RulesetInfo ruleset) { - if (statisticsCache.TryGetValue(ruleset, out var existing)) + if (AllStatistics.TryGetValue(ruleset, out var existing)) return existing; - return statisticsCache[ruleset] = parseStatisticsFor(ruleset); + return AllStatistics[ruleset] = parseStatisticsFor(ruleset); } private UserStatistics parseStatisticsFor(RulesetInfo ruleset) From 1466f36649f12edf22942b01c5bf278b1ee55f17 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 9 Feb 2021 08:55:50 +0300 Subject: [PATCH 18/90] Improve documentation on `Statistics` --- osu.Game/Users/User.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 621d70301d..58f25703fc 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -185,9 +185,8 @@ namespace osu.Game.Users private UserStatistics statistics; /// - /// The user statistics of the ruleset specified within the API request. - /// If the user is fetched from a or similar - /// (i.e. is a user compact instance), use instead. + /// User statistics for the requested ruleset (in the case of a response). + /// Otherwise empty. /// [JsonProperty(@"statistics")] public UserStatistics Statistics From 62514f23b59334ef3c6736941379a34c5ff0daf0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 9 Feb 2021 08:56:01 +0300 Subject: [PATCH 19/90] Remove unnecessary json settings override --- osu.Game/Users/User.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 58f25703fc..b8ca345f5c 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -267,9 +267,7 @@ namespace osu.Game.Users if (!(otherProperties.TryGetValue($"statistics_{ruleset.ShortName}", out var token))) return new UserStatistics(); - var settings = JsonSerializableExtensions.CreateGlobalSettings(); - settings.DefaultValueHandling = DefaultValueHandling.Include; - return token.ToObject(JsonSerializer.Create(settings)); + return token.ToObject(JsonSerializer.Create(JsonSerializableExtensions.CreateGlobalSettings())); } public override string ToString() => Username; From d15ffff9a57e6a09a1ecb6e196165eccd16c72e4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 9 Feb 2021 09:54:17 +0300 Subject: [PATCH 20/90] Simplifiy user statistics retrieval to one-time on deserialization --- .../TestSceneMultiplayerParticipantsList.cs | 4 +- .../Participants/ParticipantPanel.cs | 3 +- osu.Game/Users/User.cs | 42 +++++++------------ 3 files changed, 20 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 5a0234e379..5da5ab74b2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Username = $"User {i}", AllStatistics = { - { Ruleset.Value, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } + { Ruleset.Value.ShortName, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); @@ -198,7 +198,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Username = "User 0", AllStatistics = { - { Ruleset.Value, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } + { Ruleset.Value.ShortName, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index e78264223e..49d3bfc2dc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.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.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -164,7 +165,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID); - var currentModeRank = User.User?.GetStatisticsFor(ruleset)?.GlobalRank; + var currentModeRank = User.User?.AllStatistics.GetValueOrDefault(ruleset.ShortName)?.GlobalRank; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability); diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index b8ca345f5c..2c2f293aac 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -5,13 +5,13 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Runtime.Serialization; using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using osu.Framework.Bindables; using osu.Game.IO.Serialization; using osu.Game.Online.API.Requests; -using osu.Game.Rulesets; namespace osu.Game.Users { @@ -238,36 +238,26 @@ namespace osu.Game.Users [JsonProperty("replays_watched_counts")] public UserHistoryCount[] ReplaysWatchedCounts; + /// + /// All user statistics per ruleset's short name (in the case of a response). + /// Otherwise empty. Can be altered for testing purposes. + /// + // todo: this should likely be moved to a separate UserCompact class at some point. + [UsedImplicitly] + public readonly Dictionary AllStatistics = new Dictionary(); + [UsedImplicitly] [JsonExtensionData] private readonly IDictionary otherProperties = new Dictionary(); - /// - /// Map for ruleset with their associated user statistics, can be altered for testing purposes. - /// - internal readonly Dictionary AllStatistics = new Dictionary(); - - /// - /// Retrieves the user statistics for a certain ruleset. - /// If user is fetched from a , - /// this will always return empty instance, use instead. - /// - /// The ruleset to retrieve statistics for. - // todo: this should likely be moved to a separate UserCompact class at some point. - public UserStatistics GetStatisticsFor(RulesetInfo ruleset) + [OnDeserialized] + private void onDeserialized(StreamingContext context) { - if (AllStatistics.TryGetValue(ruleset, out var existing)) - return existing; - - return AllStatistics[ruleset] = parseStatisticsFor(ruleset); - } - - private UserStatistics parseStatisticsFor(RulesetInfo ruleset) - { - if (!(otherProperties.TryGetValue($"statistics_{ruleset.ShortName}", out var token))) - return new UserStatistics(); - - return token.ToObject(JsonSerializer.Create(JsonSerializableExtensions.CreateGlobalSettings())); + foreach (var kvp in otherProperties.Where(kvp => kvp.Key.StartsWith("statistics_", StringComparison.Ordinal))) + { + var shortName = kvp.Key.Replace("statistics_", string.Empty); + AllStatistics[shortName] = kvp.Value.ToObject(JsonSerializer.Create(JsonSerializableExtensions.CreateGlobalSettings())); + } } public override string ToString() => Username; From 5b4999e8afd8779e0da7cd153f3a59beaa59ea3c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 16 Feb 2021 04:51:21 +0300 Subject: [PATCH 21/90] Update user statistics retrieval with API changes --- .../TestSceneMultiplayerParticipantsList.cs | 5 +++-- .../Participants/ParticipantPanel.cs | 2 +- osu.Game/Users/User.cs | 22 +++---------------- 3 files changed, 7 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 5da5ab74b2..a7398ebf02 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.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.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -155,7 +156,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Id = i, Username = $"User {i}", - AllStatistics = + RulesetsStatistics = new Dictionary { { Ruleset.Value.ShortName, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } }, @@ -196,7 +197,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Id = 0, Username = "User 0", - AllStatistics = + RulesetsStatistics = new Dictionary { { Ruleset.Value.ShortName, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } }, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 49d3bfc2dc..25bc314f1b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -165,7 +165,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID); - var currentModeRank = User.User?.AllStatistics.GetValueOrDefault(ruleset.ShortName)?.GlobalRank; + var currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability); diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 2c2f293aac..4a6fd540c7 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -5,12 +5,9 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Runtime.Serialization; using JetBrains.Annotations; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using osu.Framework.Bindables; -using osu.Game.IO.Serialization; using osu.Game.Online.API.Requests; namespace osu.Game.Users @@ -243,22 +240,9 @@ namespace osu.Game.Users /// Otherwise empty. Can be altered for testing purposes. /// // todo: this should likely be moved to a separate UserCompact class at some point. - [UsedImplicitly] - public readonly Dictionary AllStatistics = new Dictionary(); - - [UsedImplicitly] - [JsonExtensionData] - private readonly IDictionary otherProperties = new Dictionary(); - - [OnDeserialized] - private void onDeserialized(StreamingContext context) - { - foreach (var kvp in otherProperties.Where(kvp => kvp.Key.StartsWith("statistics_", StringComparison.Ordinal))) - { - var shortName = kvp.Key.Replace("statistics_", string.Empty); - AllStatistics[shortName] = kvp.Value.ToObject(JsonSerializer.Create(JsonSerializableExtensions.CreateGlobalSettings())); - } - } + [JsonProperty("statistics_rulesets")] + [CanBeNull] + public Dictionary RulesetsStatistics { get; set; } public override string ToString() => Username; From 0e7f52b5ccb1ae1a14147d1df9877d22024fde6e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 16 Feb 2021 07:28:51 +0300 Subject: [PATCH 22/90] Always use JSON property `global_rank` for global ranks instead --- .../TestSceneMultiplayerParticipantsList.cs | 16 ++++++++++-- .../Participants/ParticipantPanel.cs | 2 +- osu.Game/Users/UserStatistics.cs | 25 ++++++++----------- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index a7398ebf02..1e14bbbbea 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -158,7 +158,13 @@ namespace osu.Game.Tests.Visual.Multiplayer Username = $"User {i}", RulesetsStatistics = new Dictionary { - { Ruleset.Value.ShortName, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } + { + Ruleset.Value.ShortName, + new UserStatistics + { + Ranks = new UserStatistics.UserRanks { Global = RNG.Next(1, 100000) } + } + } }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); @@ -199,7 +205,13 @@ namespace osu.Game.Tests.Visual.Multiplayer Username = "User 0", RulesetsStatistics = new Dictionary { - { Ruleset.Value.ShortName, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } + { + Ruleset.Value.ShortName, + new UserStatistics + { + Ranks = new UserStatistics.UserRanks { Global = RNG.Next(1, 100000) } + } + } }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 25bc314f1b..c4d11676e7 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -165,7 +165,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID); - var currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank; + var currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.Ranks.Global; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability); diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 6c069f674e..1fed908c39 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -3,7 +3,6 @@ using System; using Newtonsoft.Json; -using osu.Game.Online.API.Requests; using osu.Game.Scoring; using osu.Game.Utils; using static osu.Game.Users.User; @@ -27,24 +26,22 @@ namespace osu.Game.Users public int Progress; } - /// - /// This must only be used when coming from condensed user responses (e.g. from ), otherwise use Ranks.Global. - /// - // todo: this should likely be moved to a separate UserStatisticsCompact class at some point. + [JsonProperty(@"rank")] + public UserRanks Ranks; + + // eventually UserRanks object will be completely replaced with separate global and country rank properties, see https://github.com/ppy/osu-web/blob/cb79bb72186c8f1a25f6a6f5ef315123decb4231/app/Transformers/UserStatisticsTransformer.php#L53. + // but for now, always point UserRanks.Global to the global_rank property, as that is included solely for requests like GetUsersRequest. [JsonProperty(@"global_rank")] - public int? GlobalRank; - - [JsonProperty(@"pp")] - public decimal? PP; - - [JsonProperty(@"pp_rank")] // the API sometimes only returns this value in condensed user responses - private int? rank + private int? globalRank { set => Ranks.Global = value; } - [JsonProperty(@"rank")] - public UserRanks Ranks; + [JsonProperty(@"pp")] + public decimal? PP; + + [JsonProperty(@"pp_rank")] + public int PPRank; [JsonProperty(@"ranked_score")] public long RankedScore; From da42c6d2825d1a97d048c29f2bd4730b1dd989d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Feb 2021 15:14:19 +0900 Subject: [PATCH 23/90] Expose FreeMods from OnlinePlaySongSelect --- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index b201c62b7f..3f2873cbc4 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -31,7 +31,8 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room), nameof(Room.Playlist))] protected BindableList Playlist { get; private set; } - private readonly Bindable> freeMods = new Bindable>(Array.Empty()); + protected readonly Bindable> FreeMods = new Bindable>(Array.Empty()); + private readonly FreeModSelectOverlay freeModSelectOverlay; private WorkingBeatmap initialBeatmap; @@ -45,7 +46,7 @@ namespace osu.Game.Screens.OnlinePlay freeModSelectOverlay = new FreeModSelectOverlay { - SelectedMods = { BindTarget = freeMods }, + SelectedMods = { BindTarget = FreeMods }, IsValidMod = IsValidFreeMod, }; } @@ -67,14 +68,14 @@ namespace osu.Game.Screens.OnlinePlay // At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods. // Similarly, freeMods is currently empty but should only contain the allowed mods. Mods.Value = Playlist.FirstOrDefault()?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); - freeMods.Value = Playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + FreeMods.Value = Playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); Ruleset.BindValueChanged(onRulesetChanged); } private void onRulesetChanged(ValueChangedEvent ruleset) { - freeMods.Value = Array.Empty(); + FreeMods.Value = Array.Empty(); } protected sealed override bool OnStart() @@ -90,7 +91,7 @@ namespace osu.Game.Screens.OnlinePlay item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); item.AllowedMods.Clear(); - item.AllowedMods.AddRange(freeMods.Value.Select(m => m.CreateCopy())); + item.AllowedMods.AddRange(FreeMods.Value.Select(m => m.CreateCopy())); SelectItem(item); return true; @@ -133,7 +134,7 @@ namespace osu.Game.Screens.OnlinePlay protected override IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() { var buttons = base.CreateFooterButtons().ToList(); - buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = freeMods }, freeModSelectOverlay)); + buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = FreeMods }, freeModSelectOverlay)); return buttons; } From fff1cb0b355e9b8984dbc950dd8d2d4412e01a94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Feb 2021 15:13:57 +0900 Subject: [PATCH 24/90] Fix allowed mods not being copied when populating playlist items --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs index 0e8db6dfe5..21335fc90c 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs @@ -56,6 +56,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists item.RequiredMods.Clear(); item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); + + item.AllowedMods.Clear(); + item.AllowedMods.AddRange(FreeMods.Value.Select(m => m.CreateCopy())); } } } From 97a7572cb8daebc642cabe44e4ef8bbd1a0c97b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Feb 2021 15:14:48 +0900 Subject: [PATCH 25/90] Move UserModSelectOverlay to RoomSubScreen for Playlists consumption --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 68 ++++++++++++++++++- .../Multiplayer/MultiplayerMatchSubScreen.cs | 49 +------------ 2 files changed, 67 insertions(+), 50 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index e755f8c405..24d42283f7 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -3,16 +3,20 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Online.Rooms; using osu.Game.Overlays; +using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; @@ -25,6 +29,14 @@ namespace osu.Game.Screens.OnlinePlay.Match public override bool DisallowExternalBeatmapRulesetChanges => true; + private readonly ModSelectOverlay userModsSelectOverlay; + + /// + /// A container that provides controls for selection of user mods. + /// This will be shown/hidden automatically when applicable. + /// + protected Drawable UserModsSection; + private Sample sampleStart; [Resolved(typeof(Room), nameof(Room.Playlist))] @@ -53,9 +65,26 @@ namespace osu.Game.Screens.OnlinePlay.Match protected RoomSubScreen() { - AddInternal(BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker + AddRangeInternal(new Drawable[] { - SelectedItem = { BindTarget = SelectedItem } + BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker + { + SelectedItem = { BindTarget = SelectedItem } + }, + new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Depth = float.MinValue, + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }, + Child = userModsSelectOverlay = new UserModSelectOverlay + { + SelectedMods = { BindTarget = UserMods }, + IsValidMod = _ => false + } + }, }); } @@ -73,7 +102,8 @@ namespace osu.Game.Screens.OnlinePlay.Match base.LoadComplete(); SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged)); - SelectedItem.Value = Playlist.FirstOrDefault(); + + Playlist.BindCollectionChanged(onPlaylistChanged, true); managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); @@ -81,6 +111,22 @@ namespace osu.Game.Screens.OnlinePlay.Match UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods)); } + public override bool OnBackButton() + { + if (userModsSelectOverlay.State.Value == Visibility.Visible) + { + userModsSelectOverlay.Hide(); + return true; + } + + return base.OnBackButton(); + } + + private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) => + SelectedItem.Value = Playlist.FirstOrDefault(); + + protected void ShowUserModSelect() => userModsSelectOverlay.Show(); + public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -131,6 +177,18 @@ namespace osu.Game.Screens.OnlinePlay.Match UpdateMods(); Ruleset.Value = SelectedItem.Value.Ruleset.Value; + + if (SelectedItem.Value?.AllowedMods.Any() != true) + { + UserModsSection?.Hide(); + userModsSelectOverlay.Hide(); + userModsSelectOverlay.IsValidMod = _ => false; + } + else + { + UserModsSection?.Show(); + userModsSelectOverlay.IsValidMod = m => SelectedItem.Value.AllowedMods.Any(a => a.GetType() == m.GetType()); + } } private void beatmapUpdated(ValueChangedEvent> weakSet) => Schedule(updateWorkingBeatmap); @@ -190,5 +248,9 @@ namespace osu.Game.Screens.OnlinePlay.Match track.RestartPoint = 0; } } + + private class UserModSelectOverlay : LocalPlayerModSelectOverlay + { + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 2f50bee677..49ac9f64ff 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using JetBrains.Annotations; @@ -16,7 +15,6 @@ using osu.Framework.Threading; using osu.Game.Configuration; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; @@ -43,9 +41,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } - private ModSelectOverlay userModsSelectOverlay; private MultiplayerMatchSettingsOverlay settingsOverlay; - private Drawable userModsSection; private IBindable isConnected; @@ -155,7 +151,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new BeatmapSelectionControl { RelativeSizeAxes = Axes.X } } }, - userModsSection = new FillFlowContainer + UserModsSection = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -176,7 +172,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Origin = Anchor.CentreLeft, Width = 90, Text = "Select", - Action = () => userModsSelectOverlay.Show() + Action = ShowUserModSelect, }, new ModDisplay { @@ -231,19 +227,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new Dimension(GridSizeMode.AutoSize), } }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }, - Child = userModsSelectOverlay = new UserModSelectOverlay - { - SelectedMods = { BindTarget = UserMods }, - IsValidMod = _ => false - } - }, settingsOverlay = new MultiplayerMatchSettingsOverlay { RelativeSizeAxes = Axes.Both, @@ -269,7 +252,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.LoadComplete(); - Playlist.BindCollectionChanged(onPlaylistChanged, true); BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); UserMods.BindValueChanged(onUserModsChanged); @@ -303,32 +285,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return true; } - if (userModsSelectOverlay.State.Value == Visibility.Visible) - { - userModsSelectOverlay.Hide(); - return true; - } - return base.OnBackButton(); } - private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) - { - SelectedItem.Value = Playlist.FirstOrDefault(); - - if (SelectedItem.Value?.AllowedMods.Any() != true) - { - userModsSection.Hide(); - userModsSelectOverlay.Hide(); - userModsSelectOverlay.IsValidMod = _ => false; - } - else - { - userModsSection.Show(); - userModsSelectOverlay.IsValidMod = m => SelectedItem.Value.AllowedMods.Any(a => a.GetType() == m.GetType()); - } - } - private ModSettingChangeTracker modSettingChangeTracker; private ScheduledDelegate debouncedModSettingsUpdate; @@ -433,9 +392,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer modSettingChangeTracker?.Dispose(); } - - private class UserModSelectOverlay : LocalPlayerModSelectOverlay - { - } } } From fdcb6384cb808e3f616a73a8343c98c5956cacf7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Feb 2021 15:14:56 +0900 Subject: [PATCH 26/90] Add user mod selection to playlists room screen --- .../Playlists/PlaylistsRoomSubScreen.cs | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 88731a10bc..31c441bcd2 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -13,7 +13,9 @@ using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Match.Components; +using osu.Game.Screens.Play.HUD; using osu.Game.Users; +using osuTK; using Footer = osu.Game.Screens.OnlinePlay.Match.Components.Footer; namespace osu.Game.Screens.OnlinePlay.Playlists @@ -140,13 +142,55 @@ namespace osu.Game.Screens.OnlinePlay.Playlists RelativeSizeAxes = Axes.Both, Content = new[] { - new Drawable[] { new OverlinedHeader("Leaderboard"), }, + new[] + { + UserModsSection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Bottom = 10 }, + Children = new Drawable[] + { + new OverlinedHeader("Extra mods"), + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new PurpleTriangleButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 90, + Text = "Select", + Action = ShowUserModSelect, + }, + new ModDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + DisplayUnrankedText = false, + Current = UserMods, + Scale = new Vector2(0.8f), + }, + } + } + } + }, + }, + new Drawable[] + { + new OverlinedHeader("Leaderboard") + }, new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, }, new Drawable[] { new OverlinedHeader("Chat"), }, new Drawable[] { new MatchChatDisplay { RelativeSizeAxes = Axes.Both } } }, RowDimensions = new[] { + new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.AutoSize), new Dimension(), new Dimension(GridSizeMode.AutoSize), From f25b5147ef319c42329fd645490bf7ecc1907eb1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Feb 2021 15:37:45 +0900 Subject: [PATCH 27/90] Select last playlist item in match subscreen --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index c5130baa94..76608fb5c1 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -302,7 +302,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) { - SelectedItem.Value = Playlist.FirstOrDefault(); + SelectedItem.Value = Playlist.LastOrDefault(); if (SelectedItem.Value?.AllowedMods.Any() != true) { From 855d24dce769ae8ab65bd8f2daadfe638e6a76d8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Feb 2021 17:35:44 +0900 Subject: [PATCH 28/90] Cache selected item bindable from RoomSubScreen --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 1 + osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 2946d07588..6a2844fa74 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -21,6 +21,7 @@ namespace osu.Game.Screens.OnlinePlay.Match [Cached(typeof(IPreviewTrackOwner))] public abstract class RoomSubScreen : OnlinePlaySubScreen, IPreviewTrackOwner { + [Cached(typeof(IBindable))] protected readonly Bindable SelectedItem = new Bindable(); public override bool DisallowExternalBeatmapRulesetChanges => true; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index b2f3e4a1d9..c1aa93526f 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -2,10 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Users; namespace osu.Game.Screens.OnlinePlay @@ -50,5 +52,13 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable Duration { get; private set; } + + /// + /// The currently selected item in the . + /// May be null if this is not inside a . + /// + [CanBeNull] + [Resolved(typeof(Room), CanBeNull = true)] + protected IBindable SelectedItem { get; private set; } } } From 3ff9e14e35f851b32f40eb2339b6652beea37c2c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Feb 2021 18:56:13 +0900 Subject: [PATCH 29/90] Make StatefulMultiplayerClient control current playlist item --- .../Multiplayer/MultiplayerRoomSettings.cs | 12 +++++-- .../Multiplayer/StatefulMultiplayerClient.cs | 35 ++++++++++--------- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 4 --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 13 ++++--- .../Playlists/PlaylistsRoomSubScreen.cs | 7 ++-- 5 files changed, 39 insertions(+), 32 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index 4fb9d724b5..04752f4e6f 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -36,18 +36,26 @@ namespace osu.Game.Online.Multiplayer [Key(5)] public IEnumerable AllowedMods { get; set; } = Enumerable.Empty(); + /// + /// Only used for client-side mutation. + /// + [Key(6)] + public int PlaylistItemId { get; set; } + public bool Equals(MultiplayerRoomSettings other) => BeatmapID == other.BeatmapID && BeatmapChecksum == other.BeatmapChecksum && RequiredMods.SequenceEqual(other.RequiredMods) && AllowedMods.SequenceEqual(other.AllowedMods) && RulesetID == other.RulesetID - && Name.Equals(other.Name, StringComparison.Ordinal); + && Name.Equals(other.Name, StringComparison.Ordinal) + && PlaylistItemId == other.PlaylistItemId; public override string ToString() => $"Name:{Name}" + $" Beatmap:{BeatmapID} ({BeatmapChecksum})" + $" RequiredMods:{string.Join(',', RequiredMods)}" + $" AllowedMods:{string.Join(',', AllowedMods)}" - + $" Ruleset:{RulesetID}"; + + $" Ruleset:{RulesetID}" + + $" Item:{PlaylistItemId}"; } } diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 18464a5f61..f5f4c3a8ba 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -66,6 +66,8 @@ namespace osu.Game.Online.Multiplayer /// public readonly BindableList CurrentMatchPlayingUserIds = new BindableList(); + public readonly Bindable CurrentMatchPlayingItem = new Bindable(); + /// /// The corresponding to the local player, if available. /// @@ -94,9 +96,6 @@ namespace osu.Game.Online.Multiplayer private Room? apiRoom; - // Todo: This is temporary, until the multiplayer server returns the item id on match start or otherwise. - private int playlistItemId; - [BackgroundDependencyLoader] private void load() { @@ -142,7 +141,6 @@ namespace osu.Game.Online.Multiplayer { Room = joinedRoom; apiRoom = room; - playlistItemId = room.Playlist.SingleOrDefault()?.ID ?? 0; }, cancellationSource.Token); // Update room settings. @@ -218,7 +216,8 @@ namespace osu.Game.Online.Multiplayer BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash, RulesetID = item.GetOr(existingPlaylistItem).RulesetID, RequiredMods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.RequiredMods, - AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods + AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods, + PlaylistItemId = Room.Settings.PlaylistItemId, }); } @@ -506,14 +505,13 @@ namespace osu.Game.Online.Multiplayer Room.Settings = settings; apiRoom.Name.Value = Room.Settings.Name; - // The playlist update is delayed until an online beatmap lookup (below) succeeds. - // In-order for the client to not display an outdated beatmap, the playlist is forcefully cleared here. - apiRoom.Playlist.Clear(); + // The current item update is delayed until an online beatmap lookup (below) succeeds. + // In-order for the client to not display an outdated beatmap, the current item is forcefully cleared here. + CurrentMatchPlayingItem.Value = null; RoomUpdated?.Invoke(); var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId); - req.Success += res => { if (cancellationToken.IsCancellationRequested) @@ -540,18 +538,21 @@ namespace osu.Game.Online.Multiplayer var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset)); var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset)); - PlaylistItem playlistItem = new PlaylistItem - { - ID = playlistItemId, - Beatmap = { Value = beatmap }, - Ruleset = { Value = ruleset.RulesetInfo }, - }; + // Update an existing playlist item from the API room, or create a new item. + var playlistItem = apiRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId); + if (playlistItem == null) + apiRoom.Playlist.Add(playlistItem = new PlaylistItem()); + + playlistItem.ID = settings.PlaylistItemId; + playlistItem.Beatmap.Value = beatmap; + playlistItem.Ruleset.Value = ruleset.RulesetInfo; + playlistItem.RequiredMods.Clear(); playlistItem.RequiredMods.AddRange(mods); + playlistItem.AllowedMods.Clear(); playlistItem.AllowedMods.AddRange(allowedMods); - apiRoom.Playlist.Clear(); // Clearing should be unnecessary, but here for sanity. - apiRoom.Playlist.Add(playlistItem); + CurrentMatchPlayingItem.Value = playlistItem; } /// diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 6a2844fa74..7740af9f63 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -28,9 +28,6 @@ namespace osu.Game.Screens.OnlinePlay.Match private Sample sampleStart; - [Resolved(typeof(Room), nameof(Room.Playlist))] - protected BindableList Playlist { get; private set; } - /// /// Any mods applied by/to the local user. /// @@ -74,7 +71,6 @@ namespace osu.Game.Screens.OnlinePlay.Match base.LoadComplete(); SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged)); - SelectedItem.Value = Playlist.FirstOrDefault(); managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 76608fb5c1..f55b5b9713 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using JetBrains.Annotations; @@ -269,7 +268,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.LoadComplete(); - Playlist.BindCollectionChanged(onPlaylistChanged, true); + SelectedItem.BindValueChanged(onSelectedItemChanged); + SelectedItem.BindTo(client.CurrentMatchPlayingItem); + BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); UserMods.BindValueChanged(onUserModsChanged); @@ -300,11 +301,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return base.OnBackButton(); } - private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) + private void onSelectedItemChanged(ValueChangedEvent item) { - SelectedItem.Value = Playlist.LastOrDefault(); - - if (SelectedItem.Value?.AllowedMods.Any() != true) + if (item.NewValue?.AllowedMods.Any() != true) { userModsSection.Hide(); userModsSelectOverlay.Hide(); @@ -313,7 +312,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer else { userModsSection.Show(); - userModsSelectOverlay.IsValidMod = m => SelectedItem.Value.AllowedMods.Any(a => a.GetType() == m.GetType()); + userModsSelectOverlay.IsValidMod = m => item.NewValue.AllowedMods.Any(a => a.GetType() == m.GetType()); } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 88731a10bc..b4870ab580 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -27,6 +27,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [Resolved(typeof(Room), nameof(Room.RoomID))] private Bindable roomId { get; set; } + [Resolved(typeof(Room), nameof(Room.Playlist))] + private BindableList playlist { get; set; } + private MatchSettingsOverlay settingsOverlay; private MatchLeaderboard leaderboard; @@ -117,7 +120,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists new DrawableRoomPlaylistWithResults { RelativeSizeAxes = Axes.Both, - Items = { BindTarget = Playlist }, + Items = { BindTarget = playlist }, SelectedItem = { BindTarget = SelectedItem }, RequestShowResults = item => { @@ -222,7 +225,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists // 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 = Playlist.FirstOrDefault()); + Schedule(() => SelectedItem.Value = playlist.FirstOrDefault()); } }, true); } From 2a1096a3c8ca7a3c5f656963dcbcb2c6e7770eed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Feb 2021 19:02:16 +0900 Subject: [PATCH 30/90] Make BeatmapSelectionControl use the selected item --- .../Match/BeatmapSelectionControl.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs index f17e04d4d4..769596956b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs @@ -1,14 +1,15 @@ // 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.Linq; +using System.Diagnostics; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; using osu.Game.Online.API; +using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Match.Components; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match @@ -60,7 +61,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { base.LoadComplete(); - Playlist.BindCollectionChanged(onPlaylistChanged, true); + Debug.Assert(SelectedItem != null); + SelectedItem.BindValueChanged(onSelectedItemChanged, true); + Host.BindValueChanged(host => { if (RoomID.Value == null || host.NewValue?.Equals(api.LocalUser.Value) == true) @@ -70,12 +73,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }, true); } - private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) + private void onSelectedItemChanged(ValueChangedEvent selectedItem) { - if (Playlist.Any()) - beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(Playlist.Single(), false, false); - else + if (selectedItem.NewValue == null) beatmapPanelContainer.Clear(); + else + beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(selectedItem.NewValue, false, false); } } } From e24a5949c5a61c105e9f4670c07cdbccf0d7def6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Feb 2021 19:26:51 +0900 Subject: [PATCH 31/90] Fix resolve --- osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index c1aa93526f..4c4b1ff6d0 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.OnlinePlay /// May be null if this is not inside a . /// [CanBeNull] - [Resolved(typeof(Room), CanBeNull = true)] + [Resolved(CanBeNull = true)] protected IBindable SelectedItem { get; private set; } } } From 100097d78f0b28c66a86d9d8a4b8bbe309964429 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Feb 2021 21:32:35 +0900 Subject: [PATCH 32/90] Fix playlist not being handled correctly for non-joined cases --- .../Multiplayer/StatefulMultiplayerClient.cs | 8 ++++---- .../Multiplayer/Match/BeatmapSelectionControl.cs | 14 +++++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index f5f4c3a8ba..e9eb80e6a1 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -539,10 +539,7 @@ namespace osu.Game.Online.Multiplayer var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset)); // Update an existing playlist item from the API room, or create a new item. - var playlistItem = apiRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId); - - if (playlistItem == null) - apiRoom.Playlist.Add(playlistItem = new PlaylistItem()); + var playlistItem = apiRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId) ?? new PlaylistItem(); playlistItem.ID = settings.PlaylistItemId; playlistItem.Beatmap.Value = beatmap; @@ -552,6 +549,9 @@ namespace osu.Game.Online.Multiplayer playlistItem.AllowedMods.Clear(); playlistItem.AllowedMods.AddRange(allowedMods); + if (!apiRoom.Playlist.Contains(playlistItem)) + apiRoom.Playlist.Add(playlistItem); + CurrentMatchPlayingItem.Value = playlistItem; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs index 769596956b..8d394f2c2b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. 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.UserInterface; @@ -62,7 +62,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match base.LoadComplete(); Debug.Assert(SelectedItem != null); - SelectedItem.BindValueChanged(onSelectedItemChanged, true); + SelectedItem.BindValueChanged(_ => updateBeatmap()); + Playlist.BindCollectionChanged((_, __) => updateBeatmap(), true); Host.BindValueChanged(host => { @@ -73,12 +74,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }, true); } - private void onSelectedItemChanged(ValueChangedEvent selectedItem) + private void updateBeatmap() { - if (selectedItem.NewValue == null) + Debug.Assert(SelectedItem != null); + PlaylistItem item = SelectedItem.Value ?? Playlist.FirstOrDefault(); + + if (item == null) beatmapPanelContainer.Clear(); else - beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(selectedItem.NewValue, false, false); + beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(item, false, false); } } } From f61b8e6154c596a33f43dceed8f55b35ce479f58 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Feb 2021 21:32:38 +0900 Subject: [PATCH 33/90] Change to long --- osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index 04752f4e6f..473382cf5f 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -40,7 +40,7 @@ namespace osu.Game.Online.Multiplayer /// Only used for client-side mutation. /// [Key(6)] - public int PlaylistItemId { get; set; } + public long PlaylistItemId { get; set; } public bool Equals(MultiplayerRoomSettings other) => BeatmapID == other.BeatmapID From 3b4e02e5c78fd77325f188c0379785e47b61d6aa Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Feb 2021 07:29:45 +0300 Subject: [PATCH 34/90] Fix user population not immediate on bracket loading --- osu.Game.Tournament/TournamentGameBase.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 4224da4bbe..327d8f67b8 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -152,7 +152,7 @@ namespace osu.Game.Tournament { if (string.IsNullOrEmpty(p.Username) || p.Statistics == null) { - PopulateUser(p); + PopulateUser(p, immediate: true); addedInfo = true; } } @@ -211,7 +211,7 @@ namespace osu.Game.Tournament return addedInfo; } - public void PopulateUser(User user, Action success = null, Action failure = null) + public void PopulateUser(User user, Action success = null, Action failure = null, bool immediate = false) { var req = new GetUserRequest(user.Id, Ruleset.Value); @@ -231,7 +231,10 @@ namespace osu.Game.Tournament failure?.Invoke(); }; - API.Queue(req); + if (immediate) + API.Perform(req); + else + API.Queue(req); } protected override void LoadComplete() From 705e9267497bc1eec01da9d73ec2630ad2be327d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Feb 2021 07:48:23 +0300 Subject: [PATCH 35/90] Fix attempting to populate users with invalid IDs --- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 582f72429b..263bbc533c 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -277,7 +277,8 @@ namespace osu.Game.Tournament.Screens.Editors userId.Value = user.Id.ToString(); userId.BindValueChanged(idString => { - int.TryParse(idString.NewValue, out var parsed); + if (!(int.TryParse(idString.NewValue, out var parsed))) + return; user.Id = parsed; From 85ebc8e06cd45be8e66f40c059dac6b20c926cc8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Feb 2021 07:49:28 +0300 Subject: [PATCH 36/90] Fix potentially overwriting user ID from failed request --- osu.Game.Tournament/TournamentGameBase.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 327d8f67b8..0b101f050f 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -225,11 +225,7 @@ namespace osu.Game.Tournament success?.Invoke(); }; - req.Failure += _ => - { - user.Id = 1; - failure?.Invoke(); - }; + req.Failure += _ => failure?.Invoke(); if (immediate) API.Perform(req); From a845e96b7a8a6eb55615c38567e5a1716b334008 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Feb 2021 08:50:48 +0300 Subject: [PATCH 37/90] Replace `Ranks.Global` completely with a `GlobalRank` property --- osu.Desktop/DiscordRichPresence.cs | 2 +- .../TestSceneMultiplayerParticipantsList.cs | 10 ++-------- .../Visual/Online/TestSceneRankGraph.cs | 10 +++++----- .../Visual/Online/TestSceneUserProfileOverlay.cs | 3 ++- osu.Game.Tournament.Tests/TournamentTestScene.cs | 10 +++++----- osu.Game.Tournament/Models/TournamentTeam.cs | 2 +- .../Screens/TeamIntro/SeedingScreen.cs | 2 +- osu.Game.Tournament/TournamentGameBase.cs | 2 +- .../Profile/Header/CentreHeaderContainer.cs | 2 +- .../Profile/Header/DetailHeaderContainer.cs | 2 +- .../Multiplayer/Participants/ParticipantPanel.cs | 2 +- osu.Game/Users/UserStatistics.cs | 16 +++++----------- 12 files changed, 26 insertions(+), 37 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 63b12fb84b..832d26b0ef 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -105,7 +105,7 @@ namespace osu.Desktop if (privacyMode.Value == DiscordRichPresenceMode.Limited) presence.Assets.LargeImageText = string.Empty; else - presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.Ranks.Global > 0 ? $" (rank #{user.Value.Statistics.Ranks.Global:N0})" : string.Empty); + presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty); // update ruleset presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom"; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 1e14bbbbea..e713cff233 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -160,10 +160,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { { Ruleset.Value.ShortName, - new UserStatistics - { - Ranks = new UserStatistics.UserRanks { Global = RNG.Next(1, 100000) } - } + new UserStatistics { GlobalRank = RNG.Next(1, 100000), } } }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", @@ -207,10 +204,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { { Ruleset.Value.ShortName, - new UserStatistics - { - Ranks = new UserStatistics.UserRanks { Global = RNG.Next(1, 100000) } - } + new UserStatistics { GlobalRank = RNG.Next(1, 100000), } } }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs index 3b31192259..5bf9e31309 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online { graph.Statistics.Value = new UserStatistics { - Ranks = new UserStatistics.UserRanks { Global = 123456 }, + GlobalRank = 123456, PP = 12345, }; }); @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Online { graph.Statistics.Value = new UserStatistics { - Ranks = new UserStatistics.UserRanks { Global = 89000 }, + GlobalRank = 89000, PP = 12345, RankHistory = new User.RankHistoryData { @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.Online { graph.Statistics.Value = new UserStatistics { - Ranks = new UserStatistics.UserRanks { Global = 89000 }, + GlobalRank = 89000, PP = 12345, RankHistory = new User.RankHistoryData { @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Visual.Online { graph.Statistics.Value = new UserStatistics { - Ranks = new UserStatistics.UserRanks { Global = 12000 }, + GlobalRank = 12000, PP = 12345, RankHistory = new User.RankHistoryData { @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.Online { graph.Statistics.Value = new UserStatistics { - Ranks = new UserStatistics.UserRanks { Global = 12000 }, + GlobalRank = 12000, PP = 12345, RankHistory = new User.RankHistoryData { diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 7ade24f4de..b52cc6edb6 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -33,7 +33,8 @@ namespace osu.Game.Tests.Visual.Online ProfileOrder = new[] { "me" }, Statistics = new UserStatistics { - Ranks = new UserStatistics.UserRanks { Global = 2148, Country = 1 }, + GlobalRank = 2148, + Ranks = new UserStatistics.UserRanks { Country = 1, }, PP = 4567.89m, Level = new UserStatistics.LevelInfo { diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs index 47d2160561..cdfd19c157 100644 --- a/osu.Game.Tournament.Tests/TournamentTestScene.cs +++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs @@ -113,11 +113,11 @@ namespace osu.Game.Tournament.Tests }, Players = { - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 12 } }, + new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 16 } }, + new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 20 } }, + new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 24 } }, + new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 30 } }, } } }, diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs index 7fca75cea4..7074ae413c 100644 --- a/osu.Game.Tournament/Models/TournamentTeam.cs +++ b/osu.Game.Tournament/Models/TournamentTeam.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tournament.Models { get { - var ranks = Players.Select(p => p.Statistics?.Ranks.Global) + var ranks = Players.Select(p => p.Statistics?.GlobalRank) .Where(i => i.HasValue) .Select(i => i.Value) .ToArray(); diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index 55fc80dba2..4f66d89b7f 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -250,7 +250,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro }; foreach (var p in team.Players) - fill.Add(new RowDisplay(p.Username, p.Statistics?.Ranks.Global?.ToString("\\##,0") ?? "-")); + fill.Add(new RowDisplay(p.Username, p.Statistics?.GlobalRank?.ToString("\\##,0") ?? "-")); } internal class RowDisplay : CompositeDrawable diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 0b101f050f..3a2a880811 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -150,7 +150,7 @@ namespace osu.Game.Tournament { foreach (var p in t.Players) { - if (string.IsNullOrEmpty(p.Username) || p.Statistics == null) + if (string.IsNullOrEmpty(p.Username) || p.Statistics?.GlobalRank == null) { PopulateUser(p, immediate: true); addedInfo = true; diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index 04a1040e06..9285e2d875 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -144,7 +144,7 @@ namespace osu.Game.Overlays.Profile.Header private void updateDisplay(User user) { - hiddenDetailGlobal.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-"; + hiddenDetailGlobal.Content = user?.Statistics?.GlobalRank?.ToString("\\##,##0") ?? "-"; hiddenDetailCountry.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-"; } } diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index cf6ae1a3fc..05a0508e1f 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -176,7 +176,7 @@ namespace osu.Game.Overlays.Profile.Header foreach (var scoreRankInfo in scoreRankInfos) scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0; - detailGlobalRank.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-"; + detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToString("\\##,##0") ?? "-"; detailCountryRank.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-"; rankGraph.Statistics.Value = user?.Statistics; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index c4d11676e7..25bc314f1b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -165,7 +165,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID); - var currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.Ranks.Global; + var currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability); diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 1fed908c39..e50ca57d90 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -26,17 +26,14 @@ namespace osu.Game.Users public int Progress; } + [JsonProperty(@"global_rank")] + public int? GlobalRank; + + // eventually UserRanks object will be completely replaced with separate global rank (exists) and country rank properties + // see https://github.com/ppy/osu-web/blob/cb79bb72186c8f1a25f6a6f5ef315123decb4231/app/Transformers/UserStatisticsTransformer.php#L53. [JsonProperty(@"rank")] public UserRanks Ranks; - // eventually UserRanks object will be completely replaced with separate global and country rank properties, see https://github.com/ppy/osu-web/blob/cb79bb72186c8f1a25f6a6f5ef315123decb4231/app/Transformers/UserStatisticsTransformer.php#L53. - // but for now, always point UserRanks.Global to the global_rank property, as that is included solely for requests like GetUsersRequest. - [JsonProperty(@"global_rank")] - private int? globalRank - { - set => Ranks.Global = value; - } - [JsonProperty(@"pp")] public decimal? PP; @@ -120,9 +117,6 @@ namespace osu.Game.Users public struct UserRanks { - [JsonProperty(@"global")] - public int? Global; - [JsonProperty(@"country")] public int? Country; } From fb0e9d6760c2877ae97b40ad62d601b9e6774369 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Feb 2021 16:44:39 +0900 Subject: [PATCH 38/90] Add played property to playlist item --- osu.Game/Online/Rooms/PlaylistItem.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index 61982101c1..1d409d4b56 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -23,6 +23,12 @@ namespace osu.Game.Online.Rooms [JsonProperty("ruleset_id")] public int RulesetID { get; set; } + /// + /// Whether this is still a valid selection for the . + /// + [JsonProperty("expired")] + public bool Expired { get; set; } + [JsonIgnore] public readonly Bindable Beatmap = new Bindable(); From 61bf9a64bb117483093318b99ce135cd50a2e8c0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Feb 2021 11:21:33 +0300 Subject: [PATCH 39/90] Revert failed user requests changes with returning user ID instead --- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 3 +-- osu.Game.Tournament/TournamentGameBase.cs | 8 +++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 263bbc533c..582f72429b 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -277,8 +277,7 @@ namespace osu.Game.Tournament.Screens.Editors userId.Value = user.Id.ToString(); userId.BindValueChanged(idString => { - if (!(int.TryParse(idString.NewValue, out var parsed))) - return; + int.TryParse(idString.NewValue, out var parsed); user.Id = parsed; diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 0b101f050f..ffda101ee0 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -217,6 +217,8 @@ namespace osu.Game.Tournament req.Success += res => { + user.Id = res.Id; + user.Username = res.Username; user.Statistics = res.Statistics; user.Country = res.Country; @@ -225,7 +227,11 @@ namespace osu.Game.Tournament success?.Invoke(); }; - req.Failure += _ => failure?.Invoke(); + req.Failure += _ => + { + user.Id = 1; + failure?.Invoke(); + }; if (immediate) API.Perform(req); From 0d1149911c44f16b1017c96ed79638fbcfe00a5c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Feb 2021 17:33:10 +0900 Subject: [PATCH 40/90] Don't display expired playlist items --- osu.Game/Online/Rooms/Room.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 997f45ce52..aaaa712860 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -148,6 +148,12 @@ namespace osu.Game.Online.Rooms if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value) Status.Value = new RoomStatusEnded(); + // 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. + if (!(Status.Value is RoomStatusEnded)) + other.Playlist.RemoveAll(i => i.Expired); + if (!Playlist.SequenceEqual(other.Playlist)) { Playlist.Clear(); From 70a995919cde7ae34501afac229872cf7317e693 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Feb 2021 17:58:24 +0900 Subject: [PATCH 41/90] Update comments --- osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs | 3 --- osu.Game/Online/Rooms/Room.cs | 4 ++-- .../OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs | 2 ++ 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index 473382cf5f..7d6c76bc2f 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -36,9 +36,6 @@ namespace osu.Game.Online.Multiplayer [Key(5)] public IEnumerable AllowedMods { get; set; } = Enumerable.Empty(); - /// - /// Only used for client-side mutation. - /// [Key(6)] public long PlaylistItemId { get; set; } diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index aaaa712860..00a7979f2c 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -149,8 +149,8 @@ namespace osu.Game.Online.Rooms Status.Value = new RoomStatusEnded(); // 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. + // 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)) other.Playlist.RemoveAll(i => i.Expired); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs index 8d394f2c2b..3cf0767cf8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs @@ -77,6 +77,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateBeatmap() { Debug.Assert(SelectedItem != null); + + // When the selected item is null, the match hasn't yet been created. Use the playlist directly, which is mutated by song selection. PlaylistItem item = SelectedItem.Value ?? Playlist.FirstOrDefault(); if (item == null) From 604add04e495cbed44991962f3fa4e1bcb8b1451 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Feb 2021 19:06:37 +0900 Subject: [PATCH 42/90] Fix song select mods being reset incorrectly --- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index b201c62b7f..c60743a226 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using Humanizer; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -31,6 +32,10 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room), nameof(Room.Playlist))] protected BindableList Playlist { get; private set; } + [CanBeNull] + [Resolved(CanBeNull = true)] + private IBindable selectedItem { get; set; } + private readonly Bindable> freeMods = new Bindable>(Array.Empty()); private readonly FreeModSelectOverlay freeModSelectOverlay; @@ -66,8 +71,8 @@ namespace osu.Game.Screens.OnlinePlay // At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods. // Similarly, freeMods is currently empty but should only contain the allowed mods. - Mods.Value = Playlist.FirstOrDefault()?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); - freeMods.Value = Playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + Mods.Value = selectedItem?.Value?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + freeMods.Value = selectedItem?.Value?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); Ruleset.BindValueChanged(onRulesetChanged); } From 2a1bb2f578ac8b74e6a7d5e7a949a7e995acc6c1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Feb 2021 21:38:01 +0900 Subject: [PATCH 43/90] Fix selected item potentially changing during gameplay --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index c1930c525c..b5eff04532 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -46,7 +46,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private MultiplayerMatchSettingsOverlay settingsOverlay; private Drawable userModsSection; - private IBindable isConnected; + private readonly IBindable isConnected = new Bindable(); + private readonly IBindable matchCurrentItem = new Bindable(); [CanBeNull] private IDisposable readyClickOperation; @@ -268,8 +269,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.LoadComplete(); - SelectedItem.BindValueChanged(onSelectedItemChanged); - SelectedItem.BindTo(client.CurrentMatchPlayingItem); + matchCurrentItem.BindTo(client.CurrentMatchPlayingItem); + matchCurrentItem.BindValueChanged(onCurrentItemChanged, true); BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); UserMods.BindValueChanged(onUserModsChanged); @@ -277,7 +278,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer client.LoadRequested += onLoadRequested; client.RoomUpdated += onRoomUpdated; - isConnected = client.IsConnected.GetBoundCopy(); + isConnected.BindTo(client.IsConnected); isConnected.BindValueChanged(connected => { if (!connected.NewValue) @@ -285,6 +286,33 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer }, true); } + private void onCurrentItemChanged(ValueChangedEvent item) + { + if (client?.LocalUser == null) + return; + + // If we're about to enter gameplay, schedule the item to be set at a later time. + if (client.LocalUser.State > MultiplayerUserState.Ready) + { + Schedule(() => onCurrentItemChanged(item)); + return; + } + + SelectedItem.Value = item.NewValue; + + if (item.NewValue?.AllowedMods.Any() != true) + { + userModsSection.Hide(); + userModsSelectOverlay.Hide(); + userModsSelectOverlay.IsValidMod = _ => false; + } + else + { + userModsSection.Show(); + userModsSelectOverlay.IsValidMod = m => item.NewValue.AllowedMods.Any(a => a.GetType() == m.GetType()); + } + } + protected override void UpdateMods() { if (SelectedItem.Value == null || client.LocalUser == null) @@ -313,21 +341,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return base.OnBackButton(); } - private void onSelectedItemChanged(ValueChangedEvent item) - { - if (item.NewValue?.AllowedMods.Any() != true) - { - userModsSection.Hide(); - userModsSelectOverlay.Hide(); - userModsSelectOverlay.IsValidMod = _ => false; - } - else - { - userModsSection.Show(); - userModsSelectOverlay.IsValidMod = m => item.NewValue.AllowedMods.Any(a => a.GetType() == m.GetType()); - } - } - private ModSettingChangeTracker modSettingChangeTracker; private ScheduledDelegate debouncedModSettingsUpdate; From 6ef235c4c5290f3254ed247b2f20054dde1aceaf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Feb 2021 21:42:22 +0900 Subject: [PATCH 44/90] Fix beatmap panel flickering multiple times --- .../Match/BeatmapSelectionControl.cs | 17 ++------ .../Screens/OnlinePlay/OnlinePlayComposite.cs | 41 +++++++++++++++++-- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs index 3cf0767cf8..3af0d5b715 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs @@ -1,15 +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.Diagnostics; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; using osu.Game.Online.API; -using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Match.Components; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match @@ -61,10 +58,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { base.LoadComplete(); - Debug.Assert(SelectedItem != null); - SelectedItem.BindValueChanged(_ => updateBeatmap()); - Playlist.BindCollectionChanged((_, __) => updateBeatmap(), true); - + SelectedItem.BindValueChanged(_ => updateBeatmap(), true); Host.BindValueChanged(host => { if (RoomID.Value == null || host.NewValue?.Equals(api.LocalUser.Value) == true) @@ -76,15 +70,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateBeatmap() { - Debug.Assert(SelectedItem != null); - - // When the selected item is null, the match hasn't yet been created. Use the playlist directly, which is mutated by song selection. - PlaylistItem item = SelectedItem.Value ?? Playlist.FirstOrDefault(); - - if (item == null) + if (SelectedItem.Value == null) beatmapPanelContainer.Clear(); else - beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(item, false, false); + beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(SelectedItem.Value, false, false); } } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index a7058d0ede..f203ef927c 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Specialized; +using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -57,11 +59,44 @@ namespace osu.Game.Screens.OnlinePlay protected Bindable Duration { get; private set; } /// - /// The currently selected item in the . - /// May be null if this is not inside a . + /// The currently selected item in the , or the first item from + /// if this is not within a . /// + protected IBindable SelectedItem => selectedItem; + + private readonly Bindable selectedItem = new Bindable(); + [CanBeNull] [Resolved(CanBeNull = true)] - protected IBindable SelectedItem { get; private set; } + private IBindable subScreenSelectedItem { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (subScreenSelectedItem != null) + subScreenSelectedItem.BindValueChanged(onSelectedItemChanged, true); + else + Playlist.BindCollectionChanged(onPlaylistChanged, true); + } + + /// + /// Invoked when the selected item from within a changes. + /// Does not occur when this is outside a . + /// + private void onSelectedItemChanged(ValueChangedEvent item) + { + // If the room hasn't been created yet, fall-back to the first item from the playlist. + selectedItem.Value = RoomID.Value == null ? Playlist.FirstOrDefault() : item.NewValue; + } + + /// + /// Invoked when the playlist changes. + /// Does not occur when this is inside a . + /// + private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) + { + selectedItem.Value = Playlist.FirstOrDefault(); + } } } From 90dce5204218ac9132e929d374a123f3da7abe34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 14:10:28 +0900 Subject: [PATCH 45/90] Fix potential crash from cross-thread drawable manipulation in CollectionFilterDropdown --- osu.Game/Collections/CollectionFilterDropdown.cs | 13 ++++++------- osu.Game/Screens/Select/FilterControl.cs | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index ec0e9d5a89..3e55ecb084 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -41,19 +41,18 @@ namespace osu.Game.Collections ItemSource = filters; } - [BackgroundDependencyLoader(permitNulls: true)] - private void load([CanBeNull] CollectionManager collectionManager) + [Resolved(CanBeNull = true)] + private CollectionManager collectionManager { get; set; } + + protected override void LoadComplete() { + base.LoadComplete(); + if (collectionManager != null) collections.BindTo(collectionManager.Collections); collections.CollectionChanged += (_, __) => collectionsChanged(); collectionsChanged(); - } - - protected override void LoadComplete() - { - base.LoadComplete(); Current.BindValueChanged(filterChanged, true); } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 952a5d1eaa..eafd8a87d1 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Select Sort = sortMode.Value, AllowConvertedBeatmaps = showConverted.Value, Ruleset = ruleset.Value, - Collection = collectionDropdown?.Current.Value.Collection + Collection = collectionDropdown?.Current.Value?.Collection }; if (!minimumStars.IsDefault) From 56e9e10ff584742f80c21971771874c7f21ab745 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Feb 2021 15:30:31 +0900 Subject: [PATCH 46/90] Make server authoritative in playlist item id --- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index e9eb80e6a1..416162779d 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -217,7 +217,6 @@ namespace osu.Game.Online.Multiplayer RulesetID = item.GetOr(existingPlaylistItem).RulesetID, RequiredMods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.RequiredMods, AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods, - PlaylistItemId = Room.Settings.PlaylistItemId, }); } From 143e1456701ad4808c55cbb75788e81dc3df9c9a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 15:42:26 +0900 Subject: [PATCH 47/90] Update implementation of AdjustableAudioComponents --- .../Rulesets/TestSceneDrawableRulesetDependencies.cs | 4 ++-- osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs | 4 ++-- osu.Game/Skinning/PoolableSkinnableSample.cs | 4 ++-- osu.Game/Skinning/SkinnableSound.cs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs index 787f72ba79..4aebed0d31 100644 --- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs +++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs @@ -118,9 +118,9 @@ namespace osu.Game.Tests.Rulesets public BindableNumber Frequency => throw new NotImplementedException(); public BindableNumber Tempo => throw new NotImplementedException(); - public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotImplementedException(); - public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); + public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotImplementedException(); public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotImplementedException(); diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index deec948d14..bbaca7c80f 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -110,9 +110,9 @@ namespace osu.Game.Rulesets.UI public IEnumerable GetAvailableResources() => throw new NotSupportedException(); - public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotSupportedException(); - public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); + public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotSupportedException(); public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotSupportedException(); diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index 9025fdbd0f..abff57091b 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -165,9 +165,9 @@ namespace osu.Game.Skinning public BindableNumber Tempo => sampleContainer.Tempo; - public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable); + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable); - public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable); + public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable); public void RemoveAllAdjustments(AdjustableProperty type) => sampleContainer.RemoveAllAdjustments(type); diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index b3db2d6558..d3dfcb1dc0 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -176,10 +176,10 @@ namespace osu.Game.Skinning public BindableNumber Tempo => samplesContainer.Tempo; - public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => samplesContainer.AddAdjustment(type, adjustBindable); - public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) + public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => samplesContainer.RemoveAdjustment(type, adjustBindable); public void RemoveAllAdjustments(AdjustableProperty type) From e911760318edfb87b86bd6f5b74da4e285c441f5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Feb 2021 15:47:33 +0900 Subject: [PATCH 48/90] Split OnlinePlayComposite to remove if-statement --- .../Match/BeatmapSelectionControl.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 36 ++++-------------- .../OnlinePlay/RoomSubScreenComposite.cs | 38 +++++++++++++++++++ 3 files changed, 46 insertions(+), 30 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/RoomSubScreenComposite.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs index 3af0d5b715..ebe63e26d6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs @@ -11,7 +11,7 @@ using osu.Game.Screens.OnlinePlay.Match.Components; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { - public class BeatmapSelectionControl : OnlinePlayComposite + public class BeatmapSelectionControl : RoomSubScreenComposite { [Resolved] private MultiplayerMatchSubScreen matchSubScreen { get; set; } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index f203ef927c..eb0b23f13f 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -2,9 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Specialized; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; @@ -14,6 +12,9 @@ using osu.Game.Users; namespace osu.Game.Screens.OnlinePlay { + /// + /// A that exposes bindables for properties. + /// public class OnlinePlayComposite : CompositeDrawable { [Resolved(typeof(Room))] @@ -62,41 +63,18 @@ namespace osu.Game.Screens.OnlinePlay /// The currently selected item in the , or the first item from /// if this is not within a . /// - protected IBindable SelectedItem => selectedItem; - - private readonly Bindable selectedItem = new Bindable(); - - [CanBeNull] - [Resolved(CanBeNull = true)] - private IBindable subScreenSelectedItem { get; set; } + protected readonly Bindable SelectedItem = new Bindable(); protected override void LoadComplete() { base.LoadComplete(); - if (subScreenSelectedItem != null) - subScreenSelectedItem.BindValueChanged(onSelectedItemChanged, true); - else - Playlist.BindCollectionChanged(onPlaylistChanged, true); + Playlist.BindCollectionChanged((_, __) => UpdateSelectedItem(), true); } - /// - /// Invoked when the selected item from within a changes. - /// Does not occur when this is outside a . - /// - private void onSelectedItemChanged(ValueChangedEvent item) + protected virtual void UpdateSelectedItem() { - // If the room hasn't been created yet, fall-back to the first item from the playlist. - selectedItem.Value = RoomID.Value == null ? Playlist.FirstOrDefault() : item.NewValue; - } - - /// - /// Invoked when the playlist changes. - /// Does not occur when this is inside a . - /// - private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) - { - selectedItem.Value = Playlist.FirstOrDefault(); + SelectedItem.Value = Playlist.FirstOrDefault(); } } } diff --git a/osu.Game/Screens/OnlinePlay/RoomSubScreenComposite.cs b/osu.Game/Screens/OnlinePlay/RoomSubScreenComposite.cs new file mode 100644 index 0000000000..4cfd881aa3 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/RoomSubScreenComposite.cs @@ -0,0 +1,38 @@ +// 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.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay.Match; + +namespace osu.Game.Screens.OnlinePlay +{ + /// + /// An with additional logic tracking the currently-selected inside a . + /// + public class RoomSubScreenComposite : OnlinePlayComposite + { + [Resolved] + private IBindable subScreenSelectedItem { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + subScreenSelectedItem.BindValueChanged(_ => UpdateSelectedItem(), true); + } + + protected override void UpdateSelectedItem() + { + if (RoomID.Value == null) + { + // If the room hasn't been created yet, fall-back to the base logic. + base.UpdateSelectedItem(); + return; + } + + SelectedItem.Value = subScreenSelectedItem.Value; + } + } +} From 46ba5de32c5e0dc63d94ab618121a61df655c2a9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Feb 2021 16:19:36 +0900 Subject: [PATCH 49/90] Fix collections being imported from BDL thread --- osu.Game/Collections/CollectionManager.cs | 48 +++++++++++++---------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 569ac749a4..a65d9a415d 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -139,35 +139,43 @@ namespace osu.Game.Collections PostNotification?.Invoke(notification); var collection = readCollections(stream, notification); - bool importCompleted = false; - - Schedule(() => - { - importCollections(collection); - importCompleted = true; - }); - - while (!IsDisposed && !importCompleted) - await Task.Delay(10); + await importCollections(collection); notification.CompletionText = $"Imported {collection.Count} collections"; notification.State = ProgressNotificationState.Completed; } - private void importCollections(List newCollections) + private Task importCollections(List newCollections) { - foreach (var newCol in newCollections) - { - var existing = Collections.FirstOrDefault(c => c.Name == newCol.Name); - if (existing == null) - Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } }); + var tcs = new TaskCompletionSource(); - foreach (var newBeatmap in newCol.Beatmaps) + Schedule(() => + { + try { - if (!existing.Beatmaps.Contains(newBeatmap)) - existing.Beatmaps.Add(newBeatmap); + foreach (var newCol in newCollections) + { + var existing = Collections.FirstOrDefault(c => c.Name == newCol.Name); + if (existing == null) + Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } }); + + foreach (var newBeatmap in newCol.Beatmaps) + { + if (!existing.Beatmaps.Contains(newBeatmap)) + existing.Beatmaps.Add(newBeatmap); + } + } + + tcs.SetResult(true); } - } + catch (Exception e) + { + Logger.Error(e, "Failed to import collection."); + tcs.SetException(e); + } + }); + + return tcs.Task; } private List readCollections(Stream stream, ProgressNotification notification = null) From c3a98b6ad15475a86565c2501fe0faeb2e898b14 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 16:59:43 +0900 Subject: [PATCH 50/90] Fix carousel items' borders getting blown out when selected and hovered I tried restructuring the hierarchy to avoid needing this added property (moving the hover layer out of the border container) but this leads to some subpixel leakage outside the borders which looks even worse. Closes #6915. --- .../Screens/Select/Carousel/CarouselHeader.cs | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index 947334c747..73324894ee 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -24,9 +24,13 @@ namespace osu.Game.Screens.Select.Carousel public Container BorderContainer; public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); + private HoverLayer hoverLayer; protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; + private const float corner_radius = 10; + private const float border_thickness = 2.5f; + public CarouselHeader() { RelativeSizeAxes = Axes.X; @@ -36,12 +40,12 @@ namespace osu.Game.Screens.Select.Carousel { RelativeSizeAxes = Axes.Both, Masking = true, - CornerRadius = 10, + CornerRadius = corner_radius, BorderColour = new Color4(221, 255, 255, 255), Children = new Drawable[] { Content, - new HoverLayer() + hoverLayer = new HoverLayer() } }; } @@ -59,6 +63,8 @@ namespace osu.Game.Screens.Select.Carousel { case CarouselItemState.Collapsed: case CarouselItemState.NotSelected: + hoverLayer.InsetForBorder = false; + BorderContainer.BorderThickness = 0; BorderContainer.EdgeEffect = new EdgeEffectParameters { @@ -70,7 +76,9 @@ namespace osu.Game.Screens.Select.Carousel break; case CarouselItemState.Selected: - BorderContainer.BorderThickness = 2.5f; + hoverLayer.InsetForBorder = true; + + BorderContainer.BorderThickness = border_thickness; BorderContainer.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, @@ -107,6 +115,26 @@ namespace osu.Game.Screens.Select.Carousel sampleHover = audio.Samples.Get("SongSelect/song-ping"); } + public bool InsetForBorder + { + set + { + if (value) + { + // apply same border as above to avoid applying additive overlay to it (and blowing out the colour). + Masking = true; + CornerRadius = corner_radius; + BorderThickness = border_thickness; + } + else + { + BorderThickness = 0; + CornerRadius = 0; + Masking = false; + } + } + } + protected override bool OnHover(HoverEvent e) { box.FadeIn(100, Easing.OutQuint); From b713eb2eae259e086ca3b62ff6e3d7b79bed06a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 17:13:48 +0900 Subject: [PATCH 51/90] Make field readonly --- osu.Game/Screens/Select/Carousel/CarouselHeader.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index 73324894ee..2fbf64de29 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -24,7 +24,8 @@ namespace osu.Game.Screens.Select.Carousel public Container BorderContainer; public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); - private HoverLayer hoverLayer; + + private readonly HoverLayer hoverLayer; protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; From 668cc144f68c339ab03a4601c4d36faae86b051e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Feb 2021 17:39:01 +0900 Subject: [PATCH 52/90] Fix test failures + multiple filter operations firing --- .../Collections/CollectionFilterDropdown.cs | 22 ++++++++++++++----- .../Collections/CollectionFilterMenuItem.cs | 8 ++++++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index 3e55ecb084..aad8400faa 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -29,6 +29,14 @@ namespace osu.Game.Collections /// protected virtual bool ShowManageCollectionsItem => true; + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public new Bindable Current + { + get => current.Current; + set => current.Current = value; + } + private readonly IBindableList collections = new BindableList(); private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); @@ -36,14 +44,15 @@ namespace osu.Game.Collections [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } + [Resolved(CanBeNull = true)] + private CollectionManager collectionManager { get; set; } + public CollectionFilterDropdown() { ItemSource = filters; + Current.Value = new AllBeatmapsCollectionFilterMenuItem(); } - [Resolved(CanBeNull = true)] - private CollectionManager collectionManager { get; set; } - protected override void LoadComplete() { base.LoadComplete(); @@ -51,9 +60,12 @@ namespace osu.Game.Collections if (collectionManager != null) collections.BindTo(collectionManager.Collections); - collections.CollectionChanged += (_, __) => collectionsChanged(); - collectionsChanged(); + // Dropdown has logic which triggers a change on the bindable with every change to the contained items. + // This is not desirable here, as it leads to multiple filter operations running even though nothing has changed. + // An extra bindable is enough to subvert this behaviour. + base.Current.BindTo(Current); + collections.BindCollectionChanged((_, __) => collectionsChanged(), true); Current.BindValueChanged(filterChanged, true); } diff --git a/osu.Game/Collections/CollectionFilterMenuItem.cs b/osu.Game/Collections/CollectionFilterMenuItem.cs index 4a489d2945..fe79358223 100644 --- a/osu.Game/Collections/CollectionFilterMenuItem.cs +++ b/osu.Game/Collections/CollectionFilterMenuItem.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using JetBrains.Annotations; using osu.Framework.Bindables; @@ -9,7 +10,7 @@ namespace osu.Game.Collections /// /// A filter. /// - public class CollectionFilterMenuItem + public class CollectionFilterMenuItem : IEquatable { /// /// The collection to filter beatmaps from. @@ -33,6 +34,11 @@ namespace osu.Game.Collections Collection = collection; CollectionName = Collection?.Name.GetBoundCopy() ?? new Bindable("All beatmaps"); } + + public bool Equals(CollectionFilterMenuItem other) + => other != null && CollectionName.Value == other.CollectionName.Value; + + public override int GetHashCode() => CollectionName.Value.GetHashCode(); } public class AllBeatmapsCollectionFilterMenuItem : CollectionFilterMenuItem From 71316bbee568c7ded598d502204054db85ffbb53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 17:45:58 +0900 Subject: [PATCH 53/90] Allow using OnlineViewContainer without deriving it --- osu.Game/Online/OnlineViewContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/OnlineViewContainer.cs b/osu.Game/Online/OnlineViewContainer.cs index c9fb70f0cc..8868f90524 100644 --- a/osu.Game/Online/OnlineViewContainer.cs +++ b/osu.Game/Online/OnlineViewContainer.cs @@ -15,7 +15,7 @@ namespace osu.Game.Online /// A for displaying online content which require a local user to be logged in. /// Shows its children only when the local user is logged in and supports displaying a placeholder if not. /// - public abstract class OnlineViewContainer : Container + public class OnlineViewContainer : Container { protected LoadingSpinner LoadingSpinner { get; private set; } @@ -30,7 +30,7 @@ namespace osu.Game.Online [Resolved] protected IAPIProvider API { get; private set; } - protected OnlineViewContainer(string placeholderMessage) + public OnlineViewContainer(string placeholderMessage) { this.placeholderMessage = placeholderMessage; } From c3f66a0c745ce7c53fbf948625f5ad620dd2d730 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 17:46:07 +0900 Subject: [PATCH 54/90] Add login placeholder for chat overlay --- osu.Game/Overlays/ChatOverlay.cs | 73 ++++++++++++++++---------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 8bc7e21047..f5dd4fae2c 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -24,6 +24,7 @@ using osu.Game.Overlays.Chat.Tabs; using osuTK.Input; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Game.Online; namespace osu.Game.Overlays { @@ -118,40 +119,47 @@ namespace osu.Game.Overlays { RelativeSizeAxes = Axes.Both, }, - currentChannelContainer = new Container + new OnlineViewContainer("Sign in to chat") { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Bottom = textbox_height - }, - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = textbox_height, - Padding = new MarginPadding - { - Top = padding * 2, - Bottom = padding * 2, - Left = ChatLine.LEFT_PADDING + padding * 2, - Right = padding * 2, - }, Children = new Drawable[] { - textbox = new FocusedTextBox + currentChannelContainer = new Container { RelativeSizeAxes = Axes.Both, - Height = 1, - PlaceholderText = "type your message", - ReleaseFocusOnCommit = false, - HoldFocus = true, - } - } - }, - loading = new LoadingSpinner(), + Padding = new MarginPadding + { + Bottom = textbox_height + }, + }, + new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = textbox_height, + Padding = new MarginPadding + { + Top = padding * 2, + Bottom = padding * 2, + Left = ChatLine.LEFT_PADDING + padding * 2, + Right = padding * 2, + }, + Children = new Drawable[] + { + textbox = new FocusedTextBox + { + RelativeSizeAxes = Axes.Both, + Height = 1, + PlaceholderText = "type your message", + ReleaseFocusOnCommit = false, + HoldFocus = true, + } + } + }, + loading = new LoadingSpinner(), + }, + } } }, tabsArea = new TabsArea @@ -184,9 +192,7 @@ namespace osu.Game.Overlays }, }, }; - textbox.OnCommit += postMessage; - ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue; ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; ChannelSelectionOverlay.State.ValueChanged += state => @@ -203,10 +209,8 @@ namespace osu.Game.Overlays else textbox.HoldFocus = true; }; - ChannelSelectionOverlay.OnRequestJoin = channel => channelManager.JoinChannel(channel); ChannelSelectionOverlay.OnRequestLeave = channelManager.LeaveChannel; - ChatHeight = config.GetBindable(OsuSetting.ChatDisplayHeight); ChatHeight.BindValueChanged(height => { @@ -214,9 +218,7 @@ namespace osu.Game.Overlays channelSelectionContainer.Height = 1f - height.NewValue; tabBackground.FadeTo(height.NewValue == 1f ? 1f : 0.8f, 200); }, true); - chatBackground.Colour = colours.ChatBlue; - loading.Show(); // This is a relatively expensive (and blocking) operation. @@ -226,13 +228,10 @@ namespace osu.Game.Overlays { // TODO: consider scheduling bindable callbacks to not perform when overlay is not present. channelManager.JoinedChannels.CollectionChanged += joinedChannelsChanged; - foreach (Channel channel in channelManager.JoinedChannels) ChannelTabControl.AddChannel(channel); - channelManager.AvailableChannels.CollectionChanged += availableChannelsChanged; availableChannelsChanged(null, null); - currentChannel = channelManager.CurrentChannel.GetBoundCopy(); currentChannel.BindValueChanged(currentChannelChanged, true); }); From 58d8f0733cfed3f432879263e827fc4cc1128162 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 17:45:58 +0900 Subject: [PATCH 55/90] Allow using OnlineViewContainer without deriving it --- osu.Game/Online/OnlineViewContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/OnlineViewContainer.cs b/osu.Game/Online/OnlineViewContainer.cs index c9fb70f0cc..8868f90524 100644 --- a/osu.Game/Online/OnlineViewContainer.cs +++ b/osu.Game/Online/OnlineViewContainer.cs @@ -15,7 +15,7 @@ namespace osu.Game.Online /// A for displaying online content which require a local user to be logged in. /// Shows its children only when the local user is logged in and supports displaying a placeholder if not. /// - public abstract class OnlineViewContainer : Container + public class OnlineViewContainer : Container { protected LoadingSpinner LoadingSpinner { get; private set; } @@ -30,7 +30,7 @@ namespace osu.Game.Online [Resolved] protected IAPIProvider API { get; private set; } - protected OnlineViewContainer(string placeholderMessage) + public OnlineViewContainer(string placeholderMessage) { this.placeholderMessage = placeholderMessage; } From 0bd1964d8e7db31f84374aa079c16e43b2c33a24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 18:04:41 +0900 Subject: [PATCH 56/90] Add login placeholder logic to OnlineOverlay A perfect implementation of this would probably leave the filter/header content visible, but that requires some re-thinking and restructuring to how the content is displayed in these overlays (ie. the header component shouldn't be inside the `ScrollContainer` as it is fixed). Supersedes and closes #10774. Closes #933. Addresses most pieces of #7417. --- osu.Game/Overlays/ChangelogOverlay.cs | 2 +- osu.Game/Overlays/NewsOverlay.cs | 2 +- osu.Game/Overlays/OnlineOverlay.cs | 15 ++++++++++++--- osu.Game/Overlays/TabbableOnlineOverlay.cs | 3 +-- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 593f59555a..537dd00727 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays protected List Streams; public ChangelogOverlay() - : base(OverlayColourScheme.Purple) + : base(OverlayColourScheme.Purple, false) { } diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 08e8331dd3..5beb285216 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays private readonly Bindable article = new Bindable(null); public NewsOverlay() - : base(OverlayColourScheme.Purple) + : base(OverlayColourScheme.Purple, false) { } diff --git a/osu.Game/Overlays/OnlineOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs index 7c9f751d3b..0a5ceb1993 100644 --- a/osu.Game/Overlays/OnlineOverlay.cs +++ b/osu.Game/Overlays/OnlineOverlay.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Online; namespace osu.Game.Overlays { @@ -16,10 +17,16 @@ namespace osu.Game.Overlays protected readonly LoadingLayer Loading; private readonly Container content; - protected OnlineOverlay(OverlayColourScheme colourScheme) + protected OnlineOverlay(OverlayColourScheme colourScheme, bool requiresSignIn = true) : base(colourScheme) { - base.Content.AddRange(new Drawable[] + var mainContent = requiresSignIn + ? new OnlineViewContainer($"Sign in to view the {Header.Title.Title}") + : new Container(); + + mainContent.RelativeSizeAxes = Axes.Both; + + mainContent.AddRange(new Drawable[] { ScrollFlow = new OverlayScrollContainer { @@ -41,8 +48,10 @@ namespace osu.Game.Overlays } } }, - Loading = new LoadingLayer(true) + Loading = new LoadingLayer() }); + + base.Content.Add(mainContent); } } } diff --git a/osu.Game/Overlays/TabbableOnlineOverlay.cs b/osu.Game/Overlays/TabbableOnlineOverlay.cs index 8172e99c1b..9ceab12d3d 100644 --- a/osu.Game/Overlays/TabbableOnlineOverlay.cs +++ b/osu.Game/Overlays/TabbableOnlineOverlay.cs @@ -61,8 +61,7 @@ namespace osu.Game.Overlays LoadComponentAsync(display, loaded => { - if (API.IsLoggedIn) - Loading.Hide(); + Loading.Hide(); Child = loaded; }, (cancellationToken = new CancellationTokenSource()).Token); From 990c1b1d6ef118c755b8cdff7150601542f47535 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 18:19:57 +0900 Subject: [PATCH 57/90] Revert accidental removal of newlines --- osu.Game/Overlays/ChatOverlay.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index f5dd4fae2c..28f2287514 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -192,7 +192,9 @@ namespace osu.Game.Overlays }, }, }; + textbox.OnCommit += postMessage; + ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue; ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; ChannelSelectionOverlay.State.ValueChanged += state => @@ -209,8 +211,10 @@ namespace osu.Game.Overlays else textbox.HoldFocus = true; }; + ChannelSelectionOverlay.OnRequestJoin = channel => channelManager.JoinChannel(channel); ChannelSelectionOverlay.OnRequestLeave = channelManager.LeaveChannel; + ChatHeight = config.GetBindable(OsuSetting.ChatDisplayHeight); ChatHeight.BindValueChanged(height => { @@ -218,7 +222,9 @@ namespace osu.Game.Overlays channelSelectionContainer.Height = 1f - height.NewValue; tabBackground.FadeTo(height.NewValue == 1f ? 1f : 0.8f, 200); }, true); + chatBackground.Colour = colours.ChatBlue; + loading.Show(); // This is a relatively expensive (and blocking) operation. @@ -228,10 +234,13 @@ namespace osu.Game.Overlays { // TODO: consider scheduling bindable callbacks to not perform when overlay is not present. channelManager.JoinedChannels.CollectionChanged += joinedChannelsChanged; + foreach (Channel channel in channelManager.JoinedChannels) ChannelTabControl.AddChannel(channel); + channelManager.AvailableChannels.CollectionChanged += availableChannelsChanged; availableChannelsChanged(null, null); + currentChannel = channelManager.CurrentChannel.GetBoundCopy(); currentChannel.BindValueChanged(currentChannelChanged, true); }); From a01896a652ee042cc66149a99ccf5b76dddef535 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 18 Feb 2021 13:04:22 +0300 Subject: [PATCH 58/90] Fix misordered hit error in score meter types --- osu.Game/Configuration/ScoreMeterType.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Configuration/ScoreMeterType.cs b/osu.Game/Configuration/ScoreMeterType.cs index b9499c758e..ddbd2327c2 100644 --- a/osu.Game/Configuration/ScoreMeterType.cs +++ b/osu.Game/Configuration/ScoreMeterType.cs @@ -16,12 +16,12 @@ namespace osu.Game.Configuration [Description("Hit Error (right)")] HitErrorRight, - [Description("Hit Error (bottom)")] - HitErrorBottom, - [Description("Hit Error (left+right)")] HitErrorBoth, + [Description("Hit Error (bottom)")] + HitErrorBottom, + [Description("Colour (left)")] ColourLeft, From d85a4a22e525cc4914c12bbe067e79f7ef8ba8cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 19:19:28 +0900 Subject: [PATCH 59/90] Allow beatmap imports from any derived version of SongSelect, rather than only PlaySongSelect --- osu.Game/OsuGame.cs | 2 +- osu.Game/PerformFromMenuRunner.cs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 15785ea6bd..771bcd2310 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -383,7 +383,7 @@ namespace osu.Game Ruleset.Value = selection.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection); - }, validScreens: new[] { typeof(PlaySongSelect) }); + }, validScreens: new[] { typeof(SongSelect) }); } /// diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs index 7999023998..3df9ca5305 100644 --- a/osu.Game/PerformFromMenuRunner.cs +++ b/osu.Game/PerformFromMenuRunner.cs @@ -82,7 +82,9 @@ namespace osu.Game game?.CloseAllOverlays(false); // we may already be at the target screen type. - if (validScreens.Contains(current.GetType()) && !beatmap.Disabled) + var type = current.GetType(); + + if (validScreens.Any(t => type.IsAssignableFrom(t)) && !beatmap.Disabled) { finalAction(current); Cancel(); @@ -91,13 +93,14 @@ namespace osu.Game while (current != null) { - if (validScreens.Contains(current.GetType())) + if (validScreens.Any(t => type.IsAssignableFrom(t))) { current.MakeCurrent(); break; } current = current.GetParentScreen(); + type = current?.GetType(); } } From 8a1a4ea2d42874d1dc752d4ec6a3371d02940054 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Feb 2021 19:33:04 +0900 Subject: [PATCH 60/90] Set Current directly --- osu.Game/Collections/CollectionFilterDropdown.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index aad8400faa..bb743d4ccc 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -63,7 +63,7 @@ namespace osu.Game.Collections // Dropdown has logic which triggers a change on the bindable with every change to the contained items. // This is not desirable here, as it leads to multiple filter operations running even though nothing has changed. // An extra bindable is enough to subvert this behaviour. - base.Current.BindTo(Current); + base.Current = Current; collections.BindCollectionChanged((_, __) => collectionsChanged(), true); Current.BindValueChanged(filterChanged, true); From e14a59f272f7c5feaab972493c2fc8cf1a91c3e6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 18 Feb 2021 15:26:59 +0300 Subject: [PATCH 61/90] Fix creating ruleset instances per LINQ select --- .../OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 25bc314f1b..5bef934e6a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -163,7 +163,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants const double fade_time = 50; - var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID); + var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance(); var currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; @@ -177,7 +177,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants // If the mods are updated at the end of the frame, the flow container will skip a reflow cycle: https://github.com/ppy/osu-framework/issues/4187 // This looks particularly jarring here, so re-schedule the update to that start of our frame as a fix. - Schedule(() => userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset.CreateInstance())).ToList()); + Schedule(() => userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList()); } public MenuItem[] ContextMenuItems From a407bfe73bc0e4aefce7a30a00a4daf6cb30c481 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 18 Feb 2021 15:37:52 +0300 Subject: [PATCH 62/90] Privatize `UserRanks` and expose a similar `CountryRank` field instead --- .../Visual/Online/TestSceneUserProfileOverlay.cs | 2 +- .../Profile/Header/CentreHeaderContainer.cs | 2 +- .../Profile/Header/DetailHeaderContainer.cs | 2 +- osu.Game/Users/UserStatistics.cs | 13 +++++++++---- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index b52cc6edb6..03d079261d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Online Statistics = new UserStatistics { GlobalRank = 2148, - Ranks = new UserStatistics.UserRanks { Country = 1, }, + CountryRank = 1, PP = 4567.89m, Level = new UserStatistics.LevelInfo { diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index 9285e2d875..62ebee7677 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -145,7 +145,7 @@ namespace osu.Game.Overlays.Profile.Header private void updateDisplay(User user) { hiddenDetailGlobal.Content = user?.Statistics?.GlobalRank?.ToString("\\##,##0") ?? "-"; - hiddenDetailCountry.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-"; + hiddenDetailCountry.Content = user?.Statistics?.CountryRank?.ToString("\\##,##0") ?? "-"; } } } diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index 05a0508e1f..574aef02fd 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -177,7 +177,7 @@ namespace osu.Game.Overlays.Profile.Header scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0; detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToString("\\##,##0") ?? "-"; - detailCountryRank.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-"; + detailCountryRank.Content = user?.Statistics?.CountryRank?.ToString("\\##,##0") ?? "-"; rankGraph.Statistics.Value = user?.Statistics; } diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index e50ca57d90..90c1d40848 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -29,10 +29,15 @@ namespace osu.Game.Users [JsonProperty(@"global_rank")] public int? GlobalRank; - // eventually UserRanks object will be completely replaced with separate global rank (exists) and country rank properties - // see https://github.com/ppy/osu-web/blob/cb79bb72186c8f1a25f6a6f5ef315123decb4231/app/Transformers/UserStatisticsTransformer.php#L53. + public int? CountryRank; + [JsonProperty(@"rank")] - public UserRanks Ranks; + private UserRanks ranks + { + // eventually that will also become an own json property instead of reading from a `rank` object. + // see https://github.com/ppy/osu-web/blob/cb79bb72186c8f1a25f6a6f5ef315123decb4231/app/Transformers/UserStatisticsTransformer.php#L53. + set => CountryRank = value.Country; + } [JsonProperty(@"pp")] public decimal? PP; @@ -115,7 +120,7 @@ namespace osu.Game.Users } } - public struct UserRanks + private struct UserRanks { [JsonProperty(@"country")] public int? Country; From f6df5a9d2b67272e6a1e42a6cd71dad5777c0e02 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 18 Feb 2021 15:55:45 +0300 Subject: [PATCH 63/90] Suppress false warning --- osu.Game/Users/UserStatistics.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 90c1d40848..4b1e46d51a 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -122,8 +122,10 @@ namespace osu.Game.Users private struct UserRanks { +#pragma warning disable 649 [JsonProperty(@"country")] public int? Country; +#pragma warning restore 649 } public RankHistoryData RankHistory; From 10ec4cd8e07950b8a48e8da8931e3e39b16559b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 22:38:17 +0900 Subject: [PATCH 64/90] Revert change to loading layer's default state --- osu.Game/Overlays/OnlineOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OnlineOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs index 0a5ceb1993..de33e4a1bc 100644 --- a/osu.Game/Overlays/OnlineOverlay.cs +++ b/osu.Game/Overlays/OnlineOverlay.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays } } }, - Loading = new LoadingLayer() + Loading = new LoadingLayer(true) }); base.Content.Add(mainContent); From 4caca9653ab02ddbb9dea6105bb5cbeb807fae2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 10:39:56 +0900 Subject: [PATCH 65/90] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index e30416bc1c..bc5ba57d71 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 72f680f6f8..fef2f567df 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 137c96a72d..0d473290e6 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -91,7 +91,7 @@ - + From bc10fcafae2d57b6bb07d6bf666ee72577e5b8f9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Feb 2021 13:23:01 +0900 Subject: [PATCH 66/90] Remove now unnecessary schedule --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index b5eff04532..cb2b6dda95 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -47,7 +47,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private Drawable userModsSection; private readonly IBindable isConnected = new Bindable(); - private readonly IBindable matchCurrentItem = new Bindable(); [CanBeNull] private IDisposable readyClickOperation; @@ -269,8 +268,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.LoadComplete(); - matchCurrentItem.BindTo(client.CurrentMatchPlayingItem); - matchCurrentItem.BindValueChanged(onCurrentItemChanged, true); + SelectedItem.BindTo(client.CurrentMatchPlayingItem); + SelectedItem.BindValueChanged(onSelectedItemChanged, true); BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); UserMods.BindValueChanged(onUserModsChanged); @@ -286,20 +285,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer }, true); } - private void onCurrentItemChanged(ValueChangedEvent item) + private void onSelectedItemChanged(ValueChangedEvent item) { if (client?.LocalUser == null) return; - // If we're about to enter gameplay, schedule the item to be set at a later time. - if (client.LocalUser.State > MultiplayerUserState.Ready) - { - Schedule(() => onCurrentItemChanged(item)); - return; - } - - SelectedItem.Value = item.NewValue; - if (item.NewValue?.AllowedMods.Any() != true) { userModsSection.Hide(); From 841c2c56d961e7eb76cb48f4fe2686f9250e6e7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 13:30:42 +0900 Subject: [PATCH 67/90] Remove confusing pp_rank include (will be removed osu-web side too) --- osu.Game/Users/UserStatistics.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 4b1e46d51a..70969ea737 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -42,9 +42,6 @@ namespace osu.Game.Users [JsonProperty(@"pp")] public decimal? PP; - [JsonProperty(@"pp_rank")] - public int PPRank; - [JsonProperty(@"ranked_score")] public long RankedScore; From 183a481a345ea4fff4b55dd6df7fcdbc4e1dad4b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Feb 2021 13:32:32 +0900 Subject: [PATCH 68/90] Refactor playlist update to remove .Contains() check --- .../Multiplayer/StatefulMultiplayerClient.cs | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 416162779d..ed97307c95 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -537,21 +537,30 @@ namespace osu.Game.Online.Multiplayer var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset)); var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset)); - // Update an existing playlist item from the API room, or create a new item. - var playlistItem = apiRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId) ?? new PlaylistItem(); + // Try to retrieve the existing playlist item from the API room. + var playlistItem = apiRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId); - playlistItem.ID = settings.PlaylistItemId; - playlistItem.Beatmap.Value = beatmap; - playlistItem.Ruleset.Value = ruleset.RulesetInfo; - playlistItem.RequiredMods.Clear(); - playlistItem.RequiredMods.AddRange(mods); - playlistItem.AllowedMods.Clear(); - playlistItem.AllowedMods.AddRange(allowedMods); - - if (!apiRoom.Playlist.Contains(playlistItem)) + if (playlistItem != null) + updateItem(playlistItem); + else + { + // An existing playlist item does not exist, so append a new one. + updateItem(playlistItem = new PlaylistItem()); apiRoom.Playlist.Add(playlistItem); + } CurrentMatchPlayingItem.Value = playlistItem; + + void updateItem(PlaylistItem item) + { + item.ID = settings.PlaylistItemId; + item.Beatmap.Value = beatmap; + item.Ruleset.Value = ruleset.RulesetInfo; + item.RequiredMods.Clear(); + item.RequiredMods.AddRange(mods); + item.AllowedMods.Clear(); + item.AllowedMods.AddRange(allowedMods); + } } /// From 85a844a37820425e05df93d2d615593dbcc1989f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 13:40:12 +0900 Subject: [PATCH 69/90] Restructure class slightly --- osu.Game/Users/UserStatistics.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 70969ea737..78e6f5a05a 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -39,6 +39,9 @@ namespace osu.Game.Users set => CountryRank = value.Country; } + // populated via User model, as that's where the data currently lives. + public RankHistoryData RankHistory; + [JsonProperty(@"pp")] public decimal? PP; @@ -117,14 +120,12 @@ namespace osu.Game.Users } } +#pragma warning disable 649 private struct UserRanks { -#pragma warning disable 649 [JsonProperty(@"country")] public int? Country; -#pragma warning restore 649 } - - public RankHistoryData RankHistory; +#pragma warning restore 649 } } From c0e0bd4f421764ae6418597ed30bb64d501a69e8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Feb 2021 13:57:04 +0900 Subject: [PATCH 70/90] Add compatibility with old server build --- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index ed97307c95..bfd505fb19 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -94,6 +94,10 @@ namespace osu.Game.Online.Multiplayer [Resolved] private RulesetStore rulesets { get; set; } = null!; + // Only exists for compatibility with old osu-server-spectator build. + // Todo: Can be removed on 2021/02/26. + private long defaultPlaylistItemId; + private Room? apiRoom; [BackgroundDependencyLoader] @@ -141,6 +145,7 @@ namespace osu.Game.Online.Multiplayer { Room = joinedRoom; apiRoom = room; + defaultPlaylistItemId = apiRoom.Playlist.FirstOrDefault()?.ID ?? 0; }, cancellationSource.Token); // Update room settings. @@ -553,7 +558,7 @@ namespace osu.Game.Online.Multiplayer void updateItem(PlaylistItem item) { - item.ID = settings.PlaylistItemId; + item.ID = settings.PlaylistItemId == 0 ? defaultPlaylistItemId : settings.PlaylistItemId; item.Beatmap.Value = beatmap; item.Ruleset.Value = ruleset.RulesetInfo; item.RequiredMods.Clear(); From 87edf6787981a49c7160dac2d26beebf8b404416 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 14:07:39 +0900 Subject: [PATCH 71/90] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index bc5ba57d71..bfdc8f6b3c 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fef2f567df..4138fc8d6c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 0d473290e6..783b638aa0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -91,7 +91,7 @@ - + From 1701d69a602520fb75c7f39f5c3fe7ad7d9f641d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 14:33:08 +0900 Subject: [PATCH 72/90] Fix calls to IsAssignableFrom being back-to-front --- osu.Game/PerformFromMenuRunner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs index 3df9ca5305..a4179c94da 100644 --- a/osu.Game/PerformFromMenuRunner.cs +++ b/osu.Game/PerformFromMenuRunner.cs @@ -84,7 +84,7 @@ namespace osu.Game // we may already be at the target screen type. var type = current.GetType(); - if (validScreens.Any(t => type.IsAssignableFrom(t)) && !beatmap.Disabled) + if (validScreens.Any(t => t.IsAssignableFrom(type)) && !beatmap.Disabled) { finalAction(current); Cancel(); @@ -93,7 +93,7 @@ namespace osu.Game while (current != null) { - if (validScreens.Any(t => type.IsAssignableFrom(t))) + if (validScreens.Any(t => t.IsAssignableFrom(type))) { current.MakeCurrent(); break; From 39059ed82d57fa526018371089dc609c1fc0b7b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 14:36:51 +0900 Subject: [PATCH 73/90] Remove unnecessary null coalesce check --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 15e12eac40..da516798c8 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -166,19 +166,21 @@ namespace osu.Game.Screens.OnlinePlay.Match { updateWorkingBeatmap(); - if (SelectedItem.Value == null) + var selected = SelectedItem.Value; + + if (selected == null) return; // Remove any user mods that are no longer allowed. UserMods.Value = UserMods.Value - .Where(m => SelectedItem.Value.AllowedMods.Any(a => m.GetType() == a.GetType())) + .Where(m => selected.AllowedMods.Any(a => m.GetType() == a.GetType())) .ToList(); UpdateMods(); - Ruleset.Value = SelectedItem.Value.Ruleset.Value; + Ruleset.Value = selected.Ruleset.Value; - if (SelectedItem.Value?.AllowedMods.Any() != true) + if (selected.AllowedMods.Any() != true) { UserModsSection?.Hide(); userModsSelectOverlay.Hide(); @@ -187,7 +189,7 @@ namespace osu.Game.Screens.OnlinePlay.Match else { UserModsSection?.Show(); - userModsSelectOverlay.IsValidMod = m => SelectedItem.Value.AllowedMods.Any(a => a.GetType() == m.GetType()); + userModsSelectOverlay.IsValidMod = m => selected.AllowedMods.Any(a => a.GetType() == m.GetType()); } } From 484968d797852ce71cb412ade2e43e11b267cd0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 14:46:10 +0900 Subject: [PATCH 74/90] Fix weird bool check Co-authored-by: Dan Balasescu --- 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 52705302aa..4a689314db 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -172,7 +172,7 @@ namespace osu.Game.Screens.OnlinePlay.Match Ruleset.Value = selected.Ruleset.Value; - if (selected.AllowedMods.Any() != true) + if (!selected.AllowedMods.Any()) { UserModsSection?.Hide(); userModsSelectOverlay.Hide(); From ee9e6fff402b146f2c99b8a872f3fe3f5cfc703f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 15:09:41 +0900 Subject: [PATCH 75/90] Add bindable flow for expanded leaderboard state --- .../Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs | 1 + osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs | 4 ++++ osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs | 2 ++ 3 files changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index aab69d687a..026c302642 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -85,6 +85,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestScoreUpdates() { AddRepeatStep("update state", () => streamingClient.RandomlyUpdateState(), 100); + AddToggleStep("switch compact mode", expanded => leaderboard.Expanded.Value = expanded); } [Test] diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index 7b94bf19ec..20e24ed945 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using JetBrains.Annotations; +using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,6 +17,8 @@ namespace osu.Game.Screens.Play.HUD { private readonly Cached sorting = new Cached(); + public Bindable Expanded = new Bindable(); + public GameplayLeaderboard() { Width = GameplayLeaderboardScore.EXTENDED_WIDTH + GameplayLeaderboardScore.SHEAR_WIDTH; @@ -49,6 +52,7 @@ namespace osu.Game.Screens.Play.HUD { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + Expanded = { BindTarget = Expanded }, }; base.Add(drawable); diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index cb20deb272..f738f91e63 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -30,6 +30,8 @@ namespace osu.Game.Screens.Play.HUD private const float panel_shear = 0.15f; + public Bindable Expanded = new Bindable(); + private OsuSpriteText positionText, scoreText, accuracyText, comboText, usernameText; public BindableDouble TotalScore { get; } = new BindableDouble(); From 43c35c5118045f9c52a1755698073415f455d03b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 15:15:31 +0900 Subject: [PATCH 76/90] Show local user in test scene --- .../Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 026c302642..1ee848b902 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -13,6 +13,7 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Database; using osu.Game.Online; +using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Osu.Scoring; @@ -50,6 +51,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUpSteps] public override void SetUpSteps() { + AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = lookupCache.GetUserAsync(1).Result); + AddStep("create leaderboard", () => { leaderboard?.Expire(); From 691cfa5bc3bdceda779441dcd99a2437bd2beee8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 16:46:30 +0900 Subject: [PATCH 77/90] Add expanded/compact display modes for GameplayLeaderboard --- .../Screens/Play/HUD/GameplayLeaderboard.cs | 2 - .../Play/HUD/GameplayLeaderboardScore.cs | 347 +++++++++++------- 2 files changed, 210 insertions(+), 139 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index 20e24ed945..34efeab54c 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -50,8 +50,6 @@ namespace osu.Game.Screens.Play.HUD { var drawable = new GameplayLeaderboardScore(user, isTracked) { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, Expanded = { BindTarget = Expanded }, }; diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index f738f91e63..10476e5565 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -20,16 +20,31 @@ namespace osu.Game.Screens.Play.HUD { public class GameplayLeaderboardScore : CompositeDrawable, ILeaderboardScore { - public const float EXTENDED_WIDTH = 255f; + public const float EXTENDED_WIDTH = regular_width + top_player_left_width_extension; private const float regular_width = 235f; + // a bit hand-wavy, but there's a lot of hard-coded paddings in each of the grid's internals. + private const float compact_width = 77.5f; + + private const float top_player_left_width_extension = 20f; + public const float PANEL_HEIGHT = 35f; public const float SHEAR_WIDTH = PANEL_HEIGHT * panel_shear; private const float panel_shear = 0.15f; + private const float rank_text_width = 35f; + + private const float score_components_width = 85f; + + private const float avatar_size = 25f; + + private const double panel_transition_duration = 500; + + private const double text_transition_duration = 200; + public Bindable Expanded = new Bindable(); private OsuSpriteText positionText, scoreText, accuracyText, comboText, usernameText; @@ -65,8 +80,15 @@ namespace osu.Game.Screens.Play.HUD private readonly bool trackedPlayer; private Container mainFillContainer; + private Box centralFill; + private Container backgroundPaddingAdjustContainer; + + private GridContainer gridContainer; + + private Container scoreComponents; + /// /// Creates a new . /// @@ -77,7 +99,8 @@ namespace osu.Game.Screens.Play.HUD User = user; this.trackedPlayer = trackedPlayer; - Size = new Vector2(EXTENDED_WIDTH, PANEL_HEIGHT); + AutoSizeAxes = Axes.X; + Height = PANEL_HEIGHT; } [BackgroundDependencyLoader] @@ -87,147 +110,167 @@ namespace osu.Game.Screens.Play.HUD InternalChildren = new Drawable[] { - mainFillContainer = new Container + new Container { - Width = regular_width, + AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Masking = true, - CornerRadius = 5f, - Shear = new Vector2(panel_shear, 0f), - Child = new Box + Margin = new MarginPadding { Left = top_player_left_width_extension }, + Children = new Drawable[] { - Alpha = 0.5f, - RelativeSizeAxes = Axes.Both, - } - }, - new GridContainer - { - Width = regular_width, - RelativeSizeAxes = Axes.Y, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 35f), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 85f), - }, - Content = new[] - { - new Drawable[] + backgroundPaddingAdjustContainer = new Container { - positionText = new OsuSpriteText + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Padding = new MarginPadding { Right = SHEAR_WIDTH / 2 }, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = Color4.White, - Font = OsuFont.Torus.With(size: 14, weight: FontWeight.Bold), - Shadow = false, - }, - new Container - { - Padding = new MarginPadding { Horizontal = SHEAR_WIDTH / 3 }, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + mainFillContainer = new Container { - new Container + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 5f, + Shear = new Vector2(panel_shear, 0f), + Children = new Drawable[] { - Masking = true, - CornerRadius = 5f, - Shear = new Vector2(panel_shear, 0f), - RelativeSizeAxes = Axes.Both, - Children = new[] + new Box { - centralFill = new Box - { - Alpha = 0.5f, - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("3399cc"), - }, - } - }, - new FillFlowContainer - { - Padding = new MarginPadding { Left = SHEAR_WIDTH }, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(4f, 0f), - Children = new Drawable[] - { - avatarContainer = new CircularContainer - { - Masking = true, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(25f), - Children = new Drawable[] - { - new Box - { - Name = "Placeholder while avatar loads", - Alpha = 0.3f, - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray4, - } - } - }, - usernameText = new OsuSpriteText - { - RelativeSizeAxes = Axes.X, - Width = 0.6f, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Colour = Color4.White, - Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold), - Text = User?.Username, - Truncate = true, - Shadow = false, - } - } - }, - } - }, - new Container - { - Padding = new MarginPadding { Top = 2f, Right = 17.5f, Bottom = 5f }, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Colour = Color4.White, - Children = new Drawable[] - { - scoreText = new OsuSpriteText - { - Spacing = new Vector2(-1f, 0f), - Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold, fixedWidth: true), - Shadow = false, - }, - accuracyText = new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true), - Spacing = new Vector2(-1f, 0f), - Shadow = false, - }, - comboText = new OsuSpriteText - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Spacing = new Vector2(-1f, 0f), - Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true), - Shadow = false, + Alpha = 0.5f, + RelativeSizeAxes = Axes.Both, + }, }, }, } + }, + gridContainer = new GridContainer + { + RelativeSizeAxes = Axes.Y, + Width = compact_width, // will be updated by expanded state. + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, rank_text_width), + new Dimension(), + new Dimension(GridSizeMode.AutoSize, maxSize: score_components_width), + }, + Content = new[] + { + new Drawable[] + { + positionText = new OsuSpriteText + { + Padding = new MarginPadding { Right = SHEAR_WIDTH / 2 }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.White, + Font = OsuFont.Torus.With(size: 14, weight: FontWeight.Bold), + Shadow = false, + }, + new Container + { + Padding = new MarginPadding { Horizontal = SHEAR_WIDTH / 3 }, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Container + { + Masking = true, + CornerRadius = 5f, + Shear = new Vector2(panel_shear, 0f), + RelativeSizeAxes = Axes.Both, + Children = new[] + { + centralFill = new Box + { + Alpha = 0.5f, + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("3399cc"), + }, + } + }, + new FillFlowContainer + { + Padding = new MarginPadding { Left = SHEAR_WIDTH }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(4f, 0f), + Children = new Drawable[] + { + avatarContainer = new CircularContainer + { + Masking = true, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(avatar_size), + Children = new Drawable[] + { + new Box + { + Name = "Placeholder while avatar loads", + Alpha = 0.3f, + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray4, + } + } + }, + usernameText = new OsuSpriteText + { + RelativeSizeAxes = Axes.X, + Width = 0.6f, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Colour = Color4.White, + Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold), + Text = User?.Username, + Truncate = true, + Shadow = false, + } + } + }, + } + }, + scoreComponents = new Container + { + Padding = new MarginPadding { Top = 2f, Right = 17.5f, Bottom = 5f }, + AlwaysPresent = true, // required to smoothly animate autosize after hidden early. + Masking = true, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Colour = Color4.White, + Children = new Drawable[] + { + scoreText = new OsuSpriteText + { + Spacing = new Vector2(-1f, 0f), + Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold, fixedWidth: true), + Shadow = false, + }, + accuracyText = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true), + Spacing = new Vector2(-1f, 0f), + Shadow = false, + }, + comboText = new OsuSpriteText + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Spacing = new Vector2(-1f, 0f), + Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true), + Shadow = false, + }, + }, + } + } + } } } - } + }, }; LoadComponentAsync(new DrawableAvatar(User), avatarContainer.Add); @@ -243,18 +286,43 @@ namespace osu.Game.Screens.Play.HUD base.LoadComplete(); updateState(); + Expanded.BindValueChanged(changeExpandedState, true); + FinishTransforms(true); } - private const double panel_transition_duration = 500; + private void changeExpandedState(ValueChangedEvent expanded) + { + scoreComponents.ClearTransforms(); + + if (expanded.NewValue) + { + gridContainer.ResizeWidthTo(regular_width, panel_transition_duration, Easing.OutQuint); + + scoreComponents.ResizeWidthTo(score_components_width, panel_transition_duration, Easing.OutQuint); + scoreComponents.FadeIn(panel_transition_duration, Easing.OutQuint); + + usernameText.FadeIn(panel_transition_duration, Easing.OutQuint); + } + else + { + gridContainer.ResizeWidthTo(compact_width, panel_transition_duration, Easing.OutQuint); + + scoreComponents.ResizeWidthTo(0, panel_transition_duration, Easing.OutQuint); + scoreComponents.FadeOut(text_transition_duration, Easing.OutQuint); + + usernameText.FadeOut(text_transition_duration, Easing.OutQuint); + } + } private void updateState() { + bool widthExtension = false; + if (HasQuit.Value) { // we will probably want to display this in a better way once we have a design. // and also show states other than quit. - mainFillContainer.ResizeWidthTo(regular_width, panel_transition_duration, Easing.OutElastic); panelColour = Color4.Gray; textColour = Color4.White; return; @@ -262,22 +330,29 @@ namespace osu.Game.Screens.Play.HUD if (scorePosition == 1) { - mainFillContainer.ResizeWidthTo(EXTENDED_WIDTH, panel_transition_duration, Easing.OutElastic); + widthExtension = true; panelColour = Color4Extensions.FromHex("7fcc33"); textColour = Color4.White; } else if (trackedPlayer) { - mainFillContainer.ResizeWidthTo(EXTENDED_WIDTH, panel_transition_duration, Easing.OutElastic); + widthExtension = true; panelColour = Color4Extensions.FromHex("ffd966"); textColour = Color4Extensions.FromHex("2e576b"); } else { - mainFillContainer.ResizeWidthTo(regular_width, panel_transition_duration, Easing.OutElastic); panelColour = Color4Extensions.FromHex("3399cc"); textColour = Color4.White; } + + this.TransformTo(nameof(SizeContainerLeftPadding), widthExtension ? -top_player_left_width_extension : 0, panel_transition_duration, Easing.OutElastic); + } + + public float SizeContainerLeftPadding + { + get => backgroundPaddingAdjustContainer.Padding.Left; + set => backgroundPaddingAdjustContainer.Padding = new MarginPadding { Left = value }; } private Color4 panelColour @@ -289,8 +364,6 @@ namespace osu.Game.Screens.Play.HUD } } - private const double text_transition_duration = 200; - private Color4 textColour { set From 9d02f589fe70bfa9b3d1b2ed9f46400b497250a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 16:51:34 +0900 Subject: [PATCH 78/90] Compact leaderboard during gameplay --- .../Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index 04d9e0a72a..ffcf248575 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -89,6 +89,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Debug.Assert(client.Room != null); } + protected override void LoadComplete() + { + base.LoadComplete(); + + ((IBindable)leaderboard.Expanded).BindTo(IsBreakTime); + } + protected override void StartGameplay() { // block base call, but let the server know we are ready to start. From 52ebe343473007a06436cfe4c3969a129ef6c287 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 17:15:38 +0900 Subject: [PATCH 79/90] Update TestScenePause exit from fail test to actually fail --- .../Visual/Gameplay/TestScenePause.cs | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index aa56c636ab..1214a33084 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -140,7 +140,7 @@ namespace osu.Game.Tests.Visual.Gameplay confirmClockRunning(false); - pauseFromUserExitKey(); + AddStep("pause via forced pause", () => Player.Pause()); confirmPausedWithNoOverlay(); AddAssert("fail overlay still shown", () => Player.FailOverlayVisible); @@ -149,11 +149,28 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestExitFromFailedGameplay() + public void TestExitFromFailedGameplayAfterFailAnimation() { AddUntilStep("wait for fail", () => Player.HasFailed); - AddStep("exit", () => Player.Exit()); + AddUntilStep("wait for fail overlay shown", () => Player.FailOverlayVisible); + confirmClockRunning(false); + + AddStep("exit via user pause", () => Player.ExitViaPause()); + confirmExited(); + } + + [Test] + public void TestExitFromFailedGameplayDuringFailAnimation() + { + AddUntilStep("wait for fail", () => Player.HasFailed); + + // will finish the fail animation and show the fail/pause screen. + AddStep("attempt exit via pause key", () => Player.ExitViaPause()); + AddAssert("fail overlay shown", () => Player.FailOverlayVisible); + + // will actually exit. + AddStep("exit via pause key", () => Player.ExitViaPause()); confirmExited(); } From 82cc06ca57d1b8ba63739799c7db5120cfe3ae7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 17:26:54 +0900 Subject: [PATCH 80/90] Fix new logic not considering fail overlay correctly --- osu.Game/Screens/Play/Player.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8f2c1d1b92..e4fc92b5f2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -496,12 +496,13 @@ namespace osu.Game.Screens.Play return; } - bool pauseDialogShown = PauseOverlay.State.Value == Visibility.Visible; + bool pauseOrFailDialogVisible = + PauseOverlay.State.Value == Visibility.Visible || FailOverlay.State.Value == Visibility.Visible; - if (showDialogFirst && !pauseDialogShown) + if (showDialogFirst && !pauseOrFailDialogVisible) { // if the fail animation is currently in progress, accelerate it (it will show the pause dialog on completion). - if (ValidForResume && HasFailed && !FailOverlay.IsPresent) + if (ValidForResume && HasFailed) { failAnimation.FinishTransforms(true); return; From 362e4802f761980213893e30c2de0c038b1463db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 17:58:04 +0900 Subject: [PATCH 81/90] Add the ability for PerformFromMenuRunner to inspect nested screen stacks --- osu.Game/PerformFromMenuRunner.cs | 21 ++++++++++++++++--- osu.Game/Screens/IHasSubScreenStack.cs | 15 +++++++++++++ .../Screens/OnlinePlay/OnlinePlayScreen.cs | 4 +++- 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Screens/IHasSubScreenStack.cs diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs index a4179c94da..39889ea7fc 100644 --- a/osu.Game/PerformFromMenuRunner.cs +++ b/osu.Game/PerformFromMenuRunner.cs @@ -13,6 +13,7 @@ using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Notifications; +using osu.Game.Screens; using osu.Game.Screens.Menu; namespace osu.Game @@ -81,27 +82,41 @@ namespace osu.Game game?.CloseAllOverlays(false); - // we may already be at the target screen type. + findValidTarget(current); + } + + private bool findValidTarget(IScreen current) + { var type = current.GetType(); + // check if we are already at a valid target screen. if (validScreens.Any(t => t.IsAssignableFrom(type)) && !beatmap.Disabled) { finalAction(current); Cancel(); - return; + return true; } while (current != null) { + // if this has a sub stack, recursively check the screens within it. + if (current is IHasSubScreenStack currentSubScreen) + { + if (findValidTarget(currentSubScreen.SubScreenStack.CurrentScreen)) + return true; + } + if (validScreens.Any(t => t.IsAssignableFrom(type))) { current.MakeCurrent(); - break; + return true; } current = current.GetParentScreen(); type = current?.GetType(); } + + return false; } /// diff --git a/osu.Game/Screens/IHasSubScreenStack.cs b/osu.Game/Screens/IHasSubScreenStack.cs new file mode 100644 index 0000000000..c5e2015109 --- /dev/null +++ b/osu.Game/Screens/IHasSubScreenStack.cs @@ -0,0 +1,15 @@ +// 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.Screens; + +namespace osu.Game.Screens +{ + /// + /// A screen which manages a nested stack of screens within itself. + /// + public interface IHasSubScreenStack + { + ScreenStack SubScreenStack { get; } + } +} diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 71fd0d5c76..90e499c67f 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -28,7 +28,7 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay { [Cached] - public abstract class OnlinePlayScreen : OsuScreen + public abstract class OnlinePlayScreen : OsuScreen, IHasSubScreenStack { public override bool CursorVisible => (screenStack.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true; @@ -355,5 +355,7 @@ namespace osu.Game.Screens.OnlinePlay protected override double TransformDuration => 200; } } + + ScreenStack IHasSubScreenStack.SubScreenStack => screenStack; } } From 5eee46074cbe5821394539ed4812c3d1cc8af844 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 19:45:29 +0900 Subject: [PATCH 82/90] Ensure the current screen is current when a sub screen is found as the target --- osu.Game/PerformFromMenuRunner.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs index 39889ea7fc..fe75a3a607 100644 --- a/osu.Game/PerformFromMenuRunner.cs +++ b/osu.Game/PerformFromMenuRunner.cs @@ -103,7 +103,11 @@ namespace osu.Game if (current is IHasSubScreenStack currentSubScreen) { if (findValidTarget(currentSubScreen.SubScreenStack.CurrentScreen)) + { + // should be correct in theory, but currently untested/unused in existing implementations. + current.MakeCurrent(); return true; + } } if (validScreens.Any(t => t.IsAssignableFrom(type))) From 32556b1898cfb65e652a9bae2dabe827d663d27b Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sat, 20 Feb 2021 02:32:44 +0100 Subject: [PATCH 83/90] add `Exported = true` to Activity manifest --- osu.Android/OsuGameActivity.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index ad929bbac3..d087c6218d 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -17,7 +17,7 @@ using osu.Game.Database; namespace osu.Android { - [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance)] + [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream", "application/download", "application/x-zip", "application/x-zip-compressed" })] From d2ec151c67d09a8a442960ffce216ac5f04a81e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 20 Feb 2021 14:19:44 +0900 Subject: [PATCH 84/90] Add failing test for pausing when pause support is disabled --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 1214a33084..bddc7ab731 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -90,6 +90,15 @@ namespace osu.Game.Tests.Visual.Gameplay resumeAndConfirm(); } + [Test] + public void TestUserPauseWhenPauseNotAllowed() + { + AddStep("disable pause support", () => Player.Configuration.AllowPause = false); + + pauseFromUserExitKey(); + confirmExited(); + } + [Test] public void TestUserPauseDuringCooldownTooSoon() { From 38a21249213912af02c88c08037026e94ab11de3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 20 Feb 2021 13:35:25 +0900 Subject: [PATCH 85/90] Support instant exit if pausing is not allowed in the current game mode --- osu.Game/Screens/Play/Player.cs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e4fc92b5f2..1e130b7f88 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -508,10 +508,14 @@ namespace osu.Game.Screens.Play return; } - // in the case a dialog needs to be shown, attempt to pause and show it. - // this may fail (see internal checks in Pause()) at which point the exit attempt will be aborted. - Pause(); - return; + // there's a chance the pausing is not supported in the current state, at which point immediate exit should be preferred. + if (pausingSupportedByCurrentState) + { + // in the case a dialog needs to be shown, attempt to pause and show it. + // this may fail (see internal checks in Pause()) but the fail cases are temporary, so don't fall through to Exit(). + Pause(); + return; + } } this.Exit(); @@ -670,15 +674,17 @@ namespace osu.Game.Screens.Play private double? lastPauseActionTime; - private bool canPause => + /// + /// A set of conditionals which defines whether the current game state and configuration allows for + /// pausing to be attempted via . If false, the game should generally exit if a user pause + /// is attempted. + /// + private bool pausingSupportedByCurrentState => // must pass basic screen conditions (beatmap loaded, instance allows pause) LoadedBeatmapSuccessfully && Configuration.AllowPause && ValidForResume // replays cannot be paused and exit immediately && !DrawableRuleset.HasReplayLoaded.Value - // cannot pause if we are already in a fail state - && !HasFailed - // cannot pause if already paused (or in a cooldown state) unless we are in a resuming state. - && (IsResuming || (GameplayClockContainer.IsPaused.Value == false && !pauseCooldownActive)); + && !HasFailed; private bool pauseCooldownActive => lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; @@ -693,7 +699,10 @@ namespace osu.Game.Screens.Play public void Pause() { - if (!canPause) return; + if (!pausingSupportedByCurrentState) return; + + if (!IsResuming && pauseCooldownActive) + return; if (IsResuming) { From 9d229a5ec2ae17f70f68ec9d77fb99b2a6ebeaf4 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 20 Feb 2021 16:27:58 +1100 Subject: [PATCH 86/90] Add tests for clockrate adjusted difficulty calculations --- .../CatchDifficultyCalculatorTest.cs | 5 +++++ .../ManiaDifficultyCalculatorTest.cs | 5 +++++ osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs | 6 ++++++ .../TaikoDifficultyCalculatorTest.cs | 6 ++++++ 4 files changed, 22 insertions(+) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs index ee416e5a38..f4ee3f5a42 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Difficulty; +using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Difficulty; using osu.Game.Tests.Beatmaps; @@ -17,6 +18,10 @@ namespace osu.Game.Rulesets.Catch.Tests public void Test(double expected, string name) => base.Test(expected, name); + [TestCase(5.0565038923984691d, "diffcalc-test")] + public void TestClockRateAdjusted(double expected, string name) + => Test(expected, name, new CatchModDoubleTime()); + protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset(), beatmap); protected override Ruleset CreateRuleset() => new CatchRuleset(); diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs index a25551f854..09ca04be8a 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mania.Difficulty; +using osu.Game.Rulesets.Mania.Mods; using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Mania.Tests @@ -17,6 +18,10 @@ namespace osu.Game.Rulesets.Mania.Tests public void Test(double expected, string name) => base.Test(expected, name); + [TestCase(2.7646128945056723d, "diffcalc-test")] + public void TestClockRateAdjusted(double expected, string name) + => Test(expected, name, new ManiaModDoubleTime()); + protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset(), beatmap); protected override Ruleset CreateRuleset() => new ManiaRuleset(); diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index 85a41137d4..a365ea10d4 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Osu.Difficulty; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Osu.Tests @@ -19,6 +20,11 @@ namespace osu.Game.Rulesets.Osu.Tests public void Test(double expected, string name) => base.Test(expected, name); + [TestCase(8.6228371119393064d, "diffcalc-test")] + [TestCase(1.2864585434597433d, "zero-length-sliders")] + public void TestClockRateAdjusted(double expected, string name) + => Test(expected, name, new OsuModDoubleTime()); + protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset(), beatmap); protected override Ruleset CreateRuleset() => new OsuRuleset(); diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs index 71b3c23b50..eb21c02d5f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Taiko.Difficulty; +using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Taiko.Tests @@ -18,6 +19,11 @@ namespace osu.Game.Rulesets.Taiko.Tests public void Test(double expected, string name) => base.Test(expected, name); + [TestCase(3.1473940254109078d, "diffcalc-test")] + [TestCase(3.1473940254109078d, "diffcalc-test-strong")] + public void TestClockRateAdjusted(double expected, string name) + => Test(expected, name, new TaikoModDoubleTime()); + protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(new TaikoRuleset(), beatmap); protected override Ruleset CreateRuleset() => new TaikoRuleset(); From 3b7ebfa2acad63c3a426610450d681eab9947450 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 20 Feb 2021 17:17:31 +0900 Subject: [PATCH 87/90] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index bfdc8f6b3c..1513f6444d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4138fc8d6c..9c3d0c2020 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 783b638aa0..99ab88a064 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -91,7 +91,7 @@ - + From e2c5dded7f4e5b4ac1a5123e70e54728f251bb2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Feb 2021 14:14:36 +0900 Subject: [PATCH 88/90] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 1513f6444d..183ac61c90 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9c3d0c2020..37d730bf42 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 99ab88a064..ca11952cc8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -91,7 +91,7 @@ - + From 63dd55c92c9f725926f89dfa14b4ba84d65760a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Feb 2021 14:18:52 +0900 Subject: [PATCH 89/90] Add missing methods from updated audio component interface implementation --- .../TestSceneDrawableRulesetDependencies.cs | 4 ++++ .../Rulesets/UI/DrawableRulesetDependencies.cs | 4 ++++ osu.Game/Skinning/PoolableSkinnableSample.cs | 4 ++++ osu.Game/Skinning/SkinnableSound.cs | 18 ++++++++++++++++++ 4 files changed, 30 insertions(+) diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs index 4aebed0d31..f421a30283 100644 --- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs +++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs @@ -118,6 +118,10 @@ namespace osu.Game.Tests.Rulesets public BindableNumber Frequency => throw new NotImplementedException(); public BindableNumber Tempo => throw new NotImplementedException(); + public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + + public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotImplementedException(); public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotImplementedException(); diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index bbaca7c80f..b31884d246 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -134,6 +134,10 @@ namespace osu.Game.Rulesets.UI public IBindable AggregateTempo => throw new NotSupportedException(); + public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + + public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + public int PlaybackConcurrency { get => throw new NotSupportedException(); diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index abff57091b..5a0cf94d6a 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -165,6 +165,10 @@ namespace osu.Game.Skinning public BindableNumber Tempo => sampleContainer.Tempo; + public void BindAdjustments(IAggregateAudioAdjustment component) => sampleContainer.BindAdjustments(component); + + public void UnbindAdjustments(IAggregateAudioAdjustment component) => sampleContainer.UnbindAdjustments(component); + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable); public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable); diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index d3dfcb1dc0..57e20a8d31 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -176,6 +176,16 @@ namespace osu.Game.Skinning public BindableNumber Tempo => samplesContainer.Tempo; + public void BindAdjustments(IAggregateAudioAdjustment component) + { + samplesContainer.BindAdjustments(component); + } + + public void UnbindAdjustments(IAggregateAudioAdjustment component) + { + samplesContainer.UnbindAdjustments(component); + } + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => samplesContainer.AddAdjustment(type, adjustBindable); @@ -192,6 +202,14 @@ namespace osu.Game.Skinning public bool IsPlayed => samplesContainer.Any(s => s.Played); + public IBindable AggregateVolume => samplesContainer.AggregateVolume; + + public IBindable AggregateBalance => samplesContainer.AggregateBalance; + + public IBindable AggregateFrequency => samplesContainer.AggregateFrequency; + + public IBindable AggregateTempo => samplesContainer.AggregateTempo; + #endregion } } From fde026d44342534f7d06ddc0873e18f3f24e7070 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Feb 2021 14:54:48 +0900 Subject: [PATCH 90/90] Remove redundant interface specification --- osu.Game/Skinning/PoolableSkinnableSample.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index 5a0cf94d6a..9103a6a960 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -17,7 +17,7 @@ namespace osu.Game.Skinning /// /// A sample corresponding to an that supports being pooled and responding to skin changes. /// - public class PoolableSkinnableSample : SkinReloadableDrawable, IAggregateAudioAdjustment, IAdjustableAudioComponent + public class PoolableSkinnableSample : SkinReloadableDrawable, IAdjustableAudioComponent { /// /// The currently-loaded .