mirror of
https://github.com/ppy/osu.git
synced 2026-05-21 06:29:52 +08:00
135 lines
5.9 KiB
C#
135 lines
5.9 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.Linq;
|
|
using osu.Framework.Allocation;
|
|
using osu.Framework.Bindables;
|
|
using osu.Framework.Caching;
|
|
using osu.Framework.Graphics;
|
|
using osu.Game.Online.API;
|
|
using osu.Game.Online.Rooms;
|
|
using osu.Game.Screens.Play;
|
|
|
|
namespace osu.Game.Screens.Select.Leaderboards
|
|
{
|
|
[LongRunningLoad]
|
|
public partial class PlaylistsGameplayLeaderboardProvider : Component, IGameplayLeaderboardProvider
|
|
{
|
|
public IBindableList<GameplayLeaderboardScore> Scores => scores;
|
|
private readonly BindableList<GameplayLeaderboardScore> scores = new BindableList<GameplayLeaderboardScore>();
|
|
|
|
private readonly Room room;
|
|
private readonly PlaylistItem playlistItem;
|
|
|
|
private readonly Cached sorting = new Cached();
|
|
private bool isPartial;
|
|
|
|
public PlaylistsGameplayLeaderboardProvider(Room room, PlaylistItem playlistItem)
|
|
{
|
|
this.room = room;
|
|
this.playlistItem = playlistItem;
|
|
}
|
|
|
|
[BackgroundDependencyLoader]
|
|
private void load(IAPIProvider api, GameplayState? gameplayState)
|
|
{
|
|
var scoresToShow = new List<GameplayLeaderboardScore>();
|
|
|
|
var scoresRequest = new IndexPlaylistScoresRequest(room.RoomID!.Value, playlistItem.ID);
|
|
api.Perform(scoresRequest);
|
|
|
|
var response = scoresRequest.Response;
|
|
|
|
if (response != null)
|
|
{
|
|
isPartial = response.Scores.Count < response.TotalScores;
|
|
|
|
for (int i = 0; i < response.Scores.Count; i++)
|
|
{
|
|
var score = response.Scores[i];
|
|
score.Position = i + 1;
|
|
scoresToShow.Add(new GameplayLeaderboardScore(score, tracked: false, GameplayLeaderboardScore.ComboDisplayMode.Highest));
|
|
}
|
|
|
|
if (response.UserScore != null && response.Scores.All(s => s.ID != response.UserScore.ID))
|
|
scoresToShow.Add(new GameplayLeaderboardScore(response.UserScore, tracked: false, GameplayLeaderboardScore.ComboDisplayMode.Highest));
|
|
}
|
|
|
|
if (gameplayState != null)
|
|
{
|
|
var localScore = new GameplayLeaderboardScore(gameplayState, tracked: true, GameplayLeaderboardScore.ComboDisplayMode.Highest);
|
|
localScore.TotalScore.BindValueChanged(_ => sorting.Invalidate());
|
|
scoresToShow.Add(localScore);
|
|
}
|
|
|
|
// touching the public bindable must happen on the update thread for general thread safety,
|
|
// since we may have external subscribers bound already
|
|
Schedule(() =>
|
|
{
|
|
scores.AddRange(scoresToShow);
|
|
sort();
|
|
Scheduler.AddDelayed(sort, 1000, true);
|
|
});
|
|
}
|
|
|
|
// logic shared with SoloGameplayLeaderboardProvider
|
|
private void sort()
|
|
{
|
|
if (sorting.IsValid)
|
|
return;
|
|
|
|
var orderedByScore = scores
|
|
.OrderByDescending(i => i.TotalScore.Value)
|
|
.ThenBy(i => i.TotalScoreTiebreaker)
|
|
.ToList();
|
|
|
|
int delta = 0;
|
|
|
|
for (int i = 0; i < orderedByScore.Count; i++)
|
|
{
|
|
var score = orderedByScore[i];
|
|
|
|
// see `SoloResultsScreen.FetchScores()` for another place that does the same thing with slight deviations
|
|
// if this code is changed, that code should probably be changed as well
|
|
|
|
score.DisplayOrder.Value = i + 1;
|
|
|
|
// if we know we have all scores there can ever be, we can do the simple and obvious thing.
|
|
if (!isPartial)
|
|
score.Position.Value = i + 1;
|
|
else
|
|
{
|
|
// we have a partial leaderboard, with potential gaps.
|
|
// we have initial score positions which were valid at the point of starting play.
|
|
// the assumption here is that non-tracked scores here cannot move around, only tracked ones can.
|
|
if (score.Tracked)
|
|
{
|
|
int? previousScorePosition = i > 0 ? orderedByScore[i - 1].InitialPosition : 0;
|
|
int? nextScorePosition = i < orderedByScore.Count - 1 ? orderedByScore[i + 1].InitialPosition : null;
|
|
|
|
// if the tracked score is perfectly between two scores which have known neighbouring initial positions,
|
|
// we can assign it the position of the previous score plus one...
|
|
if (previousScorePosition != null && nextScorePosition != null && previousScorePosition + 1 == nextScorePosition)
|
|
{
|
|
score.Position.Value = previousScorePosition + 1;
|
|
// but we also need to ensure all subsequent scores get shifted down one position, too.
|
|
delta++;
|
|
}
|
|
// conversely, if the tracked score is not between neighbouring two scores and the leaderboard is partial,
|
|
// we can't really assign a valid position at all. it could be any number between the two neighbours.
|
|
else
|
|
score.Position.Value = null;
|
|
}
|
|
// for non-tracked scores, we just need to apply any delta that might have come from the tracked scores
|
|
// which might have been encountered and assigned a position earlier.
|
|
else
|
|
score.Position.Value = score.InitialPosition + delta;
|
|
}
|
|
}
|
|
|
|
sorting.Validate();
|
|
}
|
|
}
|
|
}
|