diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 79baae53e8..93b9efed6a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -10,6 +10,8 @@ using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Screens.Select.Carousel; @@ -161,5 +163,53 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("SS rank displayed", () => topLocalRank.DisplayedRank == ScoreRank.X); } + + [Test] + public void TestGuestScore() + { + AddStep("Add score for guest user", () => + { + var testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); + + testScoreInfo.User = new GuestUser(); + testScoreInfo.Rank = ScoreRank.B; + + scoreManager.Import(testScoreInfo); + }); + + AddUntilStep("B rank displayed", () => topLocalRank.DisplayedRank, () => Is.EqualTo(ScoreRank.B)); + } + + [Test] + public void TestUnknownUserScore() + { + AddStep("Add score for unknown user", () => + { + var testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); + + testScoreInfo.User = new APIUser { Username = "AAA", }; + testScoreInfo.Rank = ScoreRank.S; + + scoreManager.Import(testScoreInfo); + }); + + AddUntilStep("S rank displayed", () => topLocalRank.DisplayedRank, () => Is.EqualTo(ScoreRank.S)); + } + + [Test] + public void TestAnotherUserScore() + { + AddStep("Add score for not-current user", () => + { + var testScoreInfo = TestResources.CreateTestScoreInfo(importedBeatmap); + + testScoreInfo.User = new APIUser { Username = "notme", Id = 43, }; + testScoreInfo.Rank = ScoreRank.S; + + scoreManager.Import(testScoreInfo); + }); + + AddUntilStep("No rank displayed", () => topLocalRank.DisplayedRank, () => Is.Null); + } } } diff --git a/osu.Game/Scoring/ScoreInfoExtensions.cs b/osu.Game/Scoring/ScoreInfoExtensions.cs index dd08326742..2eec0399d6 100644 --- a/osu.Game/Scoring/ScoreInfoExtensions.cs +++ b/osu.Game/Scoring/ScoreInfoExtensions.cs @@ -5,8 +5,11 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; +using osu.Game.Models; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select.Leaderboards; +using Realms; namespace osu.Game.Scoring { @@ -64,5 +67,23 @@ namespace osu.Game.Scoring /// The to compute the maximum achievable combo for. /// The maximum achievable combo. public static int GetMaximumAchievableCombo(this ScoreInfo score) => score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Sum(kvp => kvp.Value); + + /// + /// Performs a realm filter that returns all scores that belong to the user with the given . + /// (for guests) is supported. + /// + /// + /// All guest scores (with user ID of ), + /// as well as scores of unknown provenance (with default user ID of 1, see ), + /// will be treated as if they belong to the local user. + /// This may not be necessarily considered fully correct in some circumstances, but in most cases it is the desired effect. + /// + public static IQueryable GetAllLocalScoresForUser(this Realm realm, int? userId) + { + return realm.All() + .Filter($@"({nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0 || {nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} <= 1)" + + $@" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.Hash)} == {nameof(ScoreInfo.BeatmapHash)}" + + $@" && {nameof(ScoreInfo.DeletePending)} == false", userId); + } } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 3c1aec745d..5c5c814c5b 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -19,7 +19,6 @@ using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Models; using osu.Game.Online.API; using osu.Game.Online.Placeholders; using osu.Game.Scoring; @@ -246,11 +245,8 @@ namespace osu.Game.Screens.Ranking.Statistics // We may want to iterate on the following conditions further in the future var localUserScore = AchievedScore ?? realm.Run(r => - r.All() - .Filter($@"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" - + $@" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" - + $@" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.Hash)} == {nameof(ScoreInfo.BeatmapHash)}" - + $@" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, newScore.BeatmapInfo.ID, newScore.BeatmapInfo.Ruleset.ShortName) + r.GetAllLocalScoresForUser(api.LocalUser.Value.Id) + .Filter($@"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $0", newScore.BeatmapInfo.ID) .AsEnumerable() .OrderByDescending(score => score.Ruleset.MatchesOnlineID(newScore.BeatmapInfo.Ruleset)) .ThenByDescending(score => score.Rank) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index da9661f702..6f1f2e8370 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Models; using osu.Game.Online.API; using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; @@ -59,12 +58,9 @@ namespace osu.Game.Screens.Select.Carousel { scoreSubscription?.Dispose(); scoreSubscription = realm.RegisterForNotifications(r => - r.All() - .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" - + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" - + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.Hash)} == {nameof(ScoreInfo.BeatmapHash)}" - + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" - + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName), + r.GetAllLocalScoresForUser(api.LocalUser.Value.Id) + .Filter($@"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $0" + + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $1", beatmapInfo.ID, ruleset.Value.ShortName), localScoresChanged); }, true); diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index ab520525a5..952e545d0a 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -25,7 +25,6 @@ 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; @@ -758,11 +757,8 @@ namespace osu.Game.Screens.SelectV2 { 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) + var allLocalScores = r.GetAllLocalScoresForUser(criteria.LocalUserId) + .Filter($@"{nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $0", criteria.Ruleset?.ShortName) .OrderByDescending(s => s.TotalScore) .ThenBy(s => s.Date); diff --git a/osu.Game/Screens/SelectV2/PanelLocalRankDisplay.cs b/osu.Game/Screens/SelectV2/PanelLocalRankDisplay.cs index 273f995794..c72835144f 100644 --- a/osu.Game/Screens/SelectV2/PanelLocalRankDisplay.cs +++ b/osu.Game/Screens/SelectV2/PanelLocalRankDisplay.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Models; using osu.Game.Online.API; using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; @@ -78,12 +77,9 @@ namespace osu.Game.Screens.SelectV2 return; scoreSubscription = realm.RegisterForNotifications(r => - r.All() - .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" - + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" - + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.Hash)} == {nameof(ScoreInfo.BeatmapHash)}" - + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" - + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmap.ID, ruleset.Value.ShortName), + r.GetAllLocalScoresForUser(api.LocalUser.Value.Id) + .Filter($@"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $0" + + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $1", beatmap.ID, ruleset.Value.ShortName), localScoresChanged); }