mirror of
https://github.com/ppy/osu.git
synced 2025-01-26 12:45:09 +08:00
Merge pull request #14603 from smoogipoo/score-ordering
Fix scores not being ordered correctly on leaderboards
This commit is contained in:
commit
fa62c846c5
@ -160,11 +160,14 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo }
|
||||
}));
|
||||
});
|
||||
|
||||
AddUntilStep("wait for load", () => resultsScreen.ChildrenOfType<ScorePanelList>().FirstOrDefault()?.AllPanelsVisible == true);
|
||||
}
|
||||
|
||||
private void waitForDisplay()
|
||||
{
|
||||
AddUntilStep("wait for request to complete", () => requestComplete);
|
||||
AddUntilStep("wait for panels to be visible", () => resultsScreen.ChildrenOfType<ScorePanelList>().FirstOrDefault()?.AllPanelsVisible == true);
|
||||
AddWaitStep("wait for display", 5);
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
TestResultsScreen screen = null;
|
||||
|
||||
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
|
||||
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||
AddUntilStep("wait for load", () => this.ChildrenOfType<ScorePanelList>().Single().AllPanelsVisible);
|
||||
|
||||
AddStep("click expanded panel", () =>
|
||||
{
|
||||
@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
TestResultsScreen screen = null;
|
||||
|
||||
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
|
||||
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||
AddUntilStep("wait for load", () => this.ChildrenOfType<ScorePanelList>().Single().AllPanelsVisible);
|
||||
|
||||
AddStep("click expanded panel", () =>
|
||||
{
|
||||
@ -177,7 +177,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
TestResultsScreen screen = null;
|
||||
|
||||
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
|
||||
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||
AddUntilStep("wait for load", () => this.ChildrenOfType<ScorePanelList>().Single().AllPanelsVisible);
|
||||
|
||||
ScorePanel expandedPanel = null;
|
||||
ScorePanel contractedPanel = null;
|
||||
@ -223,6 +223,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
TestResultsScreen screen = null;
|
||||
|
||||
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
|
||||
AddUntilStep("wait for load", () => this.ChildrenOfType<ScorePanelList>().Single().AllPanelsVisible);
|
||||
|
||||
AddAssert("download button is disabled", () => !screen.ChildrenOfType<DownloadButton>().Last().Enabled.Value);
|
||||
|
||||
|
@ -159,6 +159,9 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
var firstScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
var secondScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
|
||||
firstScore.User.Username = "A";
|
||||
secondScore.User.Username = "B";
|
||||
|
||||
createListStep(() => new ScorePanelList());
|
||||
|
||||
AddStep("add scores and select first", () =>
|
||||
@ -168,6 +171,8 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
list.SelectedScore.Value = firstScore;
|
||||
});
|
||||
|
||||
AddUntilStep("wait for load", () => list.AllPanelsVisible);
|
||||
|
||||
assertScoreState(firstScore, true);
|
||||
assertScoreState(secondScore, false);
|
||||
|
||||
@ -182,6 +187,22 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
assertExpandedPanelCentred();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddScoreImmediately()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
|
||||
createListStep(() =>
|
||||
{
|
||||
var newList = new ScorePanelList { SelectedScore = { Value = score } };
|
||||
newList.AddScore(score);
|
||||
return newList;
|
||||
});
|
||||
|
||||
assertScoreState(score, true);
|
||||
assertExpandedPanelCentred();
|
||||
}
|
||||
|
||||
private void createListStep(Func<ScorePanelList> creationFunc)
|
||||
{
|
||||
AddStep("create list", () => Child = list = creationFunc().With(d =>
|
||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
|
||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory, Scheduler));
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private BeatmapManager beatmapManager;
|
||||
private ScoreManager scoreManager;
|
||||
|
||||
private readonly List<ScoreInfo> scores = new List<ScoreInfo>();
|
||||
private readonly List<ScoreInfo> importedScores = new List<ScoreInfo>();
|
||||
private BeatmapInfo beatmap;
|
||||
|
||||
[Cached]
|
||||
@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
|
||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory, Scheduler));
|
||||
|
||||
beatmap = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Beatmaps[0];
|
||||
|
||||
@ -100,11 +100,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
User = new User { Username = "TestUser" },
|
||||
};
|
||||
|
||||
scores.Add(scoreManager.Import(score).Result);
|
||||
importedScores.Add(scoreManager.Import(score).Result);
|
||||
}
|
||||
|
||||
scores.Sort(Comparer<ScoreInfo>.Create((s1, s2) => s2.TotalScore.CompareTo(s1.TotalScore)));
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@ -134,9 +132,14 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[Test]
|
||||
public void TestDeleteViaRightClick()
|
||||
{
|
||||
ScoreInfo scoreBeingDeleted = null;
|
||||
AddStep("open menu for top score", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(leaderboard.ChildrenOfType<LeaderboardScore>().First());
|
||||
var leaderboardScore = leaderboard.ChildrenOfType<LeaderboardScore>().First();
|
||||
|
||||
scoreBeingDeleted = leaderboardScore.Score;
|
||||
|
||||
InputManager.MoveMouseTo(leaderboardScore);
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
|
||||
@ -158,14 +161,14 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scores[0].OnlineScoreID));
|
||||
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scoreBeingDeleted.OnlineScoreID));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDeleteViaDatabase()
|
||||
{
|
||||
AddStep("delete top score", () => scoreManager.Delete(scores[0]));
|
||||
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scores[0].OnlineScoreID));
|
||||
AddStep("delete top score", () => scoreManager.Delete(importedScores[0]));
|
||||
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != importedScores[0].OnlineScoreID));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,8 @@ namespace osu.Game.Online.Leaderboards
|
||||
{
|
||||
public const float HEIGHT = 60;
|
||||
|
||||
public readonly ScoreInfo Score;
|
||||
|
||||
private const float corner_radius = 5;
|
||||
private const float edge_margin = 5;
|
||||
private const float background_alpha = 0.25f;
|
||||
@ -41,7 +43,6 @@ namespace osu.Game.Online.Leaderboards
|
||||
|
||||
protected Container RankContainer { get; private set; }
|
||||
|
||||
private readonly ScoreInfo score;
|
||||
private readonly int? rank;
|
||||
private readonly bool allowHighlight;
|
||||
|
||||
@ -67,7 +68,8 @@ namespace osu.Game.Online.Leaderboards
|
||||
|
||||
public LeaderboardScore(ScoreInfo score, int? rank, bool allowHighlight = true)
|
||||
{
|
||||
this.score = score;
|
||||
Score = score;
|
||||
|
||||
this.rank = rank;
|
||||
this.allowHighlight = allowHighlight;
|
||||
|
||||
@ -78,9 +80,9 @@ namespace osu.Game.Online.Leaderboards
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IAPIProvider api, OsuColour colour, ScoreManager scoreManager)
|
||||
{
|
||||
var user = score.User;
|
||||
var user = Score.User;
|
||||
|
||||
statisticsLabels = GetStatistics(score).Select(s => new ScoreComponentLabel(s)).ToList();
|
||||
statisticsLabels = GetStatistics(Score).Select(s => new ScoreComponentLabel(s)).ToList();
|
||||
|
||||
ClickableAvatar innerAvatar;
|
||||
|
||||
@ -198,7 +200,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
{
|
||||
TextColour = Color4.White,
|
||||
GlowColour = Color4Extensions.FromHex(@"83ccfa"),
|
||||
Current = scoreManager.GetBindableTotalScoreString(score),
|
||||
Current = scoreManager.GetBindableTotalScoreString(Score),
|
||||
Font = OsuFont.Numeric.With(size: 23),
|
||||
},
|
||||
RankContainer = new Container
|
||||
@ -206,7 +208,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
Size = new Vector2(40f, 20f),
|
||||
Children = new[]
|
||||
{
|
||||
scoreRank = new UpdateableRank(score.Rank)
|
||||
scoreRank = new UpdateableRank(Score.Rank)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -223,7 +225,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(1),
|
||||
ChildrenEnumerable = score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) })
|
||||
ChildrenEnumerable = Score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) })
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -389,14 +391,14 @@ namespace osu.Game.Online.Leaderboards
|
||||
{
|
||||
List<MenuItem> items = new List<MenuItem>();
|
||||
|
||||
if (score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered) && songSelect != null)
|
||||
items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = score.Mods));
|
||||
if (Score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered) && songSelect != null)
|
||||
items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = Score.Mods));
|
||||
|
||||
if (score.Files?.Count > 0)
|
||||
items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => scoreManager.Export(score)));
|
||||
if (Score.Files?.Count > 0)
|
||||
items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => scoreManager.Export(Score)));
|
||||
|
||||
if (score.ID != 0)
|
||||
items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score))));
|
||||
if (Score.ID != 0)
|
||||
items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score))));
|
||||
|
||||
return items.ToArray();
|
||||
}
|
||||
|
@ -243,7 +243,7 @@ namespace osu.Game
|
||||
dependencies.Cache(fileStore = new FileStore(contextFactory, Storage));
|
||||
|
||||
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
|
||||
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => difficultyCache, LocalConfig));
|
||||
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Scheduler, Host, () => difficultyCache, LocalConfig));
|
||||
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, true));
|
||||
|
||||
// this should likely be moved to ArchiveModelManager when another case appears where it is necessary
|
||||
|
@ -7,6 +7,8 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osuTK;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
@ -14,6 +16,7 @@ using osu.Game.Online.API.Requests;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
using osu.Game.Users;
|
||||
|
||||
@ -42,34 +45,46 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private ScoreManager scoreManager { get; set; }
|
||||
|
||||
private GetScoresRequest getScoresRequest;
|
||||
|
||||
private CancellationTokenSource loadCancellationSource;
|
||||
|
||||
protected APILegacyScores Scores
|
||||
{
|
||||
set => Schedule(() =>
|
||||
{
|
||||
loadCancellationSource?.Cancel();
|
||||
loadCancellationSource = new CancellationTokenSource();
|
||||
|
||||
topScoresContainer.Clear();
|
||||
scoreTable.ClearScores();
|
||||
scoreTable.Hide();
|
||||
|
||||
if (value?.Scores.Any() != true)
|
||||
{
|
||||
scoreTable.ClearScores();
|
||||
scoreTable.Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList();
|
||||
var topScore = scoreInfos.First();
|
||||
scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token)
|
||||
.ContinueWith(ordered => Schedule(() =>
|
||||
{
|
||||
if (loadCancellationSource.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() == true);
|
||||
scoreTable.Show();
|
||||
var topScore = ordered.Result.First();
|
||||
|
||||
var userScore = value.UserScore;
|
||||
var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets);
|
||||
scoreTable.DisplayScores(ordered.Result, topScore.Beatmap?.Status.GrantsPerformancePoints() == true);
|
||||
scoreTable.Show();
|
||||
|
||||
topScoresContainer.Add(new DrawableTopScore(topScore));
|
||||
var userScore = value.UserScore;
|
||||
var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets);
|
||||
|
||||
if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID)
|
||||
topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position));
|
||||
topScoresContainer.Add(new DrawableTopScore(topScore));
|
||||
|
||||
if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID)
|
||||
topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position));
|
||||
}), TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
@ -36,6 +37,7 @@ namespace osu.Game.Scoring
|
||||
|
||||
private readonly RulesetStore rulesets;
|
||||
private readonly Func<BeatmapManager> beatmaps;
|
||||
private readonly Scheduler scheduler;
|
||||
|
||||
[CanBeNull]
|
||||
private readonly Func<BeatmapDifficultyCache> difficulties;
|
||||
@ -43,12 +45,13 @@ namespace osu.Game.Scoring
|
||||
[CanBeNull]
|
||||
private readonly OsuConfigManager configManager;
|
||||
|
||||
public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, IIpcHost importHost = null,
|
||||
Func<BeatmapDifficultyCache> difficulties = null, OsuConfigManager configManager = null)
|
||||
public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, Scheduler scheduler,
|
||||
IIpcHost importHost = null, Func<BeatmapDifficultyCache> difficulties = null, OsuConfigManager configManager = null)
|
||||
: base(storage, contextFactory, api, new ScoreStore(contextFactory, storage), importHost)
|
||||
{
|
||||
this.rulesets = rulesets;
|
||||
this.beatmaps = beatmaps;
|
||||
this.scheduler = scheduler;
|
||||
this.difficulties = difficulties;
|
||||
this.configManager = configManager;
|
||||
}
|
||||
@ -103,6 +106,32 @@ namespace osu.Game.Scoring
|
||||
=> base.CheckLocalAvailability(model, items)
|
||||
|| (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID));
|
||||
|
||||
/// <summary>
|
||||
/// Orders an array of <see cref="ScoreInfo"/>s by total score.
|
||||
/// </summary>
|
||||
/// <param name="scores">The array of <see cref="ScoreInfo"/>s to reorder.</param>
|
||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to cancel the process.</param>
|
||||
/// <returns>The given <paramref name="scores"/> ordered by decreasing total score.</returns>
|
||||
public async Task<ScoreInfo[]> OrderByTotalScoreAsync(ScoreInfo[] scores, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var difficultyCache = difficulties?.Invoke();
|
||||
|
||||
if (difficultyCache != null)
|
||||
{
|
||||
// Compute difficulties asynchronously first to prevent blocking via the GetTotalScore() call below.
|
||||
foreach (var s in scores)
|
||||
{
|
||||
await difficultyCache.GetDifficultyAsync(s.Beatmap, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
|
||||
// We're calling .Result, but this should not be a blocking call due to the above GetDifficultyAsync() calls.
|
||||
return scores.OrderByDescending(s => GetTotalScoreAsync(s, cancellationToken: cancellationToken).Result)
|
||||
.ThenBy(s => s.OnlineScoreID)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a bindable that represents the total score of a <see cref="ScoreInfo"/>.
|
||||
/// </summary>
|
||||
@ -111,9 +140,9 @@ namespace osu.Game.Scoring
|
||||
/// </remarks>
|
||||
/// <param name="score">The <see cref="ScoreInfo"/> to retrieve the bindable for.</param>
|
||||
/// <returns>The bindable containing the total score.</returns>
|
||||
public Bindable<long> GetBindableTotalScore(ScoreInfo score)
|
||||
public Bindable<long> GetBindableTotalScore([NotNull] ScoreInfo score)
|
||||
{
|
||||
var bindable = new TotalScoreBindable(score, difficulties);
|
||||
var bindable = new TotalScoreBindable(score, this);
|
||||
configManager?.BindWith(OsuSetting.ScoreDisplayMode, bindable.ScoringMode);
|
||||
return bindable;
|
||||
}
|
||||
@ -126,7 +155,83 @@ namespace osu.Game.Scoring
|
||||
/// </remarks>
|
||||
/// <param name="score">The <see cref="ScoreInfo"/> to retrieve the bindable for.</param>
|
||||
/// <returns>The bindable containing the formatted total score string.</returns>
|
||||
public Bindable<string> GetBindableTotalScoreString(ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score));
|
||||
public Bindable<string> GetBindableTotalScoreString([NotNull] ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score));
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the total score of a <see cref="ScoreInfo"/> in the given <see cref="ScoringMode"/>.
|
||||
/// The score is returned in a callback that is run on the update thread.
|
||||
/// </summary>
|
||||
/// <param name="score">The <see cref="ScoreInfo"/> to calculate the total score of.</param>
|
||||
/// <param name="callback">The callback to be invoked with the total score.</param>
|
||||
/// <param name="mode">The <see cref="ScoringMode"/> to return the total score as.</param>
|
||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to cancel the process.</param>
|
||||
public void GetTotalScore([NotNull] ScoreInfo score, [NotNull] Action<long> callback, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default)
|
||||
{
|
||||
GetTotalScoreAsync(score, mode, cancellationToken)
|
||||
.ContinueWith(s => scheduler.Add(() => callback(s.Result)), TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the total score of a <see cref="ScoreInfo"/> in the given <see cref="ScoringMode"/>.
|
||||
/// </summary>
|
||||
/// <param name="score">The <see cref="ScoreInfo"/> to calculate the total score of.</param>
|
||||
/// <param name="mode">The <see cref="ScoringMode"/> to return the total score as.</param>
|
||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to cancel the process.</param>
|
||||
/// <returns>The total score.</returns>
|
||||
public async Task<long> GetTotalScoreAsync([NotNull] ScoreInfo score, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (score.Beatmap == null)
|
||||
return score.TotalScore;
|
||||
|
||||
int beatmapMaxCombo;
|
||||
double accuracy = score.Accuracy;
|
||||
|
||||
if (score.IsLegacyScore)
|
||||
{
|
||||
if (score.RulesetID == 3)
|
||||
{
|
||||
// In osu!stable, a full-GREAT score has 100% accuracy in mania. Along with a full combo, the score becomes indistinguishable from a full-PERFECT score.
|
||||
// To get around this, recalculate accuracy based on the hit statistics.
|
||||
// Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together.
|
||||
double maxBaseScore = score.Statistics.Select(kvp => kvp.Value).Sum() * Judgement.ToNumericResult(HitResult.Perfect);
|
||||
double baseScore = score.Statistics.Select(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value).Sum();
|
||||
if (maxBaseScore > 0)
|
||||
accuracy = baseScore / maxBaseScore;
|
||||
}
|
||||
|
||||
// This score is guaranteed to be an osu!stable score.
|
||||
// The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used.
|
||||
if (score.Beatmap.MaxCombo != null)
|
||||
beatmapMaxCombo = score.Beatmap.MaxCombo.Value;
|
||||
else
|
||||
{
|
||||
if (score.Beatmap.ID == 0 || difficulties == null)
|
||||
{
|
||||
// We don't have enough information (max combo) to compute the score, so use the provided score.
|
||||
return score.TotalScore;
|
||||
}
|
||||
|
||||
// We can compute the max combo locally after the async beatmap difficulty computation.
|
||||
var difficulty = await difficulties().GetDifficultyAsync(score.Beatmap, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false);
|
||||
beatmapMaxCombo = difficulty.MaxCombo;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is guaranteed to be a non-legacy score.
|
||||
// The combo must be determined through the score's statistics, as both the beatmap's max combo and the difficulty calculator will provide osu!stable combo values.
|
||||
beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetValueOrDefault(r)).Sum();
|
||||
}
|
||||
|
||||
if (beatmapMaxCombo == 0)
|
||||
return 0;
|
||||
|
||||
var ruleset = score.Ruleset.CreateInstance();
|
||||
var scoreProcessor = ruleset.CreateScoreProcessor();
|
||||
scoreProcessor.Mods.Value = score.Mods;
|
||||
|
||||
return (long)Math.Round(scoreProcessor.GetScore(mode, beatmapMaxCombo, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the total score of a <see cref="ScoreInfo"/>. Responds to changes in the currently-selected <see cref="ScoringMode"/>.
|
||||
@ -136,99 +241,29 @@ namespace osu.Game.Scoring
|
||||
public readonly Bindable<ScoringMode> ScoringMode = new Bindable<ScoringMode>();
|
||||
|
||||
private readonly ScoreInfo score;
|
||||
private readonly Func<BeatmapDifficultyCache> difficulties;
|
||||
private readonly ScoreManager scoreManager;
|
||||
|
||||
private CancellationTokenSource difficultyCalculationCancellationSource;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="TotalScoreBindable"/>.
|
||||
/// </summary>
|
||||
/// <param name="score">The <see cref="ScoreInfo"/> to provide the total score of.</param>
|
||||
/// <param name="difficulties">A function to retrieve the <see cref="BeatmapDifficultyCache"/>.</param>
|
||||
public TotalScoreBindable(ScoreInfo score, Func<BeatmapDifficultyCache> difficulties)
|
||||
/// <param name="scoreManager">The <see cref="ScoreManager"/>.</param>
|
||||
public TotalScoreBindable(ScoreInfo score, ScoreManager scoreManager)
|
||||
{
|
||||
this.score = score;
|
||||
this.difficulties = difficulties;
|
||||
this.scoreManager = scoreManager;
|
||||
|
||||
ScoringMode.BindValueChanged(onScoringModeChanged, true);
|
||||
}
|
||||
|
||||
private IBindable<StarDifficulty?> difficultyBindable;
|
||||
private CancellationTokenSource difficultyCancellationSource;
|
||||
|
||||
private void onScoringModeChanged(ValueChangedEvent<ScoringMode> mode)
|
||||
{
|
||||
difficultyCancellationSource?.Cancel();
|
||||
difficultyCancellationSource = null;
|
||||
difficultyCalculationCancellationSource?.Cancel();
|
||||
difficultyCalculationCancellationSource = new CancellationTokenSource();
|
||||
|
||||
if (score.Beatmap == null)
|
||||
{
|
||||
Value = score.TotalScore;
|
||||
return;
|
||||
}
|
||||
|
||||
int beatmapMaxCombo;
|
||||
double accuracy = score.Accuracy;
|
||||
|
||||
if (score.IsLegacyScore)
|
||||
{
|
||||
if (score.RulesetID == 3)
|
||||
{
|
||||
// In osu!stable, a full-GREAT score has 100% accuracy in mania. Along with a full combo, the score becomes indistinguishable from a full-PERFECT score.
|
||||
// To get around this, recalculate accuracy based on the hit statistics.
|
||||
// Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together.
|
||||
double maxBaseScore = score.Statistics.Select(kvp => kvp.Value).Sum() * Judgement.ToNumericResult(HitResult.Perfect);
|
||||
double baseScore = score.Statistics.Select(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value).Sum();
|
||||
if (maxBaseScore > 0)
|
||||
accuracy = baseScore / maxBaseScore;
|
||||
}
|
||||
|
||||
// This score is guaranteed to be an osu!stable score.
|
||||
// The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used.
|
||||
if (score.Beatmap.MaxCombo == null)
|
||||
{
|
||||
if (score.Beatmap.ID == 0 || difficulties == null)
|
||||
{
|
||||
// We don't have enough information (max combo) to compute the score, so use the provided score.
|
||||
Value = score.TotalScore;
|
||||
return;
|
||||
}
|
||||
|
||||
// We can compute the max combo locally after the async beatmap difficulty computation.
|
||||
difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods, (difficultyCancellationSource = new CancellationTokenSource()).Token);
|
||||
difficultyBindable.BindValueChanged(d =>
|
||||
{
|
||||
if (d.NewValue is StarDifficulty diff)
|
||||
updateScore(diff.MaxCombo, accuracy);
|
||||
}, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
beatmapMaxCombo = score.Beatmap.MaxCombo.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is guaranteed to be a non-legacy score.
|
||||
// The combo must be determined through the score's statistics, as both the beatmap's max combo and the difficulty calculator will provide osu!stable combo values.
|
||||
beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetValueOrDefault(r)).Sum();
|
||||
}
|
||||
|
||||
updateScore(beatmapMaxCombo, accuracy);
|
||||
}
|
||||
|
||||
private void updateScore(int beatmapMaxCombo, double accuracy)
|
||||
{
|
||||
if (beatmapMaxCombo == 0)
|
||||
{
|
||||
Value = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
var ruleset = score.Ruleset.CreateInstance();
|
||||
var scoreProcessor = ruleset.CreateScoreProcessor();
|
||||
|
||||
scoreProcessor.Mods.Value = score.Mods;
|
||||
|
||||
Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, beatmapMaxCombo, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics));
|
||||
scoreManager.GetTotalScore(score, s => Value = s, mode.NewValue, difficultyCalculationCancellationSource.Token);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private ScoreManager scoreManager { get; set; }
|
||||
|
||||
public PlaylistsResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem, bool allowRetry, bool allowWatchingReplay = true)
|
||||
: base(score, allowRetry, allowWatchingReplay)
|
||||
{
|
||||
@ -166,23 +169,28 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
/// <param name="pivot">An optional pivot around which the scores were retrieved.</param>
|
||||
private void performSuccessCallback([NotNull] Action<IEnumerable<ScoreInfo>> callback, [NotNull] List<MultiplayerScore> scores, [CanBeNull] MultiplayerScores pivot = null)
|
||||
{
|
||||
var scoreInfos = new List<ScoreInfo>(scores.Select(s => s.CreateScoreInfo(playlistItem)));
|
||||
var scoreInfos = scores.Select(s => s.CreateScoreInfo(playlistItem)).ToArray();
|
||||
|
||||
// Select a score if we don't already have one selected.
|
||||
// Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll).
|
||||
if (SelectedScore.Value == null)
|
||||
// Score panels calculate total score before displaying, which can take some time. In order to count that calculation as part of the loading spinner display duration,
|
||||
// calculate the total scores locally before invoking the success callback.
|
||||
scoreManager.OrderByTotalScoreAsync(scoreInfos).ContinueWith(_ => Schedule(() =>
|
||||
{
|
||||
Schedule(() =>
|
||||
// Select a score if we don't already have one selected.
|
||||
// Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll).
|
||||
if (SelectedScore.Value == null)
|
||||
{
|
||||
// Prefer selecting the local user's score, or otherwise default to the first visible score.
|
||||
SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.Id == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault();
|
||||
});
|
||||
}
|
||||
Schedule(() =>
|
||||
{
|
||||
// Prefer selecting the local user's score, or otherwise default to the first visible score.
|
||||
SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.Id == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault();
|
||||
});
|
||||
}
|
||||
|
||||
// Invoke callback to add the scores. Exclude the user's current score which was added previously.
|
||||
callback.Invoke(scoreInfos.Where(s => s.OnlineScoreID != Score?.OnlineScoreID));
|
||||
// Invoke callback to add the scores. Exclude the user's current score which was added previously.
|
||||
callback.Invoke(scoreInfos.Where(s => s.OnlineScoreID != Score?.OnlineScoreID));
|
||||
|
||||
hideLoadingSpinners(pivot);
|
||||
hideLoadingSpinners(pivot);
|
||||
}));
|
||||
}
|
||||
|
||||
private void hideLoadingSpinners([CanBeNull] MultiplayerScores pivot = null)
|
||||
|
@ -52,8 +52,7 @@ namespace osu.Game.Screens.Ranking
|
||||
private Drawable bottomPanel;
|
||||
private Container<ScorePanel> detachedPanelContainer;
|
||||
|
||||
private bool fetchedInitialScores;
|
||||
private APIRequest nextPageRequest;
|
||||
private bool lastFetchCompleted;
|
||||
|
||||
private readonly bool allowRetry;
|
||||
private readonly bool allowWatchingReplay;
|
||||
@ -191,8 +190,10 @@ namespace osu.Game.Screens.Ranking
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (fetchedInitialScores && nextPageRequest == null)
|
||||
if (lastFetchCompleted)
|
||||
{
|
||||
APIRequest nextPageRequest = null;
|
||||
|
||||
if (ScorePanelList.IsScrolledToStart)
|
||||
nextPageRequest = FetchNextPage(-1, fetchScoresCallback);
|
||||
else if (ScorePanelList.IsScrolledToEnd)
|
||||
@ -200,10 +201,7 @@ namespace osu.Game.Screens.Ranking
|
||||
|
||||
if (nextPageRequest != null)
|
||||
{
|
||||
// Scheduled after children to give the list a chance to update its scroll position and not potentially trigger a second request too early.
|
||||
nextPageRequest.Success += () => ScheduleAfterChildren(() => nextPageRequest = null);
|
||||
nextPageRequest.Failure += _ => ScheduleAfterChildren(() => nextPageRequest = null);
|
||||
|
||||
lastFetchCompleted = false;
|
||||
api.Queue(nextPageRequest);
|
||||
}
|
||||
}
|
||||
@ -229,7 +227,7 @@ namespace osu.Game.Screens.Ranking
|
||||
foreach (var s in scores)
|
||||
addScore(s);
|
||||
|
||||
fetchedInitialScores = true;
|
||||
lastFetchCompleted = true;
|
||||
});
|
||||
|
||||
public override void OnEntering(IScreen last)
|
||||
|
@ -5,10 +5,14 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Scoring;
|
||||
using osuTK;
|
||||
@ -36,12 +40,14 @@ namespace osu.Game.Screens.Ranking
|
||||
/// <summary>
|
||||
/// Whether this <see cref="ScorePanelList"/> can be scrolled and is currently scrolled to the start.
|
||||
/// </summary>
|
||||
public bool IsScrolledToStart => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.Current <= scroll_endpoint_distance;
|
||||
public bool IsScrolledToStart => flow.Count > 0 && AllPanelsVisible && scroll.ScrollableExtent > 0 && scroll.Current <= scroll_endpoint_distance;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this <see cref="ScorePanelList"/> can be scrolled and is currently scrolled to the end.
|
||||
/// </summary>
|
||||
public bool IsScrolledToEnd => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.IsScrolledToEnd(scroll_endpoint_distance);
|
||||
public bool IsScrolledToEnd => flow.Count > 0 && AllPanelsVisible && scroll.ScrollableExtent > 0 && scroll.IsScrolledToEnd(scroll_endpoint_distance);
|
||||
|
||||
public bool AllPanelsVisible => flow.All(p => p.IsPresent);
|
||||
|
||||
/// <summary>
|
||||
/// The current scroll position.
|
||||
@ -60,6 +66,13 @@ namespace osu.Game.Screens.Ranking
|
||||
|
||||
public readonly Bindable<ScoreInfo> SelectedScore = new Bindable<ScoreInfo>();
|
||||
|
||||
[Resolved]
|
||||
private ScoreManager scoreManager { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private BeatmapDifficultyCache difficultyCache { get; set; }
|
||||
|
||||
private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource();
|
||||
private readonly Flow flow;
|
||||
private readonly Scroll scroll;
|
||||
private ScorePanel expandedPanel;
|
||||
@ -90,6 +103,9 @@ namespace osu.Game.Screens.Ranking
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
foreach (var d in flow)
|
||||
displayScore(d);
|
||||
|
||||
SelectedScore.BindValueChanged(selectedScoreChanged, true);
|
||||
}
|
||||
|
||||
@ -114,36 +130,56 @@ namespace osu.Game.Screens.Ranking
|
||||
};
|
||||
});
|
||||
|
||||
flow.Add(panel.CreateTrackingContainer().With(d =>
|
||||
var trackingContainer = panel.CreateTrackingContainer().With(d =>
|
||||
{
|
||||
d.Anchor = Anchor.Centre;
|
||||
d.Origin = Anchor.Centre;
|
||||
}));
|
||||
d.Hide();
|
||||
});
|
||||
|
||||
flow.Add(trackingContainer);
|
||||
|
||||
if (IsLoaded)
|
||||
{
|
||||
if (SelectedScore.Value == score)
|
||||
{
|
||||
SelectedScore.TriggerChange();
|
||||
}
|
||||
else
|
||||
{
|
||||
// We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done.
|
||||
// But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel.
|
||||
if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score))
|
||||
{
|
||||
// A somewhat hacky property is used here because we need to:
|
||||
// 1) Scroll after the scroll container's visible range is updated.
|
||||
// 2) Scroll before the scroll container's scroll position is updated.
|
||||
// Without this, we would have a 1-frame positioning error which looks very jarring.
|
||||
scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
displayScore(trackingContainer);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private void displayScore(ScorePanelTrackingContainer trackingContainer)
|
||||
{
|
||||
if (!IsLoaded)
|
||||
return;
|
||||
|
||||
var score = trackingContainer.Panel.Score;
|
||||
|
||||
// Calculating score can take a while in extreme scenarios, so only display scores after the process completes.
|
||||
scoreManager.GetTotalScoreAsync(score)
|
||||
.ContinueWith(totalScore => Schedule(() =>
|
||||
{
|
||||
flow.SetLayoutPosition(trackingContainer, totalScore.Result);
|
||||
|
||||
trackingContainer.Show();
|
||||
|
||||
if (SelectedScore.Value == score)
|
||||
{
|
||||
SelectedScore.TriggerChange();
|
||||
}
|
||||
else
|
||||
{
|
||||
// We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done.
|
||||
// But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel.
|
||||
if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score))
|
||||
{
|
||||
// A somewhat hacky property is used here because we need to:
|
||||
// 1) Scroll after the scroll container's visible range is updated.
|
||||
// 2) Scroll before the scroll container's scroll position is updated.
|
||||
// Without this, we would have a 1-frame positioning error which looks very jarring.
|
||||
scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing;
|
||||
}
|
||||
}
|
||||
}), TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Brings a <see cref="ScoreInfo"/> to the centre of the screen and expands it.
|
||||
/// </summary>
|
||||
@ -267,6 +303,9 @@ namespace osu.Game.Screens.Ranking
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (expandedPanel == null)
|
||||
return base.OnKeyDown(e);
|
||||
|
||||
var expandedPanelIndex = flow.GetPanelIndex(expandedPanel.Score);
|
||||
|
||||
switch (e.Key)
|
||||
@ -285,6 +324,12 @@ namespace osu.Game.Screens.Ranking
|
||||
return base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
loadCancellationSource?.Cancel();
|
||||
}
|
||||
|
||||
private class Flow : FillFlowContainer<ScorePanelTrackingContainer>
|
||||
{
|
||||
public override IEnumerable<Drawable> FlowingChildren => applySorting(AliveInternalChildren);
|
||||
@ -292,24 +337,8 @@ namespace osu.Game.Screens.Ranking
|
||||
public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).Count();
|
||||
|
||||
private IEnumerable<ScorePanelTrackingContainer> applySorting(IEnumerable<Drawable> drawables) => drawables.OfType<ScorePanelTrackingContainer>()
|
||||
.OrderByDescending(s => s.Panel.Score.TotalScore)
|
||||
.OrderByDescending(GetLayoutPosition)
|
||||
.ThenBy(s => s.Panel.Score.OnlineScoreID);
|
||||
|
||||
protected override int Compare(Drawable x, Drawable y)
|
||||
{
|
||||
var tX = (ScorePanelTrackingContainer)x;
|
||||
var tY = (ScorePanelTrackingContainer)y;
|
||||
|
||||
int result = tY.Panel.Score.TotalScore.CompareTo(tX.Panel.Score.TotalScore);
|
||||
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
if (tX.Panel.Score.OnlineScoreID == null || tY.Panel.Score.OnlineScoreID == null)
|
||||
return base.Compare(x, y);
|
||||
|
||||
return tX.Panel.Score.OnlineScoreID.Value.CompareTo(tY.Panel.Score.OnlineScoreID.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private class Scroll : OsuScrollContainer
|
||||
|
@ -4,6 +4,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -66,6 +68,9 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
[Resolved]
|
||||
private ScoreManager scoreManager { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private BeatmapDifficultyCache difficultyCache { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; }
|
||||
|
||||
@ -120,8 +125,13 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
|
||||
protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local;
|
||||
|
||||
private CancellationTokenSource loadCancellationSource;
|
||||
|
||||
protected override APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback)
|
||||
{
|
||||
loadCancellationSource?.Cancel();
|
||||
loadCancellationSource = new CancellationTokenSource();
|
||||
|
||||
if (Beatmap == null)
|
||||
{
|
||||
PlaceholderState = PlaceholderState.NoneSelected;
|
||||
@ -146,8 +156,8 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym)));
|
||||
}
|
||||
|
||||
Scores = scores.OrderByDescending(s => s.TotalScore).ToArray();
|
||||
PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores;
|
||||
scoreManager.OrderByTotalScoreAsync(scores.ToArray(), loadCancellationSource.Token)
|
||||
.ContinueWith(ordered => scoresCallback?.Invoke(ordered.Result), TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -182,8 +192,15 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
|
||||
req.Success += r =>
|
||||
{
|
||||
scoresCallback?.Invoke(r.Scores.Select(s => s.CreateScoreInfo(rulesets)));
|
||||
TopScore = r.UserScore?.CreateScoreInfo(rulesets);
|
||||
scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token)
|
||||
.ContinueWith(ordered => Schedule(() =>
|
||||
{
|
||||
if (loadCancellationSource.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
scoresCallback?.Invoke(ordered.Result);
|
||||
TopScore = r.UserScore?.CreateScoreInfo(rulesets);
|
||||
}), TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
};
|
||||
|
||||
return req;
|
||||
|
Loading…
Reference in New Issue
Block a user