diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 66f97f0f6956..75088fd203c0 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Filter; using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; @@ -454,6 +455,36 @@ public void TestBeatmapMustHaveAtLeastOneTagIfUserTagFilterActive() ClassicAssert.True(carouselItem.Filtered.Value); } + [Test] + [TestCase("rank=None", 0)] + [TestCase("rank=X", 1)] + [TestCase("rank=A")] + public void TestRankFilter(string query, params int[] expectedBeatmapIndexes) + { + var carouselBeatmaps = new[] + { + Guid.NewGuid(), + Guid.Empty + }.Select(info => new CarouselBeatmap(new BeatmapInfo + { + ID = info + })).ToList(); + + Dictionary localUserTopRanks = new Dictionary + { + { Guid.Empty, ScoreRank.X } + }; + + var criteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(criteria, query); + carouselBeatmaps.ForEach(b => b.Filter(criteria, localUserTopRanks)); + + int[] visibleBeatmaps = carouselBeatmaps + .Where(b => !b.Filtered.Value) + .Select(b => carouselBeatmaps.IndexOf(b)).ToArray(); + Assert.That(visibleBeatmaps, Is.EqualTo(expectedBeatmapIndexes)); + } + [Test] public void TestCustomRulesetCriteria([Values(null, true, false)] bool? matchCustomCriteria) { @@ -642,9 +673,9 @@ public CarouselBeatmap(BeatmapInfo beatmapInfo) BeatmapInfo = beatmapInfo; } - public void Filter(FilterCriteria criteria) + public void Filter(FilterCriteria criteria, IReadOnlyDictionary? localUserTopRanks = null) { - Filtered.Value = !BeatmapCarouselFilterMatching.CheckCriteriaMatch(BeatmapInfo, criteria); + Filtered.Value = !BeatmapCarouselFilterMatching.CheckCriteriaMatch(BeatmapInfo, criteria, localUserTopRanks ?? new Dictionary()); } } diff --git a/osu.Game/Scoring/ScoreRank.cs b/osu.Game/Scoring/ScoreRank.cs index 7e44e46471e4..4ab0db767408 100644 --- a/osu.Game/Scoring/ScoreRank.cs +++ b/osu.Game/Scoring/ScoreRank.cs @@ -8,6 +8,8 @@ namespace osu.Game.Scoring { public enum ScoreRank { + None = -2, + // TODO: Localisable? F = -1, diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 2cf8c7d2d041..c10791f512a8 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -102,7 +102,7 @@ public BeatmapCarousel() Filters = new ICarouselFilter[] { - new BeatmapCarouselFilterMatching(() => Criteria!), + new BeatmapCarouselFilterMatching(() => Criteria!, GetBeatmapInfoGuidToTopRankMapping), new BeatmapCarouselFilterSorting(() => Criteria!), grouping = new BeatmapCarouselFilterGrouping { diff --git a/osu.Game/Screens/Select/BeatmapCarouselFilterMatching.cs b/osu.Game/Screens/Select/BeatmapCarouselFilterMatching.cs index 5011166bb149..840175b47728 100644 --- a/osu.Game/Screens/Select/BeatmapCarouselFilterMatching.cs +++ b/osu.Game/Screens/Select/BeatmapCarouselFilterMatching.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using osu.Game.Beatmaps; using osu.Game.Graphics.Carousel; +using osu.Game.Scoring; using osu.Game.Utils; namespace osu.Game.Screens.Select @@ -15,22 +16,23 @@ namespace osu.Game.Screens.Select public class BeatmapCarouselFilterMatching : ICarouselFilter { private readonly Func getCriteria; - + private readonly Func> getLocalUserTopRanks; public int BeatmapItemsCount { get; private set; } - public BeatmapCarouselFilterMatching(Func getCriteria) + public BeatmapCarouselFilterMatching(Func getCriteria, Func> getLocalUserTopRanks) { this.getCriteria = getCriteria; + this.getLocalUserTopRanks = getLocalUserTopRanks; } public async Task> Run(IEnumerable items, CancellationToken cancellationToken) => await Task.Run(() => { var criteria = getCriteria(); - - return matchItems(items, criteria).ToList(); + var localUserTopRanks = getLocalUserTopRanks(criteria); + return matchItems(items, criteria, localUserTopRanks).ToList(); }, cancellationToken).ConfigureAwait(false); - private IEnumerable matchItems(IEnumerable items, FilterCriteria criteria) + private IEnumerable matchItems(IEnumerable items, FilterCriteria criteria, IReadOnlyDictionary localUserTopRanks) { int countMatching = 0; @@ -41,7 +43,7 @@ private IEnumerable matchItems(IEnumerable items, Fi if (beatmap.Hidden) continue; - if (!CheckCriteriaMatch(beatmap, criteria)) + if (!CheckCriteriaMatch(beatmap, criteria, localUserTopRanks)) continue; countMatching++; @@ -51,7 +53,7 @@ private IEnumerable matchItems(IEnumerable items, Fi BeatmapItemsCount = countMatching; } - public static bool CheckCriteriaMatch(BeatmapInfo beatmap, FilterCriteria criteria) + public static bool CheckCriteriaMatch(BeatmapInfo beatmap, FilterCriteria criteria, IReadOnlyDictionary localUserTopRanks) { bool match = criteria.Ruleset == null || beatmap.AllowGameplayWithRuleset(criteria.Ruleset!, criteria.AllowConvertedBeatmaps); @@ -92,6 +94,11 @@ public static bool CheckCriteriaMatch(BeatmapInfo beatmap, FilterCriteria criter match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(beatmap.BeatDivisor); match &= !criteria.OnlineStatus.HasFilter || criteria.OnlineStatus.IsInRange(beatmap.Status); + if (localUserTopRanks.TryGetValue(beatmap.ID, out ScoreRank scoreRank)) + match &= !criteria.Rank.HasFilter || criteria.Rank.IsInRange(scoreRank); + else + match &= !criteria.Rank.HasFilter || criteria.Rank.IsInRange(ScoreRank.None); + if (!match) return false; match &= !criteria.Creator.HasFilter || criteria.Creator.Matches(beatmap.Metadata.Author.Username); diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 1c70132850dd..5e555f8ebbb2 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Filter; using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; using osu.Game.Screens.Select.Filter; namespace osu.Game.Screens.Select @@ -40,6 +41,7 @@ public class FilterCriteria public OptionalTextFilter DifficultyName; public OptionalTextFilter Source; public List UserTags = []; + public OptionalSet Rank = new OptionalSet(); public OptionalRange UserStarDifficulty = new OptionalRange { diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 0adcf5d45421..31c81f55f188 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -125,6 +125,9 @@ private static bool tryParseKeywordCriteria(FilterCriteria criteria, string key, criteria.UserTags.Add(tagFilter); return true; + case "rank": + return TryUpdateCriteriaSet(ref criteria.Rank, op, value); + default: return criteria.RulesetCriteria?.TryParseCustomKeywordCriteria(key, op, value) ?? false; }