From f488974d3989de068eaa59bd00fcc704f950e229 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Oct 2025 22:07:21 +0900 Subject: [PATCH] Improve design of quick play endgame results (#35267) --- .../Matchmaking/TestSceneMatchmakingScreen.cs | 14 +- .../Matchmaking/TestScenePanelRoomAward.cs | 2 +- .../Matchmaking/TestSceneResultsScreen.cs | 54 +++- osu.Game/Graphics/OsuColour.cs | 2 +- .../Components/DailyChallengeStatsDisplay.cs | 2 +- .../Components/DailyChallengeStatsTooltip.cs | 13 +- .../Profile/Header/Components/LevelBadge.cs | 5 +- .../Matchmaking/Match/PlayerPanel.cs | 11 +- .../Match/Results/PanelRoomAward.cs | 110 ++++++- .../Match/Results/PanelUserStatistic.cs | 63 +++- .../Match/Results/SubScreenResults.cs | 268 ++++++++++-------- .../Match/ScreenMatchmaking.ScreenStack.cs | 4 +- 12 files changed, 371 insertions(+), 177 deletions(-) diff --git a/osu.Game.Tests/Visual/Matchmaking/TestSceneMatchmakingScreen.cs b/osu.Game.Tests/Visual/Matchmaking/TestSceneMatchmakingScreen.cs index 2269e1c76c..a598ce9a39 100644 --- a/osu.Game.Tests/Visual/Matchmaking/TestSceneMatchmakingScreen.cs +++ b/osu.Game.Tests/Visual/Matchmaking/TestSceneMatchmakingScreen.cs @@ -120,12 +120,16 @@ namespace osu.Game.Tests.Visual.Matchmaking changeStage(MatchmakingStage.Ended, state => { - int localUserId = API.LocalUser.Value.OnlineID; + int i = 1; - state.Users[localUserId].Placement = 1; - state.Users[localUserId].Rounds[1].Placement = 1; - state.Users[localUserId].Rounds[1].TotalScore = 1; - state.Users[localUserId].Rounds[1].Statistics[HitResult.LargeBonus] = 1; + foreach (var user in MultiplayerClient.ServerRoom!.Users.OrderBy(_ => RNG.Next())) + { + state.Users[user.UserID].Placement = i++; + state.Users[user.UserID].Points = (8 - i) * 7; + state.Users[user.UserID].Rounds[1].Placement = 1; + state.Users[user.UserID].Rounds[1].TotalScore = 1; + state.Users[user.UserID].Rounds[1].Statistics[HitResult.LargeBonus] = 1; + } }); } diff --git a/osu.Game.Tests/Visual/Matchmaking/TestScenePanelRoomAward.cs b/osu.Game.Tests/Visual/Matchmaking/TestScenePanelRoomAward.cs index 494d1c411a..bdae656855 100644 --- a/osu.Game.Tests/Visual/Matchmaking/TestScenePanelRoomAward.cs +++ b/osu.Game.Tests/Visual/Matchmaking/TestScenePanelRoomAward.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual.Matchmaking { base.SetUpSteps(); - AddStep("add statistic", () => Child = new PanelRoomAward("Statistic description", 1) + AddStep("add award", () => Child = new PanelRoomAward("Award name", "Description of what this award means", 1) { Anchor = Anchor.Centre, Origin = Anchor.Centre diff --git a/osu.Game.Tests/Visual/Matchmaking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Matchmaking/TestSceneResultsScreen.cs index d445c46a48..4d1a40cc10 100644 --- a/osu.Game.Tests/Visual/Matchmaking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Matchmaking/TestSceneResultsScreen.cs @@ -18,8 +18,6 @@ namespace osu.Game.Tests.Visual.Matchmaking { public partial class TestSceneResultsScreen : MultiplayerTestScene { - private const int invalid_user_id = 1; - public override void SetUpSteps() { base.SetUpSteps(); @@ -27,6 +25,43 @@ namespace osu.Game.Tests.Visual.Matchmaking AddStep("join room", () => JoinRoom(CreateDefaultRoom(MatchType.Matchmaking))); WaitForJoined(); + AddStep("set initial results", () => + { + var state = new MatchmakingRoomState + { + CurrentRound = 6, + Stage = MatchmakingStage.Ended + }; + + int localUserId = API.LocalUser.Value.OnlineID; + + // Overall state. + state.Users[localUserId].Placement = 1; + state.Users[localUserId].Points = 8; + for (int round = 1; round <= state.CurrentRound; round++) + state.Users[localUserId].Rounds[round].Placement = round; + + // Highest score. + state.Users[localUserId].Rounds[1].TotalScore = 1000; + + // Highest accuracy. + state.Users[localUserId].Rounds[2].Accuracy = 0.9995; + + // Highest combo. + state.Users[localUserId].Rounds[3].MaxCombo = 100; + + // Most bonus score. + state.Users[localUserId].Rounds[4].Statistics[HitResult.LargeBonus] = 50; + + // Smallest score difference. + state.Users[localUserId].Rounds[5].TotalScore = 1000; + + // Largest score difference. + state.Users[localUserId].Rounds[6].TotalScore = 1000; + + MultiplayerClient.ChangeMatchRoomState(state).WaitSafely(); + }); + AddStep("add results screen", () => { Child = new ScreenStack(new SubScreenResults()) @@ -36,7 +71,18 @@ namespace osu.Game.Tests.Visual.Matchmaking Size = new Vector2(0.8f) }; }); + } + [Test] + public void TestBasic() + { + AddStep("do nothing", () => { }); + } + + [Test] + public void TestInvalidUser() + { + const int invalid_user_id = 1; AddStep("join another user", () => MultiplayerClient.AddUser(new MultiplayerRoomUser(invalid_user_id) { User = new APIUser @@ -45,11 +91,7 @@ namespace osu.Game.Tests.Visual.Matchmaking Username = "Invalid user" } })); - } - [Test] - public void TestResults() - { AddStep("set results stage", () => { var state = new MatchmakingRoomState diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index ff78e93b5e..0eca359060 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -231,7 +231,7 @@ namespace osu.Game.Graphics /// Retrieves colour for a . /// See https://www.figma.com/file/YHWhp9wZ089YXgB7pe6L1k/Tier-Colours /// - public ColourInfo ForRankingTier(RankingTier tier) + public static ColourInfo ForRankingTier(RankingTier tier) { switch (tier) { diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs index d1be7cecce..3e48366ae2 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs @@ -157,7 +157,7 @@ namespace osu.Game.Overlays.Profile.Header.Components } dailyPlayCount.Text = DailyChallengeStatsDisplayStrings.UnitDay(stats.PlayCount.ToLocalisableString("N0")); - dailyPlayCount.Colour = colours.ForRankingTier(DailyChallengeStatsTooltip.TierForPlayCount(stats.PlayCount)); + dailyPlayCount.Colour = OsuColour.ForRankingTier(DailyChallengeStatsTooltip.TierForPlayCount(stats.PlayCount)); bool playedToday = stats.LastUpdate?.Date == DateTimeOffset.UtcNow.Date; bool userIsOnOwnProfile = stats.UserID == api.LocalUser.Value.Id; diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs index 826b40d70c..fa4937bd1f 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs @@ -36,9 +36,6 @@ namespace osu.Game.Overlays.Profile.Header.Components private Box topBackground = null!; private Box background = null!; - [Resolved] - private OsuColour colours { get; set; } = null!; - [BackgroundDependencyLoader] private void load() { @@ -117,19 +114,19 @@ namespace osu.Game.Overlays.Profile.Header.Components topBackground.Colour = colourProvider.Background5; totalParticipation.Value = DailyChallengeStatsDisplayStrings.UnitDay(statistics.PlayCount.ToLocalisableString(@"N0")); - totalParticipation.ValueColour = colours.ForRankingTier(TierForPlayCount(statistics.PlayCount)); + totalParticipation.ValueColour = OsuColour.ForRankingTier(TierForPlayCount(statistics.PlayCount)); currentDaily.Value = DailyChallengeStatsDisplayStrings.UnitDay(content.Statistics.DailyStreakCurrent.ToLocalisableString(@"N0")); - currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakCurrent)); + currentDaily.ValueColour = OsuColour.ForRankingTier(TierForDaily(statistics.DailyStreakCurrent)); currentWeekly.Value = DailyChallengeStatsDisplayStrings.UnitWeek(statistics.WeeklyStreakCurrent.ToLocalisableString(@"N0")); - currentWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakCurrent)); + currentWeekly.ValueColour = OsuColour.ForRankingTier(TierForWeekly(statistics.WeeklyStreakCurrent)); bestDaily.Value = DailyChallengeStatsDisplayStrings.UnitDay(statistics.DailyStreakBest.ToLocalisableString(@"N0")); - bestDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakBest)); + bestDaily.ValueColour = OsuColour.ForRankingTier(TierForDaily(statistics.DailyStreakBest)); bestWeekly.Value = DailyChallengeStatsDisplayStrings.UnitWeek(statistics.WeeklyStreakBest.ToLocalisableString(@"N0")); - bestWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakBest)); + bestWeekly.ValueColour = OsuColour.ForRankingTier(TierForWeekly(statistics.WeeklyStreakBest)); topTen.Value = statistics.Top10PercentPlacements.ToLocalisableString(@"N0"); topTen.ValueColour = colourProvider.Content2; diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs index 9b4df7672d..543e353f18 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs @@ -27,9 +27,6 @@ namespace osu.Game.Overlays.Profile.Header.Components private OsuSpriteText levelText = null!; private Sprite sprite = null!; - [Resolved] - private OsuColour osuColour { get; set; } = null!; - public LevelBadge() { TooltipText = UsersStrings.ShowStatsLevel("0"); @@ -91,7 +88,7 @@ namespace osu.Game.Overlays.Profile.Header.Components tier = RankingTier.Lustrous; } - return osuColour.ForRankingTier(tier); + return OsuColour.ForRankingTier(tier); } } } diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/PlayerPanel.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/PlayerPanel.cs index c899c84af6..d8b3adabb9 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/PlayerPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/PlayerPanel.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using Humanizer; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; @@ -27,6 +29,7 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking; using osu.Game.Overlays; using osu.Game.Resources.Localisation.Web; +using osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results; using osu.Game.Screens.Play; using osu.Game.Users; using osuTK; @@ -192,7 +195,8 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match Origin = Anchor.BottomCentre, Blending = BlendingParameters.Additive, Margin = new MarginPadding(4), - Font = OsuFont.Style.Title.With(size: 70), + Text = "-", + Font = OsuFont.Style.Title.With(size: 55), }, username = new OsuSpriteText { @@ -292,7 +296,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match using (BeginDelayedSequence(100)) { - rankText.FadeTo(0.6f, 600); + rankText.FadeTo(1, 600); } } } @@ -343,7 +347,8 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match if (!matchmakingState.Users.UserDictionary.TryGetValue(User.Id, out MatchmakingUser? userScore)) return; - rankText.Text = $"#{userScore.Placement}"; + rankText.Text = userScore.Placement.Ordinalize(CultureInfo.CurrentCulture); + rankText.FadeColour(SubScreenResults.ColourForPlacement(userScore.Placement)); scoreText.Text = $"{userScore.Points} pts"; }); diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/Results/PanelRoomAward.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/Results/PanelRoomAward.cs index 5e7c3865c1..fb93d5e804 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/Results/PanelRoomAward.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/Results/PanelRoomAward.cs @@ -3,55 +3,135 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Database; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; -using osuTK.Graphics; +using osu.Game.Overlays; +using osuTK; namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results { - public partial class PanelRoomAward : CompositeDrawable + public partial class PanelRoomAward : OsuClickableContainer { - private readonly Color4 backgroundColour = Color4.SaddleBrown; - private readonly string text; + private readonly string description; private readonly int userId; - public PanelRoomAward(string text, int userId) + private Box glossLayer = null!; + private Container scaleContainer = null!; + + public PanelRoomAward(string text, string description, int userId) { this.text = text; + this.description = description; this.userId = userId; - AutoSizeAxes = Axes.Both; + Height = 40; + RelativeSizeAxes = Axes.X; + + // Just make hover sounds work for now. + Action = () => { }; } [BackgroundDependencyLoader] - private void load(UserLookupCache userLookupCache) + private void load(UserLookupCache userLookupCache, OverlayColourProvider colourProvider) { // Should be cached by this point. - APIUser? user = userLookupCache.GetUserAsync(userId).GetResultSafely(); + APIUser user = userLookupCache.GetUserAsync(userId).GetResultSafely()!; - InternalChild = new CircularContainer + Child = scaleContainer = new Container { - AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, Masking = true, + CornerRadius = 5, Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = backgroundColour + Colour = colourProvider.Background3, }, - new OsuSpriteText + new FillFlowContainer { - Margin = new MarginPadding(10), - Text = $"{text}: {user?.Username}" - } + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding(10), + Spacing = new Vector2(10), + Children = new Drawable[] + { + new MatchmakingAvatar(user) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.Style.Caption1, + Text = user.Username + }, + new OsuSpriteText + { + Font = OsuFont.Style.Caption2.With(weight: FontWeight.Bold), + Text = text + } + } + }, + } + }, + glossLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + Rotation = 30, + Scale = new Vector2(0.1f, 3), + Colour = ColourInfo.GradientHorizontal( + colourProvider.Background2.Opacity(0), + colourProvider.Background2), + Alpha = 0.1f, + Blending = BlendingParameters.Additive, + }, } }; } + + protected override bool OnHover(HoverEvent e) + { + scaleContainer.ScaleTo(1.15f, 2000, Easing.OutPow10); + glossLayer + .FadeTo(0.05f, 2000, Easing.OutPow10) + .MoveToX(-8, 2000, Easing.OutPow10); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + scaleContainer.ScaleTo(1f, 500, Easing.OutQuint); + glossLayer + .FadeTo(0.1f, 500, Easing.OutQuint) + .MoveToX(0, 500, Easing.OutQuint); + base.OnHoverLost(e); + } + + public override LocalisableString TooltipText => description; } } diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/Results/PanelUserStatistic.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/Results/PanelUserStatistic.cs index 2051359f32..c1b1be0b2b 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/Results/PanelUserStatistic.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/Results/PanelUserStatistic.cs @@ -1,28 +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 System.Globalization; +using Humanizer; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osuTK.Graphics; +using osu.Game.Overlays; +using osuTK; namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results { public partial class PanelUserStatistic : CompositeDrawable { - private readonly Color4 backgroundColour = Color4.SaddleBrown; - + private readonly int position; private readonly string text; - public PanelUserStatistic(string text) + public PanelUserStatistic(int position, string text) { + this.position = position; this.text = text; AutoSizeAxes = Axes.Both; } + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + [BackgroundDependencyLoader] private void load() { @@ -32,16 +39,48 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results Masking = true, Children = new Drawable[] { - new Box + new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Colour = backgroundColour + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new Container + { + Width = 30, + Masking = true, + CornerRadius = 6, + CornerExponent = 10, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = SubScreenResults.ColourForPlacement(position), + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Default.With(weight: FontWeight.Bold), + Text = position.Ordinalize(CultureInfo.CurrentCulture), + Colour = colourProvider.Background4, + }, + } + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Style.Caption2, + Text = text + } + } }, - new OsuSpriteText - { - Margin = new MarginPadding(10), - Text = text - } } }; } diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/Results/SubScreenResults.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/Results/SubScreenResults.cs index 3e6b437f63..797519a53c 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/Results/SubScreenResults.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/Results/SubScreenResults.cs @@ -1,16 +1,22 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Globalization; using System.Linq; +using Humanizer; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking; +using osu.Game.Overlays; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Utils; using osuTK; @@ -24,133 +30,144 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results private const float grid_spacing = 5; public override PanelDisplayStyle PlayersDisplayStyle => PanelDisplayStyle.Grid; - public override Drawable PlayersDisplayArea { get; } + + public override Drawable PlayersDisplayArea { get; } = new Container { RelativeSizeAxes = Axes.Both }; [Resolved] private MultiplayerClient client { get; set; } = null!; - private readonly OsuSpriteText placementText; - private readonly FillFlowContainer userStatistics; - private readonly FillFlowContainer roomStatistics; + private OsuSpriteText placementText = null!; + private FillFlowContainer userStatistics = null!; + private FillFlowContainer roomAwards = null!; - public SubScreenResults() + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() { InternalChild = new GridContainer { + Padding = new MarginPadding(5), RelativeSizeAxes = Axes.Both, - RowDimensions = - [ + ColumnDimensions = new[] + { new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.Absolute, grid_spacing), new Dimension(), - new Dimension(GridSizeMode.Absolute, grid_spacing), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Absolute, 75) - ], - Content = new Drawable[]?[] + }, + Content = new[] { - [ - new FillFlowContainer + new[] + { + new Container { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(grid_spacing), - Children = new[] + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] { - new OsuSpriteText + new Container { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = "Placement", - Font = OsuFont.Default.With(size: 12) + Masking = true, + CornerRadius = 5, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background4, + RelativeSizeAxes = Axes.Both, + }, + } }, - placementText = new OsuSpriteText + new FillFlowContainer { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Font = OsuFont.Default.With(size: 72), - UseFullGlyphHeight = false + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(6), + Spacing = new Vector2(grid_spacing), + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "How you played", + Font = OsuFont.Style.Heading2, + Margin = new MarginPadding { Vertical = 15 }, + }, + userStatistics = new FillFlowContainer + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(grid_spacing) + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "Room Awards", + Font = OsuFont.Style.Heading2, + Margin = new MarginPadding { Vertical = 15 }, + }, + roomAwards = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(grid_spacing) + } + } } - } - } - ], - null, - [ + }, + }, + Empty(), new GridContainer { RelativeSizeAxes = Axes.Both, - ColumnDimensions = + RowDimensions = [ new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.Absolute, grid_spacing), - new Dimension() + new Dimension(), ], - Content = new Drawable?[][] + Content = new Drawable[]?[] { [ new FillFlowContainer { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, - Spacing = new Vector2(grid_spacing), - Children = new Drawable[] + Spacing = new Vector2(16), + Children = new[] { new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = "Breakdown", - Font = OsuFont.Default.With(size: 12) + Text = "Your final placement", + Font = OsuFont.Style.Heading2.With(size: 36), }, - userStatistics = new FillFlowContainer + placementText = new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(grid_spacing) + Font = OsuFont.Style.Heading1.With(size: 72), + UseFullGlyphHeight = false } } - }, - null, - PlayersDisplayArea = Empty().With(d => - { - d.RelativeSizeAxes = Axes.Both; - }) - ] - } - } - ], - null, - [ - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(grid_spacing), - Children = new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = "Statistics", - Font = OsuFont.Default.With(size: 12) - }, - roomStatistics = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(grid_spacing) - } + } + ], + null, + [ + PlayersDisplayArea, + ], } }, - ], + }, } }; } @@ -180,36 +197,62 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results if (state.Users[client.LocalUser!.UserID].Rounds.Count == 0) { placementText.Text = "-"; - addStatistic("No rounds played"); + placementText.Colour = OsuColour.Gray(1f); return; } int overallPlacement = state.Users[client.LocalUser!.UserID].Placement; + + placementText.Text = overallPlacement.Ordinalize(CultureInfo.CurrentCulture); + placementText.Colour = ColourForPlacement(overallPlacement); + int overallPoints = state.Users[client.LocalUser!.UserID].Points; - int bestPlacement = state.Users[client.LocalUser!.UserID].Rounds.Min(r => r.Placement); - var accuracyPlacement = state.Users.Select(u => (user: u, avgAcc: u.Rounds.Select(r => r.Accuracy).DefaultIfEmpty(0).Average())) - .OrderByDescending(t => t.avgAcc) - .Select((t, i) => (info: t, index: i)) - .Single(t => t.info.user.UserId == client.LocalUser!.UserID); + addStatistic(overallPlacement, $"Overall position ({overallPoints} points)"); - placementText.Text = $"#{state.Users[client.LocalUser!.UserID].Placement}"; - addStatistic($"#{overallPlacement} overall ({overallPoints}pts)"); - addStatistic($"#{bestPlacement} best placement"); - addStatistic($"#{accuracyPlacement.index + 1} accuracy ({accuracyPlacement.info.avgAcc.FormatAccuracy()})"); + var accuracyOrderedUsers = state.Users.Select(u => (user: u, avgAcc: u.Rounds.Select(r => r.Accuracy).DefaultIfEmpty(0).Average())) + .OrderByDescending(t => t.avgAcc) + .Select((t, i) => (info: t, index: i)) + .Single(t => t.info.user.UserId == client.LocalUser!.UserID); + int accuracyPlacement = accuracyOrderedUsers.index + 1; + addStatistic(accuracyPlacement, $"Overall accuracy ({accuracyOrderedUsers.info.avgAcc.FormatAccuracy()})"); - void addStatistic(string text) + var maxComboOrderedUsers = state.Users.Select(u => (user: u, maxCombo: u.Rounds.Max(r => r.MaxCombo))) + .OrderByDescending(t => t.maxCombo) + .Select((t, i) => (info: t, index: i)) + .Single(t => t.info.user.UserId == client.LocalUser!.UserID); + int maxComboPlacement = maxComboOrderedUsers.index + 1; + addStatistic(maxComboPlacement, $"Best max combo ({maxComboOrderedUsers.info.maxCombo}x)"); + + var bestPlacement = state.Users[client.LocalUser!.UserID].Rounds.MinBy(r => r.Placement); + addStatistic(bestPlacement!.Placement, $"Best round placement (round {bestPlacement.Round})"); + + void addStatistic(int position, string text) => userStatistics.Add(new PanelUserStatistic(position, text)); + } + + public static ColourInfo ColourForPlacement(int overallPlacement) + { + // for top 3 placements use special colours. + // don't for the rest. + + switch (overallPlacement) { - userStatistics.Add(new PanelUserStatistic(text) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre - }); + case 1: + return OsuColour.ForRankingTier(RankingTier.Gold); + + case 2: + return OsuColour.ForRankingTier(RankingTier.Silver); + + case 3: + return OsuColour.ForRankingTier(RankingTier.Bronze); + + default: + return OsuColour.ForRankingTier(RankingTier.Iron); } } private void populateRoomStatistics(MatchmakingRoomState state) { - roomStatistics.Clear(); + roomAwards.Clear(); long maxScore = long.MinValue; int maxScoreUserId = 0; @@ -301,35 +344,22 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results } } - // Highest score - highest score across all rounds. - addStatistic(maxScoreUserId, "Highest score"); + addAward(maxScoreUserId, "Score champ", "Highest score in a single round"); - // Most accurate - highest accuracy across all rounds. - addStatistic(maxAccuracyUserId, "Most accurate"); + addAward(maxAccuracyUserId, "Most accurate", "Highest accuracy in a single round"); - // Most combo - highest combo across all rounds. - addStatistic(maxComboUserId, "Most combo"); + addAward(maxComboUserId, "Top combo", "Highest combo in a single round"); - // Most bonus - most bonus score across all rounds. if (maxBonusScoreUserId > 0) - addStatistic(maxBonusScoreUserId, "Most bonus"); + addAward(maxBonusScoreUserId, "Biggest bonus", "Biggest bonus score across all rounds"); - // Most clutch - smallest victory in any round. if (smallestScoreDifferenceUserId > 0) - addStatistic(smallestScoreDifferenceUserId, "Most clutch"); + addAward(smallestScoreDifferenceUserId, "Most clutch", "Smallest winning score difference in a single round"); - // Best finish - largest victory in any round. if (largestScoreDifferenceUserId > 0) - addStatistic(largestScoreDifferenceUserId, "Best finish"); + addAward(largestScoreDifferenceUserId, "Best finish", "Largest score difference in a single round"); - void addStatistic(int userId, string text) - { - roomStatistics.Add(new PanelRoomAward(text, userId) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre - }); - } + void addAward(int userId, string text, string description) => roomAwards.Add(new PanelRoomAward(text, description, userId)); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/ScreenMatchmaking.ScreenStack.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/ScreenMatchmaking.ScreenStack.cs index c1f436e0c9..279dd98a5e 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Match/ScreenMatchmaking.ScreenStack.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Match/ScreenMatchmaking.ScreenStack.cs @@ -36,9 +36,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10) + Padding = new MarginPadding(6) { - Bottom = StageDisplay.HEIGHT, + Bottom = StageDisplay.HEIGHT + 6, }, Children = new Drawable[] {