From 3e782c5f5fd6eee9a34150aeaeb9aefbd9db97f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 23 Dec 2022 23:46:41 +0100 Subject: [PATCH 1/5] Extract interface for solo statistics watcher --- .../Online/Solo/ISoloStatisticsWatcher.cs | 23 +++++++++++++++++++ osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/Solo/ISoloStatisticsWatcher.cs diff --git a/osu.Game/Online/Solo/ISoloStatisticsWatcher.cs b/osu.Game/Online/Solo/ISoloStatisticsWatcher.cs new file mode 100644 index 0000000000..84986297bf --- /dev/null +++ b/osu.Game/Online/Solo/ISoloStatisticsWatcher.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Game.Scoring; + +namespace osu.Game.Online.Solo +{ + /// + /// A component that delivers updates to the logged in user's gameplay statistics after completed scores. + /// + [Cached] + public interface ISoloStatisticsWatcher + { + /// + /// Registers for a user statistics update after the given has been processed server-side. + /// + /// The score to listen for the statistics update for. + /// The callback to be invoked once the statistics update has been prepared. + void RegisterForStatisticsUpdateAfter(ScoreInfo score, Action onUpdateReady); + } +} diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 48f39504a3..6a9a20e58d 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -20,7 +20,7 @@ namespace osu.Game.Online.Solo /// /// A persistent component that binds to the spectator server and API in order to deliver updates about the logged in user's gameplay statistics. /// - public partial class SoloStatisticsWatcher : Component + public partial class SoloStatisticsWatcher : Component, ISoloStatisticsWatcher { [Resolved] private SpectatorClient spectatorClient { get; set; } = null!; From c7f248e13c7ce98c093f8f41cad30d3b9d7ca621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 23 Dec 2022 14:27:22 +0100 Subject: [PATCH 2/5] Implement overall ranking display for solo results screen --- .../Visual/Ranking/TestSceneOverallRanking.cs | 142 +++++++++++++++++ .../Statistics/User/AccuracyChangeRow.cs | 35 +++++ .../Statistics/User/GlobalRankChangeRow.cs | 58 +++++++ .../Statistics/User/MaximumComboChangeRow.cs | 34 +++++ .../Ranking/Statistics/User/OverallRanking.cs | 90 +++++++++++ .../User/PerformancePointsChangeRow.cs | 56 +++++++ .../Statistics/User/RankedScoreChangeRow.cs | 35 +++++ .../Statistics/User/RankingChangeRow.cs | 144 ++++++++++++++++++ .../Statistics/User/TotalScoreChangeRow.cs | 35 +++++ 9 files changed, 629 insertions(+) create mode 100644 osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/User/AccuracyChangeRow.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/User/GlobalRankChangeRow.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/User/MaximumComboChangeRow.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/User/PerformancePointsChangeRow.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/User/RankedScoreChangeRow.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/User/TotalScoreChangeRow.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs new file mode 100644 index 0000000000..8d2147056c --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs @@ -0,0 +1,142 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Diagnostics; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Online.Solo; +using osu.Game.Scoring; +using osu.Game.Users; +using OverallRanking = osu.Game.Screens.Ranking.Statistics.User.OverallRanking; + +namespace osu.Game.Tests.Visual.Ranking +{ + public partial class TestSceneOverallRanking : OsuTestScene + { + [Cached(typeof(ISoloStatisticsWatcher))] + private MockSoloStatisticsWatcher soloStatisticsWatcher { get; } = new MockSoloStatisticsWatcher(); + + [Test] + public void TestUpdatePending() + { + createDisplay(); + } + + [Test] + public void TestAllIncreased() + { + createDisplay(); + AddStep("trigger update success", () => + { + soloStatisticsWatcher.TriggerSuccess( + new UserStatistics + { + GlobalRank = 12_345, + Accuracy = 0.9899, + MaxCombo = 2_322, + RankedScore = 23_123_543_456, + TotalScore = 123_123_543_456, + PP = 5_072 + }, + new UserStatistics + { + GlobalRank = 1_234, + Accuracy = 0.9907, + MaxCombo = 2_352, + RankedScore = 23_124_231_435, + TotalScore = 123_124_231_435, + PP = 5_434 + }); + }); + } + + [Test] + public void TestAllDecreased() + { + createDisplay(); + AddStep("trigger update success", () => + { + soloStatisticsWatcher.TriggerSuccess( + new UserStatistics + { + GlobalRank = 1_234, + Accuracy = 0.9907, + MaxCombo = 2_352, + RankedScore = 23_124_231_435, + TotalScore = 123_124_231_435, + PP = 5_434 + }, + new UserStatistics + { + GlobalRank = 12_345, + Accuracy = 0.9899, + MaxCombo = 2_322, + RankedScore = 23_123_543_456, + TotalScore = 123_123_543_456, + PP = 5_072 + }); + }); + } + + [Test] + public void TestNoChanges() + { + var statistics = new UserStatistics + { + GlobalRank = 12_345, + Accuracy = 0.9899, + MaxCombo = 2_322, + RankedScore = 23_123_543_456, + TotalScore = 123_123_543_456, + PP = 5_072 + }; + + createDisplay(); + AddStep("trigger update success", () => soloStatisticsWatcher.TriggerSuccess(statistics, statistics)); + } + + [Test] + public void TestNotRanked() + { + var statistics = new UserStatistics + { + GlobalRank = null, + Accuracy = 0.9899, + MaxCombo = 2_322, + RankedScore = 23_123_543_456, + TotalScore = 123_123_543_456, + PP = null + }; + + createDisplay(); + AddStep("trigger update success", () => soloStatisticsWatcher.TriggerSuccess(statistics, statistics)); + } + + private void createDisplay() => AddStep("create display", () => Child = new OverallRanking(new ScoreInfo()) + { + Width = 400, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); + + private class MockSoloStatisticsWatcher : ISoloStatisticsWatcher + { + private ScoreInfo? score; + private Action? onUpdateReady; + + public void RegisterForStatisticsUpdateAfter(ScoreInfo score, Action onUpdateReady) + { + this.score = score; + this.onUpdateReady = onUpdateReady; + } + + public void TriggerSuccess(UserStatistics before, UserStatistics after) + { + Debug.Assert(score != null && onUpdateReady != null); + onUpdateReady.Invoke(new SoloStatisticsUpdate(score, before, after)); + } + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/User/AccuracyChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/AccuracyChangeRow.cs new file mode 100644 index 0000000000..0f5dd9074a --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/User/AccuracyChangeRow.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Utils; + +namespace osu.Game.Screens.Ranking.Statistics.User +{ + public partial class AccuracyChangeRow : RankingChangeRow + { + public AccuracyChangeRow() + : base(stats => stats.Accuracy) + { + } + + protected override LocalisableString Label => UsersStrings.ShowStatsHitAccuracy; + + protected override LocalisableString FormatCurrentValue(double current) => current.FormatAccuracy(); + + protected override int CalculateDifference(double previous, double current, out LocalisableString formattedDifference) + { + double difference = current - previous; + + if (difference < 0) + formattedDifference = difference.FormatAccuracy(); + else if (difference > 0) + formattedDifference = LocalisableString.Interpolate($@"+{difference.FormatAccuracy()}"); + else + formattedDifference = string.Empty; + + return current.CompareTo(previous); + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/User/GlobalRankChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/GlobalRankChangeRow.cs new file mode 100644 index 0000000000..0d91d6f8f9 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/User/GlobalRankChangeRow.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Utils; + +namespace osu.Game.Screens.Ranking.Statistics.User +{ + public partial class GlobalRankChangeRow : RankingChangeRow + { + public GlobalRankChangeRow() + : base(stats => stats.GlobalRank) + { + } + + protected override LocalisableString Label => UsersStrings.ShowRankGlobalSimple; + + protected override LocalisableString FormatCurrentValue(int? current) + => current == null ? string.Empty : current.Value.FormatRank(); + + protected override int CalculateDifference(int? previous, int? current, out LocalisableString formattedDifference) + { + if (previous == null && current == null) + { + formattedDifference = string.Empty; + return 0; + } + + if (previous == null && current != null) + { + formattedDifference = LocalisableString.Interpolate($"+{current.Value.FormatRank()}"); + return 1; + } + + if (previous != null && current == null) + { + formattedDifference = LocalisableString.Interpolate($"-{previous.Value.FormatRank()}"); + return -1; + } + + Debug.Assert(previous != null && current != null); + + // note that ranks work backwards, i.e. lower rank is _better_. + int difference = previous.Value - current.Value; + + if (difference < 0) + formattedDifference = difference.FormatRank(); + else if (difference > 0) + formattedDifference = LocalisableString.Interpolate($"+{difference.FormatRank()}"); + else + formattedDifference = string.Empty; + + return difference; + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/User/MaximumComboChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/MaximumComboChangeRow.cs new file mode 100644 index 0000000000..37e3cec52f --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/User/MaximumComboChangeRow.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Screens.Ranking.Statistics.User +{ + public partial class MaximumComboChangeRow : RankingChangeRow + { + public MaximumComboChangeRow() + : base(stats => stats.MaxCombo) + { + } + + protected override LocalisableString Label => UsersStrings.ShowStatsMaximumCombo; + + protected override LocalisableString FormatCurrentValue(int current) => LocalisableString.Interpolate($@"{current:N0}x"); + + protected override int CalculateDifference(int previous, int current, out LocalisableString formattedDifference) + { + int difference = current - previous; + + if (difference < 0) + formattedDifference = LocalisableString.Interpolate($@"{difference:N0}x"); + else if (difference > 0) + formattedDifference = LocalisableString.Interpolate($@"+{difference:N0}x"); + else + formattedDifference = string.Empty; + + return current.CompareTo(previous); + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs new file mode 100644 index 0000000000..499deb92be --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs @@ -0,0 +1,90 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Solo; +using osu.Game.Scoring; +using osuTK; + +namespace osu.Game.Screens.Ranking.Statistics.User +{ + public partial class OverallRanking : CompositeDrawable + { + private const float transition_duration = 300; + + private readonly ScoreInfo score; + + private readonly Bindable statisticsUpdate = new Bindable(); + + private LoadingLayer loadingLayer = null!; + private FillFlowContainer content = null!; + + [Resolved] + private ISoloStatisticsWatcher statisticsWatcher { get; set; } = null!; + + public OverallRanking(ScoreInfo score) + { + this.score = score; + } + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Y; + AutoSizeEasing = Easing.OutQuint; + AutoSizeDuration = transition_duration; + + InternalChildren = new Drawable[] + { + loadingLayer = new LoadingLayer(withBox: false) + { + RelativeSizeAxes = Axes.Both, + }, + content = new FillFlowContainer + { + AlwaysPresent = true, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Children = new Drawable[] + { + new GlobalRankChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, + new AccuracyChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, + new MaximumComboChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, + new RankedScoreChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, + new TotalScoreChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, + new PerformancePointsChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + statisticsWatcher.RegisterForStatisticsUpdateAfter(score, update => statisticsUpdate.Value = update); + statisticsUpdate.BindValueChanged(onUpdateReceived, true); + FinishTransforms(true); + } + + private void onUpdateReceived(ValueChangedEvent update) + { + if (update.NewValue == null) + { + loadingLayer.Show(); + content.FadeOut(transition_duration, Easing.OutQuint); + } + else + { + loadingLayer.Hide(); + content.FadeIn(transition_duration, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/User/PerformancePointsChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/PerformancePointsChangeRow.cs new file mode 100644 index 0000000000..c1faf1a3e3 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/User/PerformancePointsChangeRow.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Screens.Ranking.Statistics.User +{ + public partial class PerformancePointsChangeRow : RankingChangeRow + { + public PerformancePointsChangeRow() + : base(stats => stats.PP) + { + } + + protected override LocalisableString Label => RankingsStrings.StatPerformance; + + protected override LocalisableString FormatCurrentValue(decimal? current) + => current == null ? string.Empty : LocalisableString.Interpolate($@"{current:N0}pp"); + + protected override int CalculateDifference(decimal? previous, decimal? current, out LocalisableString formattedDifference) + { + if (previous == null && current == null) + { + formattedDifference = string.Empty; + return 0; + } + + if (previous == null && current != null) + { + formattedDifference = LocalisableString.Interpolate($"+{current.Value:N0}pp"); + return 1; + } + + if (previous != null && current == null) + { + formattedDifference = LocalisableString.Interpolate($"-{previous.Value:N0}pp"); + return -1; + } + + Debug.Assert(previous != null && current != null); + + decimal difference = current.Value - previous.Value; + + if (difference < 0) + formattedDifference = LocalisableString.Interpolate($@"{difference:N0}pp"); + else if (difference > 0) + formattedDifference = LocalisableString.Interpolate($@"+{difference:N0}pp"); + else + formattedDifference = string.Empty; + + return current.Value.CompareTo(previous.Value); + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/User/RankedScoreChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/RankedScoreChangeRow.cs new file mode 100644 index 0000000000..1cdf22bd75 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/User/RankedScoreChangeRow.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Screens.Ranking.Statistics.User +{ + public partial class RankedScoreChangeRow : RankingChangeRow + { + public RankedScoreChangeRow() + : base(stats => stats.RankedScore) + { + } + + protected override LocalisableString Label => UsersStrings.ShowStatsRankedScore; + + protected override LocalisableString FormatCurrentValue(long current) => current.ToLocalisableString(@"N0"); + + protected override int CalculateDifference(long previous, long current, out LocalisableString formattedDifference) + { + long difference = current - previous; + + if (difference < 0) + formattedDifference = difference.ToLocalisableString(@"N0"); + else if (difference > 0) + formattedDifference = LocalisableString.Interpolate($@"+{difference:N0}"); + else + formattedDifference = string.Empty; + + return current.CompareTo(previous); + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs new file mode 100644 index 0000000000..5348b4a522 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs @@ -0,0 +1,144 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Solo; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Screens.Ranking.Statistics.User +{ + public abstract partial class RankingChangeRow : CompositeDrawable + { + public Bindable StatisticsUpdate { get; } = new Bindable(); + + private readonly Func accessor; + + private OsuSpriteText currentValueText = null!; + private SpriteIcon changeIcon = null!; + private OsuSpriteText changeText = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + protected RankingChangeRow( + Func accessor) + { + this.accessor = accessor; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + new OsuSpriteText + { + Text = Label, + Font = OsuFont.Default.With(size: 18) + }, + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5), + Children = new Drawable[] + { + changeIcon = new SpriteIcon + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(18) + }, + currentValueText = new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Font = OsuFont.Default.With(size: 18, weight: FontWeight.Bold) + }, + } + }, + changeText = new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Font = OsuFont.Default.With(weight: FontWeight.Bold) + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + StatisticsUpdate.BindValueChanged(onStatisticsUpdate, true); + } + + private void onStatisticsUpdate(ValueChangedEvent statisticsUpdate) + { + var update = statisticsUpdate.NewValue; + + if (update == null) + return; + + T previousValue = accessor.Invoke(update.Before); + T currentValue = accessor.Invoke(update.After); + int comparisonResult = CalculateDifference(previousValue, currentValue, out var formattedDifference); + + Colour4 comparisonColour; + IconUsage icon; + + if (comparisonResult < 0) + { + comparisonColour = colours.Red1; + icon = FontAwesome.Solid.ArrowDown; + } + else if (comparisonResult > 0) + { + comparisonColour = colours.Lime1; + icon = FontAwesome.Solid.ArrowUp; + } + else + { + comparisonColour = colours.Orange1; + icon = FontAwesome.Solid.Minus; + } + + currentValueText.Text = FormatCurrentValue(currentValue); + + changeIcon.Icon = icon; + changeIcon.Colour = comparisonColour; + + changeText.Text = formattedDifference; + changeText.Colour = comparisonColour; + } + + protected abstract LocalisableString Label { get; } + + protected abstract LocalisableString FormatCurrentValue(T current); + protected abstract int CalculateDifference(T previous, T current, out LocalisableString formattedDifference); + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/User/TotalScoreChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/TotalScoreChangeRow.cs new file mode 100644 index 0000000000..346de18e14 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/User/TotalScoreChangeRow.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Screens.Ranking.Statistics.User +{ + public partial class TotalScoreChangeRow : RankingChangeRow + { + public TotalScoreChangeRow() + : base(stats => stats.TotalScore) + { + } + + protected override LocalisableString Label => UsersStrings.ShowStatsTotalScore; + + protected override LocalisableString FormatCurrentValue(long current) => current.ToLocalisableString(@"N0"); + + protected override int CalculateDifference(long previous, long current, out LocalisableString formattedDifference) + { + long difference = current - previous; + + if (difference < 0) + formattedDifference = difference.ToLocalisableString(@"N0"); + else if (difference > 0) + formattedDifference = LocalisableString.Interpolate($@"+{difference:N0}"); + else + formattedDifference = string.Empty; + + return current.CompareTo(previous); + } + } +} From 4e5109a6495668f118e7466c5d1ec8cc2b8c435b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 10:27:28 +0100 Subject: [PATCH 3/5] Use plain bindable flow instead of binding to watcher directly --- .../Visual/Ranking/TestSceneOverallRanking.cs | 113 +++++++----------- .../Online/Solo/ISoloStatisticsWatcher.cs | 23 ---- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 2 +- .../Ranking/Statistics/User/OverallRanking.cs | 20 ++-- 4 files changed, 53 insertions(+), 105 deletions(-) delete mode 100644 osu.Game/Online/Solo/ISoloStatisticsWatcher.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs index 8d2147056c..11bb61affb 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Diagnostics; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Online.Solo; using osu.Game.Scoring; @@ -15,8 +12,7 @@ namespace osu.Game.Tests.Visual.Ranking { public partial class TestSceneOverallRanking : OsuTestScene { - [Cached(typeof(ISoloStatisticsWatcher))] - private MockSoloStatisticsWatcher soloStatisticsWatcher { get; } = new MockSoloStatisticsWatcher(); + private OverallRanking overallRanking = null!; [Test] public void TestUpdatePending() @@ -28,56 +24,50 @@ namespace osu.Game.Tests.Visual.Ranking public void TestAllIncreased() { createDisplay(); - AddStep("trigger update success", () => - { - soloStatisticsWatcher.TriggerSuccess( - new UserStatistics - { - GlobalRank = 12_345, - Accuracy = 0.9899, - MaxCombo = 2_322, - RankedScore = 23_123_543_456, - TotalScore = 123_123_543_456, - PP = 5_072 - }, - new UserStatistics - { - GlobalRank = 1_234, - Accuracy = 0.9907, - MaxCombo = 2_352, - RankedScore = 23_124_231_435, - TotalScore = 123_124_231_435, - PP = 5_434 - }); - }); + displayUpdate( + new UserStatistics + { + GlobalRank = 12_345, + Accuracy = 0.9899, + MaxCombo = 2_322, + RankedScore = 23_123_543_456, + TotalScore = 123_123_543_456, + PP = 5_072 + }, + new UserStatistics + { + GlobalRank = 1_234, + Accuracy = 0.9907, + MaxCombo = 2_352, + RankedScore = 23_124_231_435, + TotalScore = 123_124_231_435, + PP = 5_434 + }); } [Test] public void TestAllDecreased() { createDisplay(); - AddStep("trigger update success", () => - { - soloStatisticsWatcher.TriggerSuccess( - new UserStatistics - { - GlobalRank = 1_234, - Accuracy = 0.9907, - MaxCombo = 2_352, - RankedScore = 23_124_231_435, - TotalScore = 123_124_231_435, - PP = 5_434 - }, - new UserStatistics - { - GlobalRank = 12_345, - Accuracy = 0.9899, - MaxCombo = 2_322, - RankedScore = 23_123_543_456, - TotalScore = 123_123_543_456, - PP = 5_072 - }); - }); + displayUpdate( + new UserStatistics + { + GlobalRank = 1_234, + Accuracy = 0.9907, + MaxCombo = 2_352, + RankedScore = 23_124_231_435, + TotalScore = 123_124_231_435, + PP = 5_434 + }, + new UserStatistics + { + GlobalRank = 12_345, + Accuracy = 0.9899, + MaxCombo = 2_322, + RankedScore = 23_123_543_456, + TotalScore = 123_123_543_456, + PP = 5_072 + }); } [Test] @@ -94,7 +84,7 @@ namespace osu.Game.Tests.Visual.Ranking }; createDisplay(); - AddStep("trigger update success", () => soloStatisticsWatcher.TriggerSuccess(statistics, statistics)); + displayUpdate(statistics, statistics); } [Test] @@ -111,32 +101,17 @@ namespace osu.Game.Tests.Visual.Ranking }; createDisplay(); - AddStep("trigger update success", () => soloStatisticsWatcher.TriggerSuccess(statistics, statistics)); + displayUpdate(statistics, statistics); } - private void createDisplay() => AddStep("create display", () => Child = new OverallRanking(new ScoreInfo()) + private void createDisplay() => AddStep("create display", () => Child = overallRanking = new OverallRanking(new ScoreInfo()) { Width = 400, Anchor = Anchor.Centre, Origin = Anchor.Centre }); - private class MockSoloStatisticsWatcher : ISoloStatisticsWatcher - { - private ScoreInfo? score; - private Action? onUpdateReady; - - public void RegisterForStatisticsUpdateAfter(ScoreInfo score, Action onUpdateReady) - { - this.score = score; - this.onUpdateReady = onUpdateReady; - } - - public void TriggerSuccess(UserStatistics before, UserStatistics after) - { - Debug.Assert(score != null && onUpdateReady != null); - onUpdateReady.Invoke(new SoloStatisticsUpdate(score, before, after)); - } - } + private void displayUpdate(UserStatistics before, UserStatistics after) => + AddStep("display update", () => overallRanking.StatisticsUpdate.Value = new SoloStatisticsUpdate(new ScoreInfo(), before, after)); } } diff --git a/osu.Game/Online/Solo/ISoloStatisticsWatcher.cs b/osu.Game/Online/Solo/ISoloStatisticsWatcher.cs deleted file mode 100644 index 84986297bf..0000000000 --- a/osu.Game/Online/Solo/ISoloStatisticsWatcher.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Allocation; -using osu.Game.Scoring; - -namespace osu.Game.Online.Solo -{ - /// - /// A component that delivers updates to the logged in user's gameplay statistics after completed scores. - /// - [Cached] - public interface ISoloStatisticsWatcher - { - /// - /// Registers for a user statistics update after the given has been processed server-side. - /// - /// The score to listen for the statistics update for. - /// The callback to be invoked once the statistics update has been prepared. - void RegisterForStatisticsUpdateAfter(ScoreInfo score, Action onUpdateReady); - } -} diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 6a9a20e58d..48f39504a3 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -20,7 +20,7 @@ namespace osu.Game.Online.Solo /// /// A persistent component that binds to the spectator server and API in order to deliver updates about the logged in user's gameplay statistics. /// - public partial class SoloStatisticsWatcher : Component, ISoloStatisticsWatcher + public partial class SoloStatisticsWatcher : Component { [Resolved] private SpectatorClient spectatorClient { get; set; } = null!; diff --git a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs index 499deb92be..8f922f8015 100644 --- a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs +++ b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs @@ -18,14 +18,11 @@ namespace osu.Game.Screens.Ranking.Statistics.User private readonly ScoreInfo score; - private readonly Bindable statisticsUpdate = new Bindable(); + public Bindable StatisticsUpdate { get; } = new Bindable(); private LoadingLayer loadingLayer = null!; private FillFlowContainer content = null!; - [Resolved] - private ISoloStatisticsWatcher statisticsWatcher { get; set; } = null!; - public OverallRanking(ScoreInfo score) { this.score = score; @@ -53,12 +50,12 @@ namespace osu.Game.Screens.Ranking.Statistics.User Spacing = new Vector2(10), Children = new Drawable[] { - new GlobalRankChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, - new AccuracyChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, - new MaximumComboChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, - new RankedScoreChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, - new TotalScoreChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, - new PerformancePointsChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } } + new GlobalRankChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, + new AccuracyChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, + new MaximumComboChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, + new RankedScoreChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, + new TotalScoreChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, + new PerformancePointsChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } } } } }; @@ -68,8 +65,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User { base.LoadComplete(); - statisticsWatcher.RegisterForStatisticsUpdateAfter(score, update => statisticsUpdate.Value = update); - statisticsUpdate.BindValueChanged(onUpdateReceived, true); + StatisticsUpdate.BindValueChanged(onUpdateReceived, true); FinishTransforms(true); } From 83a50816b6b1b2b922d34a8d59553a205312571c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 10:44:38 +0100 Subject: [PATCH 4/5] Remove unused constructor param --- osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs | 2 +- .../Screens/Ranking/Statistics/User/OverallRanking.cs | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs index 11bb61affb..c2d7b33079 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.Ranking displayUpdate(statistics, statistics); } - private void createDisplay() => AddStep("create display", () => Child = overallRanking = new OverallRanking(new ScoreInfo()) + private void createDisplay() => AddStep("create display", () => Child = overallRanking = new OverallRanking { Width = 400, Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs index 8f922f8015..447f206128 100644 --- a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs +++ b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Solo; -using osu.Game.Scoring; using osuTK; namespace osu.Game.Screens.Ranking.Statistics.User @@ -16,18 +15,11 @@ namespace osu.Game.Screens.Ranking.Statistics.User { private const float transition_duration = 300; - private readonly ScoreInfo score; - public Bindable StatisticsUpdate { get; } = new Bindable(); private LoadingLayer loadingLayer = null!; private FillFlowContainer content = null!; - public OverallRanking(ScoreInfo score) - { - this.score = score; - } - [BackgroundDependencyLoader] private void load() { From 8c7814aaf0d296886644206b8633cce64e76ff12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Dec 2022 21:48:04 +0800 Subject: [PATCH 5/5] Fix weird using statement --- osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs index c2d7b33079..2edc577a95 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs @@ -5,8 +5,8 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online.Solo; using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Statistics.User; using osu.Game.Users; -using OverallRanking = osu.Game.Screens.Ranking.Statistics.User.OverallRanking; namespace osu.Game.Tests.Visual.Ranking {