Path: blob/master/osu.Game/Screens/OnlinePlay/Matchmaking/Match/Results/SubScreenResults.cs
5504 views
// Copyright (c) ppy Pty Ltd <[email protected]>. 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; namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results { /// <summary> /// Final room results, during <see cref="MatchmakingStage.Ended"/> /// </summary> public partial class SubScreenResults : MatchmakingSubScreen { private const float grid_spacing = 5; public override PanelDisplayStyle PlayersDisplayStyle => PanelDisplayStyle.Grid; public override Drawable PlayersDisplayArea { get; } = new Container { RelativeSizeAxes = Axes.Both }; [Resolved] private MultiplayerClient client { get; set; } = null!; private OsuSpriteText placementText = null!; private FillFlowContainer<PanelUserStatistic> userStatistics = null!; private FillFlowContainer<PanelRoomAward> roomAwards = null!; [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; [BackgroundDependencyLoader] private void load() { InternalChild = new GridContainer { Padding = new MarginPadding(5), RelativeSizeAxes = Axes.Both, ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.Absolute, grid_spacing), new Dimension(), }, Content = new[] { new[] { new Container { AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, Children = new Drawable[] { new Container { Masking = true, CornerRadius = 5, RelativeSizeAxes = Axes.Both, Children = new Drawable[] { new Box { Colour = colourProvider.Background4, RelativeSizeAxes = Axes.Both, }, } }, new FillFlowContainer { 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<PanelUserStatistic> { 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<PanelRoomAward> { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Spacing = new Vector2(grid_spacing) } } } }, }, Empty(), new GridContainer { RelativeSizeAxes = Axes.Both, RowDimensions = [ new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.Absolute, grid_spacing), new Dimension(), ], Content = new Drawable[]?[] { [ new FillFlowContainer { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, Spacing = new Vector2(16), Children = new[] { new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = "Your final placement", Font = OsuFont.Style.Heading2.With(size: 36), }, placementText = new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Font = OsuFont.Style.Heading1.With(size: 72), UseFullGlyphHeight = false } } } ], null, [ PlayersDisplayArea, ], } }, }, } }; } protected override void LoadComplete() { base.LoadComplete(); client.MatchRoomStateChanged += onRoomStateChanged; onRoomStateChanged(client.Room?.MatchState); } private void onRoomStateChanged(MatchRoomState? state) => Scheduler.Add(() => { if (state is not MatchmakingRoomState matchmakingState || matchmakingState.Stage != MatchmakingStage.Ended) return; populateUserStatistics(matchmakingState); populateRoomStatistics(matchmakingState); }); private void populateUserStatistics(MatchmakingRoomState state) { userStatistics.Clear(); var localUserState = state.Users.GetOrAdd(client.LocalUser!.UserID); if (localUserState.Rounds.Count == 0) { placementText.Text = "-"; placementText.Colour = OsuColour.Gray(1f); return; } int? overallPlacement = localUserState.Placement; if (overallPlacement != null) { placementText.Text = overallPlacement.Value.Ordinalize(CultureInfo.CurrentCulture); placementText.Colour = ColourForPlacement(overallPlacement.Value); int overallPoints = localUserState.Points; addStatistic(overallPlacement.Value, $"Overall position ({overallPoints} points)"); } 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()})"); var maxComboOrderedUsers = state.Users.Select(u => (user: u, maxCombo: u.Rounds.Select(r => r.MaxCombo).DefaultIfEmpty(0).Max())) .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 = localUserState.Rounds.MinBy(r => r.Placement); if (bestPlacement != null) 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) { 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) { roomAwards.Clear(); long maxScore = long.MinValue; int maxScoreUserId = -1; double maxAccuracy = double.MinValue; int maxAccuracyUserId = -1; int maxCombo = int.MinValue; int maxComboUserId = -1; long maxBonusScore = 0; int maxBonusScoreUserId = -1; long largestScoreDifference = long.MinValue; int largestScoreDifferenceUserId = -1; long smallestScoreDifference = long.MaxValue; int smallestScoreDifferenceUserId = -1; for (int round = 1; round <= state.CurrentRound; round++) { long roundHighestScore = long.MinValue; int roundHighestScoreUserId = -1; long roundLowestScore = long.MaxValue; foreach (MatchmakingUser user in state.Users) { if (!user.Rounds.RoundsDictionary.TryGetValue(round, out MatchmakingRound? mmRound)) continue; if (mmRound.TotalScore > maxScore) { maxScore = mmRound.TotalScore; maxScoreUserId = user.UserId; } if (mmRound.Accuracy > maxAccuracy) { maxAccuracy = mmRound.Accuracy; maxAccuracyUserId = user.UserId; } if (mmRound.MaxCombo > maxCombo) { maxCombo = mmRound.MaxCombo; maxComboUserId = user.UserId; } if (mmRound.TotalScore > roundHighestScore) { roundHighestScore = mmRound.TotalScore; roundHighestScoreUserId = user.UserId; } if (mmRound.TotalScore < roundLowestScore) roundLowestScore = mmRound.TotalScore; } long roundScoreDifference = roundHighestScore - roundLowestScore; if (roundScoreDifference > 0 && roundScoreDifference > largestScoreDifference) { largestScoreDifference = roundScoreDifference; largestScoreDifferenceUserId = roundHighestScoreUserId; } if (roundScoreDifference > 0 && roundScoreDifference < smallestScoreDifference) { smallestScoreDifference = roundScoreDifference; smallestScoreDifferenceUserId = roundHighestScoreUserId; } } foreach (MatchmakingUser user in state.Users) { int userBonusScore = 0; foreach (MatchmakingRound round in user.Rounds) { userBonusScore += round.Statistics.TryGetValue(HitResult.LargeBonus, out int bonus) ? bonus * 5 : 0; userBonusScore += round.Statistics.TryGetValue(HitResult.SmallBonus, out bonus) ? bonus : 0; } if (userBonusScore > maxBonusScore) { maxBonusScore = userBonusScore; maxBonusScoreUserId = user.UserId; } } if (maxScoreUserId > 0) addAward(maxScoreUserId, "Score champ", "Highest score in a single round"); if (maxAccuracyUserId > 0) addAward(maxAccuracyUserId, "Most accurate", "Highest accuracy in a single round"); if (maxComboUserId > 0) addAward(maxComboUserId, "Top combo", "Highest combo in a single round"); if (maxBonusScoreUserId > 0) addAward(maxBonusScoreUserId, "Biggest bonus", "Biggest bonus score across all rounds"); if (smallestScoreDifferenceUserId > 0) addAward(smallestScoreDifferenceUserId, "Most clutch", "Smallest winning score difference in a single round"); if (largestScoreDifferenceUserId > 0) addAward(largestScoreDifferenceUserId, "Best finish", "Largest score difference in a single round"); void addAward(int userId, string text, string description) => roomAwards.Add(new PanelRoomAward(text, description, userId)); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); if (client.IsNotNull()) client.MatchRoomStateChanged -= onRoomStateChanged; } } }