diff --git a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs index a9f3e70e1d..939a5e6e7c 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs @@ -11,6 +11,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Graphics.Carousel; +using osu.Game.Scoring; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; using osu.Game.Screens.SelectV2; @@ -364,7 +365,11 @@ namespace osu.Game.Tests.Visual.SongSelectV2 private static async Task> runGrouping(GroupMode group, List beatmapSets) { - var groupingFilter = new BeatmapCarouselFilterGrouping(() => new FilterCriteria { Group = group }, () => new List()); + var groupingFilter = new BeatmapCarouselFilterGrouping( + () => new FilterCriteria { Group = group }, + () => new List(), + _ => new Dictionary()); + return await groupingFilter.Run(beatmapSets.SelectMany(s => s.Beatmaps.Select(b => new CarouselItem(b))).ToList(), CancellationToken.None); } diff --git a/osu.Game.Tests/Visual/SongSelectV2/SongSelectTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/SongSelectTestScene.cs index bbd5be3e3e..b1d1ed8c61 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/SongSelectTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/SongSelectTestScene.cs @@ -161,9 +161,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2 protected void WaitForFiltering() => AddUntilStep("wait for filtering", () => !SongSelect.IsFiltering); - protected void ImportBeatmapForRuleset(params int[] rulesetIds) => ImportBeatmapForRuleset(_ => { }, rulesetIds); + protected void ImportBeatmapForRuleset(params int[] rulesetIds) => ImportBeatmapForRuleset(_ => { }, 3, rulesetIds); - protected void ImportBeatmapForRuleset(Action applyToBeatmap, params int[] rulesetIds) + protected void ImportBeatmapForRuleset(Action applyToBeatmap, int difficultyCount, params int[] rulesetIds) { int beatmapsCount = 0; @@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { beatmapsCount = SongSelect.IsNull() ? 0 : Carousel.Filters.OfType().Single().SetItems.Count; - var beatmapSet = TestResources.CreateTestBeatmapSetInfo(3, Rulesets.AvailableRulesets.Where(r => rulesetIds.Contains(r.OnlineID)).ToArray()); + var beatmapSet = TestResources.CreateTestBeatmapSetInfo(difficultyCount, Rulesets.AvailableRulesets.Where(r => rulesetIds.Contains(r.OnlineID)).ToArray()); applyToBeatmap(beatmapSet); Beatmaps.Import(beatmapSet); }); diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectGrouping.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectGrouping.cs index 38af74c4b9..0f7c42946d 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectGrouping.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectGrouping.cs @@ -1,20 +1,31 @@ // 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.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Extensions; using osu.Game.Models; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Scoring; using osu.Game.Screens.Select.Filter; using osu.Game.Screens.SelectV2; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.SongSelectV2 { + /// + /// Test suite for grouping modes which require the presence of API / realm. + /// All other grouping modes are tested separately in . + /// public partial class TestSceneSongSelectGrouping : SongSelectTestScene { private BeatmapCarouselFilterGrouping grouping => Carousel.Filters.OfType().Single(); @@ -50,25 +61,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2 GroupBy(GroupMode.Collections); WaitForFiltering(); - AddAssert("first collection present", () => - { - var group = grouping.GroupItems.Single(g => g.Key.Title == "My Collection #1"); - return group.Value.Select(i => i.Model).OfType().Single().Equals(beatmapSets[0]); - }); - - AddAssert("second collection present", () => - { - var group = grouping.GroupItems.Single(g => g.Key.Title == "My Collection #2"); - return group.Value.Select(i => i.Model).OfType().Single().Equals(beatmapSets[1]); - }); - - AddAssert("third collection not present", () => grouping.GroupItems.All(g => g.Key.Title != "My Collection #3")); - - AddAssert("no-collection group present", () => - { - var group = grouping.GroupItems.Single(g => g.Key.Title == "Not in collection"); - return group.Value.Select(i => i.Model).OfType().Single().Equals(beatmapSets[2]); - }); + assertGroupPresent("My Collection #1", () => new[] { beatmapSets[0] }); + assertGroupPresent("My Collection #2", () => new[] { beatmapSets[1] }); + assertGroupPresent("Not in collection", () => new[] { beatmapSets[2] }); + assertGroupsCount(3); } [Test] @@ -112,13 +108,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2 WaitForFiltering(); - AddAssert("collection present", () => - { - var group = grouping.GroupItems.Single(g => g.Key.Title == "My Collection #4"); - return group.Value.Select(i => i.Model).OfType().Single().Equals(beatmapSet); - }); - - AddAssert("no-collection group not present", () => grouping.GroupItems.All(g => g.Key.Title != "Not in collection")); + assertGroupPresent("My Collection #4", () => new[] { beatmapSet }); + assertGroupsCount(1); } #endregion @@ -128,9 +119,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2 [Test] public void TestMyMapsGrouping() { - ImportBeatmapForRuleset(s => ((RealmUser)s.Metadata.Author).Username = "user1", 0); - ImportBeatmapForRuleset(s => ((RealmUser)s.Metadata.Author).Username = "user2", 0); - ImportBeatmapForRuleset(s => ((RealmUser)s.Metadata.Author).Username = "user3", 0); + ImportBeatmapForRuleset(s => ((RealmUser)s.Metadata.Author).Username = "user1", 3, 0); + ImportBeatmapForRuleset(s => ((RealmUser)s.Metadata.Author).Username = "user2", 3, 0); + ImportBeatmapForRuleset(s => ((RealmUser)s.Metadata.Author).Username = "user3", 3, 0); BeatmapSetInfo[] beatmapSets = null!; @@ -146,11 +137,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2 GroupBy(GroupMode.MyMaps); WaitForFiltering(); - AddAssert("'my maps' present", () => - { - var group = grouping.GroupItems.Single(); - return group.Key.Title == "My maps" && group.Value.Select(i => i.Model).OfType().Single().Equals(beatmapSets[0]); - }); + assertGroupPresent("My maps", () => new[] { beatmapSets[0] }); + assertGroupsCount(1); } [Test] @@ -160,9 +148,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { ((RealmUser)s.Metadata.Author).Username = "user1_old"; ((RealmUser)s.Metadata.Author).OnlineID = DummyAPIAccess.DUMMY_USER_ID; - }, 0); - ImportBeatmapForRuleset(s => ((RealmUser)s.Metadata.Author).Username = "user2", 0); - ImportBeatmapForRuleset(s => ((RealmUser)s.Metadata.Author).Username = "user3", 0); + }, 3, 0); + ImportBeatmapForRuleset(s => ((RealmUser)s.Metadata.Author).Username = "user2", 3, 0); + ImportBeatmapForRuleset(s => ((RealmUser)s.Metadata.Author).Username = "user3", 3, 0); BeatmapSetInfo[] beatmapSets = null!; @@ -178,19 +166,16 @@ namespace osu.Game.Tests.Visual.SongSelectV2 GroupBy(GroupMode.MyMaps); WaitForFiltering(); - AddAssert("'my maps' present", () => - { - var group = grouping.GroupItems.Single(); - return group.Key.Title == "My maps" && group.Value.Select(i => i.Model).OfType().Single().Equals(beatmapSets[0]); - }); + assertGroupPresent("My maps", () => new[] { beatmapSets[0] }); + assertGroupsCount(1); } [Test] public void TestMyMapsGroupingUpdatesOnUserChange() { - ImportBeatmapForRuleset(s => ((RealmUser)s.Metadata.Author).Username = "user1", 0); - ImportBeatmapForRuleset(s => ((RealmUser)s.Metadata.Author).Username = "user2", 0); - ImportBeatmapForRuleset(s => ((RealmUser)s.Metadata.Author).Username = new GuestUser().Username, 0); + ImportBeatmapForRuleset(s => ((RealmUser)s.Metadata.Author).Username = "user1", 3, 0); + ImportBeatmapForRuleset(s => ((RealmUser)s.Metadata.Author).Username = "user2", 3, 0); + ImportBeatmapForRuleset(s => ((RealmUser)s.Metadata.Author).Username = new GuestUser().Username, 3, 0); BeatmapSetInfo[] beatmapSets = null!; @@ -213,17 +198,144 @@ namespace osu.Game.Tests.Visual.SongSelectV2 WaitForFiltering(); - AddAssert("'my maps' present", () => - { - var group = grouping.GroupItems.Single(); - return group.Key.Title == "My maps" && group.Value.Select(i => i.Model).OfType().Single().Equals(beatmapSets[1]); - }); + assertGroupPresent("My maps", () => new[] { beatmapSets[1] }); + assertGroupsCount(1); } #endregion + #region Rank Achieved grouping + + [Test] + public void TestRankAchievedGrouping() + { + ImportBeatmapForRuleset(_ => { }, 1, 0); + ImportBeatmapForRuleset(_ => { }, 1, 0); + ImportBeatmapForRuleset(_ => { }, 1, 0); + ImportBeatmapForRuleset(_ => { }, 1, 0); + ImportBeatmapForRuleset(_ => { }, 1, 0); + + AddStep("log in", () => + { + API.Login("user1", string.Empty); // match username in test scores. + API.AuthenticateSecondFactor("abcdefgh"); + }); + + BeatmapSetInfo[] beatmapSets = null!; + + AddStep("add scores", () => + { + beatmapSets = Beatmaps.GetAllUsableBeatmapSets().OrderBy(b => b.OnlineID).ToArray(); + + ScoreManager.Import(createTestScoreInfo(beatmapSets[0].Beatmaps[0], ScoreRank.SH)); + ScoreManager.Import(createTestScoreInfo(beatmapSets[1].Beatmaps[0], ScoreRank.A)); + ScoreManager.Import(createTestScoreInfo(beatmapSets[2].Beatmaps[0], ScoreRank.C)); + + // score belonging to another user on an unplayed beatmap. + ScoreManager.Import(createTestScoreInfo(beatmapSets[3].Beatmaps[0], ScoreRank.XH, s => s.User = new APIUser { Id = 1337, Username = "user2" })); + + // score belonging to another user on a played beatmap. + ScoreManager.Import(createTestScoreInfo(beatmapSets[0].Beatmaps[0], ScoreRank.XH, s => s.User = new APIUser { Id = 1337, Username = "user2" })); + + // score belonging to local user but with less rank. + ScoreManager.Import(createTestScoreInfo(beatmapSets[0].Beatmaps[0], ScoreRank.D)); + }); + + LoadSongSelect(); + GroupBy(GroupMode.RankAchieved); + WaitForFiltering(); + + assertGroupPresent("S+", () => new[] { beatmapSets[0] }); + assertGroupPresent("A", () => new[] { beatmapSets[1] }); + assertGroupPresent("C", () => new[] { beatmapSets[2] }); + assertGroupPresent("Unplayed", () => new[] { beatmapSets[3], beatmapSets[4] }); + assertGroupsCount(4); + } + + #endregion + + #region Benchmarks + + [Test] + [Explicit("Manual benchmark")] + public void TestPerformance() + { + const int sets_count = 100; + const int diffs_count = 100; + + AddStep("log in", () => + { + API.Login("user1", string.Empty); // match username in test scores. + API.AuthenticateSecondFactor("abcdefgh"); + }); + + int count = 0; + + AddStep("populate database", () => + { + count = 0; + + Task.Factory.StartNew(() => + { + for (int i = 0; i < sets_count; i++) + { + var liveSet = Beatmaps.Import(TestResources.CreateTestBeatmapSetInfo(diffs_count, Rulesets.AvailableRulesets.ToArray()))!; + + liveSet.PerformRead(s => + { + foreach (var beatmap in s.Beatmaps + .GroupBy(b => b.Ruleset.OnlineID) + .Select(g => g.OrderBy(_ => RNG.Next()).Take(4)) // take 4 difficulties from each ruleset randomly + .SelectMany(g => g)) + { + for (int k = 0; k < 3; k++) // create 3 scores per difficulty + ScoreManager.Import(createTestScoreInfo(beatmap)); + } + }); + + count++; + } + }, TaskCreationOptions.LongRunning); + }); + + AddUntilStep("wait for population", () => count, () => Is.GreaterThan(sets_count / 3)); + AddUntilStep("this takes a while", () => count, () => Is.GreaterThan(sets_count / 3 * 2)); + AddUntilStep("maybe they are done now", () => count, () => Is.EqualTo(sets_count)); + + LoadSongSelect(); + } + + #endregion + + private void assertGroupsCount(int expected) + { + AddAssert($"groups = {expected}", () => grouping.GroupItems, () => Has.Count.EqualTo(expected)); + } + + private void assertGroupPresent(string name, Func> getBeatmaps) + { + AddAssert($"\"{name}\" present", () => + { + var group = grouping.GroupItems.Single(g => g.Key.Title == name); + var actualBeatmaps = group.Value.Select(i => i.Model).OfType().OrderBy(b => b.ID); + var expectedBeatmaps = getBeatmaps().SelectMany(s => s.Beatmaps).OrderBy(b => b.ID); + return actualBeatmaps.SequenceEqual(expectedBeatmaps); + }); + } + private NoResultsPlaceholder? getPlaceholder() => SongSelect.ChildrenOfType().FirstOrDefault(); private void checkMatchedBeatmaps(int expected) => AddUntilStep($"{expected} matching shown", () => Carousel.MatchedBeatmapsCount, () => Is.EqualTo(expected)); + + private ScoreInfo createTestScoreInfo(BeatmapInfo beatmap, ScoreRank? rank = null, Action? applyToScore = null) + { + var score = TestResources.CreateTestScoreInfo(beatmap); + score.User = API.LocalUser.Value; + score.Rank = rank ?? Enum.GetValues().MinBy(_ => RNG.Next()); + score.TotalScore = (long)(((double)score.Rank + 1) / (Enum.GetValues().Length + 1) * 1000000); + score.Date = DateTimeOffset.Now; + applyToScore?.Invoke(score); + return score; + } } } diff --git a/osu.Game/Online/Leaderboards/UpdateableRank.cs b/osu.Game/Online/Leaderboards/UpdateableRank.cs index b64fab6861..ea5a985ef7 100644 --- a/osu.Game/Online/Leaderboards/UpdateableRank.cs +++ b/osu.Game/Online/Leaderboards/UpdateableRank.cs @@ -11,7 +11,9 @@ namespace osu.Game.Online.Leaderboards { public partial class UpdateableRank : ModelBackedDrawable { - protected override double TransformDuration => 600; + private readonly bool animate; + + protected override double TransformDuration => animate ? 600 : 0; protected override bool TransformImmediately => true; public ScoreRank? Rank @@ -20,8 +22,10 @@ namespace osu.Game.Online.Leaderboards set => Model = value; } - public UpdateableRank(ScoreRank? rank = null) + public UpdateableRank(ScoreRank? rank = null, bool animate = true) { + this.animate = animate; + Rank = rank; } @@ -58,7 +62,6 @@ namespace osu.Game.Online.Leaderboards protected override TransformSequence ApplyHideTransforms(Drawable drawable) { drawable.ScaleTo(1.8f, TransformDuration, Easing.Out); - return base.ApplyHideTransforms(drawable); } } diff --git a/osu.Game/Screens/Select/Filter/GroupMode.cs b/osu.Game/Screens/Select/Filter/GroupMode.cs index 9ce5b36202..06d3a71b0f 100644 --- a/osu.Game/Screens/Select/Filter/GroupMode.cs +++ b/osu.Game/Screens/Select/Filter/GroupMode.cs @@ -44,8 +44,8 @@ namespace osu.Game.Screens.Select.Filter [LocalisableDescription(typeof(SongSelectStrings), nameof(SongSelectStrings.MyMaps))] MyMaps, - // [LocalisableDescription(typeof(SongSelectStrings), nameof(SongSelectStrings.RankAchieved))] - // RankAchieved, + [LocalisableDescription(typeof(SongSelectStrings), nameof(SongSelectStrings.RankAchieved))] + RankAchieved, [LocalisableDescription(typeof(SongSelectStrings), nameof(SongSelectStrings.RankedStatus))] RankedStatus, diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index d67fd5e23e..bdb0f86d85 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -24,7 +24,11 @@ using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Carousel; using osu.Game.Graphics.UserInterface; +using osu.Game.Models; +using osu.Game.Rulesets; +using osu.Game.Scoring; using osu.Game.Screens.Select; +using Realms; namespace osu.Game.Screens.SelectV2 { @@ -98,7 +102,7 @@ namespace osu.Game.Screens.SelectV2 { new BeatmapCarouselFilterMatching(() => Criteria!), new BeatmapCarouselFilterSorting(() => Criteria!), - grouping = new BeatmapCarouselFilterGrouping(() => Criteria!, () => detachedCollections()) + grouping = new BeatmapCarouselFilterGrouping(() => Criteria!, getDetachedCollections, getTopRanksMapping) }; AddInternal(loading = new LoadingLayer()); @@ -109,7 +113,6 @@ namespace osu.Game.Screens.SelectV2 { setupPools(); detachedBeatmaps = beatmapStore.GetBeatmapSets(cancellationToken); - detachedCollections = () => realm.Run(r => r.All().AsEnumerable().Detach()); loadSamples(audio); config.BindWith(OsuSetting.RandomSelectAlgorithm, randomAlgorithm); @@ -622,6 +625,40 @@ namespace osu.Game.Screens.SelectV2 #endregion + #region Database fetches for grouping support + + [Resolved] + private RealmAccess realm { get; set; } = null!; + + private List getDetachedCollections() => realm.Run(r => r.All().AsEnumerable().Detach()); + + private Dictionary getTopRanksMapping(FilterCriteria criteria) => realm.Run(r => + { + var topRankMapping = new Dictionary(); + + var allLocalScores = r.All() + .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.Hash)} == {nameof(ScoreInfo.BeatmapHash)}" + + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $1" + + $" && {nameof(ScoreInfo.DeletePending)} == false", criteria.LocalUserId, criteria.Ruleset?.ShortName) + .OrderByDescending(s => s.TotalScore) + .ThenBy(s => s.Date); + + foreach (var score in allLocalScores) + { + Debug.Assert(score.BeatmapInfo != null); + + if (topRankMapping.ContainsKey(score.BeatmapInfo.ID)) + continue; + + topRankMapping[score.BeatmapInfo.ID] = score.Rank; + } + + return topRankMapping; + }); + + #endregion + #region Drawable pooling private readonly DrawablePool beatmapPanelPool = new DrawablePool(100); @@ -697,8 +734,6 @@ namespace osu.Game.Screens.SelectV2 private Sample? spinSample; private Sample? randomSelectSample; - private Func> detachedCollections = null!; - public bool NextRandom() { var carouselItems = GetCarouselItems(); diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs index 5048e4a7b5..6be620899b 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs @@ -10,6 +10,7 @@ using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Graphics.Carousel; +using osu.Game.Scoring; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; using osu.Game.Utils; @@ -39,12 +40,14 @@ namespace osu.Game.Screens.SelectV2 private Dictionary> groupMap = new Dictionary>(); private readonly Func getCriteria; - private readonly Func>? getCollections; + private readonly Func> getCollections; + private readonly Func> getLocalUserTopRanks; - public BeatmapCarouselFilterGrouping(Func getCriteria, Func>? getCollections) + public BeatmapCarouselFilterGrouping(Func getCriteria, Func> getCollections, Func> getLocalUserTopRanks) { this.getCriteria = getCriteria; this.getCollections = getCollections; + this.getLocalUserTopRanks = getLocalUserTopRanks; } public async Task> Run(IEnumerable items, CancellationToken cancellationToken) @@ -152,6 +155,8 @@ namespace osu.Game.Screens.SelectV2 return false; if (criteria.Sort == SortMode.LastPlayed && criteria.Group == GroupMode.LastPlayed) return false; + if (criteria.Group == GroupMode.RankAchieved) + return false; // In the majority case we group sets together for display. return true; @@ -185,7 +190,7 @@ namespace osu.Game.Screens.SelectV2 var date = b.LastPlayed; if (BeatmapSetsGroupedTogether) - date = aggregateMax(b, static b => (b.LastPlayed ?? DateTimeOffset.MinValue)); + date = aggregateMax(b, static b => b.LastPlayed ?? DateTimeOffset.MinValue); if (date == null || date == DateTimeOffset.MinValue) return new GroupDefinition(int.MaxValue, "Never"); @@ -225,18 +230,23 @@ namespace osu.Game.Screens.SelectV2 return getGroupsBy(b => defineGroupBySource(b.BeatmapSet!.Metadata.Source), items); case GroupMode.Collections: - var collections = getCollections?.Invoke() ?? Enumerable.Empty(); + { + var collections = getCollections(); return getGroupsBy(b => defineGroupByCollection(b, collections), items); + } case GroupMode.MyMaps: return getGroupsBy(b => defineGroupByOwnMaps(b, criteria.LocalUserId, criteria.LocalUserUsername), items); + case GroupMode.RankAchieved: + { + var topRankMapping = getLocalUserTopRanks(criteria); + return getGroupsBy(b => defineGroupByRankAchieved(b, topRankMapping), items); + } + // TODO: need implementation // case GroupMode.Favourites: // goto case GroupMode.None; - // - // case GroupMode.RankAchieved: - // goto case GroupMode.None; default: throw new ArgumentOutOfRangeException(); @@ -415,6 +425,14 @@ namespace osu.Game.Screens.SelectV2 return null; } + private GroupDefinition defineGroupByRankAchieved(BeatmapInfo beatmap, IReadOnlyDictionary topRankMapping) + { + if (topRankMapping.TryGetValue(beatmap.ID, out var rank)) + return new GroupDefinition(-(int)rank, rank.GetDescription()); + + return new GroupDefinition(int.MaxValue, "Unplayed"); + } + private static T? aggregateMax(BeatmapInfo b, Func func) { var beatmaps = b.BeatmapSet!.Beatmaps.Where(bb => !bb.Hidden); diff --git a/osu.Game/Screens/SelectV2/PanelLocalRankDisplay.cs b/osu.Game/Screens/SelectV2/PanelLocalRankDisplay.cs index 130c1cd05a..273f995794 100644 --- a/osu.Game/Screens/SelectV2/PanelLocalRankDisplay.cs +++ b/osu.Game/Screens/SelectV2/PanelLocalRankDisplay.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.SelectV2 { AutoSizeAxes = Axes.Both; - InternalChild = updateable = new UpdateableRank + InternalChild = updateable = new UpdateableRank(animate: false) { Size = new Vector2(40, 20), Alpha = 0,