diff --git a/osu.Game.Tests/Visual/Matchmaking/TestSceneMatchmakingQueueScreen.cs b/osu.Game.Tests/Visual/Matchmaking/TestSceneMatchmakingQueueScreen.cs index 5193d58ee6..07d0fe6ed9 100644 --- a/osu.Game.Tests/Visual/Matchmaking/TestSceneMatchmakingQueueScreen.cs +++ b/osu.Game.Tests/Visual/Matchmaking/TestSceneMatchmakingQueueScreen.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Matchmaking { base.SetUpSteps(); - AddStep("load screen", () => LoadScreen(new IntroScreen())); + AddStep("load screen", () => LoadScreen(new ScreenIntro())); } [Test] diff --git a/osu.Game.Tests/Visual/Online/TestSceneGlobalRankDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneGlobalRankDisplay.cs new file mode 100644 index 0000000000..07fe8c6172 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneGlobalRankDisplay.cs @@ -0,0 +1,67 @@ +// 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.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Tests.Visual.UserInterface; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Tests.Visual.Online +{ + public partial class TestSceneGlobalRankDisplay : ThemeComparisonTestScene + { + public TestSceneGlobalRankDisplay() + : base(false) + { + } + + protected override Drawable CreateContent() => new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Full, + Padding = new MarginPadding(20), + Spacing = new Vector2(40), + ChildrenEnumerable = new int?[] { 64, 423, 1453, 3468, 18_367, 48_342, 178_432, 375_231, 897_783, null }.Select(createDisplay) + }; + + private GlobalRankDisplay createDisplay(int? rank) => new GlobalRankDisplay + { + UserStatistics = + { + Value = new UserStatistics + { + GlobalRank = rank, + GlobalRankPercent = rank / 1_000_000f, + Variants = + [ + new UserStatistics.Variant + { + VariantType = UserStatistics.RulesetVariant.FourKey, + GlobalRank = rank / 3, + }, + new UserStatistics.Variant + { + VariantType = UserStatistics.RulesetVariant.SevenKey, + GlobalRank = 2 * rank / 3, + } + ] + }, + }, + HighestRank = + { + Value = rank == null + ? null + : new APIUser.UserRankHighest + { + Rank = rank.Value / 2, + UpdatedAt = DateTimeOffset.Now.AddMonths(-3), + } + } + }; + } +} diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselArtistGrouping.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselArtistGrouping.cs index 2c3013af12..2390261cdb 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselArtistGrouping.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselArtistGrouping.cs @@ -4,9 +4,12 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics.Carousel; using osu.Game.Screens.Select.Filter; using osu.Game.Screens.SelectV2; +using osu.Game.Tests.Resources; using osuTK; namespace osu.Game.Tests.Visual.SongSelectV2 @@ -322,5 +325,38 @@ namespace osu.Game.Tests.Visual.SongSelectV2 SelectNextSet(); AddUntilStep("no beatmap panels visible", () => GetVisiblePanels().Count(), () => Is.Zero); } + + [Test] + public void TestGroupChangedAfterEngagingArtistGrouping() + { + RemoveAllBeatmaps(); + AddStep("add test beatmaps", () => + { + for (int i = 0; i < 5; ++i) + { + var baseTestBeatmap = TestResources.CreateTestBeatmapSetInfo(3); + + var metadata = new BeatmapMetadata + { + Artist = $"{(char)('A' + i)} artist", + Title = $"{(char)('A' + 4 - i)} title", + }; + + foreach (var b in baseTestBeatmap.Beatmaps) + b.Metadata = metadata; + + Realm.Write(r => r.Add(baseTestBeatmap, update: true)); + BeatmapSets.Add(baseTestBeatmap.Detach()); + } + + SortAndGroupBy(SortMode.Title, GroupMode.Title); + SelectNextSet(); + SelectNextSet(); + WaitForExpandedGroup(1); + + SortAndGroupBy(SortMode.Artist, GroupMode.Artist); + WaitForExpandedGroup(3); + }); + } } } diff --git a/osu.Game/Online/PersistentEndpointClientConnector.cs b/osu.Game/Online/PersistentEndpointClientConnector.cs index 9e7543ce2b..2674c29103 100644 --- a/osu.Game/Online/PersistentEndpointClientConnector.cs +++ b/osu.Game/Online/PersistentEndpointClientConnector.cs @@ -150,7 +150,7 @@ namespace osu.Game.Online // compare: https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/Online/BanchoClient.cs#L539 retryDelay = Math.Min(120000, (int)(retryDelay * 1.5)); - Logger.Log($"{ClientName} connect attempt failed: {exception.Message}. Next attempt in {thisDelay / 1000:N0} seconds.", LoggingTarget.Network); + Logger.Log($"{ClientName} connect attempt failed. Next attempt in {thisDelay / 1000:N0} seconds.\n{exception}", LoggingTarget.Network); await Task.Delay(thisDelay, cancellationToken).ConfigureAwait(false); } diff --git a/osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs new file mode 100644 index 0000000000..3560986925 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/GlobalRankDisplay.cs @@ -0,0 +1,137 @@ +// 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.Extensions; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public partial class GlobalRankDisplay : CompositeDrawable + { + public Bindable UserStatistics = new Bindable(); + public Bindable HighestRank = new Bindable(); + + private ProfileValueDisplay info = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + public GlobalRankDisplay() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = info = new ProfileValueDisplay(big: true) + { + Title = UsersStrings.ShowRankGlobalSimple + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + UserStatistics.BindValueChanged(_ => updateState()); + HighestRank.BindValueChanged(_ => updateState(), true); + } + + private void updateState() + { + info.Content.Text = UserStatistics.Value?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; + info.Content.TooltipText = getGlobalRankTooltipText(); + + var tier = getRankingTier(); + info.Content.Colour = tier == null ? colourProvider.Content2 : OsuColour.ForRankingTier(tier.Value); + info.Content.Font = info.Content.Font.With(weight: tier == null || tier == RankingTier.Iron ? FontWeight.Regular : FontWeight.Bold); + } + + /// + private RankingTier? getRankingTier() + { + var stats = UserStatistics.Value; + + int? rank = stats?.GlobalRank; + float? percent = stats?.GlobalRankPercent; + + if (rank == null || percent == null) + return null; + + if (rank <= 100) + return RankingTier.Lustrous; + + if (percent < 0.0005) + return RankingTier.Radiant; + + if (percent < 0.0025) + return RankingTier.Rhodium; + + if (percent < 0.005) + return RankingTier.Platinum; + + if (percent < 0.025) + return RankingTier.Gold; + + if (percent < 0.05) + return RankingTier.Silver; + + if (percent < 0.25) + return RankingTier.Bronze; + + if (percent < 0.5) + return RankingTier.Iron; + + return null; + } + + private LocalisableString getGlobalRankTooltipText() + { + var rankHighest = HighestRank.Value; + var variants = UserStatistics.Value?.Variants; + + LocalisableString? result = null; + + if (variants?.Count > 0) + { + foreach (var variant in variants) + { + if (variant.GlobalRank != null) + { + var variantText = LocalisableString.Interpolate($"{variant.VariantType.GetLocalisableDescription()}: {variant.GlobalRank.ToLocalisableString("\\##,##0")}"); + + if (result == null) + result = variantText; + else + result = LocalisableString.Interpolate($"{result}\n{variantText}"); + } + } + } + + if (rankHighest != null) + { + var rankHighestText = UsersStrings.ShowRankHighest( + rankHighest.Rank.ToLocalisableString("\\##,##0"), + rankHighest.UpdatedAt.ToLocalisableString(@"d MMM yyyy")); + + if (result == null) + result = rankHighestText; + else + result = LocalisableString.Interpolate($"{result}\n{rankHighestText}"); + } + + return result ?? default; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs index 10bb69f0f5..029de96c41 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private readonly Dictionary scoreRankInfos = new Dictionary(); private ProfileValueDisplay medalInfo = null!; private ProfileValueDisplay ppInfo = null!; - private ProfileValueDisplay detailGlobalRank = null!; + private GlobalRankDisplay detailGlobalRank = null!; private ProfileValueDisplay detailCountryRank = null!; private RankGraph rankGraph = null!; @@ -64,10 +64,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { new[] { - detailGlobalRank = new ProfileValueDisplay(true) - { - Title = UsersStrings.ShowRankGlobalSimple, - }, + detailGlobalRank = new GlobalRankDisplay(), Empty(), detailCountryRank = new ProfileValueDisplay(true) { @@ -156,59 +153,22 @@ namespace osu.Game.Overlays.Profile.Header.Components { var user = data?.User; - medalInfo.Content = user?.Achievements?.Length.ToString() ?? "0"; - ppInfo.Content = user?.Statistics?.PP?.ToLocalisableString("#,##0") ?? (LocalisableString)"0"; + medalInfo.Content.Text = user?.Achievements?.Length.ToString() ?? "0"; + ppInfo.Content.Text = user?.Statistics?.PP?.ToLocalisableString("#,##0") ?? (LocalisableString)"0"; + ppInfo.Content.TooltipText = getPPInfoTooltipText(user); foreach (var scoreRankInfo in scoreRankInfos) scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0; - detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; - detailGlobalRank.ContentTooltipText = getGlobalRankTooltipText(user); + detailGlobalRank.HighestRank.Value = user?.RankHighest; + detailGlobalRank.UserStatistics.Value = user?.Statistics; - detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; - detailCountryRank.ContentTooltipText = getCountryRankTooltipText(user); + detailCountryRank.Content.Text = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; + detailCountryRank.Content.TooltipText = getCountryRankTooltipText(user); rankGraph.Statistics.Value = user?.Statistics; } - private static LocalisableString getGlobalRankTooltipText(APIUser? user) - { - var rankHighest = user?.RankHighest; - var variants = user?.Statistics?.Variants; - - LocalisableString? result = null; - - if (variants?.Count > 0) - { - foreach (var variant in variants) - { - if (variant.GlobalRank != null) - { - var variantText = LocalisableString.Interpolate($"{variant.VariantType.GetLocalisableDescription()}: {variant.GlobalRank.ToLocalisableString("\\##,##0")}"); - - if (result == null) - result = variantText; - else - result = LocalisableString.Interpolate($"{result}\n{variantText}"); - } - } - } - - if (rankHighest != null) - { - var rankHighestText = UsersStrings.ShowRankHighest( - rankHighest.Rank.ToLocalisableString("\\##,##0"), - rankHighest.UpdatedAt.ToLocalisableString(@"d MMM yyyy")); - - if (result == null) - result = rankHighestText; - else - result = LocalisableString.Interpolate($"{result}\n{rankHighestText}"); - } - - return result ?? default; - } - private static LocalisableString getCountryRankTooltipText(APIUser? user) { var variants = user?.Statistics?.Variants; @@ -234,6 +194,28 @@ namespace osu.Game.Overlays.Profile.Header.Components return result ?? default; } + private static LocalisableString getPPInfoTooltipText(APIUser? user) + { + var variants = user?.Statistics?.Variants; + + LocalisableString? result = null; + + if (variants?.Count > 0) + { + foreach (var variant in variants) + { + var variantText = LocalisableString.Interpolate($"{variant.VariantType.GetLocalisableDescription()}: {variant.PP.ToLocalisableString("#,##0")}"); + + if (result == null) + result = variantText; + else + result = LocalisableString.Interpolate($"{result}\n{variantText}"); + } + } + + return result ?? default; + } + private partial class ScoreRankInfo : CompositeDrawable { private readonly OsuSpriteText rankCount; diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileValueDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileValueDisplay.cs index b2c23458b1..db384ed9d7 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileValueDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileValueDisplay.cs @@ -14,22 +14,13 @@ namespace osu.Game.Overlays.Profile.Header.Components public partial class ProfileValueDisplay : CompositeDrawable { private readonly OsuSpriteText title; - private readonly ContentText content; public LocalisableString Title { set => title.Text = value; } - public LocalisableString Content - { - set => content.Text = value; - } - - public LocalisableString ContentTooltipText - { - set => content.TooltipText = value; - } + public ContentText Content { get; } public ProfileValueDisplay(bool big = false, int minimumWidth = 60) { @@ -44,9 +35,9 @@ namespace osu.Game.Overlays.Profile.Header.Components { Font = OsuFont.GetFont(size: 12) }, - content = new ContentText + Content = new ContentText { - Font = OsuFont.GetFont(size: big ? 30 : 20, weight: FontWeight.Light), + Font = OsuFont.GetFont(size: big ? 30 : 20, weight: big ? FontWeight.Regular : FontWeight.Light), }, new Container // Add a minimum size to the FillFlowContainer { @@ -60,10 +51,10 @@ namespace osu.Game.Overlays.Profile.Header.Components private void load(OverlayColourProvider colourProvider) { title.Colour = colourProvider.Content1; - content.Colour = colourProvider.Content2; + Content.Colour = colourProvider.Content2; } - private partial class ContentText : OsuSpriteText, IHasTooltip + public partial class ContentText : OsuSpriteText, IHasTooltip { public LocalisableString TooltipText { get; set; } } diff --git a/osu.Game/Overlays/Profile/Header/Components/TotalPlayTime.cs b/osu.Game/Overlays/Profile/Header/Components/TotalPlayTime.cs index a3c22d61d2..3cc7bc15e8 100644 --- a/osu.Game/Overlays/Profile/Header/Components/TotalPlayTime.cs +++ b/osu.Game/Overlays/Profile/Header/Components/TotalPlayTime.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Profile.Header.Components InternalChild = info = new ProfileValueDisplay(minimumWidth: 140) { Title = UsersStrings.ShowStatsPlayTime, - ContentTooltipText = "0 hours", + Content = { TooltipText = "0 hours", } }; User.BindValueChanged(updateTime, true); @@ -35,8 +35,8 @@ namespace osu.Game.Overlays.Profile.Header.Components private void updateTime(ValueChangedEvent user) { int? playTime = user.NewValue?.User.Statistics?.PlayTime; - info.ContentTooltipText = (playTime ?? 0) / 3600 + " hours"; - info.Content = formatTime(playTime); + info.Content.TooltipText = (playTime ?? 0) / 3600 + " hours"; + info.Content.Text = formatTime(playTime); } private string formatTime(int? secondsNull) diff --git a/osu.Game/Overlays/Wiki/WikiHeader.cs b/osu.Game/Overlays/Wiki/WikiHeader.cs index d64d6b934a..a5129eaefd 100644 --- a/osu.Game/Overlays/Wiki/WikiHeader.cs +++ b/osu.Game/Overlays/Wiki/WikiHeader.cs @@ -5,13 +5,20 @@ using System; using System.Linq; +using JetBrains.Annotations; +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.UserInterfaceV2; using osu.Game.Localisation; +using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; +using osuTK; namespace osu.Game.Overlays.Wiki { @@ -19,11 +26,15 @@ namespace osu.Game.Overlays.Wiki { public static LocalisableString IndexPageString => LayoutStrings.HeaderHelpIndex; + private const string github_wiki_base = @"https://github.com/ppy/osu-wiki/blob/master/wiki"; + public readonly Bindable WikiPageData = new Bindable(); public Action ShowIndexPage; public Action ShowParentPage; + private readonly Bindable githubPath = new Bindable(); + public WikiHeader() { TabControl.AddItem(IndexPageString); @@ -35,6 +46,9 @@ namespace osu.Game.Overlays.Wiki private void onWikiPageChange(ValueChangedEvent e) { + // Clear the path beforehand in case we got an error page. + githubPath.Value = null; + if (e.NewValue == null) return; @@ -42,6 +56,7 @@ namespace osu.Game.Overlays.Wiki Current.Value = null; TabControl.AddItem(IndexPageString); + githubPath.Value = $"{github_wiki_base}/{e.NewValue.Path}/{e.NewValue.Locale}.md"; if (e.NewValue.Path == WikiOverlay.INDEX_PATH) { @@ -56,6 +71,27 @@ namespace osu.Game.Overlays.Wiki Current.Value = e.NewValue.Title; } + protected override Drawable CreateTabControlContent() + { + return new FillFlowContainer + { + Height = 40, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new ShowOnGitHubButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(32), + TargetPath = { BindTarget = githubPath }, + }, + }, + }; + } + private void onCurrentChange(ValueChangedEvent e) { if (e.NewValue == TabControl.Items.LastOrDefault()) @@ -83,5 +119,39 @@ namespace osu.Game.Overlays.Wiki Icon = OsuIcon.Wiki; } } + + private partial class ShowOnGitHubButton : RoundedButton + { + public override LocalisableString TooltipText => WikiStrings.ShowEditLink; + + public readonly Bindable TargetPath = new Bindable(); + + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] ILinkHandler linkHandler) + { + Width = 42; + + Add(new SpriteIcon + { + Size = new Vector2(12), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Brands.Github, + }); + + Action = () => linkHandler?.HandleLink(TargetPath.Value); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + TargetPath.BindValueChanged(e => + { + this.FadeTo(e.NewValue != null ? 1 : 0); + Enabled.Value = e.NewValue != null; + }, true); + } + } } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index c4ba3145b5..2296213dd6 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -482,7 +482,7 @@ namespace osu.Game.Screens.Menu private void loadSongSelect() => this.Push(new SoloSongSelect()); - private void joinOrLeaveMatchmakingQueue() => this.Push(new OnlinePlay.Matchmaking.Intro.IntroScreen()); + private void joinOrLeaveMatchmakingQueue() => this.Push(new OnlinePlay.Matchmaking.Intro.ScreenIntro()); private partial class MobileDisclaimerDialog : PopupDialog { diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Intro/ScreenIntro.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Intro/ScreenIntro.cs index b3fff7dc00..093d9f6117 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Intro/ScreenIntro.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Intro/ScreenIntro.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Intro /// /// A brief intro animation that introduces matchmaking to the user. /// - public partial class IntroScreen : OsuScreen + public partial class ScreenIntro : OsuScreen { public override bool DisallowExternalBeatmapRulesetChanges => false; @@ -55,7 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Intro protected override BackgroundScreen CreateBackground() => new MatchmakingIntroBackgroundScreen(colourProvider); - public IntroScreen() + public ScreenIntro() { ValidForResume = false; } diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Queue/QueueController.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Queue/QueueController.cs index 468e024a65..f72f26f26e 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Queue/QueueController.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Queue/QueueController.cs @@ -119,7 +119,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue if (backgroundNotification != null) return; - notifications?.Post(backgroundNotification = new BackgroundQueueNotification()); + notifications?.Post(backgroundNotification = new BackgroundQueueNotification(this)); } private void closeNotifications() @@ -154,9 +154,16 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue [Resolved] private MultiplayerClient client { get; set; } = null!; + private readonly QueueController controller; + private Notification? foundNotification; private Sample? matchFoundSample; + public BackgroundQueueNotification(QueueController controller) + { + this.controller = controller; + } + [BackgroundDependencyLoader] private void load(AudioManager audio) { @@ -165,7 +172,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue CompletionClickAction = () => { client.MatchmakingAcceptInvitation().FireAndForget(); - performer?.PerformFromScreen(s => s.Push(new IntroScreen())); + controller.CurrentState.Value = ScreenQueue.MatchmakingScreenState.AcceptedWaitingForRoom; + + performer?.PerformFromScreen(s => s.Push(new ScreenIntro())); Close(false); return true; diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index 36b066a308..edee63c0fa 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -497,25 +497,35 @@ namespace osu.Game.Screens.SelectV2 // The filter might have changed the set of available groups, which means that the current selection may point to a stale group. // Check whether that is the case. bool groupingRemainsOff = currentGroupedBeatmap?.Group == null && grouping.GroupItems.Count == 0; - bool groupStillExists = currentGroupedBeatmap?.Group != null && grouping.GroupItems.ContainsKey(currentGroupedBeatmap.Group); - if (groupingRemainsOff || groupStillExists) + bool groupStillValid = false; + + if (currentGroupedBeatmap?.Group != null) + { + groupStillValid = grouping.GroupItems.TryGetValue(currentGroupedBeatmap.Group, out var items) + && items.Any(i => CheckModelEquality(i.Model, currentGroupedBeatmap)); + } + + if (groupingRemainsOff || groupStillValid) { // Only update the visual state of the selected item. HandleItemSelected(currentGroupedBeatmap); } else if (currentGroupedBeatmap != null) { - // If the group no longer exists, grab an arbitrary other instance of the beatmap under the first group encountered. + // If the group no longer exists (or the item no longer exists in the previous group), grab an arbitrary other instance of the beatmap under the first group encountered. var newSelection = GetCarouselItems()?.Select(i => i.Model).OfType().FirstOrDefault(gb => gb.Beatmap.Equals(currentGroupedBeatmap.Beatmap)); + // Only change the selection if we actually got a positive hit. // This is necessary so that selection isn't lost if the panel reappears later due to e.g. unapplying some filter criteria that made it disappear in the first place. if (newSelection != null) + { CurrentSelection = newSelection; + groupForReselection = newSelection.Group; + } } // If a group was selected that is not the one containing the selection, attempt to reselect it. - // If the original group was not found, ExpandedGroup will already have been updated to a valid value in `HandleItemSelected` above. if (groupForReselection != null && grouping.GroupItems.TryGetValue(groupForReselection, out _)) setExpandedGroup(groupForReselection); } diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index ff8adf055c..251c21a89a 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -27,7 +27,7 @@ namespace osu.Game.Users private const int padding = 10; private const int main_content_height = 80; - private ProfileValueDisplay globalRankDisplay = null!; + private GlobalRankDisplay globalRankDisplay = null!; private ProfileValueDisplay countryRankDisplay = null!; private LoadingLayer loadingLayer = null!; @@ -71,8 +71,13 @@ namespace osu.Game.Users var statistics = statisticsProvider?.GetStatisticsFor(ruleset.Value); loadingLayer.State.Value = statistics == null ? Visibility.Visible : Visibility.Hidden; - globalRankDisplay.Content = statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? "-"; - countryRankDisplay.Content = statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? "-"; + + // TODO: implement highest rank tooltip + // `RankHighest` resides in `APIUser`, but `api.LocalUser` doesn't update + // maybe move to `UserStatistics` in api, so `UserStatisticsWatcher` can update the value + globalRankDisplay.UserStatistics.Value = statistics; + + countryRankDisplay.Content.Text = statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? "-"; } protected override Drawable CreateLayout() @@ -187,13 +192,7 @@ namespace osu.Game.Users { new Drawable[] { - globalRankDisplay = new ProfileValueDisplay(true) - { - Title = UsersStrings.ShowRankGlobalSimple, - // TODO: implement highest rank tooltip - // `RankHighest` resides in `APIUser`, but `api.LocalUser` doesn't update - // maybe move to `UserStatistics` in api, so `UserStatisticsWatcher` can update the value - }, + globalRankDisplay = new GlobalRankDisplay(), countryRankDisplay = new ProfileValueDisplay(true) { Title = UsersStrings.ShowRankCountrySimple, diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 687dd52594..65bea41e20 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -40,6 +40,9 @@ namespace osu.Game.Users [JsonProperty(@"global_rank")] public int? GlobalRank; + [JsonProperty(@"global_rank_percent")] + public float? GlobalRankPercent; + [JsonProperty(@"country_rank")] public int? CountryRank;