From fb724ca8a705ac6f093be9c96c7e2f30c41b5a07 Mon Sep 17 00:00:00 2001 From: naoey Date: Wed, 28 Feb 2018 08:32:30 +0530 Subject: [PATCH 1/6] Make song select ensure current beatmap is always playable in the active ruleset. - Add a to TestCasePlaySongSelect testing this scenario --- .../Visual/TestCasePlaySongSelect.cs | 38 ++++++++++++++--- osu.Game/Screens/Select/SongSelect.cs | 41 ++++++++++++++++++- 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index 13b2be9fdb..a4086ea2cd 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.MathUtils; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Screens.Select; @@ -53,10 +54,14 @@ namespace osu.Game.Tests.Visual public WorkingBeatmap CurrentBeatmap => Beatmap.Value; public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap; public new BeatmapCarousel Carousel => base.Carousel; + + public void SetRuleset(RulesetInfo ruleset) => Ruleset.Value = ruleset; + + public int? RulesetID => Ruleset.Value.ID; } [BackgroundDependencyLoader] - private void load(OsuGameBase game) + private void load(OsuGameBase game, OsuConfigManager config) { TestSongSelect songSelect = null; @@ -113,6 +118,24 @@ namespace osu.Game.Tests.Visual AddStep(@"Sort by Title", delegate { songSelect.FilterControl.Sort = SortMode.Title; }); AddStep(@"Sort by Author", delegate { songSelect.FilterControl.Sort = SortMode.Author; }); AddStep(@"Sort by Difficulty", delegate { songSelect.FilterControl.Sort = SortMode.Difficulty; }); + + AddWaitStep(5); + + AddStep(@"Set unplayable WorkingBeatmap", () => + { + var testMap = manager.GetAllUsableBeatmapSets().First().Beatmaps.First(b => b.RulesetID != 0); + songSelect.SetRuleset(rulesets.AvailableRulesets.First()); + game.Beatmap.Value = manager.GetWorkingBeatmap(testMap); + }); + AddAssert(@"WorkingBeatmap changed to playable ruleset", () => songSelect.RulesetID == 0 && game.Beatmap.Value.BeatmapInfo.RulesetID == 0); + AddStep(@"Disallow beatmap conversion", () => + { + config.GetBindable(OsuSetting.ShowConvertedBeatmaps).Value = false; + game.Beatmap.Value = manager.GetWorkingBeatmap(manager.GetAllUsableBeatmapSets().First().Beatmaps.First()); + }); + loadNewSongSelect(); + AddWaitStep(3); + AddAssert(@"Ruleset matches beatmap", () => songSelect.RulesetID == game.Beatmap.Value.BeatmapInfo.RulesetID); } private BeatmapSetInfo createTestBeatmapSet(int i) @@ -134,7 +157,8 @@ namespace osu.Game.Tests.Visual new BeatmapInfo { OnlineBeatmapID = 1234 + i, - Ruleset = rulesets.AvailableRulesets.First(), + Ruleset = rulesets.AvailableRulesets.ElementAt(0), + RulesetID = 0, Path = "normal.osu", Version = "Normal", BaseDifficulty = new BeatmapDifficulty @@ -145,8 +169,9 @@ namespace osu.Game.Tests.Visual new BeatmapInfo { OnlineBeatmapID = 1235 + i, - Ruleset = rulesets.AvailableRulesets.First(), - Path = "hard.osu", + Ruleset = rulesets.AvailableRulesets.First(r => r.ID != 0), + RulesetID = 1, + Path = "hard.taiko", Version = "Hard", BaseDifficulty = new BeatmapDifficulty { @@ -156,8 +181,9 @@ namespace osu.Game.Tests.Visual new BeatmapInfo { OnlineBeatmapID = 1236 + i, - Ruleset = rulesets.AvailableRulesets.First(), - Path = "insane.osu", + Ruleset = rulesets.AvailableRulesets.ElementAt(2), + RulesetID = 2, + Path = "insane.fruits", Version = "Insane", BaseDifficulty = new BeatmapDifficulty { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index de6847d866..6e1d95d42e 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Linq; using System.Threading; using OpenTK; using OpenTK.Input; @@ -9,12 +10,14 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Overlays; @@ -63,6 +66,8 @@ namespace osu.Game.Screens.Select private SampleChannel sampleChangeDifficulty; private SampleChannel sampleChangeBeatmap; + private Bindable rulesetConversionAllowed; + private CancellationTokenSource initialAddSetsTask; private DependencyContainer dependencies; @@ -179,7 +184,7 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader(permitNulls: true)] - private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours) + private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours, OsuConfigManager config) { dependencies.CacheAs(this); @@ -194,6 +199,8 @@ namespace osu.Game.Screens.Select if (this.beatmaps == null) this.beatmaps = beatmaps; + rulesetConversionAllowed = config.GetBindable(OsuSetting.ShowConvertedBeatmaps); + if (osu != null) Ruleset.BindTo(osu.Ruleset); @@ -217,7 +224,10 @@ namespace osu.Game.Screens.Select Beatmap.ValueChanged += b => { if (IsCurrentScreen) + { Carousel.SelectBeatmap(b?.BeatmapInfo); + ensurePlayableRuleset(); + } }; } @@ -316,6 +326,7 @@ namespace osu.Game.Screens.Select { base.OnEntering(last); + ensurePlayableRuleset(); Content.FadeInFromZero(250); FilterControl.Activate(); } @@ -441,6 +452,34 @@ namespace osu.Game.Screens.Select } } + private void ensurePlayableRuleset() + { + if (Beatmap.IsDefault) + // DummyBeatmap won't be playable anyway + return; + + bool conversionAllowed = rulesetConversionAllowed.Value; + int? currentRuleset = Ruleset.Value.ID; + int beatmapRuleset = Beatmap.Value.BeatmapInfo.RulesetID; + + if (currentRuleset == beatmapRuleset || conversionAllowed && beatmapRuleset == 0) + // Current beatmap is playable, nothing more to do + return; + + // Otherwise, first check if the current beatmapset has any playable beatmaps + BeatmapInfo beatmap = Beatmap.Value.BeatmapSetInfo.Beatmaps?.FirstOrDefault(b => b.RulesetID == currentRuleset || conversionAllowed && b.RulesetID == 0); + + // If it does then update the WorkingBeatmap + if (beatmap != null) + { + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap); + return; + } + + // If it doesn't, then update the current ruleset so that the current beatmap is playable + Ruleset.Value = Beatmap.Value.BeatmapInfo.Ruleset; + } + private void onBeatmapSetAdded(BeatmapSetInfo s) => Carousel.UpdateBeatmapSet(s); private void onBeatmapSetRemoved(BeatmapSetInfo s) => Carousel.RemoveBeatmapSet(s); private void onBeatmapRestored(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); From a57dc154f9aa323c6ab367442c417b7bb48363ae Mon Sep 17 00:00:00 2001 From: naoey Date: Sat, 3 Mar 2018 19:54:54 +0530 Subject: [PATCH 2/6] More specific tests. --- .../Visual/TestCasePlaySongSelect.cs | 80 ++++++++++++++++--- osu.Game/Screens/Select/SongSelect.cs | 18 +++-- 2 files changed, 81 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index d895080afe..8532962389 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -60,6 +60,14 @@ namespace osu.Game.Tests.Visual public void SetRuleset(RulesetInfo ruleset) => Ruleset.Value = ruleset; public int? RulesetID => Ruleset.Value.ID; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + // Necessary while running tests because gc is moody and uncollected object interferes with OnEntering test + Beatmap.ValueChanged -= WorkingBeatmapChanged; + } } [BackgroundDependencyLoader] @@ -82,6 +90,7 @@ namespace osu.Game.Tests.Visual { if (deleteMaps) { + // TODO: check why this alone doesn't allow import test to run twice in the same session, probably because the delete op is not saved? manager.Delete(manager.GetAllUsableBeatmapSets()); game.Beatmap.SetDefault(); } @@ -93,6 +102,8 @@ namespace osu.Game.Tests.Visual } Add(songSelect = new TestSongSelect()); + + songSelect?.SetRuleset(rulesets.AvailableRulesets.First()); }); loadNewSongSelect(true); @@ -107,6 +118,36 @@ namespace osu.Game.Tests.Visual { for (int i = 0; i < 100; i += 10) manager.Import(createTestBeatmapSet(i)); + + // also import a set which has a single non - osu ruleset beatmap + manager.Import(new BeatmapSetInfo + { + OnlineBeatmapSetID = 1993, + Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), + Metadata = new BeatmapMetadata + { + OnlineBeatmapSetID = 1993, + // Create random metadata, then we can check if sorting works based on these + Artist = "MONACA " + RNG.Next(0, 9), + Title = "Black Song " + RNG.Next(0, 9), + AuthorString = "Some Guy " + RNG.Next(0, 9), + }, + Beatmaps = new List + { + new BeatmapInfo + { + OnlineBeatmapID = 1994, + Ruleset = rulesets.AvailableRulesets.ElementAt(3), + RulesetID = 3, + Path = "normal.fruits", + Version = "Normal", + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = 3.5f, + } + }, + } + }); }); AddWaitStep(3); @@ -121,23 +162,44 @@ namespace osu.Game.Tests.Visual AddStep(@"Sort by Author", delegate { songSelect.FilterControl.Sort = SortMode.Author; }); AddStep(@"Sort by Difficulty", delegate { songSelect.FilterControl.Sort = SortMode.Difficulty; }); - AddWaitStep(5); + // Test that song select sets a playable beatmap while entering + AddStep(@"Remove song select", () => + { + Remove(songSelect); + songSelect.Dispose(); + songSelect = null; + }); + AddStep(@"Set non-osu beatmap", () => game.Beatmap.Value = manager.GetWorkingBeatmap(manager.GetAllUsableBeatmapSets().First().Beatmaps.First(b => b.RulesetID != 0))); + AddAssert(@"Non-osu beatmap set", () => game.Beatmap.Value.BeatmapInfo.RulesetID != 0); + loadNewSongSelect(); + AddWaitStep(3); + AddAssert(@"osu beatmap set", () => game.Beatmap.Value.BeatmapInfo.RulesetID == 0); - AddStep(@"Set unplayable WorkingBeatmap", () => + // Test that song select changes WorkingBeatmap to be playable in current ruleset when updated externally + AddStep(@"Try set non-osu beatmap", () => { var testMap = manager.GetAllUsableBeatmapSets().First().Beatmaps.First(b => b.RulesetID != 0); songSelect.SetRuleset(rulesets.AvailableRulesets.First()); game.Beatmap.Value = manager.GetWorkingBeatmap(testMap); }); - AddAssert(@"WorkingBeatmap changed to playable ruleset", () => songSelect.RulesetID == 0 && game.Beatmap.Value.BeatmapInfo.RulesetID == 0); - AddStep(@"Disallow beatmap conversion", () => + AddAssert(@"Beatmap changed to osu", () => songSelect.RulesetID == 0 && game.Beatmap.Value.BeatmapInfo.RulesetID == 0); + + // Test that song select updates WorkingBeatmap when ruleset conversion is disabled + AddStep(@"Disable beatmap conversion", () => config.Set(OsuSetting.ShowConvertedBeatmaps, false)); + AddStep(@"Set osu beatmap taiko rs", () => { - config.GetBindable(OsuSetting.ShowConvertedBeatmaps).Value = false; - game.Beatmap.Value = manager.GetWorkingBeatmap(manager.GetAllUsableBeatmapSets().First().Beatmaps.First()); + game.Beatmap.Value = manager.GetWorkingBeatmap(manager.GetAllUsableBeatmapSets().First().Beatmaps.First(b => b.RulesetID == 0)); + songSelect.SetRuleset(rulesets.AvailableRulesets.First(r => r.ID == 1)); }); - loadNewSongSelect(); - AddWaitStep(3); - AddAssert(@"Ruleset matches beatmap", () => songSelect.RulesetID == game.Beatmap.Value.BeatmapInfo.RulesetID); + AddAssert(@"taiko beatmap set", () => songSelect.RulesetID == 1); + + // Test that song select changes the active ruleset when externally set beatmapset has no playable beatmaps + AddStep(@"Set fruits only beatmapset", () => + { + songSelect.SetRuleset(rulesets.AvailableRulesets.First()); + game.Beatmap.Value = manager.GetWorkingBeatmap(manager.QueryBeatmapSet(b => b.OnlineBeatmapSetID == 1993).Beatmaps.First()); + }); + AddAssert(@"Ruleset changed to fruits", () => songSelect.RulesetID == game.Beatmap.Value.BeatmapInfo.RulesetID); } private BeatmapSetInfo createTestBeatmapSet(int i) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 8033f8da8b..d4fd64dcd9 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -221,14 +221,7 @@ namespace osu.Game.Screens.Select Beatmap.DisabledChanged += disabled => Carousel.AllowSelection = !disabled; Beatmap.TriggerChange(); - Beatmap.ValueChanged += b => - { - if (IsCurrentScreen) - { - Carousel.SelectBeatmap(b?.BeatmapInfo); - ensurePlayableRuleset(); - } - }; + Beatmap.ValueChanged += WorkingBeatmapChanged; } public void Edit(BeatmapInfo beatmap) @@ -271,6 +264,15 @@ namespace osu.Game.Screens.Select // We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds. private BeatmapInfo beatmapNoDebounce; + protected void WorkingBeatmapChanged(WorkingBeatmap beatmap) + { + if (IsCurrentScreen) + { + Carousel.SelectBeatmap(beatmap?.BeatmapInfo); + ensurePlayableRuleset(); + } + } + /// /// selection has been changed as the result of interaction with the carousel. /// From 25fb527cc7bd7f1d1c3a855db9ca2281ee3175a3 Mon Sep 17 00:00:00 2001 From: naoey Date: Fri, 9 Mar 2018 15:51:00 +0530 Subject: [PATCH 3/6] Remove previous fix and move filtered logic to carousel. - Add an optional bool parameter to SelectBeatmap to skip selecting filtered maps --- osu.Game/Screens/Select/BeatmapCarousel.cs | 31 +++++++++++++-------- osu.Game/Screens/Select/SongSelect.cs | 32 ---------------------- 2 files changed, 20 insertions(+), 43 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 9793440348..02bad82ca9 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -169,20 +169,29 @@ namespace osu.Game.Screens.Select }); } - public void SelectBeatmap(BeatmapInfo beatmap) + /// + /// Selects a given beatmap on the carousel. + /// + /// The beatmap to select. + /// Whether to skip selecting filtered beatmaps. + /// True if a selection was made, false if it was skipped. + public bool SelectBeatmap(BeatmapInfo beatmap, bool skipFiltered = false) { if (beatmap?.Hidden != false) - return; + return false; - foreach (CarouselBeatmapSet group in beatmapSets) - { - var item = group.Beatmaps.FirstOrDefault(p => p.Beatmap.Equals(beatmap)); - if (item != null) - { - select(item); - return; - } - } + var group = beatmapSets.FirstOrDefault(s => s.BeatmapSet.OnlineBeatmapSetID == beatmap.BeatmapSet.OnlineBeatmapSetID); + + if (group == null || !skipFiltered && group.Filtered) + return false; + + var item = group.Beatmaps.FirstOrDefault(p => p.Beatmap.Equals(beatmap)); + + if (item == null || !skipFiltered && item.Filtered) + return false; + + select(item); + return true; } /// diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index d4fd64dcd9..2c8dcae3cf 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -267,10 +267,7 @@ namespace osu.Game.Screens.Select protected void WorkingBeatmapChanged(WorkingBeatmap beatmap) { if (IsCurrentScreen) - { Carousel.SelectBeatmap(beatmap?.BeatmapInfo); - ensurePlayableRuleset(); - } } /// @@ -328,7 +325,6 @@ namespace osu.Game.Screens.Select { base.OnEntering(last); - ensurePlayableRuleset(); Content.FadeInFromZero(250); FilterControl.Activate(); } @@ -456,34 +452,6 @@ namespace osu.Game.Screens.Select } } - private void ensurePlayableRuleset() - { - if (Beatmap.IsDefault) - // DummyBeatmap won't be playable anyway - return; - - bool conversionAllowed = rulesetConversionAllowed.Value; - int? currentRuleset = Ruleset.Value.ID; - int beatmapRuleset = Beatmap.Value.BeatmapInfo.RulesetID; - - if (currentRuleset == beatmapRuleset || conversionAllowed && beatmapRuleset == 0) - // Current beatmap is playable, nothing more to do - return; - - // Otherwise, first check if the current beatmapset has any playable beatmaps - BeatmapInfo beatmap = Beatmap.Value.BeatmapSetInfo.Beatmaps?.FirstOrDefault(b => b.RulesetID == currentRuleset || conversionAllowed && b.RulesetID == 0); - - // If it does then update the WorkingBeatmap - if (beatmap != null) - { - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap); - return; - } - - // If it doesn't, then update the current ruleset so that the current beatmap is playable - Ruleset.Value = Beatmap.Value.BeatmapInfo.Ruleset; - } - private void onBeatmapSetAdded(BeatmapSetInfo s) => Carousel.UpdateBeatmapSet(s); private void onBeatmapSetRemoved(BeatmapSetInfo s) => Carousel.RemoveBeatmapSet(s); private void onBeatmapRestored(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); From d04f47718fa73622a6610897357dd3c9de8acdfb Mon Sep 17 00:00:00 2001 From: naoey Date: Fri, 9 Mar 2018 16:22:59 +0530 Subject: [PATCH 4/6] Make song select choose random when initial selection fails. - Revert TestCasePlaySongSelect to master --- .../Visual/TestCasePlaySongSelect.cs | 100 ++---------------- osu.Game/Screens/Select/BeatmapCarousel.cs | 32 ++++-- osu.Game/Screens/Select/SongSelect.cs | 25 ++--- 3 files changed, 37 insertions(+), 120 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index 8532962389..cede0160bc 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -11,7 +11,6 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.MathUtils; using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Screens.Select; @@ -56,22 +55,10 @@ namespace osu.Game.Tests.Visual public WorkingBeatmap CurrentBeatmap => Beatmap.Value; public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap; public new BeatmapCarousel Carousel => base.Carousel; - - public void SetRuleset(RulesetInfo ruleset) => Ruleset.Value = ruleset; - - public int? RulesetID => Ruleset.Value.ID; - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - // Necessary while running tests because gc is moody and uncollected object interferes with OnEntering test - Beatmap.ValueChanged -= WorkingBeatmapChanged; - } } [BackgroundDependencyLoader] - private void load(OsuGameBase game, OsuConfigManager config) + private void load(OsuGameBase game) { TestSongSelect songSelect = null; @@ -90,7 +77,6 @@ namespace osu.Game.Tests.Visual { if (deleteMaps) { - // TODO: check why this alone doesn't allow import test to run twice in the same session, probably because the delete op is not saved? manager.Delete(manager.GetAllUsableBeatmapSets()); game.Beatmap.SetDefault(); } @@ -102,8 +88,6 @@ namespace osu.Game.Tests.Visual } Add(songSelect = new TestSongSelect()); - - songSelect?.SetRuleset(rulesets.AvailableRulesets.First()); }); loadNewSongSelect(true); @@ -118,36 +102,6 @@ namespace osu.Game.Tests.Visual { for (int i = 0; i < 100; i += 10) manager.Import(createTestBeatmapSet(i)); - - // also import a set which has a single non - osu ruleset beatmap - manager.Import(new BeatmapSetInfo - { - OnlineBeatmapSetID = 1993, - Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), - Metadata = new BeatmapMetadata - { - OnlineBeatmapSetID = 1993, - // Create random metadata, then we can check if sorting works based on these - Artist = "MONACA " + RNG.Next(0, 9), - Title = "Black Song " + RNG.Next(0, 9), - AuthorString = "Some Guy " + RNG.Next(0, 9), - }, - Beatmaps = new List - { - new BeatmapInfo - { - OnlineBeatmapID = 1994, - Ruleset = rulesets.AvailableRulesets.ElementAt(3), - RulesetID = 3, - Path = "normal.fruits", - Version = "Normal", - BaseDifficulty = new BeatmapDifficulty - { - OverallDifficulty = 3.5f, - } - }, - } - }); }); AddWaitStep(3); @@ -161,45 +115,6 @@ namespace osu.Game.Tests.Visual AddStep(@"Sort by Title", delegate { songSelect.FilterControl.Sort = SortMode.Title; }); AddStep(@"Sort by Author", delegate { songSelect.FilterControl.Sort = SortMode.Author; }); AddStep(@"Sort by Difficulty", delegate { songSelect.FilterControl.Sort = SortMode.Difficulty; }); - - // Test that song select sets a playable beatmap while entering - AddStep(@"Remove song select", () => - { - Remove(songSelect); - songSelect.Dispose(); - songSelect = null; - }); - AddStep(@"Set non-osu beatmap", () => game.Beatmap.Value = manager.GetWorkingBeatmap(manager.GetAllUsableBeatmapSets().First().Beatmaps.First(b => b.RulesetID != 0))); - AddAssert(@"Non-osu beatmap set", () => game.Beatmap.Value.BeatmapInfo.RulesetID != 0); - loadNewSongSelect(); - AddWaitStep(3); - AddAssert(@"osu beatmap set", () => game.Beatmap.Value.BeatmapInfo.RulesetID == 0); - - // Test that song select changes WorkingBeatmap to be playable in current ruleset when updated externally - AddStep(@"Try set non-osu beatmap", () => - { - var testMap = manager.GetAllUsableBeatmapSets().First().Beatmaps.First(b => b.RulesetID != 0); - songSelect.SetRuleset(rulesets.AvailableRulesets.First()); - game.Beatmap.Value = manager.GetWorkingBeatmap(testMap); - }); - AddAssert(@"Beatmap changed to osu", () => songSelect.RulesetID == 0 && game.Beatmap.Value.BeatmapInfo.RulesetID == 0); - - // Test that song select updates WorkingBeatmap when ruleset conversion is disabled - AddStep(@"Disable beatmap conversion", () => config.Set(OsuSetting.ShowConvertedBeatmaps, false)); - AddStep(@"Set osu beatmap taiko rs", () => - { - game.Beatmap.Value = manager.GetWorkingBeatmap(manager.GetAllUsableBeatmapSets().First().Beatmaps.First(b => b.RulesetID == 0)); - songSelect.SetRuleset(rulesets.AvailableRulesets.First(r => r.ID == 1)); - }); - AddAssert(@"taiko beatmap set", () => songSelect.RulesetID == 1); - - // Test that song select changes the active ruleset when externally set beatmapset has no playable beatmaps - AddStep(@"Set fruits only beatmapset", () => - { - songSelect.SetRuleset(rulesets.AvailableRulesets.First()); - game.Beatmap.Value = manager.GetWorkingBeatmap(manager.QueryBeatmapSet(b => b.OnlineBeatmapSetID == 1993).Beatmaps.First()); - }); - AddAssert(@"Ruleset changed to fruits", () => songSelect.RulesetID == game.Beatmap.Value.BeatmapInfo.RulesetID); } private BeatmapSetInfo createTestBeatmapSet(int i) @@ -221,8 +136,7 @@ namespace osu.Game.Tests.Visual new BeatmapInfo { OnlineBeatmapID = 1234 + i, - Ruleset = rulesets.AvailableRulesets.ElementAt(0), - RulesetID = 0, + Ruleset = rulesets.AvailableRulesets.First(), Path = "normal.osu", Version = "Normal", BaseDifficulty = new BeatmapDifficulty @@ -233,9 +147,8 @@ namespace osu.Game.Tests.Visual new BeatmapInfo { OnlineBeatmapID = 1235 + i, - Ruleset = rulesets.AvailableRulesets.First(r => r.ID != 0), - RulesetID = 1, - Path = "hard.taiko", + Ruleset = rulesets.AvailableRulesets.First(), + Path = "hard.osu", Version = "Hard", BaseDifficulty = new BeatmapDifficulty { @@ -245,9 +158,8 @@ namespace osu.Game.Tests.Visual new BeatmapInfo { OnlineBeatmapID = 1236 + i, - Ruleset = rulesets.AvailableRulesets.ElementAt(2), - RulesetID = 2, - Path = "insane.fruits", + Ruleset = rulesets.AvailableRulesets.First(), + Path = "insane.osu", Version = "Insane", BaseDifficulty = new BeatmapDifficulty { diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 02bad82ca9..287584bf2f 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -171,27 +171,41 @@ namespace osu.Game.Screens.Select /// /// Selects a given beatmap on the carousel. + /// + /// If skipFiltered is true, we will try to select another unfiltered beatmap in the same set. If the + /// entire set is filtered, no selection is made. /// /// The beatmap to select. /// Whether to skip selecting filtered beatmaps. - /// True if a selection was made, false if it was skipped. + /// True if a selection was made, False if it wasn't. public bool SelectBeatmap(BeatmapInfo beatmap, bool skipFiltered = false) { if (beatmap?.Hidden != false) return false; - var group = beatmapSets.FirstOrDefault(s => s.BeatmapSet.OnlineBeatmapSetID == beatmap.BeatmapSet.OnlineBeatmapSetID); + foreach (CarouselBeatmapSet set in beatmapSets) + { + if (skipFiltered && set.Filtered) + continue; - if (group == null || !skipFiltered && group.Filtered) - return false; + var item = set.Beatmaps.FirstOrDefault(p => p.Beatmap.Equals(beatmap)); - var item = group.Beatmaps.FirstOrDefault(p => p.Beatmap.Equals(beatmap)); + if (item == null) + // The beatmap that needs to be selected doesn't exist in this set + continue; - if (item == null || !skipFiltered && item.Filtered) - return false; + if (skipFiltered && item.Filtered) + // The beatmap exists in this set but is filtered, so look for the first unfiltered map in the set + item = set.Beatmaps.FirstOrDefault(b => !b.Filtered); - select(item); - return true; + if (item != null) + { + select(item); + return true; + } + } + + return false; } /// diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 2c8dcae3cf..461b17338d 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Linq; using System.Threading; using OpenTK; using OpenTK.Input; @@ -10,14 +9,12 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Overlays; @@ -66,8 +63,6 @@ namespace osu.Game.Screens.Select private SampleChannel sampleChangeDifficulty; private SampleChannel sampleChangeBeatmap; - private Bindable rulesetConversionAllowed; - private CancellationTokenSource initialAddSetsTask; private DependencyContainer dependencies; @@ -184,7 +179,7 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader(permitNulls: true)] - private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours, OsuConfigManager config) + private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours) { dependencies.CacheAs(this); @@ -199,8 +194,6 @@ namespace osu.Game.Screens.Select if (this.beatmaps == null) this.beatmaps = beatmaps; - rulesetConversionAllowed = config.GetBindable(OsuSetting.ShowConvertedBeatmaps); - if (osu != null) Ruleset.BindTo(osu.Ruleset); @@ -459,16 +452,14 @@ namespace osu.Game.Screens.Select private void carouselBeatmapsLoaded() { - if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false && Beatmap.Value.BeatmapSetInfo?.Protected == false) + if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false && Beatmap.Value.BeatmapSetInfo?.Protected == false && Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, true)) + return; + + if (Carousel.SelectedBeatmapSet == null && !Carousel.SelectNextRandom()) { - Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo); - } - else if (Carousel.SelectedBeatmapSet == null) - { - if (!Carousel.SelectNextRandom()) - // in the case random selection failed, we want to trigger selectionChanged - // to show the dummy beatmap (we have nothing else to display). - carouselSelectionChanged(null); + // in the case random selection failed, we want to trigger selectionChanged + // to show the dummy beatmap (we have nothing else to display). + carouselSelectionChanged(null); } } From 2c0488b1f17ac169632cc6e575318ddfd9d360e6 Mon Sep 17 00:00:00 2001 From: naoey Date: Fri, 9 Mar 2018 19:39:28 +0530 Subject: [PATCH 5/6] Invert bool, add test, and handle ruleset change. --- .../Visual/TestCaseBeatmapCarousel.cs | 42 ++++++++++++++++++- osu.Game/Screens/Select/BeatmapCarousel.cs | 10 ++--- osu.Game/Screens/Select/SongSelect.cs | 11 +++-- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index fe26366362..c68e548f44 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -12,6 +12,7 @@ using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Rulesets; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; @@ -22,6 +23,7 @@ namespace osu.Game.Tests.Visual public class TestCaseBeatmapCarousel : OsuTestCase { private TestBeatmapCarousel carousel; + private RulesetStore rulesets; public override IReadOnlyList RequiredTypes => new[] { @@ -46,8 +48,10 @@ namespace osu.Game.Tests.Visual private const int set_count = 5; [BackgroundDependencyLoader] - private void load() + private void load(RulesetStore rulesets) { + this.rulesets = rulesets; + Add(carousel = new TestBeatmapCarousel { RelativeSizeAxes = Axes.Both, @@ -75,6 +79,7 @@ namespace osu.Game.Tests.Visual testRemoveAll(); testEmptyTraversal(); testHiding(); + testSelectingFilteredRuleset(); } private void ensureRandomFetchSuccess() => @@ -363,6 +368,41 @@ namespace osu.Game.Tests.Visual } } + private void testSelectingFilteredRuleset() + { + var testMixed = createTestBeatmapSet(set_count + 1); + AddStep("add mixed ruleset beatmapset", () => + { + for (int i = 0; i <= 2; i++) + { + testMixed.Beatmaps[i].Ruleset = rulesets.AvailableRulesets.ElementAt(i); + testMixed.Beatmaps[i].RulesetID = i; + } + + carousel.UpdateBeatmapSet(testMixed); + }); + AddStep("filter to ruleset 0", () => + carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }, false)); + AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false)); + AddAssert("unfiltered beatmap selected", () => carousel.SelectedBeatmap.Equals(testMixed.Beatmaps[0])); + + AddStep("remove mixed set", () => + { + carousel.RemoveBeatmapSet(testMixed); + testMixed = null; + }); + var testSingle = createTestBeatmapSet(set_count + 2); + testSingle.Beatmaps.ForEach(b => + { + b.Ruleset = rulesets.AvailableRulesets.ElementAt(1); + b.RulesetID = b.Ruleset.ID ?? 1; + }); + AddStep("add single ruleset beatmapset", () => carousel.UpdateBeatmapSet(testSingle)); + AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testSingle.Beatmaps[0], false)); + checkNoSelection(); + AddStep("remove single ruleset set", () => carousel.RemoveBeatmapSet(testSingle)); + } + private BeatmapSetInfo createTestBeatmapSet(int id) { return new BeatmapSetInfo diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 287584bf2f..c2bb155753 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -172,20 +172,20 @@ namespace osu.Game.Screens.Select /// /// Selects a given beatmap on the carousel. /// - /// If skipFiltered is true, we will try to select another unfiltered beatmap in the same set. If the + /// If bypassFilters is false, we will try to select another unfiltered beatmap in the same set. If the /// entire set is filtered, no selection is made. /// /// The beatmap to select. - /// Whether to skip selecting filtered beatmaps. + /// Whether to select the beatmap even if it is filtered (i.e., not visible on carousel). /// True if a selection was made, False if it wasn't. - public bool SelectBeatmap(BeatmapInfo beatmap, bool skipFiltered = false) + public bool SelectBeatmap(BeatmapInfo beatmap, bool bypassFilters = true) { if (beatmap?.Hidden != false) return false; foreach (CarouselBeatmapSet set in beatmapSets) { - if (skipFiltered && set.Filtered) + if (!bypassFilters && set.Filtered) continue; var item = set.Beatmaps.FirstOrDefault(p => p.Beatmap.Equals(beatmap)); @@ -194,7 +194,7 @@ namespace osu.Game.Screens.Select // The beatmap that needs to be selected doesn't exist in this set continue; - if (skipFiltered && item.Filtered) + if (!bypassFilters && item.Filtered) // The beatmap exists in this set but is filtered, so look for the first unfiltered map in the set item = set.Beatmaps.FirstOrDefault(b => !b.Filtered); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 461b17338d..b12ab69edd 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -259,8 +259,13 @@ namespace osu.Game.Screens.Select protected void WorkingBeatmapChanged(WorkingBeatmap beatmap) { - if (IsCurrentScreen) - Carousel.SelectBeatmap(beatmap?.BeatmapInfo); + if (IsCurrentScreen && !Carousel.SelectBeatmap(beatmap?.BeatmapInfo, false)) + // If selecting new beatmap without bypassing filters failed, there's possibly a ruleset mismatch + if (beatmap?.BeatmapInfo?.Ruleset != null && beatmap.BeatmapInfo.Ruleset != Ruleset.Value) + { + Ruleset.Value = beatmap.BeatmapInfo.Ruleset; + Carousel.SelectBeatmap(beatmap.BeatmapInfo); + } } /// @@ -452,7 +457,7 @@ namespace osu.Game.Screens.Select private void carouselBeatmapsLoaded() { - if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false && Beatmap.Value.BeatmapSetInfo?.Protected == false && Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, true)) + if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false && Beatmap.Value.BeatmapSetInfo?.Protected == false && Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, false)) return; if (Carousel.SelectedBeatmapSet == null && !Carousel.SelectNextRandom()) From db2a663234aea7180f577a807fb879c00f4d4908 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 11 Mar 2018 01:26:03 +0900 Subject: [PATCH 6/6] Use private instead of protected --- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b12ab69edd..ca8a1cae41 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -214,7 +214,7 @@ namespace osu.Game.Screens.Select Beatmap.DisabledChanged += disabled => Carousel.AllowSelection = !disabled; Beatmap.TriggerChange(); - Beatmap.ValueChanged += WorkingBeatmapChanged; + Beatmap.ValueChanged += workingBeatmapChanged; } public void Edit(BeatmapInfo beatmap) @@ -257,7 +257,7 @@ namespace osu.Game.Screens.Select // We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds. private BeatmapInfo beatmapNoDebounce; - protected void WorkingBeatmapChanged(WorkingBeatmap beatmap) + private void workingBeatmapChanged(WorkingBeatmap beatmap) { if (IsCurrentScreen && !Carousel.SelectBeatmap(beatmap?.BeatmapInfo, false)) // If selecting new beatmap without bypassing filters failed, there's possibly a ruleset mismatch