From 772471a6d826a730ca17176f2c542b7e4d2ba44a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 09:34:39 +0300 Subject: [PATCH 01/45] Add failing test case --- .../Gameplay/TestScenePauseWhenInactive.cs | 63 ++++++++++++++++--- osu.Game/Screens/Play/Player.cs | 10 +-- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs index e43e5ba3ce..15412fea00 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs @@ -1,28 +1,28 @@ // 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.Allocation; using osu.Framework.Bindables; using osu.Framework.Platform; using osu.Framework.Testing; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Storyboards; +using osu.Game.Tests.Beatmaps; +using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { [HeadlessTest] // we alter unsafe properties on the game host to test inactive window state. public class TestScenePauseWhenInactive : OsuPlayerTestScene { - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) - { - var beatmap = (Beatmap)base.CreateBeatmap(ruleset); - - beatmap.HitObjects.RemoveAll(h => h.StartTime < 30000); - - return beatmap; - } - [Resolved] private GameHost host { get; set; } @@ -33,10 +33,53 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("resume player", () => Player.GameplayClockContainer.Start()); AddAssert("ensure not paused", () => !Player.GameplayClockContainer.IsPaused.Value); + + AddStep("progress time to gameplay", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.GameplayStartTime)); + AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value); + } + + /// + /// Tests that if a pause from focus lose is performed while in pause cooldown, + /// the player will still pause after the cooldown is finished. + /// + [Test] + public void TestPauseWhileInCooldown() + { + AddStep("resume player", () => Player.GameplayClockContainer.Start()); + AddStep("skip to gameplay", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.GameplayStartTime)); + + AddStep("set inactive", () => ((Bindable)host.IsActive).Value = false); + AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value); + + AddStep("set active", () => ((Bindable)host.IsActive).Value = true); + + AddStep("resume player", () => Player.Resume()); + AddStep("click resume overlay", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("pause cooldown active", () => Player.PauseCooldownActive); + AddStep("set inactive again", () => ((Bindable)host.IsActive).Value = false); AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value); - AddAssert("time of pause is after gameplay start time", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= Player.DrawableRuleset.GameplayStartTime); } protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + return new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 30000 }, + new HitCircle { StartTime = 35000 }, + }, + }; + } + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + => new TestWorkingBeatmap(beatmap, storyboard, Audio); } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 74059da21a..a7acda926b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -667,6 +667,9 @@ namespace osu.Game.Screens.Play private double? lastPauseActionTime; + public bool PauseCooldownActive => + lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; + private bool canPause => // must pass basic screen conditions (beatmap loaded, instance allows pause) LoadedBeatmapSuccessfully && Configuration.AllowPause && ValidForResume @@ -675,10 +678,7 @@ namespace osu.Game.Screens.Play // 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)); - - private bool pauseCooldownActive => - lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; + && (IsResuming || (GameplayClockContainer.IsPaused.Value == false && !PauseCooldownActive)); private bool canResume => // cannot resume from a non-paused state @@ -812,7 +812,7 @@ namespace osu.Game.Screens.Play // ValidForResume is false when restarting if (ValidForResume) { - if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value) + if (PauseCooldownActive && !GameplayClockContainer.IsPaused.Value) // still want to block if we are within the cooldown period and not already paused. return true; } From 4436585aa4dea5bec025e5bf816ed23008b4c87e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 09:35:29 +0300 Subject: [PATCH 02/45] Keep attempting to pause gameplay while window not active --- osu.Game/Screens/Play/Player.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a7acda926b..72d9a60c91 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -427,11 +427,16 @@ namespace osu.Game.Screens.Play private void updatePauseOnFocusLostState() { - if (!PauseOnFocusLost || breakTracker.IsBreakTime.Value) + if (!PauseOnFocusLost || DrawableRuleset.HasReplayLoaded.Value || breakTracker.IsBreakTime.Value) return; if (gameActive.Value == false) - Pause(); + { + if (canPause) + Pause(); + else + Scheduler.AddDelayed(updatePauseOnFocusLostState, 200); + } } private IBeatmap loadPlayableBeatmap() From ddd1dcff88428460cfcde74c963196a0518924fe Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 11:33:26 +0300 Subject: [PATCH 03/45] Attempt pausing every single frame --- 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 72d9a60c91..fa545859d4 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -435,7 +435,7 @@ namespace osu.Game.Screens.Play if (canPause) Pause(); else - Scheduler.AddDelayed(updatePauseOnFocusLostState, 200); + Scheduler.AddOnce(updatePauseOnFocusLostState); } } From 0771154dd2831f4d7de9d28913965f49df8909ce Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 11:42:30 +0300 Subject: [PATCH 04/45] Make `PauseCooldownActive` protected and expose on test class --- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Tests/Visual/TestPlayer.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index fa545859d4..8c816e8030 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -672,7 +672,7 @@ namespace osu.Game.Screens.Play private double? lastPauseActionTime; - public bool PauseCooldownActive => + protected bool PauseCooldownActive => lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; private bool canPause => diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index f47391ce6a..0addc9de75 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -34,6 +34,8 @@ namespace osu.Game.Tests.Visual public new HealthProcessor HealthProcessor => base.HealthProcessor; + public new bool PauseCooldownActive => base.PauseCooldownActive; + public readonly List Results = new List(); public TestPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) From fe5e45ea8180931f1c4c8d02a162e50b0000f186 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 11:43:33 +0300 Subject: [PATCH 05/45] Move gameplay cursor outside instead and fix potential failure --- .../Gameplay/TestScenePauseWhenInactive.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs index 15412fea00..fa596c4823 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs @@ -2,21 +2,18 @@ // 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.Allocation; using osu.Framework.Bindables; using osu.Framework.Platform; using osu.Framework.Testing; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.UI; using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps; -using osuTK.Input; +using osuTK; namespace osu.Game.Tests.Visual.Gameplay { @@ -45,6 +42,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestPauseWhileInCooldown() { + AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); + AddStep("resume player", () => Player.GameplayClockContainer.Start()); AddStep("skip to gameplay", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.GameplayStartTime)); @@ -54,14 +53,15 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set active", () => ((Bindable)host.IsActive).Value = true); AddStep("resume player", () => Player.Resume()); - AddStep("click resume overlay", () => - { - InputManager.MoveMouseTo(this.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); - AddAssert("pause cooldown active", () => Player.PauseCooldownActive); - AddStep("set inactive again", () => ((Bindable)host.IsActive).Value = false); + bool pauseCooldownActive = false; + + AddStep("set inactive again", () => + { + pauseCooldownActive = Player.PauseCooldownActive; + ((Bindable)host.IsActive).Value = false; + }); + AddAssert("pause cooldown active", () => pauseCooldownActive); AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value); } From f6c279ab00d6af3231e09332e4d35b4c2ce7e106 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 11:45:45 +0300 Subject: [PATCH 06/45] Add assert ensuring player resumed properly --- osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs index fa596c4823..49c1163c6c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs @@ -53,6 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set active", () => ((Bindable)host.IsActive).Value = true); AddStep("resume player", () => Player.Resume()); + AddAssert("unpaused", () => !Player.GameplayClockContainer.IsPaused.Value); bool pauseCooldownActive = false; From 8d463987dd6c7260f25c90a31804d180c147b5df Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Feb 2021 13:21:50 +0900 Subject: [PATCH 07/45] Fix being able to select incompatible freemods --- .../Screens/OnlinePlay/OnlinePlaySongSelect.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index f0c77b79bf..3f30ef1176 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -75,9 +75,18 @@ namespace osu.Game.Screens.OnlinePlay Mods.Value = selectedItem?.Value?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); FreeMods.Value = selectedItem?.Value?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + Mods.BindValueChanged(onModsChanged); Ruleset.BindValueChanged(onRulesetChanged); } + private void onModsChanged(ValueChangedEvent> mods) + { + FreeMods.Value = FreeMods.Value.Where(checkCompatibleFreeMod).ToList(); + + // Reset the validity delegate to update the overlay's display. + freeModSelectOverlay.IsValidMod = IsValidFreeMod; + } + private void onRulesetChanged(ValueChangedEvent ruleset) { FreeMods.Value = Array.Empty(); @@ -155,6 +164,10 @@ namespace osu.Game.Screens.OnlinePlay /// /// The to check. /// Whether is a selectable free-mod. - protected virtual bool IsValidFreeMod(Mod mod) => IsValidMod(mod); + protected virtual bool IsValidFreeMod(Mod mod) => IsValidMod(mod) && checkCompatibleFreeMod(mod); + + private bool checkCompatibleFreeMod(Mod mod) + => Mods.Value.All(m => m.Acronym != mod.Acronym) // Mod must not be contained in the required mods. + && ModUtils.CheckCompatibleSet(Mods.Value.Append(mod).ToArray()); // Mod must be compatible with all the required mods. } } From ca92ad715a9a11bd772b2f53d04f3cb5a8e13431 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Feb 2021 13:32:54 +0900 Subject: [PATCH 08/45] Add test --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 2 +- .../TestSceneMultiplayerMatchSongSelect.cs | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index df0a41455f..4b0939db16 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Skinning.Default; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModTraceable : ModWithVisibilityAdjustment + public class OsuModTraceable : ModWithVisibilityAdjustment { public override string Name => "Traceable"; public override string Acronym => "TC"; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 95c333e9f4..faa5d9e6fc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -7,17 +7,23 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Platform; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Taiko.Mods; +using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.Select; @@ -137,8 +143,30 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("mods not changed", () => SelectedMods.Value.Single() is TaikoModDoubleTime); } + [TestCase(typeof(OsuModHidden), typeof(OsuModHidden))] // Same mod. + [TestCase(typeof(OsuModHidden), typeof(OsuModTraceable))] // Incompatible. + public void TestAllowedModDeselectedWhenRequired(Type allowedMod, Type requiredMod) + { + AddStep($"select {allowedMod.ReadableName()} as allowed", () => songSelect.FreeMods.Value = new[] { (Mod)Activator.CreateInstance(allowedMod) }); + AddStep($"select {requiredMod.ReadableName()} as required", () => songSelect.Mods.Value = new[] { (Mod)Activator.CreateInstance(requiredMod) }); + + AddAssert("freemods empty", () => songSelect.FreeMods.Value.Count == 0); + assertHasFreeModButton(allowedMod, false); + assertHasFreeModButton(requiredMod, false); + } + + private void assertHasFreeModButton(Type type, bool hasButton = true) + { + AddAssert($"{type.ReadableName()} {(hasButton ? "displayed" : "not displayed")} in freemod overlay", + () => songSelect.ChildrenOfType().Single().ChildrenOfType().All(b => b.Mod.GetType() != type)); + } + private class TestMultiplayerMatchSongSelect : MultiplayerMatchSongSelect { + public new Bindable> Mods => base.Mods; + + public new Bindable> FreeMods => base.FreeMods; + public new BeatmapCarousel Carousel => base.Carousel; } } From 541237ef16c62d259e4e84fa13943c32a7a54be0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Feb 2021 14:48:04 +0900 Subject: [PATCH 09/45] Use a shorter test beatmap for tests which need to run to completion --- .../Beatmaps/IO/ImportBeatmapTest.cs | 15 +++++++++++ ...241526 Soleily - Renatus_virtual_quick.osz | Bin 0 -> 89215 bytes osu.Game.Tests/Resources/TestResources.cs | 25 +++++++++++++++++- .../Navigation/TestSceneScreenNavigation.cs | 2 +- 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus_virtual_quick.osz diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index c32e359de6..0c35e9471d 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -852,6 +852,21 @@ namespace osu.Game.Tests.Beatmaps.IO } } + public static async Task LoadQuickOszIntoOsu(OsuGameBase osu) + { + var temp = TestResources.GetQuickTestBeatmapForImport(); + + var manager = osu.Dependencies.Get(); + + var importedSet = await manager.Import(new ImportTask(temp)); + + ensureLoaded(osu); + + waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); + + return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID); + } + public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) { var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack); diff --git a/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus_virtual_quick.osz b/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus_virtual_quick.osz new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fb03282e189ffc23d77a0db40b3fa2488850 GIT binary patch literal 89215 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 + /// Retrieve a path to a copy of a shortened (~10 second) beatmap archive with a virtual track. + /// + /// + /// This is intended for use in tests which need to run to completion as soon as possible and don't need to test a full length beatmap. + /// A path to a copy of a beatmap archive (osz). Should be deleted after use. + public static string GetQuickTestBeatmapForImport() + { + var tempPath = Path.GetTempFileName() + ".osz"; + + using (var stream = GetTestBeatmapStream(true, true)) + using (var newFile = File.Create(tempPath)) + stream.CopyTo(newFile); + + Assert.IsTrue(File.Exists(tempPath)); + return tempPath; + } + + /// + /// Retrieve a path to a copy of a full-fledged beatmap archive. + /// + /// Whether the audio track should be virtual. + /// A path to a copy of a beatmap archive (osz). Should be deleted after use. public static string GetTestBeatmapForImport(bool virtualTrack = false) { var tempPath = Path.GetTempFileName() + ".osz"; diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index d8380b2dd3..e5959a3edf 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => new TestSongSelect()); - AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait()); + AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); From cdbf8de29db80994e903c1e92837d6208e78312f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Feb 2021 14:53:32 +0900 Subject: [PATCH 10/45] Update other tests which can benefit from using a shorter beatmap --- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 2 +- osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs | 2 +- .../Visual/Collections/TestSceneManageCollectionsDialog.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs | 2 +- osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs | 2 +- osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs | 2 +- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 3ffb512b7f..8c30802ce3 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Online { beatmaps.AllowImport = new TaskCompletionSource(); - testBeatmapFile = TestResources.GetTestBeatmapForImport(); + testBeatmapFile = TestResources.GetQuickTestBeatmapForImport(); testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile); testBeatmapSet = testBeatmapInfo.BeatmapSet; diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 7ade7725d9..ba4d12b19f 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Background Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); - manager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + manager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); Beatmap.SetDefault(); } diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index fef1605f0c..1655adf811 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Collections Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); - beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); base.Content.AddRange(new Drawable[] { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 3b3b1bee86..b44e5b1e5b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); - beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); Add(beatmapTracker = new OnlinePlayBeatmapAvailablilityTracker { diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs index 63bda08c88..0c199bfb62 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online ensureSoleilyRemoved(); createButtonWithBeatmap(createSoleily()); AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded); - AddStep("import soleily", () => beatmaps.Import(TestResources.GetTestBeatmapForImport())); + AddStep("import soleily", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport())); AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineBeatmapSetID == 241526)); createButtonWithBeatmap(createSoleily()); AddAssert("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 5d0fb248df..c13bdf0955 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.SongSelect Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); - beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); base.Content.AddRange(new Drawable[] { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 81862448a8..d615f1f440 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory)); - beatmap = beatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).Result.Beatmaps[0]; + beatmap = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Beatmaps[0]; for (int i = 0; i < 50; i++) { From adf2dc36c9112200699ac8680b81a32bda9b937f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Feb 2021 15:43:58 +0900 Subject: [PATCH 11/45] Fix PlaylistResults tests performing delays in real-time when headless --- .../TestScenePlaylistsResultsScreen.cs | 87 +++++++------------ 1 file changed, 32 insertions(+), 55 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index cdcded8f61..e34da1ef0c 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ }; - bindHandler(3000, userScore); + bindHandler(true, userScore); }); createResults(() => userScore); @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestShowNullUserScoreWithDelay() { - AddStep("bind delayed handler", () => bindHandler(3000)); + AddStep("bind delayed handler", () => bindHandler(true)); createResults(); waitForDisplay(); @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Playlists createResults(); waitForDisplay(); - AddStep("bind delayed handler", () => bindHandler(3000)); + AddStep("bind delayed handler", () => bindHandler(true)); for (int i = 0; i < 2; i++) { @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Playlists createResults(() => userScore); waitForDisplay(); - AddStep("bind delayed handler", () => bindHandler(3000)); + AddStep("bind delayed handler", () => bindHandler(true)); for (int i = 0; i < 2; i++) { @@ -169,70 +169,47 @@ namespace osu.Game.Tests.Visual.Playlists AddWaitStep("wait for display", 5); } - private void bindHandler(double delay = 0, ScoreInfo userScore = null, bool failRequests = false) => ((DummyAPIAccess)API).HandleRequest = request => + private void bindHandler(bool delayed = false, ScoreInfo userScore = null, bool failRequests = false) => ((DummyAPIAccess)API).HandleRequest = request => { requestComplete = false; - if (failRequests) - { - triggerFail(request, delay); - return; - } + double delay = delayed ? 3000 : 0; - switch (request) + Scheduler.AddDelayed(() => { - case ShowPlaylistUserScoreRequest s: - if (userScore == null) - triggerFail(s, delay); - else - triggerSuccess(s, createUserResponse(userScore), delay); - break; + if (failRequests) + { + triggerFail(request); + return; + } - case IndexPlaylistScoresRequest i: - triggerSuccess(i, createIndexResponse(i), delay); - break; - } + switch (request) + { + case ShowPlaylistUserScoreRequest s: + if (userScore == null) + triggerFail(s); + else + triggerSuccess(s, createUserResponse(userScore)); + break; + + case IndexPlaylistScoresRequest i: + triggerSuccess(i, createIndexResponse(i)); + break; + } + }, delay); }; - private void triggerSuccess(APIRequest req, T result, double delay) + private void triggerSuccess(APIRequest req, T result) where T : class { - if (delay == 0) - success(); - else - { - Task.Run(async () => - { - await Task.Delay(TimeSpan.FromMilliseconds(delay)); - Schedule(success); - }); - } - - void success() - { - requestComplete = true; - req.TriggerSuccess(result); - } + requestComplete = true; + req.TriggerSuccess(result); } - private void triggerFail(APIRequest req, double delay) + private void triggerFail(APIRequest req) { - if (delay == 0) - fail(); - else - { - Task.Run(async () => - { - await Task.Delay(TimeSpan.FromMilliseconds(delay)); - Schedule(fail); - }); - } - - void fail() - { - requestComplete = true; - req.TriggerFailure(new WebException("Failed.")); - } + requestComplete = true; + req.TriggerFailure(new WebException("Failed.")); } private MultiplayerScore createUserResponse([NotNull] ScoreInfo userScore) From ccb83ef3a374f173b18473665c06c5002d68aecb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Feb 2021 15:47:47 +0900 Subject: [PATCH 12/45] Fix checkbox not being updated --- osu.Game/Overlays/Mods/ModSection.cs | 20 +++++++++---------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 8 ++++++++ .../OnlinePlay/FreeModSelectOverlay.cs | 14 ++++++++++++- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index c3e56abd05..aa8a5efd39 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -23,13 +23,15 @@ namespace osu.Game.Overlays.Mods public FillFlowContainer ButtonsContainer { get; } + protected IReadOnlyList Buttons { get; private set; } = Array.Empty(); + public Action Action; public Key[] ToggleKeys; public readonly ModType ModType; - public IEnumerable SelectedMods => buttons.Select(b => b.SelectedMod).Where(m => m != null); + public IEnumerable SelectedMods => Buttons.Select(b => b.SelectedMod).Where(m => m != null); private CancellationTokenSource modsLoadCts; @@ -77,7 +79,7 @@ namespace osu.Game.Overlays.Mods ButtonsContainer.ChildrenEnumerable = c; }, (modsLoadCts = new CancellationTokenSource()).Token); - buttons = modContainers.OfType().ToArray(); + Buttons = modContainers.OfType().ToArray(); header.FadeIn(200); this.FadeIn(200); @@ -88,8 +90,6 @@ namespace osu.Game.Overlays.Mods { } - private ModButton[] buttons = Array.Empty(); - protected override bool OnKeyDown(KeyDownEvent e) { if (e.ControlPressed) return false; @@ -97,8 +97,8 @@ namespace osu.Game.Overlays.Mods if (ToggleKeys != null) { var index = Array.IndexOf(ToggleKeys, e.Key); - if (index > -1 && index < buttons.Length) - buttons[index].SelectNext(e.ShiftPressed ? -1 : 1); + if (index > -1 && index < Buttons.Count) + Buttons[index].SelectNext(e.ShiftPressed ? -1 : 1); } return base.OnKeyDown(e); @@ -141,7 +141,7 @@ namespace osu.Game.Overlays.Mods { pendingSelectionOperations.Clear(); - foreach (var button in buttons.Where(b => !b.Selected)) + foreach (var button in Buttons.Where(b => !b.Selected)) pendingSelectionOperations.Enqueue(() => button.SelectAt(0)); } @@ -151,7 +151,7 @@ namespace osu.Game.Overlays.Mods public void DeselectAll() { pendingSelectionOperations.Clear(); - DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); + DeselectTypes(Buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); } /// @@ -161,7 +161,7 @@ namespace osu.Game.Overlays.Mods /// Whether the deselection should happen immediately. Should only be used when required to ensure correct selection flow. public void DeselectTypes(IEnumerable modTypes, bool immediate = false) { - foreach (var button in buttons) + foreach (var button in Buttons) { if (button.SelectedMod == null) continue; @@ -184,7 +184,7 @@ namespace osu.Game.Overlays.Mods /// The new list of selected mods to select. public void UpdateSelectedButtons(IReadOnlyList newSelectedMods) { - foreach (var button in buttons) + foreach (var button in Buttons) updateButtonSelection(button, newSelectedMods); } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index eef91deb4c..26b8632d7f 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -456,6 +456,7 @@ namespace osu.Game.Overlays.Mods } updateSelectedButtons(); + OnAvailableModsChanged(); } /// @@ -533,6 +534,13 @@ namespace osu.Game.Overlays.Mods private void playSelectedSound() => sampleOn?.Play(); private void playDeselectedSound() => sampleOff?.Play(); + /// + /// Invoked after has changed. + /// + protected virtual void OnAvailableModsChanged() + { + } + /// /// Invoked when a new has been selected. /// diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index ab7be13479..66262e7dc4 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -75,6 +75,14 @@ namespace osu.Game.Screens.OnlinePlay section.DeselectAll(); } + protected override void OnAvailableModsChanged() + { + base.OnAvailableModsChanged(); + + foreach (var section in ModSectionsContainer.Children) + ((FreeModSection)section).UpdateCheckboxState(); + } + protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); private class FreeModSection : ModSection @@ -108,10 +116,14 @@ namespace osu.Game.Screens.OnlinePlay protected override void ModButtonStateChanged(Mod mod) { base.ModButtonStateChanged(mod); + UpdateCheckboxState(); + } + public void UpdateCheckboxState() + { if (!SelectionAnimationRunning) { - var validButtons = ButtonsContainer.OfType().Where(b => b.Mod.HasImplementation); + var validButtons = Buttons.Where(b => b.Mod.HasImplementation); checkbox.Current.Value = validButtons.All(b => b.Selected); } } From d985b8ab2aafd86c9f4d24fdcd39634de8a0b10c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Feb 2021 17:14:39 +0900 Subject: [PATCH 13/45] Increase beatmapset download timeout --- osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs b/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs index 707c59436d..e8871bef05 100644 --- a/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs +++ b/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.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 osu.Framework.IO.Network; using osu.Game.Beatmaps; namespace osu.Game.Online.API.Requests @@ -15,6 +16,13 @@ namespace osu.Game.Online.API.Requests this.noVideo = noVideo; } + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + req.Timeout = 60000; + return req; + } + protected override string Target => $@"beatmapsets/{Model.OnlineBeatmapSetID}/download{(noVideo ? "?noVideo=1" : "")}"; } } From 5e9040c29108cf423ddcf0d6ea418f4aa6690b46 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 22 Feb 2021 16:26:35 +0300 Subject: [PATCH 14/45] Use "pausing supported" conditional instead --- 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 5a86ac646a..0046eea91c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -427,7 +427,7 @@ namespace osu.Game.Screens.Play private void updatePauseOnFocusLostState() { - if (!PauseOnFocusLost || DrawableRuleset.HasReplayLoaded.Value || breakTracker.IsBreakTime.Value) + if (!PauseOnFocusLost || pausingSupportedByCurrentState || breakTracker.IsBreakTime.Value) return; if (gameActive.Value == false) From 5493c55da7287bea7f77651ea0474587cc426626 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 22 Feb 2021 16:59:35 +0300 Subject: [PATCH 15/45] Fix silly mistake --- 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 0046eea91c..2ded1752da 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -427,7 +427,7 @@ namespace osu.Game.Screens.Play private void updatePauseOnFocusLostState() { - if (!PauseOnFocusLost || pausingSupportedByCurrentState || breakTracker.IsBreakTime.Value) + if (!PauseOnFocusLost || !pausingSupportedByCurrentState || breakTracker.IsBreakTime.Value) return; if (gameActive.Value == false) From f62120c66b6cbb852f96d2ede5e60b933214f08b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Feb 2021 22:45:55 +0100 Subject: [PATCH 16/45] Remove unused using directive --- .../Visual/Playlists/TestScenePlaylistsResultsScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index e34da1ef0c..be8032cde8 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net; -using System.Threading.Tasks; using JetBrains.Annotations; using Newtonsoft.Json.Linq; using NUnit.Framework; From 6a5c6febc56567cd5acb43fdb274f9918c8ef230 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Feb 2021 13:23:32 +0900 Subject: [PATCH 17/45] Add inline comment explaining the retry loop --- osu.Game/Screens/Play/Player.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 2ded1752da..e81efdac78 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -434,6 +434,8 @@ namespace osu.Game.Screens.Play { bool paused = Pause(); + // if the initial pause could not be satisfied, the pause cooldown may be active. + // reschedule the pause attempt until it can be achieved. if (!paused) Scheduler.AddOnce(updatePauseOnFocusLostState); } From 996c0897d1faa058fa12ea07b13799e01b67aab9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Feb 2021 13:40:21 +0900 Subject: [PATCH 18/45] Seek via GameplayClockContainer for better reliability --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index e5959a3edf..5d070b424a 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Navigation 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)); + AddStep("seek to end", () => player.ChildrenOfType().First().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); From 672fd3f9d2935099f24ffa5f2a879295c6970f77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Feb 2021 14:24:24 +0900 Subject: [PATCH 19/45] When disable mouse buttons during gameplay is selected, disable more globally Until now the disable setting would only apply to left/right buttons, and only in gameplay. This change will cause any global actions bound to mouse buttons to also not work during gameplay. Closes #11879. --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 07de2bf601..963c3427d0 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -109,9 +109,9 @@ namespace osu.Game.Rulesets.UI { switch (e) { - case MouseDownEvent mouseDown when mouseDown.Button == MouseButton.Left || mouseDown.Button == MouseButton.Right: + case MouseDownEvent _: if (mouseDisabled.Value) - return false; + return true; // importantly, block upwards propagation so global bindings also don't fire. break; From ec4b770cbac2260bfb731c64d8ba4f7990c9c02a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Feb 2021 14:56:03 +0900 Subject: [PATCH 20/45] Remove unused using statement --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 963c3427d0..d6f002ea2c 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -16,7 +16,6 @@ using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; using osu.Game.Screens.Play; -using osuTK.Input; using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.UI From 664d243003b3fab7d4d6b008302b95fdf7ac12c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Feb 2021 15:22:46 +0900 Subject: [PATCH 21/45] Disable multiplayer/spectator on iOS until it can be supported again --- osu.Game/Online/API/APIAccess.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 8ffa0221c8..ce01378b17 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -10,6 +10,7 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json.Linq; +using osu.Framework; using osu.Framework.Bindables; using osu.Framework.Extensions.ExceptionExtensions; using osu.Framework.Extensions.ObjectExtensions; @@ -246,7 +247,14 @@ namespace osu.Game.Online.API this.password = password; } - public IHubClientConnector GetHubConnector(string clientName, string endpoint) => new HubClientConnector(clientName, endpoint, this, versionHash); + public IHubClientConnector GetHubConnector(string clientName, string endpoint) + { + // disabled until the underlying runtime issue is resolved, see https://github.com/mono/mono/issues/20805. + if (RuntimeInfo.OS == RuntimeInfo.Platform.iOS) + return null; + + return new HubClientConnector(clientName, endpoint, this, versionHash); + } public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password) { From c514233141756f9700bb3b51881480ddba98f8ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Feb 2021 15:57:41 +0900 Subject: [PATCH 22/45] Fix importing collections twice from stable causing a hard crash Somehow a bindable equality check failure got through review. Not sure if there's some way to protect against this going forward, but we may want to. --- osu.Game/Collections/CollectionManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index a65d9a415d..fb9c230c7a 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -138,10 +138,10 @@ namespace osu.Game.Collections PostNotification?.Invoke(notification); - var collection = readCollections(stream, notification); - await importCollections(collection); + var collections = readCollections(stream, notification); + await importCollections(collections); - notification.CompletionText = $"Imported {collection.Count} collections"; + notification.CompletionText = $"Imported {collections.Count} collections"; notification.State = ProgressNotificationState.Completed; } @@ -155,7 +155,7 @@ namespace osu.Game.Collections { foreach (var newCol in newCollections) { - var existing = Collections.FirstOrDefault(c => c.Name == newCol.Name); + var existing = Collections.FirstOrDefault(c => c.Name.Value == newCol.Name.Value); if (existing == null) Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } }); From f45cedeb8524206a37f7dd7a59cadf5ed20fde21 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Tue, 23 Feb 2021 15:38:09 +0000 Subject: [PATCH 23/45] Adjust initial and final rate ranges and prevent them from overlapping --- osu.Game/Rulesets/Mods/ModWindDown.cs | 13 +++++++++++-- osu.Game/Rulesets/Mods/ModWindUp.cs | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index 679b50057b..c47ec5fbde 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Initial rate", "The starting speed of the track")] public override BindableNumber InitialRate { get; } = new BindableDouble { - MinValue = 1, + MinValue = 0.5, MaxValue = 2, Default = 1, Value = 1, @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mods public override BindableNumber FinalRate { get; } = new BindableDouble { MinValue = 0.5, - MaxValue = 0.99, + MaxValue = 2, Default = 0.75, Value = 0.75, Precision = 0.01, @@ -45,5 +45,14 @@ namespace osu.Game.Rulesets.Mods }; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray(); + + public ModWindDown() + { + InitialRate.BindValueChanged(val => + InitialRate.Value = Math.Max(val.NewValue, FinalRate.Value + 0.01)); + + FinalRate.BindValueChanged(val => + FinalRate.Value = Math.Min(val.NewValue, InitialRate.Value - 0.01)); + } } } diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index b733bf423e..5a0fab5e67 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mods public override BindableNumber InitialRate { get; } = new BindableDouble { MinValue = 0.5, - MaxValue = 1, + MaxValue = 2, Default = 1, Value = 1, Precision = 0.01, @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Final rate", "The speed increase to ramp towards")] public override BindableNumber FinalRate { get; } = new BindableDouble { - MinValue = 1.01, + MinValue = 0.5, MaxValue = 2, Default = 1.5, Value = 1.5, @@ -45,5 +45,14 @@ namespace osu.Game.Rulesets.Mods }; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray(); + + public ModWindUp() + { + InitialRate.BindValueChanged(val => + InitialRate.Value = Math.Min(val.NewValue, FinalRate.Value - 0.01)); + + FinalRate.BindValueChanged(val => + FinalRate.Value = Math.Max(val.NewValue, InitialRate.Value + 0.01)); + } } } From a6e840634b255ea86e21cfa8140df3794965ebe6 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Tue, 23 Feb 2021 15:52:53 +0000 Subject: [PATCH 24/45] Adjust scrubbing behaviour to allow dragging through rate values --- osu.Game/Rulesets/Mods/ModWindDown.cs | 8 ++++---- osu.Game/Rulesets/Mods/ModWindUp.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index c47ec5fbde..f9e6854dd4 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Initial rate", "The starting speed of the track")] public override BindableNumber InitialRate { get; } = new BindableDouble { - MinValue = 0.5, + MinValue = 0.51, MaxValue = 2, Default = 1, Value = 1, @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mods public override BindableNumber FinalRate { get; } = new BindableDouble { MinValue = 0.5, - MaxValue = 2, + MaxValue = 1.99, Default = 0.75, Value = 0.75, Precision = 0.01, @@ -49,10 +49,10 @@ namespace osu.Game.Rulesets.Mods public ModWindDown() { InitialRate.BindValueChanged(val => - InitialRate.Value = Math.Max(val.NewValue, FinalRate.Value + 0.01)); + FinalRate.Value = Math.Min(FinalRate.Value, val.NewValue - 0.01)); FinalRate.BindValueChanged(val => - FinalRate.Value = Math.Min(val.NewValue, InitialRate.Value - 0.01)); + InitialRate.Value = Math.Max(InitialRate.Value, val.NewValue + 0.01)); } } } diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index 5a0fab5e67..0d57bbb52d 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mods public override BindableNumber InitialRate { get; } = new BindableDouble { MinValue = 0.5, - MaxValue = 2, + MaxValue = 1.99, Default = 1, Value = 1, Precision = 0.01, @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Final rate", "The speed increase to ramp towards")] public override BindableNumber FinalRate { get; } = new BindableDouble { - MinValue = 0.5, + MinValue = 0.51, MaxValue = 2, Default = 1.5, Value = 1.5, @@ -49,10 +49,10 @@ namespace osu.Game.Rulesets.Mods public ModWindUp() { InitialRate.BindValueChanged(val => - InitialRate.Value = Math.Min(val.NewValue, FinalRate.Value - 0.01)); + FinalRate.Value = Math.Max(FinalRate.Value, val.NewValue + 0.01)); FinalRate.BindValueChanged(val => - FinalRate.Value = Math.Max(val.NewValue, InitialRate.Value + 0.01)); + InitialRate.Value = Math.Min(InitialRate.Value, val.NewValue - 0.01)); } } } From 7394c62cc8ee4c30ce12543fa7c6609d7ee9dc58 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Tue, 23 Feb 2021 18:10:03 +0000 Subject: [PATCH 25/45] Make ModTimeRamp and ModRateAdjust incompatible --- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 3 +++ osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index b016a6d43b..e66650f7b4 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.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 osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; @@ -24,6 +25,8 @@ namespace osu.Game.Rulesets.Mods public double ApplyToRate(double time, double rate) => rate * SpeedChange.Value; + public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) }; + public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x"; } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 330945d3d3..b5cd64dafa 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] public abstract BindableBool AdjustPitch { get; } + public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust) }; + public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x"; private double finalRateTime; From dbde47fe94e5c26270e4b124f7539c953f32b5b4 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Tue, 23 Feb 2021 19:43:04 +0000 Subject: [PATCH 26/45] Fix test failure --- osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 7a0dd5b719..650ae68ffc 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Gameplay break; case ModTimeRamp m: - m.InitialRate.Value = m.FinalRate.Value = expectedRate; + m.FinalRate.Value = m.InitialRate.Value = expectedRate; break; } From f6d3cd6413e55eb4f44dc87d66644da55ecb0699 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Tue, 23 Feb 2021 21:25:59 +0000 Subject: [PATCH 27/45] Change SamplePlaybackWithRateMods to use rate calulated from the sample Replace hardcoded numbers --- osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | 9 +++++++-- osu.Game/Rulesets/Mods/ModWindDown.cs | 4 ++-- osu.Game/Rulesets/Mods/ModWindUp.cs | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 650ae68ffc..10a1a13ba0 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.IO; using osu.Game.Rulesets; @@ -90,6 +91,7 @@ namespace osu.Game.Tests.Gameplay public void TestSamplePlaybackWithRateMods(Type expectedMod, double expectedRate) { GameplayClockContainer gameplayContainer = null; + StoryboardSampleInfo sampleInfo = null; TestDrawableStoryboardSample sample = null; Mod testedMod = Activator.CreateInstance(expectedMod) as Mod; @@ -117,7 +119,7 @@ namespace osu.Game.Tests.Gameplay Child = beatmapSkinSourceContainer }); - beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1)) + beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(sampleInfo = new StoryboardSampleInfo("test-sample", 1, 1)) { Clock = gameplayContainer.GameplayClock }); @@ -125,7 +127,10 @@ namespace osu.Game.Tests.Gameplay AddStep("start", () => gameplayContainer.Start()); - AddAssert("sample playback rate matches mod rates", () => sample.ChildrenOfType().First().AggregateFrequency.Value == expectedRate); + AddAssert("sample playback rate matches mod rates", () => + testedMod != null && Precision.AlmostEquals( + sample.ChildrenOfType().First().AggregateFrequency.Value, + ((IApplicableToRate)testedMod).ApplyToRate(sampleInfo.StartTime))); } private class TestSkin : LegacySkin diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index f9e6854dd4..9bd5b5eefd 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -49,10 +49,10 @@ namespace osu.Game.Rulesets.Mods public ModWindDown() { InitialRate.BindValueChanged(val => - FinalRate.Value = Math.Min(FinalRate.Value, val.NewValue - 0.01)); + FinalRate.Value = Math.Min(FinalRate.Value, val.NewValue - FinalRate.Precision)); FinalRate.BindValueChanged(val => - InitialRate.Value = Math.Max(InitialRate.Value, val.NewValue + 0.01)); + InitialRate.Value = Math.Max(InitialRate.Value, val.NewValue + InitialRate.Precision)); } } } diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index 0d57bbb52d..39d3c9c5d5 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -49,10 +49,10 @@ namespace osu.Game.Rulesets.Mods public ModWindUp() { InitialRate.BindValueChanged(val => - FinalRate.Value = Math.Max(FinalRate.Value, val.NewValue + 0.01)); + FinalRate.Value = Math.Max(FinalRate.Value, val.NewValue + FinalRate.Precision)); FinalRate.BindValueChanged(val => - InitialRate.Value = Math.Min(InitialRate.Value, val.NewValue - 0.01)); + InitialRate.Value = Math.Min(InitialRate.Value, val.NewValue - InitialRate.Precision)); } } } From 71182347d677be782005acaf1e227c6cd21a0275 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Feb 2021 11:30:13 +0900 Subject: [PATCH 28/45] Also add a notifiation when trying to enter the multiplayer screen Turns out the only check required to get into this screen was that the API was online, which it always is even if the multiplayer component isn't. This provides a better end-user experience. --- osu.Game/Screens/Menu/ButtonSystem.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 81b1cb0bf1..dd1e318aa0 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -172,6 +172,23 @@ namespace osu.Game.Screens.Menu return; } + // disabled until the underlying runtime issue is resolved, see https://github.com/mono/mono/issues/20805. + if (RuntimeInfo.OS == RuntimeInfo.Platform.iOS) + { + notifications?.Post(new SimpleNotification + { + Text = "Multiplayer is temporarily unavailable on iOS as we figure out some low level issues.", + Icon = FontAwesome.Solid.AppleAlt, + Activated = () => + { + loginOverlay?.Show(); + return true; + } + }); + + return; + } + OnMultiplayer?.Invoke(); } From e1f71038e39b09134ae2587692f7a9b9fa884d75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Feb 2021 12:13:55 +0900 Subject: [PATCH 29/45] Remove unncessary action --- osu.Game/Screens/Menu/ButtonSystem.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index dd1e318aa0..f93bfd7705 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -179,11 +179,6 @@ namespace osu.Game.Screens.Menu { Text = "Multiplayer is temporarily unavailable on iOS as we figure out some low level issues.", Icon = FontAwesome.Solid.AppleAlt, - Activated = () => - { - loginOverlay?.Show(); - return true; - } }); return; From 7000132d034c6cf012b475ec44178c7202ca4c3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Feb 2021 12:45:00 +0900 Subject: [PATCH 30/45] Specify full filename inline for quick beatmap --- osu.Game.Tests/Resources/TestResources.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 14bc2c8733..c979b5c695 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Resources public static Stream OpenResource(string name) => GetStore().GetStream($"Resources/{name}"); - public static Stream GetTestBeatmapStream(bool virtualTrack = false, bool quick = false) => OpenResource($"Archives/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}{(quick ? "_quick" : "")}.osz"); + public static Stream GetTestBeatmapStream(bool virtualTrack = false) => OpenResource($"Archives/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz"); /// /// Retrieve a path to a copy of a shortened (~10 second) beatmap archive with a virtual track. @@ -24,8 +24,7 @@ namespace osu.Game.Tests.Resources public static string GetQuickTestBeatmapForImport() { var tempPath = Path.GetTempFileName() + ".osz"; - - using (var stream = GetTestBeatmapStream(true, true)) + using (var stream = OpenResource($"Archives/241526 Soleily - Renatus_virtual_quick.osz")) using (var newFile = File.Create(tempPath)) stream.CopyTo(newFile); From 59e6bad0b9cf128c4f208a67fface6ad82ff48bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Feb 2021 12:46:35 +0900 Subject: [PATCH 31/45] Remove unnecessary interpolated string specification --- osu.Game.Tests/Resources/TestResources.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index c979b5c695..cef0532f9d 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Resources public static string GetQuickTestBeatmapForImport() { var tempPath = Path.GetTempFileName() + ".osz"; - using (var stream = OpenResource($"Archives/241526 Soleily - Renatus_virtual_quick.osz")) + using (var stream = OpenResource("Archives/241526 Soleily - Renatus_virtual_quick.osz")) using (var newFile = File.Create(tempPath)) stream.CopyTo(newFile); From dd702ccfd22ef251000985bdb72e71812855893e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 24 Feb 2021 13:39:15 +0900 Subject: [PATCH 32/45] Make mania FI/HD incompatible with each other --- .../Mods/ManiaModFadeIn.cs | 10 +++-- .../Mods/ManiaModHidden.cs | 33 +------------- .../Mods/ManiaModPlayfieldCover.cs | 43 +++++++++++++++++++ 3 files changed, 51 insertions(+), 35 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs index cbdcd49c5b..f80c9e1f7c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs @@ -1,18 +1,20 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; +using System; +using System.Linq; using osu.Game.Rulesets.Mania.UI; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModFadeIn : ManiaModHidden + public class ManiaModFadeIn : ManiaModPlayfieldCover { public override string Name => "Fade In"; public override string Acronym => "FI"; - public override IconUsage? Icon => OsuIcon.ModHidden; public override string Description => @"Keys appear out of nowhere!"; + public override double ScoreMultiplier => 1; + + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModHidden)).ToArray(); protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll; } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index 4bdb15526f..a68f12cb84 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs @@ -3,43 +3,14 @@ using System; using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModHidden : ModHidden, IApplicableToDrawableRuleset + public class ManiaModHidden : ManiaModPlayfieldCover { public override string Description => @"Keys fade out before you hit them!"; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; - /// - /// The direction in which the cover should expand. - /// - protected virtual CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll; - - public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield; - - foreach (Column column in maniaPlayfield.Stages.SelectMany(stage => stage.Columns)) - { - HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer; - Container hocParent = (Container)hoc.Parent; - - hocParent.Remove(hoc); - hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c => - { - c.RelativeSizeAxes = Axes.Both; - c.Direction = ExpandDirection; - c.Coverage = 0.5f; - })); - } - } + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModFadeIn)).ToArray(); } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs new file mode 100644 index 0000000000..78c3331fbf --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs @@ -0,0 +1,43 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public abstract class ManiaModPlayfieldCover : ModHidden, IApplicableToDrawableRuleset + { + public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; + + /// + /// The direction in which the cover should expand. + /// + protected virtual CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll; + + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield; + + foreach (Column column in maniaPlayfield.Stages.SelectMany(stage => stage.Columns)) + { + HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer; + Container hocParent = (Container)hoc.Parent; + + hocParent.Remove(hoc); + hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c => + { + c.RelativeSizeAxes = Axes.Both; + c.Direction = ExpandDirection; + c.Coverage = 0.5f; + })); + } + } + } +} From 30a58691f04b48126fb8714331a6d84cf88b6cd6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 24 Feb 2021 14:32:50 +0900 Subject: [PATCH 33/45] Make SD and PF incompatible with each other --- osu.Game/Rulesets/Mods/ModFailCondition.cs | 25 ++++++++++++++++++++++ osu.Game/Rulesets/Mods/ModPerfect.cs | 9 +++++++- osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 15 ++++--------- 3 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/ModFailCondition.cs diff --git a/osu.Game/Rulesets/Mods/ModFailCondition.cs b/osu.Game/Rulesets/Mods/ModFailCondition.cs new file mode 100644 index 0000000000..40a0843e06 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModFailCondition.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModFailCondition : Mod, IApplicableToHealthProcessor, IApplicableFailOverride + { + public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) }; + + public bool PerformFail() => true; + + public bool RestartOnFail => true; + + public void ApplyToHealthProcessor(HealthProcessor healthProcessor) + { + healthProcessor.FailConditions += FailCondition; + } + + protected abstract bool FailCondition(HealthProcessor healthProcessor, JudgementResult result); + } +} diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index df0fc9c4b6..d0b09b50f2 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; @@ -8,13 +10,18 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModPerfect : ModSuddenDeath + public abstract class ModPerfect : ModFailCondition { public override string Name => "Perfect"; public override string Acronym => "PF"; public override IconUsage? Icon => OsuIcon.ModPerfect; + public override ModType Type => ModType.DifficultyIncrease; + public override bool Ranked => true; + public override double ScoreMultiplier => 1; public override string Description => "SS or quit."; + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModSuddenDeath)).ToArray(); + protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) => result.Type.AffectsAccuracy() && result.Type != result.Judgement.MaxResult; diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index ae71041a64..617ae38feb 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; @@ -9,7 +10,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModSuddenDeath : Mod, IApplicableToHealthProcessor, IApplicableFailOverride + public abstract class ModSuddenDeath : ModFailCondition { public override string Name => "Sudden Death"; public override string Acronym => "SD"; @@ -18,18 +19,10 @@ namespace osu.Game.Rulesets.Mods public override string Description => "Miss and fail."; public override double ScoreMultiplier => 1; public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) }; - public bool PerformFail() => true; + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModPerfect)).ToArray(); - public bool RestartOnFail => true; - - public void ApplyToHealthProcessor(HealthProcessor healthProcessor) - { - healthProcessor.FailConditions += FailCondition; - } - - protected virtual bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) + protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) => result.Type.AffectsCombo() && !result.IsHit; } From 14160b897e238ebcba242f5fa09f6b237066c960 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 24 Feb 2021 14:42:04 +0900 Subject: [PATCH 34/45] Fix references to ModSuddenDeath --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 2 +- osu.Game/Rulesets/Mods/ModAutoplay.cs | 2 +- osu.Game/Rulesets/Mods/ModNoFail.cs | 2 +- osu.Game/Rulesets/Mods/ModRelax.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 77de0cb45b..aac830801b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Automation; public override string Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay) }; public bool PerformFail() => false; diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index d1d23def67..d6e1d46b06 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mods public bool RestartOnFail => false; - public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) }; + public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) }; public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index b95ec7490e..c0f24e116a 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Mods public override string Description => "You can't fail, no matter what."; public override double ScoreMultiplier => 0.5; public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModAutoplay) }; + public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModAutoplay) }; } } diff --git a/osu.Game/Rulesets/Mods/ModRelax.cs b/osu.Game/Rulesets/Mods/ModRelax.cs index b6fec42f43..e5995ff180 100644 --- a/osu.Game/Rulesets/Mods/ModRelax.cs +++ b/osu.Game/Rulesets/Mods/ModRelax.cs @@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModRelax; public override ModType Type => ModType.Automation; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModSuddenDeath) }; + public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModFailCondition) }; } } From 0b44d2483b6f02dd415c461ab6d3081e96cd9971 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 24 Feb 2021 15:03:37 +0900 Subject: [PATCH 35/45] Make some properties virtual I think they were intended to be this way from the beginning. --- osu.Game/Rulesets/Mods/ModFailCondition.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFailCondition.cs b/osu.Game/Rulesets/Mods/ModFailCondition.cs index 40a0843e06..c0d7bae2b2 100644 --- a/osu.Game/Rulesets/Mods/ModFailCondition.cs +++ b/osu.Game/Rulesets/Mods/ModFailCondition.cs @@ -11,9 +11,9 @@ namespace osu.Game.Rulesets.Mods { public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) }; - public bool PerformFail() => true; + public virtual bool PerformFail() => true; - public bool RestartOnFail => true; + public virtual bool RestartOnFail => true; public void ApplyToHealthProcessor(HealthProcessor healthProcessor) { From 6b6811063b617b3cf5c0e38a6eae95193759dd18 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 24 Feb 2021 15:05:12 +0900 Subject: [PATCH 36/45] Make ExpandDirection abstract --- osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs | 3 +++ osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index a68f12cb84..e3ac624a6e 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using osu.Game.Rulesets.Mania.UI; namespace osu.Game.Rulesets.Mania.Mods { @@ -12,5 +13,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModFadeIn)).ToArray(); + + protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs index 78c3331fbf..87501d07a5 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Mods /// /// The direction in which the cover should expand. /// - protected virtual CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll; + protected abstract CoverExpandDirection ExpandDirection { get; } public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { From 165da3204454999cd8497d0f55987d871774b34c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Feb 2021 18:41:42 +0900 Subject: [PATCH 37/45] Fix dropdown crash on collection name collisions --- osu.Game/Collections/CollectionFilterMenuItem.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Collections/CollectionFilterMenuItem.cs b/osu.Game/Collections/CollectionFilterMenuItem.cs index fe79358223..0617996872 100644 --- a/osu.Game/Collections/CollectionFilterMenuItem.cs +++ b/osu.Game/Collections/CollectionFilterMenuItem.cs @@ -36,7 +36,19 @@ namespace osu.Game.Collections } public bool Equals(CollectionFilterMenuItem other) - => other != null && CollectionName.Value == other.CollectionName.Value; + { + if (other == null) + return false; + + // collections may have the same name, so compare first on reference equality. + // this relies on the assumption that only one instance of the BeatmapCollection exists game-wide, managed by CollectionManager. + if (Collection != null) + return Collection == other.Collection; + + // fallback to name-based comparison. + // this is required for special dropdown items which don't have a collection (all beatmaps / manage collections items below). + return CollectionName.Value == other.CollectionName.Value; + } public override int GetHashCode() => CollectionName.Value.GetHashCode(); } From 6e6fb31c050ad03ce1f064fb8077f8df4d0f7027 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Feb 2021 18:42:26 +0900 Subject: [PATCH 38/45] Add test coverage --- .../TestSceneManageCollectionsDialog.cs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 1655adf811..eca857f9e5 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Collections { manager = new CollectionManager(LocalStorage), Content, - dialogOverlay = new DialogOverlay() + dialogOverlay = new DialogOverlay(), }); Dependencies.Cache(manager); @@ -134,6 +134,27 @@ namespace osu.Game.Tests.Visual.Collections assertCollectionName(0, "2"); } + [Test] + public void TestCollectionNameCollisions() + { + AddStep("add dropdown", () => + { + Add(new CollectionFilterDropdown + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + Width = 0.4f, + } + ); + }); + AddStep("add two collections with same name", () => manager.Collections.AddRange(new[] + { + new BeatmapCollection { Name = { Value = "1" } }, + new BeatmapCollection { Name = { Value = "1" }, Beatmaps = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0] } }, + })); + } + [Test] public void TestRemoveCollectionViaButton() { From 5dc0aefb2bf36f7ab18e8c41a1643bcc31b05c98 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Feb 2021 19:54:52 +0900 Subject: [PATCH 39/45] Cancel request on leaving results screen --- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 76b549da1a..4c35096910 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -15,6 +15,8 @@ namespace osu.Game.Screens.Ranking { public class SoloResultsScreen : ResultsScreen { + private GetScoresRequest getScoreRequest; + [Resolved] private RulesetStore rulesets { get; set; } @@ -28,9 +30,16 @@ namespace osu.Game.Screens.Ranking if (Score.Beatmap.OnlineBeatmapID == null || Score.Beatmap.Status <= BeatmapSetOnlineStatus.Pending) return null; - var req = new GetScoresRequest(Score.Beatmap, Score.Ruleset); - req.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets))); - return req; + getScoreRequest = new GetScoresRequest(Score.Beatmap, Score.Ruleset); + getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != this.Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets))); + return getScoreRequest; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + getScoreRequest?.Cancel(); } } } From 9ed8d902f7ca20f47179d5f6387e0c9583b8b320 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Feb 2021 19:57:42 +0900 Subject: [PATCH 40/45] Fix requests being indefinitely queued when user is offline --- osu.Game/Online/API/APIAccess.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index ce01378b17..569481d491 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -381,7 +381,13 @@ namespace osu.Game.Online.API public void Queue(APIRequest request) { - lock (queue) queue.Enqueue(request); + lock (queue) + { + if (state.Value == APIState.Offline) + return; + + queue.Enqueue(request); + } } private void flushQueue(bool failOldRequests = true) @@ -402,8 +408,6 @@ namespace osu.Game.Online.API public void Logout() { - flushQueue(); - password = null; authentication.Clear(); @@ -415,6 +419,7 @@ namespace osu.Game.Online.API }); state.Value = APIState.Offline; + flushQueue(); } private static User createGuestUser() => new GuestUser(); From fa6d797adf9860bbde472efffcca6fa77256fb14 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Feb 2021 20:30:17 +0900 Subject: [PATCH 41/45] Remove redundant prefix --- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 4c35096910..9bc696948f 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Ranking return null; getScoreRequest = new GetScoresRequest(Score.Beatmap, Score.Ruleset); - getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != this.Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets))); + getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets))); return getScoreRequest; } From 73d6a3687eacd25e26501cc2b9ea061b86512c38 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Wed, 24 Feb 2021 14:40:56 +0000 Subject: [PATCH 42/45] Change rate correction logic to be more explicit --- osu.Game/Rulesets/Mods/ModWindDown.cs | 10 ++++++++-- osu.Game/Rulesets/Mods/ModWindUp.cs | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index 9bd5b5eefd..c8d79325a3 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -49,10 +49,16 @@ namespace osu.Game.Rulesets.Mods public ModWindDown() { InitialRate.BindValueChanged(val => - FinalRate.Value = Math.Min(FinalRate.Value, val.NewValue - FinalRate.Precision)); + { + if (val.NewValue <= FinalRate.Value) + FinalRate.Value = val.NewValue - FinalRate.Precision; + }); FinalRate.BindValueChanged(val => - InitialRate.Value = Math.Max(InitialRate.Value, val.NewValue + InitialRate.Precision)); + { + if (val.NewValue >= InitialRate.Value) + InitialRate.Value = val.NewValue + FinalRate.Precision; + }); } } } diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index 39d3c9c5d5..4fc1f61e02 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -49,10 +49,16 @@ namespace osu.Game.Rulesets.Mods public ModWindUp() { InitialRate.BindValueChanged(val => - FinalRate.Value = Math.Max(FinalRate.Value, val.NewValue + FinalRate.Precision)); + { + if (val.NewValue >= FinalRate.Value) + FinalRate.Value = val.NewValue + FinalRate.Precision; + }); FinalRate.BindValueChanged(val => - InitialRate.Value = Math.Min(InitialRate.Value, val.NewValue - InitialRate.Precision)); + { + if (val.NewValue <= InitialRate.Value) + InitialRate.Value = val.NewValue - FinalRate.Precision; + }); } } } From 421b7877d4eb9eed06942666c37d60d962d78b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Feb 2021 19:16:10 +0100 Subject: [PATCH 43/45] Avoid mixing precision across time ramp bindables Bears no functional difference, it's just a bit less of an eyesore. --- osu.Game/Rulesets/Mods/ModWindDown.cs | 2 +- osu.Game/Rulesets/Mods/ModWindUp.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index c8d79325a3..08bd44f7bd 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Mods FinalRate.BindValueChanged(val => { if (val.NewValue >= InitialRate.Value) - InitialRate.Value = val.NewValue + FinalRate.Precision; + InitialRate.Value = val.NewValue + InitialRate.Precision; }); } } diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index 4fc1f61e02..df8f781148 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Mods FinalRate.BindValueChanged(val => { if (val.NewValue <= InitialRate.Value) - InitialRate.Value = val.NewValue - FinalRate.Precision; + InitialRate.Value = val.NewValue - InitialRate.Precision; }); } } From a08a3d44c796bafb3fac49b8612869b3f8131bd8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Feb 2021 14:51:23 +0900 Subject: [PATCH 44/45] Add failing test coverage for using hotkeys from main menu before toolbar displayed --- .../Navigation/TestSceneScreenNavigation.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 5d070b424a..fc49517cdf 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -214,6 +214,21 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("Options overlay still visible", () => songSelect.BeatmapOptionsOverlay.State.Value == Visibility.Visible); } + [Test] + public void TestSettingsViaHotkeyFromMainMenu() + { + AddAssert("toolbar not displayed", () => Game.Toolbar.State.Value == Visibility.Hidden); + + AddStep("press settings hotkey", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.O); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + AddUntilStep("settings displayed", () => Game.Settings.State.Value == Visibility.Visible); + } + private void pushEscape() => AddStep("Press escape", () => InputManager.Key(Key.Escape)); From 2c8e62ae3589a28fd00dd68a72c20e38f53ca60b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Feb 2021 14:52:51 +0900 Subject: [PATCH 45/45] Fix toolbar not completing enough of layout to propagate hotkeys to buttons before initial display --- osu.Game/Overlays/Toolbar/Toolbar.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 393e349bd0..0ccb22df3a 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -37,6 +37,15 @@ namespace osu.Game.Overlays.Toolbar { RelativeSizeAxes = Axes.X; Size = new Vector2(1, HEIGHT); + AlwaysPresent = true; + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + // this only needed to be set for the initial LoadComplete/Update, so layout completes and gets buttons in a state they can correctly handle keyboard input for hotkeys. + AlwaysPresent = false; } [BackgroundDependencyLoader(true)]

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 Ni{DEH3<&Xe{{o+^{JH=D literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index e882229570..14bc2c8733 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -13,8 +13,31 @@ namespace osu.Game.Tests.Resources public static Stream OpenResource(string name) => GetStore().GetStream($"Resources/{name}"); - public static Stream GetTestBeatmapStream(bool virtualTrack = false) => OpenResource($"Archives/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz"); + public static Stream GetTestBeatmapStream(bool virtualTrack = false, bool quick = false) => OpenResource($"Archives/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}{(quick ? "_quick" : "")}.osz"); + ///