diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index fe2636636219..c68e548f449e 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -12,6 +12,7 @@ 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 @@ public class TestCaseBeatmapCarousel : OsuTestCase 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 @@ private void load() testRemoveAll(); testEmptyTraversal(); testHiding(); + testSelectingFilteredRuleset(); } private void ensureRandomFetchSuccess() => @@ -363,6 +368,41 @@ void setHidden(int diff, bool hidden = true) } } + 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 9793440348cf..c2bb155753e0 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -169,20 +169,43 @@ public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) }); } - public void SelectBeatmap(BeatmapInfo beatmap) + /// + /// Selects a given beatmap on the carousel. + /// + /// 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 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 bypassFilters = true) { if (beatmap?.Hidden != false) - return; + return false; - foreach (CarouselBeatmapSet group in beatmapSets) + foreach (CarouselBeatmapSet set in beatmapSets) { - var item = group.Beatmaps.FirstOrDefault(p => p.Beatmap.Equals(beatmap)); + if (!bypassFilters && set.Filtered) + continue; + + var item = set.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 (!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); + if (item != null) { select(item); - return; + return true; } } + + return false; } /// diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 43d7cb637a88..ca8a1cae4179 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -214,11 +214,7 @@ private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dia Beatmap.DisabledChanged += disabled => Carousel.AllowSelection = !disabled; Beatmap.TriggerChange(); - Beatmap.ValueChanged += b => - { - if (IsCurrentScreen) - Carousel.SelectBeatmap(b?.BeatmapInfo); - }; + Beatmap.ValueChanged += workingBeatmapChanged; } public void Edit(BeatmapInfo beatmap) @@ -261,6 +257,17 @@ public void FinaliseSelection(BeatmapInfo beatmap = null) // We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds. private BeatmapInfo beatmapNoDebounce; + 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 + if (beatmap?.BeatmapInfo?.Ruleset != null && beatmap.BeatmapInfo.Ruleset != Ruleset.Value) + { + Ruleset.Value = beatmap.BeatmapInfo.Ruleset; + Carousel.SelectBeatmap(beatmap.BeatmapInfo); + } + } + /// /// selection has been changed as the result of interaction with the carousel. /// @@ -450,16 +457,14 @@ private void ensurePlayingSelected(bool preview = false) private void carouselBeatmapsLoaded() { - if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false && Beatmap.Value.BeatmapSetInfo?.Protected == false) - { - Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo); - } - else if (Carousel.SelectedBeatmapSet == null) + 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()) { - 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); } }