1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-18 12:00:22 +08:00

Merge pull request #34904 from bdach/local-rank-includes-unproven-scores

Treat guest user scores & scores of unknown users as the local user's
This commit is contained in:
Dean Herbert
2025-09-04 17:06:51 +09:00
committed by GitHub
Unverified
6 changed files with 81 additions and 26 deletions
@@ -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);
}
}
}
+21
View File
@@ -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
/// <param name="score">The <see cref="ScoreInfo"/> to compute the maximum achievable combo for.</param>
/// <returns>The maximum achievable combo.</returns>
public static int GetMaximumAchievableCombo(this ScoreInfo score) => score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Sum(kvp => kvp.Value);
/// <summary>
/// Performs a realm filter that returns all scores that belong to the user with the given <paramref name="userId"/>.
/// <see langword="null"/> <paramref name="userId"/> (for guests) is supported.
/// </summary>
/// <remarks>
/// All guest scores (with user ID of <see cref="APIUser.SYSTEM_USER_ID"/>),
/// as well as scores of unknown provenance (with default user ID of 1, see <see cref="APIUser.OnlineID"/>),
/// 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.
/// </remarks>
public static IQueryable<ScoreInfo> GetAllLocalScoresForUser(this Realm realm, int? userId)
{
return realm.All<ScoreInfo>()
.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);
}
}
}
@@ -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<ScoreInfo>()
.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)
@@ -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<ScoreInfo>()
.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);
+2 -6
View File
@@ -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<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)
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);
@@ -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<ScoreInfo>()
.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);
}