1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-27 06:29:54 +08:00

Merge branch 'master' into qp-player-download-progress

This commit is contained in:
Bartłomiej Dach
2025-11-04 12:52:01 +01:00
Unverified
15 changed files with 391 additions and 87 deletions
@@ -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]
@@ -0,0 +1,67 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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),
}
}
};
}
}
@@ -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<PanelBeatmap>().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);
});
}
}
}
@@ -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);
}
@@ -0,0 +1,137 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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?> UserStatistics = new Bindable<UserStatistics?>();
public Bindable<APIUser.UserRankHighest?> HighestRank = new Bindable<APIUser.UserRankHighest?>();
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);
}
/// <seealso href="https://github.com/ppy/osu-web/blob/6fcd85eb006ce7699d6f747597435c01344b2d2d/resources/js/profile-page/rank.tsx#L19-L46"/>
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;
}
}
}
@@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
private readonly Dictionary<ScoreRank, ScoreRankInfo> scoreRankInfos = new Dictionary<ScoreRank, ScoreRankInfo>();
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;
@@ -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; }
}
@@ -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<UserProfileData?> 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)
+70
View File
@@ -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<APIWikiPage> WikiPageData = new Bindable<APIWikiPage>();
public Action ShowIndexPage;
public Action ShowParentPage;
private readonly Bindable<string> githubPath = new Bindable<string>();
public WikiHeader()
{
TabControl.AddItem(IndexPageString);
@@ -35,6 +46,9 @@ namespace osu.Game.Overlays.Wiki
private void onWikiPageChange(ValueChangedEvent<APIWikiPage> 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<LocalisableString?> 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<string> TargetPath = new Bindable<string>();
[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);
}
}
}
}
+1 -1
View File
@@ -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
{
@@ -21,7 +21,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Intro
/// <summary>
/// A brief intro animation that introduces matchmaking to the user.
/// </summary>
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;
}
@@ -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;
+14 -4
View File
@@ -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<GroupedBeatmap>().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);
}
+9 -10
View File
@@ -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,
+3
View File
@@ -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;