mirror of
https://github.com/ppy/osu.git
synced 2026-05-25 02:29:54 +08:00
Merge pull request #34548 from frenzibyte/rank-achieved-grouping
Implement "rank achieved" grouping mode
This commit is contained in:
@@ -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<List<CarouselItem>> runGrouping(GroupMode group, List<BeatmapSetInfo> beatmapSets)
|
||||
{
|
||||
var groupingFilter = new BeatmapCarouselFilterGrouping(() => new FilterCriteria { Group = group }, () => new List<BeatmapCollection>());
|
||||
var groupingFilter = new BeatmapCarouselFilterGrouping(
|
||||
() => new FilterCriteria { Group = group },
|
||||
() => new List<BeatmapCollection>(),
|
||||
_ => new Dictionary<Guid, ScoreRank>());
|
||||
|
||||
return await groupingFilter.Run(beatmapSets.SelectMany(s => s.Beatmaps.Select(b => new CarouselItem(b))).ToList(), CancellationToken.None);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<BeatmapSetInfo> applyToBeatmap, params int[] rulesetIds)
|
||||
protected void ImportBeatmapForRuleset(Action<BeatmapSetInfo> 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<BeatmapCarouselFilterGrouping>().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);
|
||||
});
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Test suite for grouping modes which require the presence of API / realm.
|
||||
/// All other grouping modes are tested separately in <see cref="BeatmapCarouselFilterGroupingTest"/>.
|
||||
/// </summary>
|
||||
public partial class TestSceneSongSelectGrouping : SongSelectTestScene
|
||||
{
|
||||
private BeatmapCarouselFilterGrouping grouping => Carousel.Filters.OfType<BeatmapCarouselFilterGrouping>().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<BeatmapSetInfo>().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<BeatmapSetInfo>().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<BeatmapSetInfo>().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<BeatmapSetInfo>().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<BeatmapSetInfo>().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<BeatmapSetInfo>().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<BeatmapSetInfo>().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<IEnumerable<BeatmapSetInfo>> getBeatmaps)
|
||||
{
|
||||
AddAssert($"\"{name}\" present", () =>
|
||||
{
|
||||
var group = grouping.GroupItems.Single(g => g.Key.Title == name);
|
||||
var actualBeatmaps = group.Value.Select(i => i.Model).OfType<BeatmapInfo>().OrderBy(b => b.ID);
|
||||
var expectedBeatmaps = getBeatmaps().SelectMany(s => s.Beatmaps).OrderBy(b => b.ID);
|
||||
return actualBeatmaps.SequenceEqual(expectedBeatmaps);
|
||||
});
|
||||
}
|
||||
|
||||
private NoResultsPlaceholder? getPlaceholder() => SongSelect.ChildrenOfType<NoResultsPlaceholder>().FirstOrDefault();
|
||||
|
||||
private void checkMatchedBeatmaps(int expected) => AddUntilStep($"{expected} matching shown", () => Carousel.MatchedBeatmapsCount, () => Is.EqualTo(expected));
|
||||
|
||||
private ScoreInfo createTestScoreInfo(BeatmapInfo beatmap, ScoreRank? rank = null, Action<ScoreInfo>? applyToScore = null)
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo(beatmap);
|
||||
score.User = API.LocalUser.Value;
|
||||
score.Rank = rank ?? Enum.GetValues<ScoreRank>().MinBy(_ => RNG.Next());
|
||||
score.TotalScore = (long)(((double)score.Rank + 1) / (Enum.GetValues<ScoreRank>().Length + 1) * 1000000);
|
||||
score.Date = DateTimeOffset.Now;
|
||||
applyToScore?.Invoke(score);
|
||||
return score;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ namespace osu.Game.Online.Leaderboards
|
||||
{
|
||||
public partial class UpdateableRank : ModelBackedDrawable<ScoreRank?>
|
||||
{
|
||||
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<Drawable> ApplyHideTransforms(Drawable drawable)
|
||||
{
|
||||
drawable.ScaleTo(1.8f, TransformDuration, Easing.Out);
|
||||
|
||||
return base.ApplyHideTransforms(drawable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<BeatmapCollection>().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<BeatmapCollection> getDetachedCollections() => realm.Run(r => r.All<BeatmapCollection>().AsEnumerable().Detach());
|
||||
|
||||
private Dictionary<Guid, ScoreRank> getTopRanksMapping(FilterCriteria criteria) => realm.Run(r =>
|
||||
{
|
||||
var topRankMapping = new Dictionary<Guid, ScoreRank>();
|
||||
|
||||
var allLocalScores = r.All<ScoreInfo>()
|
||||
.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<PanelBeatmap> beatmapPanelPool = new DrawablePool<PanelBeatmap>(100);
|
||||
@@ -697,8 +734,6 @@ namespace osu.Game.Screens.SelectV2
|
||||
private Sample? spinSample;
|
||||
private Sample? randomSelectSample;
|
||||
|
||||
private Func<List<BeatmapCollection>> detachedCollections = null!;
|
||||
|
||||
public bool NextRandom()
|
||||
{
|
||||
var carouselItems = GetCarouselItems();
|
||||
|
||||
@@ -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<GroupDefinition, HashSet<CarouselItem>> groupMap = new Dictionary<GroupDefinition, HashSet<CarouselItem>>();
|
||||
|
||||
private readonly Func<FilterCriteria> getCriteria;
|
||||
private readonly Func<List<BeatmapCollection>>? getCollections;
|
||||
private readonly Func<List<BeatmapCollection>> getCollections;
|
||||
private readonly Func<FilterCriteria, IReadOnlyDictionary<Guid, ScoreRank>> getLocalUserTopRanks;
|
||||
|
||||
public BeatmapCarouselFilterGrouping(Func<FilterCriteria> getCriteria, Func<List<BeatmapCollection>>? getCollections)
|
||||
public BeatmapCarouselFilterGrouping(Func<FilterCriteria> getCriteria, Func<List<BeatmapCollection>> getCollections, Func<FilterCriteria, IReadOnlyDictionary<Guid, ScoreRank>> getLocalUserTopRanks)
|
||||
{
|
||||
this.getCriteria = getCriteria;
|
||||
this.getCollections = getCollections;
|
||||
this.getLocalUserTopRanks = getLocalUserTopRanks;
|
||||
}
|
||||
|
||||
public async Task<List<CarouselItem>> Run(IEnumerable<CarouselItem> 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<BeatmapCollection>();
|
||||
{
|
||||
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<Guid, ScoreRank> 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<T>(BeatmapInfo b, Func<BeatmapInfo, T> func)
|
||||
{
|
||||
var beatmaps = b.BeatmapSet!.Beatmaps.Where(bb => !bb.Hidden);
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user