From bebff61a9dde4ea61b27094ae9c4c4c214dababa Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Feb 2021 21:13:00 +0300 Subject: [PATCH 01/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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/84] 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 668cc144f68c339ab03a4601c4d36faae86b051e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Feb 2021 17:39:01 +0900 Subject: [PATCH 49/84] 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 58d8f0733cfed3f432879263e827fc4cc1128162 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 17:45:58 +0900 Subject: [PATCH 50/84] 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 51/84] 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 a01896a652ee042cc66149a99ccf5b76dddef535 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 18 Feb 2021 13:04:22 +0300 Subject: [PATCH 52/84] 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 53/84] 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 54/84] 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 55/84] 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 56/84] 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 57/84] 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 58/84] 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 59/84] 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 60/84] 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 61/84] 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 62/84] 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 63/84] 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 64/84] 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 65/84] 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 66/84] 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 67/84] 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 68/84] 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 69/84] 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 70/84] 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 71/84] 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 72/84] 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 73/84] 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 74/84] 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 75/84] 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 76/84] 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 77/84] 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 78/84] 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 79/84] 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 80/84] 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 81/84] 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 82/84] 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 83/84] 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 84/84] 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 .