mirror of
https://github.com/ppy/osu.git
synced 2026-05-26 13:50:33 +08:00
0353b1461f
This could be the case if the results screen goes away and is no longer running the update/scheduler loop.
165 lines
7.0 KiB
C#
165 lines
7.0 KiB
C#
// 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.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using osu.Framework.Allocation;
|
|
using osu.Framework.Bindables;
|
|
using osu.Framework.Logging;
|
|
using osu.Game.Extensions;
|
|
using osu.Game.Online.API;
|
|
using osu.Game.Online.Leaderboards;
|
|
using osu.Game.Scoring;
|
|
using osu.Game.Screens.Select.Leaderboards;
|
|
|
|
namespace osu.Game.Screens.Ranking
|
|
{
|
|
public partial class SoloResultsScreen : ResultsScreen
|
|
{
|
|
private readonly IBindable<LeaderboardScores?> globalScores = new Bindable<LeaderboardScores?>();
|
|
|
|
private TaskCompletionSource<LeaderboardScores>? requestTaskSource;
|
|
|
|
[Resolved]
|
|
private IAPIProvider api { get; set; } = null!;
|
|
|
|
[Resolved]
|
|
private LeaderboardManager leaderboardManager { get; set; } = null!;
|
|
|
|
public SoloResultsScreen(ScoreInfo score)
|
|
: base(score)
|
|
{
|
|
}
|
|
|
|
protected override void LoadComplete()
|
|
{
|
|
base.LoadComplete();
|
|
globalScores.BindTo(leaderboardManager.Scores);
|
|
}
|
|
|
|
protected override void Dispose(bool isDisposing)
|
|
{
|
|
base.Dispose(isDisposing);
|
|
|
|
if (requestTaskSource?.Task.IsCompleted == false)
|
|
requestTaskSource.SetCanceled();
|
|
}
|
|
|
|
protected override async Task<ScoreInfo[]> FetchScores()
|
|
{
|
|
Debug.Assert(Score != null);
|
|
|
|
// sort mode intentionally omitted to default to score - results screen only supports sorting by score, so don't pass any other to avoid confusion
|
|
var criteria = new LeaderboardCriteria(
|
|
Score.BeatmapInfo!,
|
|
Score.Ruleset,
|
|
leaderboardManager.CurrentCriteria?.Scope ?? BeatmapLeaderboardScope.Global,
|
|
leaderboardManager.CurrentCriteria?.ExactMods
|
|
);
|
|
|
|
Debug.Assert(requestTaskSource == null || requestTaskSource.Task.IsCompleted);
|
|
|
|
requestTaskSource = new TaskCompletionSource<LeaderboardScores>();
|
|
|
|
globalScores.BindValueChanged(_ =>
|
|
{
|
|
if (globalScores.Value != null && leaderboardManager.CurrentCriteria?.Equals(criteria) == true)
|
|
requestTaskSource.TrySetResult(globalScores.Value);
|
|
});
|
|
|
|
Schedule(() => leaderboardManager.FetchWithCriteria(criteria, forceRefresh: true));
|
|
|
|
var result = await requestTaskSource.Task.ConfigureAwait(false);
|
|
|
|
if (result.FailState != null)
|
|
{
|
|
Logger.Log($"Failed to fetch scores (beatmap: {Score.BeatmapInfo}, ruleset: {Score.Ruleset}): {result.FailState}");
|
|
return [];
|
|
}
|
|
|
|
var clonedScores = result.AllScores.Select(s => s.DeepClone()).ToArray();
|
|
|
|
List<ScoreInfo> sortedScores = [];
|
|
|
|
foreach (var clonedScore in clonedScores)
|
|
{
|
|
// ensure that we do not double up on the score being presented here.
|
|
// additionally, ensure that the reference that ends up in `sortedScores` is the `Score` reference specifically.
|
|
// this simplifies handling later.
|
|
if (clonedScore.Equals(Score) || clonedScore.MatchesOnlineID(Score))
|
|
{
|
|
// this is a precautionary guard that prevents `Score` from appearing multiple times in the list.
|
|
// that can occur in rare cases wherein two local scores have the same online ID but different replay contents
|
|
// (this is possible e.g. in cases of client-side vs server-side recorded replays, see https://github.com/ppy/osu-server-spectator/issues/193)
|
|
if (sortedScores.Contains(Score))
|
|
continue;
|
|
|
|
Score.Position = clonedScore.Position;
|
|
sortedScores.Add(Score);
|
|
}
|
|
else
|
|
{
|
|
bool isOnlineLeaderboard = criteria.Scope != BeatmapLeaderboardScope.Local;
|
|
bool presentingLocalUserScore = Score.UserID == api.LocalUser.Value.OnlineID;
|
|
bool presentedLocalUserScoreIsBetter = presentingLocalUserScore && clonedScore.UserID == api.LocalUser.Value.OnlineID && clonedScore.TotalScore < Score.TotalScore;
|
|
|
|
if (isOnlineLeaderboard && presentedLocalUserScoreIsBetter)
|
|
continue;
|
|
|
|
sortedScores.Add(clonedScore);
|
|
}
|
|
}
|
|
|
|
// if we haven't encountered a match for the presented score, we still need to attach it.
|
|
// note that the above block ensuring that the `Score` reference makes it in here makes this valid to write in this way.
|
|
if (!sortedScores.Contains(Score))
|
|
sortedScores.Add(Score);
|
|
|
|
sortedScores = sortedScores.OrderByTotalScore().ToList();
|
|
|
|
int delta = 0;
|
|
bool isPartialLeaderboard = leaderboardManager.CurrentCriteria?.Scope != BeatmapLeaderboardScope.Local && result.TopScores.Count >= 50;
|
|
|
|
for (int i = 0; i < sortedScores.Count; i++)
|
|
{
|
|
var sortedScore = sortedScores[i];
|
|
|
|
// see `SoloGameplayLeaderboardProvider.sort()` for another place that does the same thing with slight deviations
|
|
// if this code is changed, that code should probably be changed as well
|
|
|
|
if (!isPartialLeaderboard)
|
|
sortedScore.Position = i + 1;
|
|
else
|
|
{
|
|
if (ReferenceEquals(sortedScore, Score) && sortedScore.Position == null)
|
|
{
|
|
int? previousScorePosition = i > 0 ? sortedScores[i - 1].Position : 0;
|
|
int? nextScorePosition = i < result.TopScores.Count - 1 ? sortedScores[i + 1].Position : null;
|
|
|
|
if (previousScorePosition != null && nextScorePosition != null && previousScorePosition + 1 == nextScorePosition)
|
|
{
|
|
sortedScore.Position = previousScorePosition + 1;
|
|
delta += 1;
|
|
}
|
|
else
|
|
sortedScore.Position = null;
|
|
}
|
|
else
|
|
sortedScore.Position += delta;
|
|
}
|
|
}
|
|
|
|
// there's a non-zero chance that the `Score.Position` was mutated above,
|
|
// but that is not actually coupled to `ScorePosition` of the relevant score panel in any way,
|
|
// so ensure that the drawable panel also receives the updated position.
|
|
// note that this is valid to do precisely because we ensured `Score` was in `sortedScores` earlier.
|
|
ScorePanelList.GetPanelForScore(Score).ScorePosition.Value = Score.Position;
|
|
|
|
sortedScores.Remove(Score);
|
|
return sortedScores.ToArray();
|
|
}
|
|
}
|
|
}
|