1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-10 18:17:19 +08:00

Merge pull request #32199 from bdach/negative-leaderboard-position

Fix playlists results screens potentially displaying negative score positions
This commit is contained in:
Dan Balasescu 2025-03-05 16:13:12 +09:00 committed by GitHub
commit f6cf63edae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 94 additions and 27 deletions

View File

@ -69,9 +69,11 @@ namespace osu.Game.Tests.Visual.Playlists
totalCount = 0; totalCount = 0;
userScore = TestResources.CreateTestScoreInfo(); userScore = TestResources.CreateTestScoreInfo();
userScore.OnlineID = 1;
userScore.TotalScore = 0; userScore.TotalScore = 0;
userScore.Statistics = new Dictionary<HitResult, int>(); userScore.Statistics = new Dictionary<HitResult, int>();
userScore.MaximumStatistics = new Dictionary<HitResult, int>(); userScore.MaximumStatistics = new Dictionary<HitResult, int>();
userScore.Position = real_user_position;
// Beatmap is required to be an actual beatmap so the scores can get their scores correctly // Beatmap is required to be an actual beatmap so the scores can get their scores correctly
// calculated for standardised scoring, else the tests that rely on ordering will fall over. // calculated for standardised scoring, else the tests that rely on ordering will fall over.
@ -243,6 +245,35 @@ namespace osu.Game.Tests.Visual.Playlists
AddAssert("placeholder shown", () => this.ChildrenOfType<MessagePlaceholder>().Count(), () => Is.EqualTo(1)); AddAssert("placeholder shown", () => this.ChildrenOfType<MessagePlaceholder>().Count(), () => Is.EqualTo(1));
} }
[Test]
public void TestFetchingAllTheWayToFirstNeverDisplaysNegativePosition()
{
AddStep("set user position", () => userScore.Position = 20);
AddStep("bind user score info handler", () => bindHandler(userScore: userScore));
createResultsWithScore(() => userScore);
waitForDisplay();
AddStep("bind delayed handler", () => bindHandler(true));
for (int i = 0; i < 2; i++)
{
AddStep("simulate user falling down ranking", () => userScore.Position += 2);
AddStep("scroll to left", () => resultsScreen.ChildrenOfType<ScorePanelList>().Single().ChildrenOfType<OsuScrollContainer>().Single().ScrollToStart(false));
AddAssert("left loading spinner shown", () =>
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreLeft).State.Value == Visibility.Visible);
waitForDisplay();
AddAssert("left loading spinner hidden", () =>
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreLeft).State.Value == Visibility.Hidden);
}
AddAssert("total count is 34", () => this.ChildrenOfType<ScorePanel>().Count(), () => Is.EqualTo(34));
AddUntilStep("all panels have non-negative position", () => this.ChildrenOfType<ScorePanel>().All(p => p.ScorePosition.Value > 0));
}
private void createResultsWithScore(Func<ScoreInfo> getScore) private void createResultsWithScore(Func<ScoreInfo> getScore)
{ {
AddStep("load results", () => AddStep("load results", () =>
@ -331,7 +362,7 @@ namespace osu.Game.Tests.Visual.Playlists
if (userScore == null) if (userScore == null)
triggerFail(s); triggerFail(s);
else else
triggerSuccess(s, createUserResponse(userScore)); triggerSuccess(s, () => createUserResponse(userScore));
break; break;
@ -339,12 +370,12 @@ namespace osu.Game.Tests.Visual.Playlists
if (userScore == null) if (userScore == null)
triggerFail(u); triggerFail(u);
else else
triggerSuccess(u, createUserResponse(userScore)); triggerSuccess(u, () => createUserResponse(userScore));
break; break;
case IndexPlaylistScoresRequest i: case IndexPlaylistScoresRequest i:
triggerSuccess(i, createIndexResponse(i, noScores)); triggerSuccess(i, () => createIndexResponse(i, noScores));
break; break;
} }
}, delay); }, delay);
@ -352,11 +383,11 @@ namespace osu.Game.Tests.Visual.Playlists
return true; return true;
}; };
private void triggerSuccess<T>(APIRequest<T> req, T result) private void triggerSuccess<T>(APIRequest<T> req, Func<T> result)
where T : class where T : class
{ {
requestComplete = true; requestComplete = true;
req.TriggerSuccess(result); req.TriggerSuccess(result.Invoke());
} }
private void triggerFail(APIRequest req) private void triggerFail(APIRequest req)
@ -367,28 +398,13 @@ namespace osu.Game.Tests.Visual.Playlists
private MultiplayerScore createUserResponse(ScoreInfo userScore) private MultiplayerScore createUserResponse(ScoreInfo userScore)
{ {
var multiplayerUserScore = new MultiplayerScore var multiplayerUserScore = createMultiplayerUserScore(userScore);
{
ID = highestScoreId,
Accuracy = userScore.Accuracy,
Passed = userScore.Passed,
Rank = userScore.Rank,
Position = real_user_position,
MaxCombo = userScore.MaxCombo,
User = userScore.User,
BeatmapId = RNG.Next(0, 7),
ScoresAround = new MultiplayerScoresAround
{
Higher = new MultiplayerScores(),
Lower = new MultiplayerScores()
}
};
totalCount++; totalCount++;
for (int i = 1; i <= scores_per_result; i++) for (int i = 1; i <= scores_per_result; i++)
{ {
multiplayerUserScore.ScoresAround.Lower.Scores.Add(new MultiplayerScore multiplayerUserScore.ScoresAround!.Lower!.Scores.Add(new MultiplayerScore
{ {
ID = getNextLowestScoreId(), ID = getNextLowestScoreId(),
Accuracy = userScore.Accuracy, Accuracy = userScore.Accuracy,
@ -404,7 +420,7 @@ namespace osu.Game.Tests.Visual.Playlists
}, },
}); });
multiplayerUserScore.ScoresAround.Higher.Scores.Add(new MultiplayerScore multiplayerUserScore.ScoresAround!.Higher!.Scores.Add(new MultiplayerScore
{ {
ID = getNextHighestScoreId(), ID = getNextHighestScoreId(),
Accuracy = userScore.Accuracy, Accuracy = userScore.Accuracy,
@ -423,12 +439,32 @@ namespace osu.Game.Tests.Visual.Playlists
totalCount += 2; totalCount += 2;
} }
addCursor(multiplayerUserScore.ScoresAround.Lower); addCursor(multiplayerUserScore.ScoresAround!.Lower!);
addCursor(multiplayerUserScore.ScoresAround.Higher); addCursor(multiplayerUserScore.ScoresAround!.Higher!);
return multiplayerUserScore; return multiplayerUserScore;
} }
private MultiplayerScore createMultiplayerUserScore(ScoreInfo userScore)
{
return new MultiplayerScore
{
ID = highestScoreId,
Accuracy = userScore.Accuracy,
Passed = userScore.Passed,
Rank = userScore.Rank,
Position = userScore.Position,
MaxCombo = userScore.MaxCombo,
User = userScore.User,
BeatmapId = RNG.Next(0, 7),
ScoresAround = new MultiplayerScoresAround
{
Higher = new MultiplayerScores(),
Lower = new MultiplayerScores()
}
};
}
private IndexedMultiplayerScores createIndexResponse(IndexPlaylistScoresRequest req, bool noScores) private IndexedMultiplayerScores createIndexResponse(IndexPlaylistScoresRequest req, bool noScores)
{ {
var result = new IndexedMultiplayerScores(); var result = new IndexedMultiplayerScores();
@ -437,11 +473,21 @@ namespace osu.Game.Tests.Visual.Playlists
string sort = req.IndexParams?.Properties["sort"].ToObject<string>() ?? "score_desc"; string sort = req.IndexParams?.Properties["sort"].ToObject<string>() ?? "score_desc";
bool reachedEnd = false;
for (int i = 1; i <= scores_per_result; i++) for (int i = 1; i <= scores_per_result; i++)
{ {
int nextId = sort == "score_asc" ? getNextHighestScoreId() : getNextLowestScoreId();
if (userScore.OnlineID - nextId >= userScore.Position)
{
reachedEnd = true;
break;
}
result.Scores.Add(new MultiplayerScore result.Scores.Add(new MultiplayerScore
{ {
ID = sort == "score_asc" ? getNextHighestScoreId() : getNextLowestScoreId(), ID = nextId,
Accuracy = 1, Accuracy = 1,
Passed = true, Passed = true,
Rank = ScoreRank.X, Rank = ScoreRank.X,
@ -458,8 +504,11 @@ namespace osu.Game.Tests.Visual.Playlists
totalCount++; totalCount++;
} }
if (!reachedEnd)
addCursor(result); addCursor(result);
result.UserScore = createMultiplayerUserScore(userScore);
return result; return result;
} }

View File

@ -185,6 +185,24 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{ {
higherScores = index; higherScores = index;
setPositions(index, pivot, -1); setPositions(index, pivot, -1);
// when paginating the results, it's possible for the user's score to naturally fall down the rankings.
// unmitigated, this can cause scores at the very top of the rankings to have zero or negative positions
// because the positions are counted backwards from the user's score, which has increased in this case during pagination.
// if this happens, just give the top score the first position.
// note that this isn't 100% correct, but it *is* however the most reliable way to mask the problem.
int smallestPosition = index.Scores.Min(s => s.Position ?? 1);
if (smallestPosition < 1)
{
int offset = 1 - smallestPosition;
foreach (var scorePanel in ScorePanelList.GetScorePanels())
scorePanel.ScorePosition.Value += offset;
foreach (var score in index.Scores)
score.Position += offset;
}
} }
return await transformScores(index.Scores).ConfigureAwait(false); return await transformScores(index.Scores).ConfigureAwait(false);