From eda489b911a1f8dbcf718f26eb21a396283f6870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 13 Jun 2025 08:58:01 +0200 Subject: [PATCH] Adjust implementation of star rating filtering in song select to new star rating flooring behaviour Required for things to not be broken after the previous change. --- .../Filtering/FilterQueryParserTest.cs | 34 ++++++++++++------- .../Select/Carousel/CarouselBeatmap.cs | 3 +- osu.Game/Screens/Select/FilterQueryParser.cs | 8 ++++- .../SelectV2/BeatmapCarouselFilterMatching.cs | 3 +- osu.Game/Utils/FormatUtils.cs | 14 ++++---- 5 files changed, 41 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index f4e324d7ba..37b7d71d2b 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -85,16 +85,6 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.That(filterCriteria.SearchTerms[0].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.IsolatedPhrase)); } - /* - * The following tests have been written a bit strangely (they don't check exact - * bound equality with what the filter says). - * This is to account for floating-point arithmetic issues. - * For example, specifying a bpm<140 filter would previously match beatmaps with BPM - * of 139.99999, which would be displayed in the UI as 140. - * Due to this the tests check the last tick inside the range and the first tick - * outside of the range. - */ - [TestCase("star")] [TestCase("stars")] public void TestApplyStarQueries(string variant) @@ -105,11 +95,31 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual("easy", filterCriteria.SearchText.Trim()); Assert.AreEqual(1, filterCriteria.SearchTerms.Length); Assert.IsNotNull(filterCriteria.StarDifficulty.Max); - Assert.Greater(filterCriteria.StarDifficulty.Max, 3.99d); - Assert.Less(filterCriteria.StarDifficulty.Max, 4.00d); + Assert.AreEqual(filterCriteria.StarDifficulty.Max, 4.00d); Assert.IsNull(filterCriteria.StarDifficulty.Min); } + [Test] + public void TestStarQueriesInclusive() + { + const string query = $"stars>=6"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(filterCriteria.StarDifficulty.Min, 6.00d); + Assert.True(filterCriteria.StarDifficulty.IsLowerInclusive); + Assert.IsNull(filterCriteria.StarDifficulty.Max); + } + + /* + * The following tests have been written a bit strangely (they don't check exact + * bound equality with what the filter says). + * This is to account for floating-point arithmetic issues. + * For example, specifying a bpm<140 filter would previously match beatmaps with BPM + * of 139.99999, which would be displayed in the UI as 140. + * Due to this the tests check the last tick inside the range and the first tick + * outside of the range. + */ + [Test] public void TestApplyApproachRateQueries() { diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index dc77b0101e..02b5eb5b7a 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Screens.Select.Filter; +using osu.Game.Utils; namespace osu.Game.Screens.Select.Carousel { @@ -59,7 +60,7 @@ namespace osu.Game.Screens.Select.Carousel if (!match) return false; - match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(BeatmapInfo.StarRating); + match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(BeatmapInfo.StarRating.FloorToDecimalDigits(2)); match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(BeatmapInfo.Difficulty.ApproachRate); match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(BeatmapInfo.Difficulty.DrainRate); match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(BeatmapInfo.Difficulty.CircleSize); diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 1094d88730..02a6da146e 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Select case "star": case "stars": case "sr": - return TryUpdateCriteriaRange(ref criteria.StarDifficulty, op, value, 0.01d / 2); + return TryUpdateCriteriaRange(ref criteria.StarDifficulty, op, value, 0); case "ar": return TryUpdateCriteriaRange(ref criteria.ApproachRate, op, value); @@ -309,6 +309,8 @@ namespace osu.Game.Screens.Select case Operator.Equal: range.Min = value - tolerance; range.Max = value + tolerance; + if (tolerance == 0) + range.IsLowerInclusive = range.IsUpperInclusive = true; break; case Operator.Greater: @@ -317,6 +319,8 @@ namespace osu.Game.Screens.Select case Operator.GreaterOrEqual: range.Min = value - tolerance; + if (tolerance == 0) + range.IsLowerInclusive = true; break; case Operator.Less: @@ -325,6 +329,8 @@ namespace osu.Game.Screens.Select case Operator.LessOrEqual: range.Max = value + tolerance; + if (tolerance == 0) + range.IsUpperInclusive = true; break; } diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterMatching.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterMatching.cs index a776b2f796..f2f246093d 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterMatching.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterMatching.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using osu.Game.Beatmaps; using osu.Game.Graphics.Carousel; using osu.Game.Screens.Select; +using osu.Game.Utils; namespace osu.Game.Screens.SelectV2 { @@ -81,7 +82,7 @@ namespace osu.Game.Screens.SelectV2 if (!match) return false; - match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(beatmap.StarRating); + match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(beatmap.StarRating.FloorToDecimalDigits(2)); match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(beatmap.Difficulty.ApproachRate); match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(beatmap.Difficulty.DrainRate); match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(beatmap.Difficulty.CircleSize); diff --git a/osu.Game/Utils/FormatUtils.cs b/osu.Game/Utils/FormatUtils.cs index fa7d6595e9..28776ea0bf 100644 --- a/osu.Game/Utils/FormatUtils.cs +++ b/osu.Game/Utils/FormatUtils.cs @@ -10,6 +10,12 @@ namespace osu.Game.Utils { public static class FormatUtils { + public static double FloorToDecimalDigits(this double value, uint digits) + { + double base10 = Math.Pow(10, digits); + return Math.Floor(value * base10) / base10; + } + /// /// Turns the provided accuracy into a percentage with 2 decimal places. /// @@ -21,9 +27,7 @@ namespace osu.Game.Utils // ie. a score which gets 89.99999% shouldn't ever show as 90%. // the reasoning for this is that cutoffs for grade increases are at whole numbers and displaying the required // percentile with a non-matching grade is confusing. - accuracy = Math.Floor(accuracy * 10000) / 10000; - - return accuracy.ToLocalisableString("0.00%"); + return accuracy.FloorToDecimalDigits(4).ToLocalisableString("0.00%"); } /// @@ -42,9 +46,7 @@ namespace osu.Game.Utils // i.e. a beatmap which has a star rating of 6.9999* should never show as 7.00*. // this matters for star rating medals which use hard cutoffs at whole numbers, // which then confuses users when they beat a 6.9999* beatmap but don't get the 7-star medal. - starRating = Math.Floor(starRating * 100) / 100; - - return starRating.ToLocalisableString("0.00"); + return starRating.FloorToDecimalDigits(2).ToLocalisableString("0.00"); } ///